问题:元类有哪些(具体的)用例?

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

我认为您几乎不需要使用元类。为什么?因为我认为如果您正在对一个类进行类似的操作,那么您可能应该对一个对象进行此操作。并进行了少量的重新设计/重构。

能够使用元类已经使很多地方的许多人将类用作某种二流对象,这对我来说似乎是灾难性的。用元编程代替编程吗?不幸的是,添加了类装饰器使它变得更加可以接受。

所以,我很想知道您在Python中对元类的有效(具体)用例。还是要启发一下为什么有时候变异类比变异对象更好。

我将开始:

有时在使用第三方库时,能够以某种方式对类进行更改很有用。

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

I have a friend who likes to use metaclasses, and regularly offers them as a solution.

I am of the mind that you almost never need to use metaclasses. Why? because I figure if you are doing something like that to a class, you should probably be doing it to an object. And a small redesign/refactor is in order.

Being able to use metaclasses has caused a lot of people in a lot of places to use classes as some kind of second rate object, which just seems disastrous to me. Is programming to be replaced by meta-programming? The addition of class decorators has unfortunately made it even more acceptable.

So please, I am desperate to know your valid (concrete) use-cases for metaclasses in Python. Or to be enlightened as to why mutating classes is better than mutating objects, sometimes.

I will start:

Sometimes when using a third-party library it is useful to be able to mutate the class in a certain way.

(This is the only case I can think of, and it’s not concrete)


回答 0

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

I have a class that handles non-interactive plotting, as a frontend to Matplotlib. However, on occasion one wants to do interactive plotting. With only a couple functions I found that I was able to increment the figure count, call draw manually, etc, but I needed to do these before and after every plotting call. So to create both an interactive plotting wrapper and an offscreen plotting wrapper, I found it was more efficient to do this via metaclasses, wrapping the appropriate methods, than to do something like:

class PlottingInteractive:
    add_slice = wrap_pylab_newplot(add_slice)

This method doesn’t keep up with API changes and so on, but one that iterates over the class attributes in __init__ before re-setting the class attributes is more efficient and keeps things up to date:

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))

Of course, there might be better ways to do this, but I’ve found this to be effective. Of course, this could also be done in __new__ or __init__, but this was the solution I found the most straightforward.


回答 1

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

我见过的大多数元类都执行以下两项操作之一:

  1. 注册(将类添加到数据结构中):

    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,您的Class都会在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 :(
  2. 重构(修改类属性或添加新属性):

    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)

除了难看之外,它的灵活性也更低:如果您想要有序的文字属性(如整数和字符串)怎么办?如果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,编程应该很有趣。始终使用标准的编程构造和设计模式既无聊又无济于事,并且阻碍了您的想象力。坚持一下!这是一个元数据类,仅供您使用。

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

编辑

这是一个很老的问题,但仍在投票中,因此我想添加一个指向更全面答案的链接。如果您想了解有关元类及其使用的更多信息,我刚刚在这里发表了一篇有关元类的文章。

I was asked the same question recently, and came up with several answers. I hope it’s OK to revive this thread, as I wanted to elaborate on a few of the use cases mentioned, and add a few new ones.

Most metaclasses I’ve seen do one of two things:

  1. Registration (adding a class to a data structure):

    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
    

    Whenever you subclass Model, your class is registered in the models dictionary:

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

    This can also be done with class decorators:

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

    Or with an explicit registration function:

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

    Actually, this is pretty much the same: you mention class decorators unfavorably, but it’s really nothing more than syntactic sugar for a function invocation on a class, so there’s no magic about it.

    Anyway, the advantage of metaclasses in this case is inheritance, as they work for any subclasses, whereas the other solutions only work for subclasses explicitly decorated or registered.

    >>> class B(A):
    ...     pass
    ...
    >>> models
    {'A': <__main__.A class at 0x...> # No B :(
    
  2. Refactoring (modifying class attributes or adding new ones):

    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
    

    Whenever you subclass Model and define some Field attributes, they are injected with their names (for more informative error messages, for example), and grouped into a _fields dictionary (for easy iteration, without having to look through all the class attributes and all its base classes’ attributes every time):

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

    Again, this can be done (without inheritance) with a class decorator:

    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')} :(
    

    Or explicitly:

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

    Although, on the contrary to your advocacy for readable and maintainable non-meta programming, this is much more cumbersome, redundant and error prone:

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

Having considered the most common and concrete use cases, the only cases where you absolutely HAVE to use metaclasses are when you want to modify the class name or list of base classes, because once defined, these parameters are baked into the class, and no decorator or function can unbake them.

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

This may be useful in frameworks for issuing warnings whenever classes with similar names or incomplete inheritance trees are defined, but I can’t think of a reason beside trolling to actually change these values. Maybe David Beazley can.

Anyway, in Python 3, metaclasses also have the __prepare__ method, which lets you evaluate the class body into a mapping other than a dict, thus supporting ordered attributes, overloaded attributes, and other wicked cool stuff:

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...>

You might argue ordered attributes can be achieved with creation counters, and overloading can be simulated with default arguments:

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)

