你的(具体的)用途是什么?

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

我记住你几乎不需要使用元类。 为什么? 因为我认为如果你正在为一个类做类似的事情,你应该把它做到一个对象。 而一个小的重新设计/重构是为了。

能够使用元类已经导致许多地方的许多人将类用作某种二级对象,这对我来说似乎是灾难性的。 编程是否被元编程所取代? 不幸的是,类装饰器的添加使其更加可以接受。

所以请,我非常想知道你在Python中对元类的有效(具体)用例。 或者,为了让人们明白为什么变异类比变异对象更好,有时候。

我将开始:

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

(这是我能想到的唯一情况,它不是具体的)


我有一个处理非交互式绘图的类,作为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__ ,但这是我找到的最直接的解决方案。


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

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

  • 注册(向数据结构中添加一个类):

    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
    

    这对于每当定义具有相似名称或不完全继承树的类时都会发出警告的框架非常有用,但除了曳引实际更改这些值之外,我无法想到一个理由。 也许大卫比兹利可以。

    无论如何,在Python 3中,元类还有__prepare__方法,它可以让您将类体评估为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 [<function foo at 0x...>, <function foo at 0x...>] rather than <function foo at 0x...>
    

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

    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)
    

    除了更丑陋之外,它也不那么灵活:如果您想要订购字面属性,如整数和字符串,该怎么办? 如果Nonex的有效值,该怎么办?

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

    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,编程应该很有趣。 一直使用标准的编程结构和设计模式是无聊和不愉快的,并且阻碍了你的想象力。 坚持一下! 这是一个metameta类,只为你。

    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
    

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

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

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

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

  • 改变行为。 你可能想要给特定的属性附加一个特殊的含义,当它们出现时会导致行为改变。 例如,您可能想要查找名为_get_foo_set_foo方法,并将它们透明地转换为属性。 作为一个真实世界的例子,这里给出了一个我写给更多类C结构体定义的方法。 元类用于将声明的项目转换为结构格式字符串,处理继承等,并生成一个能够处理它的类。

    对于其他现实世界的例子,请看看各种ORM,例如sqlalchemy的ORM或sqlobject。 同样,目的是解释具有特定含义的定义(这里是SQL列定义)。

  • 链接地址: http://www.djcxy.com/p/23825.html

    上一篇: What are your (concrete) use

    下一篇: Is there a simple, elegant way to define singletons?