当前位置:  开发笔记 > 编程语言 > 正文

元类的一些(具体)用例是什么?

如何解决《元类的一些(具体)用例是什么?》经验,为你挑选了8个好方法。

我有一个朋友喜欢使用元类,并定期提供它们作为解决方案.

我很想你几乎不需要使用元类.为什么?因为我认为如果你正在对一个类做类似的事情,你应该把它做成一个对象.并且需要一个小的重新设计/重构.

能够使用元类导致许多地方的很多人使用类作为某种二流对象,这对我来说似乎是灾难性的.编程是否被元编程取代?遗憾的是,类装饰器的添加使其更加可接受.

所以,我非常想知道Python中元类的有效(具体)用例.或者开悟为什么变异类有时比变异对象更好.

我将开始:

有时,在使用第三方库时,能够以某种方式改变类是有用的.

(这是我能想到的唯一一个案例,并不具体)



1> Dan Gittik..:

最近我被问到了同样的问题,并提出了几个答案.我希望恢复这个线程是可以的,因为我想详细说明一些提到的用例,并添加一些新的用例.

我见过的大多数元类都做两件事之一:

    注册(向数据结构添加类):

    models = {}
    
    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            models[name] = cls = type.__new__(meta, name, bases, attrs)
            return cls
    
    class Model(object):
        __metaclass__ = ModelMetaclass
    

    无论何时子类化Model,您的类都在models字典中注册:

    >>> class A(Model):
    ...     pass
    ...
    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...>,
     'B': <__main__.B class at 0x...>}
    

    这也可以使用类装饰器完成:

    models = {}
    
    def model(cls):
        models[cls.__name__] = cls
        return cls
    
    @model
    class A(object):
        pass
    

    或者使用显式注册功能:

    models = {}
    
    def register_model(cls):
        models[cls.__name__] = cls
    
    class A(object):
        pass
    
    register_model(A)
    

    实际上,这几乎是一样的:你提到类装饰器是不利的,但它实际上只不过是对一个类上的函数调用的语法糖,所以对它没有任何魔力.

    无论如何,在这种情况下元类的优点是继承,因为它们适用于任何子类,而其他解决方案仅适用于显式修饰或注册的子类.

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
    

    重构(修改类属性或添加新属性):

    class ModelMetaclass(type):
        def __new__(meta, name, bases, attrs):
            fields = {}
            for key, value in attrs.items():
                if isinstance(value, Field):
                    value.name = '%s.%s' % (name, key)
                    fields[key] = value
            for base in bases:
                if hasattr(base, '_fields'):
                    fields.update(base._fields)
            attrs['_fields'] = fields
            return type.__new__(meta, name, bases, attrs)
    
    class Model(object):
        __metaclass__ = ModelMetaclass
    

    无论何时子类Model和定义某些Field属性,都会注入它们的名称(例如,用于提供更多信息性错误消息),并将其分组到一个_fields字典中(以便于迭代,无需查看所有类属性及其所有基类)属性每次):

    >>> class A(Model):
    ...     foo = Integer()
    ...
    >>> class B(A):
    ...     bar = String()
    ...
    >>> B._fields
    {'foo': Integer('A.foo'), 'bar': String('B.bar')}
    

    同样,这可以使用类装饰器完成(没有继承):

    def model(cls):
        fields = {}
        for key, value in vars(cls).items():
            if isinstance(value, Field):
                value.name = '%s.%s' % (cls.__name__, key)
                fields[key] = value
        for base in cls.__bases__:
            if hasattr(base, '_fields'):
                fields.update(base._fields)
        cls._fields = fields
        return cls
    
    @model
    class A(object):
        foo = Integer()
    
    class B(A):
        bar = String()
    
    # B.bar has no name :(
    # B._fields is {'foo': Integer('A.foo')} :(
    

    或明确:

    class A(object):
        foo = Integer('A.foo')
        _fields = {'foo': foo} # Don't forget all the base classes' fields, too!
    

    虽然与您倡导可读和可维护的非元编程相反,但这更加麻烦,冗余且容易出错:

    class B(A):
        bar = String()
    
    # vs.
    
    class B(A):
        bar = String('bar')
        _fields = {'B.bar': bar, 'A.foo': A.foo}
    

在考虑了最常见和最具体的用例之后,您绝对必须使用元类的唯一情况是,当您想要修改类名或基类列表时,因为一旦定义,这些参数将被烘焙到类中,并且没有装饰器或功能可以解开它们.