Besides being much more ugly, it’s also less flexible: what if you want ordered literal attributes, like integers and strings? What if None is a valid value for x?

Here’s a creative way to solve the first problem:

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']

And here’s a creative way to solve the second one:

_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)

But this is much, MUCH voodoo-er than a simple metaclass (especially the first one, which really melts your brain). My point is, you look at metaclasses as unfamiliar and counter-intuitive, but you can also look at them as the next step of evolution in programming languages: you just have to adjust your mindset. After all, you could probably do everything in C, including defining a struct with function pointers and passing it as the first argument to its functions. A person seeing C++ for the first time might say, “what is this magic? Why is the compiler implicitly passing this to methods, but not to regular and static functions? It’s better to be explicit and verbose about your arguments”. But then, object-oriented programming is much more powerful once you get it; and so is this, uh… quasi-aspect-oriented programming, I guess. And once you understand metaclasses, they’re actually very simple, so why not use them when convenient?

And finally, metaclasses are rad, and programming should be fun. Using standard programming constructs and design patterns all the time is boring and uninspiring, and hinders your imagination. Live a little! Here’s a metametaclass, just for you.

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

Edit

This is a pretty old question, but it’s still getting upvotes, so I thought I’d add a link to a more comprehensive answer. If you’d like to read more about metaclasses and their uses, I’ve just published an article about it here.


回答 2

元类的目的不是用元类/类代替类/对象的区别,而是以某种方式改变类定义(及其实例)的行为。有效地是,以对默认域更有用的方式更改类语句的行为。我使用它们的目的是:

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

    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列定义)。

The purpose of metaclasses isn’t to replace the class/object distinction with metaclass/class – it’s to change the behaviour of class definitions (and thus their instances) in some way. Effectively it’s to alter the behaviour of the class statement in ways that may be more useful for your particular domain than the default. The things I have used them for are:

  • Tracking subclasses, usually to register handlers. This is handy when using a plugin style setup, where you wish to register a handler for a particular thing simply by subclassing and setting up a few class attributes. eg. suppose you write a handler for various music formats, where each class implements appropriate methods (play / get tags etc) for its type. Adding a handler for a new type becomes:

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

    The metaclass then maintains a dictionary of {'.mp3' : MP3File, ... } etc, and constructs an object of the appropriate type when you request a handler through a factory function.

  • Changing behaviour. You may want to attach a special meaning to certain attributes, resulting in altered behaviour when they are present. For example, you may want to look for methods with the name _get_foo and _set_foo and transparently convert them to properties. As a real-world example, here’s a recipe I wrote to give more C-like struct definitions. The metaclass is used to convert the declared items into a struct format string, handling inheritance etc, and produce a class capable of dealing with it.

    For other real-world examples, take a look at various ORMs, like sqlalchemy’s ORM or sqlobject. Again, the purpose is to interpret defintions (here SQL column definitions) with a particular meaning.


回答 3

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

元类是比99%的用户应该担心的更深的魔力。如果您想知道是否需要它们,则不需要(实际上需要它们的人肯定会知道他们需要它们,并且不需要解释原因)。蒂姆·彼得斯(clp post 2002-12-22)

话虽如此,我(定期地)遇到了元类的真实用法。我想到的是在Django中,所有模型都继承自models.Model。反过来,models.Model确实做了一些严肃的魔术,将您的数据库模型与Django的ORM优势包装在一起。这种魔术通过元类发生。它创建各种形式的异常类,管理器类等。

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

Let’s start with Tim Peter’s classic quote:

Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why). Tim Peters (c.l.p post 2002-12-22)

