What are some (concrete) use-cases for metaclasses?
我有一个朋友喜欢使用元类,并经常提供它们作为解决方案。
我认为您几乎不需要使用元类。为什么?因为我想如果你对一个类做类似的事情,你应该对一个对象做。一个小的重新设计/重构是有序的。
能够使用元类已经导致很多地方的很多人使用类作为某种二流对象,这对我来说是灾难性的。编程是否被元编程取代?不幸的是,添加了类装饰器使得它更容易被接受。
所以,我非常想知道您在Python中对元类的有效(具体)用例。或者启发我们为什么有时改变类比改变对象更好。
我将开始:
Sometimes when using a third-party
library it is useful to be able to
mutate the class in a certain way.
(这是我唯一能想到的情况,而且不具体)
最近有人问我同样的问题,我想出了几个答案。我希望可以重新激活这个线程,因为我想详细说明上面提到的一些用例,并添加一些新的用例。好的。
我见过的大多数元类都做两件事之一:好的。
注册(向数据结构添加类):好的。
1 2 3 4 5 6 7 8 9 | 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 |
每当您子类
1 2 3 4 5 6 7 8 9 | >>> class A(Model): ... pass ... >>> class B(A): ... pass ... >>> models {'A': <__main__.A class at 0x...>, 'B': <__main__.B class at 0x...>} |
这也可以通过类修饰符来实现:好的。
1 2 3 4 5 6 7 8 9 | models = {} def model(cls): models[cls.__name__] = cls return cls @model class A(object): pass |
或具有显式注册功能:好的。
1 2 3 4 5 6 7 8 9 | models = {} def register_model(cls): models[cls.__name__] = cls class A(object): pass register_model(A) |
实际上,这几乎是一样的:你提到类修饰器时很不情愿,但它实际上只是类上函数调用的语法甜头,所以没有什么魔力。好的。
无论如何,在这种情况下,元类的优点是继承,因为它们适用于任何子类,而其他解决方案只适用于显式修饰或注册的子类。好的。
1 2 3 4 5 | >>> class B(A): ... pass ... >>> models {'A': <__main__.A class at 0x...> # No B :( |
重构(修改类属性或添加新属性):好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 |
每当您对
1 2 3 4 5 6 7 8 | >>> class A(Model): ... foo = Integer() ... >>> class B(A): ... bar = String() ... >>> B._fields {'foo': Integer('A.foo'), 'bar': String('B.bar')} |
同样,这可以通过类修饰器(不继承)完成:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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')} :( |
或明确地:好的。
1 2 3 | class A(object): foo = Integer('A.foo') _fields = {'foo': foo} # Don't forget all the base classes' fields, too! |
尽管与您倡导可读和可维护的非元编程相反,这更为麻烦、冗余和容易出错:好的。
1 2 3 4 5 6 7 8 | class B(A): bar = String() # vs. class B(A): bar = String('bar') _fields = {'B.bar': bar, 'A.foo': A.foo} |
考虑到最常见和最具体的用例,您绝对必须使用元类的唯一情况是,当您想要修改类名或基类列表时,因为一旦定义了这些参数,这些参数就会被烘焙到类中,并且没有任何装饰器或函数可以将它们取消绑定。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 |
这可能在框架中很有用,用于在定义具有相似名称或不完整继承树的类时发出警告,但除了Trolling之外,我想不出实际更改这些值的原因。也许大卫比兹利可以。好的。
无论如何,在python 3中,元类也有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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'] |
nbsp;好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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...> |
您可能认为可以使用创建计数器实现有序属性,并且可以使用默认参数模拟重载:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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) |
nbsp;好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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) |
除了更难看之外,它也不那么灵活:如果你想要有序的文字属性,比如整数和字符串,怎么办?如果
以下是解决第一个问题的创造性方法:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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'] |
下面是解决第二个问题的创造性方法:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | _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++的人可能会说:"这是什么魔法?"为什么编译器隐式地将
最后,元类是RAD,编程应该很有趣。使用标准的编程结构和设计模式一直都是无聊和无趣的,并且阻碍了你的想象力。多活一点!这是一个元类,只为您。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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 |
好啊。
元类的目的不是用元类/类替换类/对象的区别,而是以某种方式改变类定义(及其实例)的行为。实际上,它是改变类语句的行为,其方式对于特定域可能比默认域更有用。我使用它们的目的是:
跟踪子类,通常用于注册处理程序。这在使用插件样式设置时很方便,您只需通过子类化和设置几个类属性,就可以注册特定事物的处理程序。假设您为各种音乐格式编写一个处理程序,其中每个类为其类型实现适当的方法(播放/获取标记等)。为新类型添加处理程序将变为:
1
2
3
4class 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的结构定义。元类用于将声明的项转换为结构格式字符串、处理继承等,并生成能够处理它的类。对于其他现实世界的例子,请看一下各种各样的窗体,比如SQLAlchemy的ORM或SQLObject。同样,目的是解释具有特定含义的定义(这里是SQL列定义)。
我有一个处理非交互式绘图的类,作为Matplotlib的前端。然而,有时人们想进行交互式绘图。只使用了几个函数,我发现我可以增加数字计数、手动调用绘图等,但我需要在每次绘图调用之前和之后执行这些操作。因此,为了创建一个交互式绘图包装器和一个屏幕外绘图包装器,我发现通过元类(包装适当的方法)这样做比执行以下操作更有效:
1 2 | class PlottingInteractive: add_slice = wrap_pylab_newplot(add_slice) |
这个方法不跟上API的变化等等,但是在重新设置类属性之前,迭代
1 2 3 4 5 6 7 8 9 10 11 12 | 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)) |
当然,也许有更好的方法可以做到这一点,但我发现这是有效的。当然,这也可以在
让我们从蒂姆·彼得的经典名言开始:
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)
说到这一点,我(定期)遇到了元类的真正用途。想到的是在Django中,所有模型都继承自models.model。模型。模型,反过来,做了一些严重的魔术,用Django的ORM美德包装您的数据库模型。这种魔力是通过元类产生的。它创建各种各样的异常类、管理器类等。
请参见django/db/models/base.py,class modelbase()了解故事的开头。
元类可以方便地在Python中构造特定于域的语言。具体的例子是Django,sqlObject的数据库模式声明性语法。
伊恩·比金的保守元类的一个基本例子:
The metaclasses I've used have been
primarily to support a sort of
declarative style of programming. For
instance, consider a validation
schema:
1 2 3 4 5 6 7 8 | 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):使用集合和实例执行此操作的一个示例是我喜欢的。重要的事实是实例,它给了您更多的能力,并消除了使用元类的原因。更值得注意的是,您的示例使用了类和实例的混合,这显然表明您不能只使用元类来完成这一切。并创造出一种真正不统一的方式。
1 2 3 4 5 6 7 8 9 10 11 | 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中唯一一次使用元类是在为FlickrAPI编写包装器时。
我的目标是抓取Flickr的API站点并动态生成完整的类层次结构,以允许使用Python对象访问API:
1 2 3 4 | # 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,所以我真的不知道运行时的类定义。能够动态生成类型非常有用。
我昨天也是这么想的,完全同意。在我看来,代码中的复杂之处在于试图使其更具声明性,这通常会使代码库更难维护、更难阅读,也会减少Python。它通常还需要大量的copy.copy()ing(以维护继承和从类到实例的复制),这意味着您必须在许多地方查看发生了什么(总是从元类向上看),这也违背了Python的粒度。我一直在挑选formEncode和sqlacalchemy代码,看看这样的声明性风格是否值得,显然不值得。这样的样式应该留给描述符(如属性和方法)和不可变的数据。Ruby对这种声明性风格有更好的支持,我很高兴核心的Python语言不会走这条路。
我可以看到它们用于调试,向所有的基类添加元类以获得更丰富的信息。我也看到它们只在(非常)大的项目中使用,以去掉一些样板代码(但失去了清晰性)。例如,SQLAlchemy在其他地方也使用它们,根据类定义中的属性值向所有子类添加特定的自定义方法。例如玩具例子
1 2 | class test(baseclass_with_metaclass): method_maker_value ="hello" |
可能有一个元类,它在该类中生成了一个具有基于"hello"的特殊属性的方法(比如在字符串末尾添加"hello"的方法)。确保您不必在每个子类中编写一个方法,这对可维护性很有好处,相反,您需要定义的只是方法 maker 值。
这种需求是非常罕见的,而且只减少了一些输入,除非您有足够大的代码库,否则不值得考虑。
您绝对不需要使用元类,因为您可以始终使用要修改的类的继承或聚合来构造一个执行所需操作的类。
也就是说,在Smalltalk和Ruby中,能够修改现有的类非常方便,但Python不喜欢直接这样做。
有一篇关于Python中元类化的优秀developerWorks文章可能会有所帮助。维基百科的文章也不错。
我使用元类的方法是为类提供一些属性。例如:
1 2 3 4 | class NameClass(type): def __init__(cls, *args, **kwargs): type.__init__(cls, *args, **kwargs) cls.name = cls.__name__ |
将把name属性放在将要将元类设置为指向name class的每个类上。
一些GUI库在多个线程尝试与它们交互时遇到问题。
在某些情况下,能够动态地重写整个库,从而使其在多线程应用程序中正常工作,这是非常有用的。safetkinter模块通过threadbox模块提供的元类(不需要事件和队列)来实现这一点。
元类不能取代编程!它们只是一个可以自动化或使某些任务更优雅的技巧。Pygments语法高亮显示库就是一个很好的例子。它有一个名为
它们就像盐,很容易用得太多。
元类唯一合法的用例是防止其他多管闲事的开发人员接触您的代码。一旦一个爱管闲事的开发人员掌握了元类并开始使用您的元类,就可以添加一两个级别来阻止它们。如果这不起作用,就开始使用
(在脸上写着"舌头",但我见过这种混淆。Django就是一个很好的例子)
这只是一个小用途,但是…我发现有一件事很有用,那就是在创建子类时调用一个函数。我将其编码为一个元类,它查找
请注意,我慢慢地意识到这种行为的隐式魔力有点不受欢迎,因为从上下文中看类定义是不可预料的…因此,除了为每个类和实例初始化
最近,我不得不使用一个元类来帮助声明性地在一个数据库表周围定义一个sqlachemy模型,该数据库表由来自http://census.ire.org/data/bulkdata.html的美国人口普查数据填充。
IRE为人口普查数据表提供数据库shell,该表根据人口普查局的p012015、p012016、p012017等命名约定创建整数列。
我希望a)能够使用
1 2 3 4 5 6 7 8 9 10 11 | 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_) |
然后,我可以将这个元类用于模型定义,并访问模型上自动枚举的字段:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 ... |
这里描述了一种合法的用法——用一个元类重写python docstring。
为了便于使用,我必须对二进制分析器使用它们一次。您可以定义一个消息类,该类具有线中存在的字段的属性。它们需要按照声明的方式进行排序,以从中构造最终的线格式。如果使用有序的名称空间dict,则可以对元类执行此操作。实际上,它在元类示例中有:
https://docs.python.org/3/reference/datamodel.html元类示例
但是总的来说:如果您真的需要增加元类的复杂性,请仔细评估。