class Metaclass(type):
    def __new__(meta, name, bases, attrs):
        return type.__new__(meta, 'foo', (int,), attrs)

class Baseclass(object):
    __metaclass__ = Metaclass

class A(Baseclass):
    pass

class B(A):
    pass

print A.__name__ # foo
print B.__name__ # foo
print issubclass(B, A)   # False
print issubclass(B, int) # True

这可能在用于在定义具有相似名称或不完整继承树的类时发出警告的框架中有用,但我想不出除了实际更改这些值之外的原因.也许David Beazley可以.

无论如何,在Python 3中,元类也有这个__prepare__方法,它允许你将类体评估为除了a之外的映射dict,从而支持有序属性,重载属性和其他邪恶的酷东西:

import collections

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return collections.OrderedDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(list(attrs))
        # Do more stuff...

class A(metaclass=Metaclass):
    x = 1
    y = 2

# prints ['x', 'y'] rather than ['y', 'x']

 

class ListDict(dict):
    def __setitem__(self, key, value):
        self.setdefault(key, []).append(value)

class Metaclass(type):

    @classmethod
    def __prepare__(meta, name, bases, **kwds):
        return ListDict()

    def __new__(meta, name, bases, attrs, **kwds):
        print(attrs['foo'])
        # Do more stuff...

class A(metaclass=Metaclass):

    def foo(self):
        pass

    def foo(self, x):
        pass

# prints [, ] rather than 

您可能会认为可以使用创建计数器实现有序属性,并且可以使用默认参数模拟重载:

import itertools

class Attribute(object):
    _counter = itertools.count()
    def __init__(self):
        self._count = Attribute._counter.next()

class A(object):
    x = Attribute()
    y = Attribute()

A._order = sorted([(k, v) for k, v in vars(A).items() if isinstance(v, Attribute)],
                  key = lambda (k, v): v._count)

 

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=None):
        if x is None:
            return self._foo0()
        else:
            return self._foo1(x)

除了更难看之外,它也不太灵活:如果你想要有序的文字属性,如整数和字符串,该怎么办?如果None是有效值x怎么办?

这是解决第一个问题的创造性方法:

import sys

class Builder(object):
    def __call__(self, cls):
        cls._order = self.frame.f_code.co_names
        return cls

def ordered():
    builder = Builder()
    def trace(frame, event, arg):
        builder.frame = frame
        sys.settrace(None)
    sys.settrace(trace)
    return builder

@ordered()
class A(object):
    x = 1
    y = 'foo'

print A._order # ['x', 'y']

这是解决第二个问题的创造性方法:

_undefined = object()

class A(object):

    def _foo0(self):
        pass

    def _foo1(self, x):
        pass

    def foo(self, x=_undefined):
        if x is _undefined:
            return self._foo0()
        else:
            return self._foo1(x)

但这比一个简单的元类(特别是第一个真正融化你的大脑)的伏都教更多.我的观点是,你认为元类是不熟悉和反直觉的,但你也可以将它们视为编程语言演变的下一步:你只需要调整你的思维方式.毕竟,你可能在C中做所有事情,包括用函数指针定义一个结构并将它作为第一个参数传递给它的函数.第一次看到C++的人可能会说,"这是什么魔法?为什么编译器会隐式传递this给方法,而不是传递给常规函数和静态函数?最好是对你的参数进行明确和冗长".但是,一旦你得到它,面向对象编程就会变得更加强大; 我猜是这样的,呃......面向方面的编程.一旦你理解了元类,它们实际上非常简单,那么为什么不在方便的时候使用它们呢?

最后,元类是rad,编程应该很有趣.使用标准的编程结构和设计模式总是令人厌烦和缺乏灵感,并且阻碍了你的想象力.坚持一下!这是一个metametaclass,只为你.

class MetaMetaclass(type):
    def __new__(meta, name, bases, attrs):
        def __new__(meta, name, bases, attrs):
            cls = type.__new__(meta, name, bases, attrs)
            cls._label = 'Made in %s' % meta.__name__
            return cls 
        attrs['__new__'] = __new__
        return type.__new__(meta, name, bases, attrs)

class China(type):
    __metaclass__ = MetaMetaclass

class Taiwan(type):
    __metaclass__ = MetaMetaclass

class A(object):
    __metaclass__ = China

class B(object):
    __metaclass__ = Taiwan

print A._label # Made in China
print B._label # Made in Taiwan