Having said that, I have (periodically) run across true uses of metaclasses. The one that comes to mind is in Django where all of your models inherit from models.Model. models.Model, in turn, does some serious magic to wrap your DB models with Django’s ORM goodness. That magic happens by way of metaclasses. It creates all manner of exception classes, manager classes, etc. etc.

See django/db/models/base.py, class ModelBase() for the beginning of the story.


回答 4

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

Ian Bicking的“ 保守的元类”中的一个基本示例:

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

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,])
]

这不是完美的,但是魔术几乎为零,不需要元类,并且一致性更高。

Metaclasses can be handy for construction of Domain Specific Languages in Python. Concrete examples are Django, SQLObject ‘s declarative syntax of database schemata.

A basic example from A Conservative Metaclass by Ian Bicking:

The metaclasses I’ve used have been primarily to support a sort of declarative style of programming. For instance, consider a validation schema:

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()

Some other techniques: Ingredients for Building a DSL in Python (pdf).

Edit (by Ali): An example of doing this using collections and instances is what I would prefer. The important fact is the instances, which give you more power, and eliminate reason to use metaclasses. Further worth noting that your example uses a mixture of classes and instances, which is surely an indication that you can’t just do it all with metaclasses. And creates a truly non-uniform way of doing it.

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,])
]

It’s not perfect, but already there is almost zero magic, no need for metaclasses, and improved uniformity.


回答 5

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

当多个类共享相同的特殊行为时,重复__metaclass__=X显然比重复专用代码和/或引入即席共享超类更好。

但是,即使只有一个特殊类且没有可预见的扩展,对于元类而言, __new__与在类定义主体中__init__混合专用代码和normal defclassstatement 相比,初始化类变量或其他全局数据是一种更干净的方法。

A reasonable pattern of metaclass use is doing something once when a class is defined rather than repeatedly whenever the same class is instantiated.

When multiple classes share the same special behaviour, repeating __metaclass__=X is obviously better than repeating the special purpose code and/or introducing ad-hoc shared superclasses.

But even with only one special class and no foreseeable extension, __new__ and __init__ of a metaclass are a cleaner way to initialize class variables or other global data than intermixing special-purpose code and normal def and class statements in the class definition body.


回答 6

我唯一在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,所以我真的不知道运行时的类定义。能够动态生成类型非常有用。

The only time I used metaclasses in Python was when writing a wrapper for the Flickr API.

My goal was to scrape flickr’s api site and dynamically generate a complete class hierarchy to allow API access using Python objects:

# 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

So in that example, because I generated the entire Python Flickr API from the website, I really don’t know the class definitions at runtime. Being able to dynamically generate types was very useful.


回答 7

就在昨天,我在想同样的事情,完全同意。我认为,尝试使声明式更具声明性而导致的代码复杂性通常会使代码库更难以维护,更难阅读且Python的使用率也更低。它通常也需要大量的copy.copy()ing(以保持继承并从类复制到实例),这意味着您必须在许多地方查看发生了什么(始终从元类向上查看),这与蟒纹也。我一直在浏览formencode和sqlalchemy代码,以查看这种声明式样式是否值得,而显然不值得。这种样式应留给描述符(例如属性和方法)和不可变数据。Ruby对这样的声明式样式提供了更好的支持,我很高兴核心python语言没有走这条路。

我可以看到它们在调试中的用途,可以将一个元类添加到您所有的基类中以获得更丰富的信息。我还看到它们仅在(非常)大型项目中使用,以摆脱一些样板代码(但有一点不清楚)。例如,sqlalchemy 确实在其他地方使用了它们,以基于所有子类的类定义中的属性值向所有子类添加特定的自定义方法,例如玩具示例

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

可能有一个元类,该元类在该类中生成了一个基于“ hello”的特殊属性的方法(例如,将“ hello”添加到字符串末尾的方法)。确保不必在您创建的每个子类中都编写方法,而对于您所要定义的只是method_maker_value,这对维护性有好处。

但是,对此的需求如此罕见,并且仅减少了一点打字,除非您有足够大的代码库,否则它根本不值得考虑。

I was thinking the same thing just yesterday and completely agree. The complications in the code caused by attempts to make it more declarative generally make the codebase harder to maintain, harder to read and less pythonic in my opinion. It also normally requires a lot of copy.copy()ing (to maintain inheritance and to copy from class to instance) and means you have to look in many places to see whats going on (always looking from metaclass up) which goes against the python grain also. I have been picking through formencode and sqlalchemy code to see if such a declarative style was worth it and its clearly not. Such style should be left to descriptors (such as property and methods) and immutable data. Ruby has better support for such declarative styles and I am glad the core python language is not going down that route.

