问题:元类有哪些(具体的)用例?
我有一个喜欢使用元类的朋友,并定期提供它们作为解决方案。
我认为您几乎不需要使用元类。为什么?因为我认为如果您正在对一个类进行类似的操作,那么您可能应该对一个对象进行此操作。并进行了少量的重新设计/重构。
能够使用元类已经使很多地方的许多人将类用作某种二流对象,这对我来说似乎是灾难性的。用元编程代替编程吗?不幸的是,添加了类装饰器使它变得更加可以接受。
所以,我很想知道您在Python中对元类的有效(具体)用例。还是要启发一下为什么有时候变异类比变异对象更好。
我将开始:
有时在使用第三方库时,能够以某种方式对类进行更改很有用。
(这是我能想到的唯一情况,并不具体)
回答 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__
,但这是我发现最直接的解决方案。
回答 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 :(
重构(修改类属性或添加新属性):
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
编辑
这是一个很老的问题,但仍在投票中,因此我想添加一个指向更全面答案的链接。如果您想了解有关元类及其使用的更多信息,我刚刚在这里发表了一篇有关元类的文章。
回答 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列定义)。
回答 3
让我们从蒂姆·彼得的经典名言开始:
元类是比99%的用户应该担心的更深的魔力。如果您想知道是否需要它们,则不需要(实际上需要它们的人肯定会知道他们需要它们,并且不需要解释原因)。蒂姆·彼得斯(clp post 2002-12-22)
话虽如此,我(定期地)遇到了元类的真实用法。我想到的是在Django中,所有模型都继承自models.Model。反过来,models.Model确实做了一些严肃的魔术,将您的数据库模型与Django的ORM优势包装在一起。这种魔术通过元类发生。它创建各种形式的异常类,管理器类等。
有关故事的开头,请参见django / db / models / base.py,类ModelBase()。
回答 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,])
]
这不是完美的,但是魔术几乎为零,不需要元类,并且一致性更高。
回答 5
元类使用的合理模式是在定义类时执行一次操作,而不是在实例化相同类时重复执行操作。
当多个类共享相同的特殊行为时,重复__metaclass__=X
显然比重复专用代码和/或引入即席共享超类更好。
但是,即使只有一个特殊类且没有可预见的扩展,对于元类而言, __new__
与在类定义主体中__init__
混合专用代码和normal def
和class
statement 相比,初始化类变量或其他全局数据是一种更干净的方法。
回答 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,所以我真的不知道运行时的类定义。能够动态生成类型非常有用。
回答 7
就在昨天,我在想同样的事情,完全同意。我认为,尝试使声明式更具声明性而导致的代码复杂性通常会使代码库更难以维护,更难阅读且Python的使用率也更低。它通常也需要大量的copy.copy()ing(以保持继承并从类复制到实例),这意味着您必须在许多地方查看发生了什么(始终从元类向上查看),这与蟒纹也。我一直在浏览formencode和sqlalchemy代码,以查看这种声明式样式是否值得,而显然不值得。这种样式应留给描述符(例如属性和方法)和不可变数据。Ruby对这样的声明式样式提供了更好的支持,我很高兴核心python语言没有走这条路。
我可以看到它们在调试中的用途,可以将一个元类添加到您所有的基类中以获得更丰富的信息。我还看到它们仅在(非常)大型项目中使用,以摆脱一些样板代码(但有一点不清楚)。例如,sqlalchemy 确实在其他地方使用了它们,以基于所有子类的类定义中的属性值向所有子类添加特定的自定义方法,例如玩具示例
class test(baseclass_with_metaclass):
method_maker_value = "hello"
可能有一个元类,该元类在该类中生成了一个基于“ hello”的特殊属性的方法(例如,将“ hello”添加到字符串末尾的方法)。确保不必在您创建的每个子类中都编写方法,而对于您所要定义的只是method_maker_value,这对维护性有好处。
但是,对此的需求如此罕见,并且仅减少了一点打字,除非您有足够大的代码库,否则它根本不值得考虑。
回答 8
您永远不必绝对使用元类,因为您始终可以使用要修改的类的继承或聚合来构造一个可以满足您需要的类。
就是说,在Smalltalk和Ruby中,能够修改现有的类非常方便,但是Python不喜欢直接这样做。
关于Python元类化的一篇很好的DeveloperWorks文章可能会有所帮助。在维基百科的文章也还不错。
回答 9
当多个线程尝试与它们进行交互时,某些GUI库会遇到麻烦。tkinter
就是这样一个例子;尽管可以通过事件和队列显式处理问题,但以完全忽略该问题的方式使用该库要简单得多。看得出-元类的魔力。
在某些情况下,能够无缝地动态重写整个库,使其在多线程应用程序中按预期正常工作,这可能会非常有用。该safetkinter模块这是否与所提供的元类的帮助threadbox模块-事件和队列没有必要的。
它的一个整洁的方面threadbox
是它不在乎它克隆什么类。它提供了一个示例,说明如果需要,元类可以如何触及所有基类。元类附带的另一个好处是它们也可以在继承类上运行。编写自己的程序-为什么不呢?
回答 10
元类的唯一合法用例是防止其他管闲的开发人员接触您的代码。当一个管闲的开发人员掌握了元类并开始与您的类一起玩耍时,请投入另一个或两个级别以将其排除在外。如果这样不起作用,请开始使用type.__new__
递归元类或者使用某种方案使用递归元类。
(用舌头写在脸颊上,但我已经看到这种混淆处理了。Django是一个很好的例子)
回答 11
元类不能代替编程!它们只是一个技巧,可以使某些任务自动化或变得更加优雅。一个很好的例子是Pygments语法高亮库。它具有一个称为的类,该类RegexLexer
使用户可以将一组词法化规则定义为类上的正则表达式。元类用于将定义变成有用的解析器。
他们就像盐一样。过多使用很容易。
回答 12
我使用元类的方式是为类提供一些属性。举个例子:
class NameClass(type):
def __init__(cls, *args, **kwargs):
type.__init__(cls, *args, **kwargs)
cls.name = cls.__name__
将在每个将元类设置为指向NameClass的类上放置name属性。
回答 13
这是次要用途,但是…我发现元类有用的一件事是每当创建子类时都调用一个函数。我将其编码为一个寻找__initsubclass__
属性的元类:每当创建一个子类时,所有使用该方法定义该方法的父类都将被调用__initsubclass__(cls, subcls)
。这允许创建一个父类,该父类随后在某个全局注册表中注册所有子类,在定义子类时对它们进行不变检查,执行后期绑定操作等…所有这些都无需手动调用函数或创建自定义元类,履行这些单独的职责。
提醒您,我逐渐意识到这种行为的隐含魔力在某种程度上是不可取的,因为如果从上下文中查看类定义是意外的……因此,我不再使用该解决方案来处理其他严重问题__super
为每个类和实例初始化一个属性。
回答 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
...
回答 15
似乎是一个合法的使用说明在这里 -重写Python的文档字符串与元类。
回答 16
我不得不将它们一次用于二进制解析器,以使其更易于使用。您可以定义消息类,其中包含电线上存在的字段的属性。需要按照声明的顺序来订购它们,以从中构造最终的电汇格式。如果使用有序命名空间dict,则可以使用元类来实现。实际上,在元类的示例中:
https://docs.python.org/3/reference/datamodel.html#metaclass-example
但总的来说:如果您确实确实需要增加元类的复杂性,请仔细评估。
回答 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
- 一切都是对象,所以类是对象
- 类对象是由元类创建的
- 从类型继承的所有类都是元类
- 元类可以控制类的创建
- 元类也可以控制元类的创建(因此它可以永远循环)
- 这是元编程…您可以在运行时控制类型系统
- 再次,一切都是对象,这是一个统一的系统,类型创建类型,类型创建实例
回答 18
另一个用例是,当您希望能够修改类级别的属性并确保它仅影响手头的对象时。实际上,这意味着“合并”元类和类实例化的阶段,从而使您仅处理它们自己(唯一)种类的类实例。
当(出于对可读性和多态性的考虑)我们要动态定义 property
s时,该返回值(可能)是基于(经常更改的)实例级属性的计算结果的,而这只能在类级别完成,因此我还必须这样做,即在元类实例化之后和类实例化之前。