这是一个很好的答案,感谢您花时间编写并提供多个示例
对于Python 3也是这样,因为从A继承的类B(其元类为M)也是M的类型。因此,在对B求值时,将调用M来创建它,这实际上使您可以(在A的任何子类上工作)。话虽如此,Python 3.6引入了更为简单的[``init_subclass``](https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__),因此现在您可以在基类中操作子类了。 ,因此不再需要元类。

2> Brian..:

元类的目的不是用metaclass/class替换类/对象的区别 - 它是以某种方式改变类定义(以及它们的实例)的行为.实际上,改变类语句的行为的方式可能对您的特定域比默认值更有用.我用过的东西是:

跟踪子类,通常用于注册处理程序.这在使用插件样式设置时非常方便,您只希望通过子类化和设置一些类属性来为特定事物注册处理程序.例如.假设您为各种音乐格式编写处理程序,其中每个类为其类型实现适当的方法(播放/获取标记等).为新类型添加处理程序变为:

class Mp3File(MusicFile):
    extensions = ['.mp3']  # Register this type as a handler for mp3 files
    ...
    # Implementation of mp3 methods go here

然后,元类维护{'.mp3' : MP3File, ... }etc 的字典,并在通过工厂函数请求处理程序时构造适当类型的对象.

改变行为.您可能希望为某些属性附加特殊含义,从而导致存在时行为发生改变.例如,你可能想寻找一个名为方法_get_foo_set_foo和透明地转换为性能.作为一个真实的例子,这里是我写的一个配方,用于提供更多类似C的结构定义.元类用于将声明的项转换为结构格式字符串,处理继承等,并生成一个能够处理它的类.

对于其他实际示例,请查看各种ORM,例如sqlalchemy 的 ORM或sqlobject.同样,目的是解释具有特定含义的定义(此处为SQL列定义).


我更喜欢更具声明性的样式,而不是每个子类都需要额外的注册方法 - 如果所有内容都包含在一个位置,那就更好了.
嗯,是的,跟踪子类.但你为什么要这样呢?您的示例仅对register_music_file(Mp3File,['.mp3'])是隐式的,并且显式方式更具可读性和可维护性.这是我所说的不良案例的一个例子.
抱歉,我的一个conmments在SO超时场景中丢失了.我觉得陈述的阶级几乎是令人憎恶的.我知道人们喜欢它,这是公认的行为.但是(根据经验)我知道在你想要联合国声明事情的情况下它无法使用.取消注册一个类是*hard*.

3> 小智..:

我有一个处理非交互式绘图的类,作为Matplotlib的前端.但是,有时人们想要进行交互式绘图.只有几个函数,我发现我能够增加数字计数,手动调用绘图等,但我需要在每次绘图调用之前和之后执行这些操作.因此,要创建交互式绘图包装器和屏幕外绘图包装器,我发现通过元类包装适当的方法来执行此操作更有效,而不是执行以下操作:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

此方法无法跟上API更改等,但__init__在重新设置类属性之前迭代类属性的方法更有效并且保持最新:

class _Interactify(type):
    def __init__(cls, name, bases, d):
        super(_Interactify, cls).__init__(name, bases, d)
        for base in bases:
            for attrname in dir(base):
                if attrname in d: continue # If overridden, don't reset
                attr = getattr(cls, attrname)
                if type(attr) == types.MethodType:
                    if attrname.startswith("add_"):
                        setattr(cls, attrname, wrap_pylab_newplot(attr))
                    elif attrname.startswith("set_"):
                        setattr(cls, attrname, wrap_pylab_show(attr))

当然,可能有更好的方法来做到这一点,但我发现这是有效的.当然,这也可以在__new__或中完成__init__,但这是我发现最直接的解决方案.



4> Peter Rowell..:

让我们从蒂姆彼得的经典名言开始:

元类比99%的用户应该担心的更深刻.如果你想知道你是否需要它们,你就不会(实际需要它们的人确切地知道他们需要它们,并且不需要解释为什么).蒂姆彼得斯(clp post 2002-12-22)

话虽如此,我(定期)遇到了元类的真正用法.想到的是Django,你的所有模型都继承自models.Model.反过来,models.Model用Django的ORM优点来包装你的数据库模型.这种魔法通过元类来实现.它创建了各种异常类,管理器类等.

有关故事的开头,请参阅django/db/models/base.py,类ModelBase().


在阅读过蒂姆·彼得斯的话之后,时间表明他的陈述相当无益.直到在StackOverflow上研究Python元类之后,才明白如何实现它们.在强迫自己学习如何编写和使用元类之后,他们的能力使我惊讶并让我更好地理解了Python如何工作.类可以提供可重用的代码,元类可以为这些类提供可重用的增强功能.