I can see their use for debugging, add a metaclass to all your base classes to get richer info. I also see their use only in (very) large projects to get rid of some boilerplate code (but at the loss of clarity). sqlalchemy for example does use them elsewhere, to add a particular custom method to all subclasses based on an attribute value in their class definition e.g a toy example

class test(baseclass_with_metaclass):
    method_maker_value = "hello"

could have a metaclass that generated a method in that class with special properties based on “hello” (say a method that added “hello” to the end of a string). It could be good for maintainability to make sure you did not have to write a method in every subclass you make instead all you have to define is method_maker_value.

The need for this is so rare though and only cuts down on a bit of typing that its not really worth considering unless you have a large enough codebase.


回答 8

您永远不必绝对使用元类,因为您始终可以使用要修改的类的继承或聚合来构造一个可以满足您需要的类。

就是说,在Smalltalk和Ruby中,能够修改现有的类非常方便,但是Python不喜欢直接这样做。

关于Python元类化的一篇很好的DeveloperWorks文章可能会有所帮助。在维基百科的文章也还不错。

You never absolutely need to use a metaclass, since you can always construct a class that does what you want using inheritance or aggregation of the class you want to modify.

That said, it can be very handy in Smalltalk and Ruby to be able to modify an existing class, but Python doesn’t like to do that directly.

There’s an excellent DeveloperWorks article on metaclassing in Python that might help. The Wikipedia article is also pretty good.


回答 9

当多个线程尝试与它们进行交互时,某些GUI库会遇到麻烦。tkinter就是这样一个例子;尽管可以通过事件和队列显式处理问题,但以完全忽略该问题的方式使用该库要简单得多。看得出-元类的魔力。

在某些情况下,能够无缝地动态重写整个库,使其在多线程应用程序中按预期正常工作,这可能会非常有用。该safetkinter模块这是否与所提供的元类的帮助threadbox模块-事件和队列没有必要的。

它的一个整洁的方面threadbox是它不在乎它克隆什么类。它提供了一个示例,说明如果需要,元类可以如何触及所有基类。元类附带的另一个好处是它们也可以在继承类上运行。编写自己的程序-为什么不呢?

Some GUI libraries have trouble when multiple threads try to interact with them. tkinter is one such example; and while one can explicitly handle the problem with events and queues, it can be far simpler to use the library in a manner that ignores the problem altogether. Behold — the magic of metaclasses.

Being able to dynamically rewrite an entire library seamlessly so that it works properly as expected in a multithreaded application can be extremely helpful in some circumstances. The safetkinter module does that with the help of a metaclass provided by the threadbox module — events and queues not needed.

One neat aspect of threadbox is that it does not care what class it clones. It provides an example of how all base classes can be touched by a metaclass if needed. A further benefit that comes with metaclasses is that they run on inheriting classes as well. Programs that write themselves — why not?


回答 10

元类的唯一合法用例是防止其他管闲的开发人员接触您的代码。当一个管闲的开发人员掌握了元类并开始与您的类一起玩耍时,请投入另一个或两个级别以将其排除在外。如果这样不起作用,请开始使用type.__new__递归元类或者使用某种方案使用递归元类。

(用舌头写在脸颊上,但我已经看到这种混淆处理了。Django是一个很好的例子)

The only legitimate use-case of a metaclass is to keep other nosy developers from touching your code. Once a nosy developer masters metaclasses and starts poking around with yours, throw in another level or two to keep them out. If that doesn’t work, start using type.__new__ or perhaps some scheme using a recursive metaclass.

(written tongue in cheek, but I’ve seen this kind of obfuscation done. Django is a perfect example)


回答 11

元类不能代替编程!它们只是一个技巧,可以使某些任务自动化或变得更加优雅。一个很好的例子是Pygments语法高亮库。它具有一个称为的类,该类RegexLexer使用户可以将一组词法化规则定义为类上的正则表达式。元类用于将定义变成有用的解析器。

他们就像盐一样。过多使用很容易。

Metaclasses aren’t replacing programming! They’re just a trick which can automate or make more elegant some tasks. A good example of this is Pygments syntax highlighting library. It has a class called RegexLexer which lets the user define a set of lexing rules as regular expressions on a class. A metaclass is used to turn the definitions into a useful parser.

They’re like salt; it’s easy to use too much.


回答 12

我使用元类的方式是为类提供一些属性。举个例子:

class NameClass(type):
    def __init__(cls, *args, **kwargs):
       type.__init__(cls, *args, **kwargs)
       cls.name = cls.__name__

将在每个将元类设置为指向NameClass的类上放置name属性。

The way I used metaclasses was to provide some attributes to classes. Take for example:

class NameClass(type):
    def __init__(cls, *args, **kwargs):
       type.__init__(cls, *args, **kwargs)
       cls.name = cls.__name__

will put the name attribute on every class that will have the metaclass set to point to NameClass.


回答 13

这是次要用途,但是…我发现元类有用的一件事是每当创建子类时都调用一个函数。我将其编码为一个寻找__initsubclass__属性的元类:每当创建一个子类时,所有使用该方法定义该方法的父类都将被调用__initsubclass__(cls, subcls)。这允许创建一个父类,该父类随后在某个全局注册表中注册所有子类,在定义子类时对它们进行不变检查,执行后期绑定操作等…所有这些都无需手动调用函数创建自定义元类,履行这些单独的职责。

提醒您,我逐渐意识到这种行为的隐含魔力在某种程度上是不可取的,因为如果从上下文中查看类定义是意外的……因此,我不再使用该解决方案来处理其他严重问题__super为每个类和实例初始化一个属性。

This is a minor use, but… one thing I’ve found metaclasses useful for is to invoke a function whenever a subclass is created. I codified this into a metaclass which looks for an __initsubclass__ attribute: whenever a subclass is created, all parent classes which define that method are invoked with __initsubclass__(cls, subcls). This allows creation of a parent class which then registers all subclasses with some global registry, runs invariant checks on subclasses whenever they are defined, perform late-binding operations, etc… all without have to manually call functions or to create custom metaclasses that perform each of these separate duties.

Mind you, I’ve slowly come to realize the implicit magicalness of this behavior is somewhat undesirable, since it’s unexpected if looking at a class definition out of context… and so I’ve moved away from using that solution for anything serious besides initializing a __super attribute for each class and instance.


回答 14

最近,我不得不使用元类来帮助围绕填充有来自美国的人口普查数据的数据库表来声明性定义SQLAlchemy模型 http://census.ire.org/data/bulkdata.html的

IRE提供数据库外壳了人口普查数据表的,这些根据p012015,p012016,p012017等人口普查局的命名约定创建整数列。

我想要a)能够使用model_instance.p012017语法访问这些列,b)对我正在做的事情相当明确,c)不必在模型上明确定义数十个字段,因此我将SQLAlchemy的子类化为DeclarativeMeta一个迭代范围,列并自动创建与列对应的模型字段:

from sqlalchemy.ext.declarative.api import DeclarativeMeta

class CensusTableMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        table = 'p012'
        for i in range(1, 49):
            fname = "%s%03d" % (table, i)
            dict_[fname] = Column(Integer)
            setattr(cls, fname, dict_[fname])

        super(CensusTableMeta, cls).__init__(classname, bases, dict_)

然后,我可以将此元类用于模型定义,并访问模型上自动枚举的字段:

CensusTableBase = declarative_base(metaclass=CensusTableMeta)

class P12Tract(CensusTableBase):
    __tablename__ = 'ire_p12'

    geoid = Column(String(12), primary_key=True)

    @property
    def male_under_5(self):
        return self.p012003

    ...

I recently had to use a metaclass to help declaratively define an SQLAlchemy model around a database table populated with U.S. Census data from http://census.ire.org/data/bulkdata.html

IRE provides database shells for the census data tables, which create integer columns following a naming convention from the Census Bureau of p012015, p012016, p012017, etc.

I wanted to a) be able to access these columns using a model_instance.p012017 syntax, b) be fairly explicit about what I was doing and c) not have to explicitly define dozens of fields on the model, so I subclassed SQLAlchemy’s DeclarativeMeta to iterate through a range of the columns and automatically create model fields corresponding to the columns:

from sqlalchemy.ext.declarative.api import DeclarativeMeta

class CensusTableMeta(DeclarativeMeta):
    def __init__(cls, classname, bases, dict_):
        table = 'p012'
        for i in range(1, 49):
            fname = "%s%03d" % (table, i)
            dict_[fname] = Column(Integer)
            setattr(cls, fname, dict_[fname])

        super(CensusTableMeta, cls).__init__(classname, bases, dict_)