5> jfs..:

元类可以方便地在Python中构建域特定语言.具体的例子是Django,SQLObject的数据库模式的声明性语法.

Ian Bicking的A Conservative Metaclass的一个基本例子:

我使用过的元类主要是为了支持一种声明式的编程风格.例如,考虑验证模式:

class Registration(schema.Schema):
    first_name = validators.String(notEmpty=True)
    last_name = validators.String(notEmpty=True)
    mi = validators.MaxLength(1)
    class Numbers(foreach.ForEach):
        class Number(schema.Schema):
            type = validators.OneOf(['home', 'work'])
            phone_number = validators.PhoneNumber()

其他一些技术:用Python构建DSL的成分(pdf).

编辑(由Ali):使用集合和实例执行此操作的示例是我更喜欢的.重要的事实是实例,它们可以为您提供更多功能,并消除使用元类的原因.进一步值得注意的是,您的示例使用了类和实例的混合,这肯定表明您不能只使用元类来完成所有操作.并创造了一种真正非均匀的方式.

number_validator = [
    v.OneOf('type', ['home', 'work']),
    v.PhoneNumber('phone_number'),
]

validators = [
    v.String('first_name', notEmpty=True),
    v.String('last_name', notEmpty=True),
    v.MaxLength('mi', 1),
    v.ForEach([number_validator,])
]

它并不完美,但已经几乎没有魔法,不需要元类,并且改善了均匀性.


第二个例子是丑陋的,因为你必须将验证器实例与它们的名称联系起来.一个稍微好一点的方法是使用字典而不是列表,但是,在python类中只是字典的语法糖,那么为什么不使用类呢?您也可以获得免费的名称验证,因为python babes不能包含字符串可以包含的空格或特殊字符.

6> 小智..:

元类使用的合理模式是在定义类时执行一次,而不是在实例化同一类时重复执行.

当多个类共享相同的特殊行为时,重复__metaclass__=X显然比重复特殊用途代码和/或引入ad-hoc共享超类更好.

但即使只有一个特殊的类,没有可预见的扩展, __new__并且__init__元类是一种更简洁的方法来初始化类变量或其他全局数据,而不是在类定义体中混合使用特殊用途代码和普通defclass语句.



7> Triptych..:

我在Python中使用元类的唯一一次是为Flickr API编写包装器.

我的目标是刮掉flickr的api站点并动态生成一个完整的类层次结构,以允许使用Python对象进行API访问:

# Both the photo type and the flickr.photos.search API method 
# are generated at "run-time"
for photo in flickr.photos.search(text=balloons):
    print photo.description

所以在那个例子中,因为我从网站生成了整个Python Flickr API,所以我真的不知道运行时的类定义.能够动态生成类型非常有用.


即使你不知道它,你也可以使用元类.type是一个元类,实际上是最常见的一个.:-)
您可以在不使用元类的情况下动态生成类型.>>>帮助(类型)

8> David Raznic..:

我昨天也在考虑同样的事情并完全同意.由于试图使其更具说明性而导致的代码复杂性通常会使代码库更难维护,更难以阅读并且在我看来更少pythonic.它通常还需要大量copy.copy()ing(以保持继承并从一个类复制到实例)并且意味着你必须在许多地方查看最新情况(总是从元类向上看)这与蟒蛇纹也.我一直在挑选formencode和sqlalchemy代码,看看这样的声明风格是否值得,而且显然不是.这种样式应该留给描述符(例如属性和方法)和不可变数据.Ruby对这种声明性样式有更好的支持,我很高兴核心python语言不会沿着那条路走下去.

我可以看到它们用于调试,为所有基类添加元类以获得更丰富的信息.我也看到他们只在(非常)大型项目中使用以摆脱一些样板代码(但是在清晰度方面).sqlalchemy 例如在其他地方使用它们,根据类定义中的属性值向所有子类添加特定的自定义方法,例如玩具示例

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

可能有一个元类,它在该类中生成一个方法,该方法具有基于"hello"的特殊属性(比如在字符串末尾添加"hello"的方法).可维护性确保您不必在您创建的每个子类中编写方法,而您必须定义的是method_maker_value.

对此的需求是如此罕见,只是减少了一些打字,除非你有足够大的代码库,否则它不值得考虑.

推荐阅读
Life一切安好
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有