I could then use this metaclass for my model definition and access the automatically enumerated fields on the model:

CensusTableBase = declarative_base(metaclass=CensusTableMeta)

class P12Tract(CensusTableBase):
    __tablename__ = 'ire_p12'

    geoid = Column(String(12), primary_key=True)

    @property
    def male_under_5(self):
        return self.p012003

    ...

回答 15

似乎是一个合法的使用说明在这里 -重写Python的文档字符串与元类。

There seems to be a legitimate use described here – Rewriting Python Docstrings with a Metaclass.


回答 16

我不得不将它们一次用于二进制解析器,以使其更易于使用。您可以定义消息类,其中包含电线上存在的字段的属性。需要按照声明的顺序来订购它们,以从中构造最终的电汇格式。如果使用有序命名空间dict,则可以使用元类来实现。实际上,在元类的示例中:

https://docs.python.org/3/reference/datamodel.html#metaclass-example

但总的来说:如果您确实确实需要增加元类的复杂性,请仔细评估。

I had to use them once for a binary parser to make it easier to use. You define a message class with attributes of the fields present on the wire. They needed to be ordered in the way they were declared to construct the final wire format from it. You can do that with metaclasses, if you use an ordered namespace dict. In fact, its in the examples for Metaclasses:

https://docs.python.org/3/reference/datamodel.html#metaclass-example

But in general: Very carefully evaluate, if you really really need the added complexity of metaclasses.


回答 17

@Dan Gittik的回答很酷

最后的示例可以澄清很多事情,我将其更改为python 3并给出了一些解释:

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)

#China is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class China(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#Taiwan is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class Taiwan(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#A is a normal class and it's __new__ method would be changed by China(metaclass)
class A(metaclass=China):
    __metaclass__ = China

#B is a normal class and it's __new__ method would be changed by Taiwan(metaclass)
class B(metaclass=Taiwan):
    __metaclass__ = Taiwan


print(A._label)  # Made in China
print(B._label)  # Made in Taiwan
  • 一切都是对象,所以类是对象
  • 类对象是由元类创建的
  • 从类型继承的所有类都是元类
  • 元类可以控制类的创建
  • 元类也可以控制元类的创建(因此它可以永远循环)
  • 这是元编程…您可以在运行时控制类型系统
  • 再次,一切都是对象,这是一个统一的系统,类型创建类型,类型创建实例

the answer from @Dan Gittik is cool

the examples at the end could clarify many things,I changed it to python 3 and give some explanation:

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)

#China is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class China(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#Taiwan is metaclass and it's __new__ method would be changed by MetaMetaclass(metaclass)
class Taiwan(MetaMetaclass, metaclass=MetaMetaclass):
    __metaclass__ = MetaMetaclass

#A is a normal class and it's __new__ method would be changed by China(metaclass)
class A(metaclass=China):
    __metaclass__ = China

#B is a normal class and it's __new__ method would be changed by Taiwan(metaclass)
class B(metaclass=Taiwan):
    __metaclass__ = Taiwan


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

  • everything is object,so class is object
  • class object is created by metaclass
  • all class inheritted from type is metaclass
  • metaclass could control class creating
  • metaclass could control metaclass creating too(so it could loop for ever)
  • this’s metaprograming…you could control the type system at running time
  • again,everything is object,this’s a uniform system,type create type,and type create instance

回答 18

另一个用例是,当您希望能够修改类级别的属性并确保它仅影响手头的对象时。实际上,这意味着“合并”元类和类实例化的阶段,从而使您仅处理它们自己(唯一)种类的类实例。

当(出于对可读性多态性的考虑)我们要动态定义 property s时,该返回值(可能)是基于(经常更改的)实例级属性的计算结果的,而这只能在类级别完成,因此我还必须这样做在元类实例化之后和类实例化之前。

Another use case is when you want to be able to modify class-level attributes and be sure that it only affects the object at hand. In practice, this implies “merging” the phases of metaclasses and classes instantiations, thus leading you to deal only with class instances of their own (unique) kind.

I also had to do that when (for concerns of readibility and polymorphism) we wanted to dynamically define propertys which returned values (may) result from calculations based on (often changing) instance-level attributes, which can only be done at the class level, i.e. after the metaclass instantiation and before the class instantiation.


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。