class AutonamingType(type):
def __init__(cls, name, bases, attrs):
for k,v in attrs.iteritems():
if getattr(v, '__autoname__', False):
v.name = k
class Autonamer(object):
__metaclass__ = AutonamingType
class Foo(object):
__autoname__ = True
def __init__(self, name=None):
self.name = name
def do_something(self):
if self.name is None:
raise ValueError('name is None')
# now, do something
元类系统可以将所有这些需求组合在一起,这样您的单元测试只需要指定标题,并且类“知道”它必须抓取<title>的页面,检查内容类型并检查响应代码。 这里有一个使用元类的例子。我们打印的信件有不同的页眉模板,这些模板被组装在一起。页脚可以使用相同的机制,但是使用mixins a la Python’s super() considered super!。这两种方法--组装类级属性和通过super链接__init__以外的方法--可以很好地一起工作。
def get_annos(v):
return getattr(v, "__annotations__", {})
class MailMeta(type):
def get_annos(cls: type, ancestors_in: list[type]):
res = {}
for acls in ancestors_in:
res.update(**get_annos(acls))
return res
def _calc_annotations(cls, li_bases):
""" concatenate the annotations for the class and ancestors """
annos = cls.get_annos(li_bases + [cls])
cls.annos = annos
def _concat_template(cls, varname, bases : list[type]):
"""this glues together some templates
assembling complex attributes from multiple classes is
something metaclasses are uniquely good at.
how to do this can be tricky though! "Unordered" attributes
like sets or dictionaries can be easier but use `copy`
liberally.
"""
li = []
cls_var = f"cls_{varname}"
for acls in bases:
v = getattr(acls, cls_var, "")
if not v:
continue
if v in li:
continue
li.append(v)
all_ = "".join(li)
setattr(cls, varname, all_)
def __init__(cls, name, bases, attrs, **kwargs):
"""assemble together some of the attributes from the mro"""
li_bases2current = [
v for v in list(reversed(cls.mro())) if not v is object and not v is cls
]
cls._calc_annotations(li_bases2current)
cls._concat_template("header", [cls] + list(reversed(li_bases2current)) )
super().__init__(name, bases, attrs)
class Mail(metaclass=MailMeta):
""" specify some base behavior"""
name : str # mail always need to state who it is for
salutation : str
cls_header : str = ""
template : str
footer : str = ""
def _set_annotations(self, **kwargs):
""""""
undefined = object()
for attrname in self.annos.keys():
v = kwargs.get(attrname,undefined)
if v is not undefined:
setattr(self, attrname, v)
else:
#maybe its set on the classes
v = getattr(self, attrname,undefined)
if v is undefined:
raise ValueError(f"{attrname} is undefined")
def __init__(self, **kwargs):
""" track which variables we were expecting"""
self._set_annotations(**kwargs)
self.fulltemplate = "\n".join([self.header,self.template,self.get_footer()])
def get_footer(self):
return f"\n{self.footer}"
def print(self):
print(f"\n{'-' * 40}\n{self.__class__.__name__}:\n")
di = dict(**vars(self.__class__))
di.update(**vars(self))
print(self.fulltemplate % (di))
class Email:
cls_header = "Dear %(salutation)s %(name)s,"
cls_footer = "Sincerely,"
class Spam(Email, Mail):
amount : int
template = "You have won a prize of $%(amount)s"
class Signature:
t_footer = "%(from_)s"
from_ : str
def get_footer(self):
return super().get_footer() + self.t_footer % vars(self)
class TrustedSpam(Signature, Spam):
"Trusted cuz signed"
template = "You have won $%(amount)s. This is a once in a lifetime opportunity."
def get_footer(self):
return super().get_footer() + "\nPlease trust me"
class SnailMailTrustedSpamForDoctors(TrustedSpam):
"Doctors like to be called Dr."
salutation = "Dr."
cls_header = "101 Honest Fellows Street\n\n"
try:
mail = Mail(saluation="Ms.", template="")
except (ValueError,) as exc:
print(f"ooops. forgot `name`. All good: {exc=} ✅")
try:
mail = Spam(salutation="Ms.", name="Johnson")
except (Exception,) as exc:
print(f"ooops. forgot `amount` on a Spam. All good: {exc=} ✅")
mail = Spam(salutation="Ms.", amount=2000, name="Johnson")
mail.print()
mail = TrustedSpam(salutation="Ms.", amount=2000, name="Johnson", from_="Joe Trustworthy")
mail.print()
mail = SnailMailTrustedSpamForDoctors(amount=10000, name="Gullible", from_="Joe Trustworthy")
mail.print()
并且输出:
ooops. forgot `name`. All good: exc=ValueError('name is undefined') ✅
ooops. forgot `amount` on a Spam. All good: exc=ValueError('amount is undefined') ✅
----------------------------------------
Spam:
Dear Ms. Johnson,
You have won a prize of $2000
----------------------------------------
TrustedSpam:
Dear Ms. Johnson,
You have won $2000. This is a once in a lifetime opportunity.
Joe Trustworthy
Please trust me
----------------------------------------
SnailMailTrustedSpamForDoctors:
101 Honest Fellows Street
Dear Dr. Gullible,
You have won $10000. This is a once in a lifetime opportunity.
Joe Trustworthy
Please trust me
9条答案
按热度按时间whlutmcx1#
如果你想让类对象(而不是类对象的示例)具有“特殊的定制行为”,元类是必不可少的,因为对象的行为依赖于对象的类型上的特殊方法,而类对象的类型恰恰是元类的同义词。
例如,如果您希望类对象X的“print X”发出“Time is now 8:46 am”(8:46 am,或者更一般地说,当前时间),这就必须意味着
type(x)
(AKA X的元类)有一个特殊的定制__str__
方法--类似地(使用各种适用的特殊方法),如果您希望为X + Y
(其中X和Y都是类对象)或X[23]
(其中X也是类对象)等表达式给予意义。大多数其他定制任务现在(在Python 2.6或更高版本中)更容易用类装饰器来实现,它可以在
class
语句结束后立即修改类对象,但还有一些情况是不可行的,因为修改必须在很早的时候进行,如果它们要产生任何效果的话(例如,设置或修改__slots__
)。在Python 3中,元类获得了一点额外的用处:元类现在可以选择性地指定要在
class
语句主体执行期间填充的Map对象(默认情况下,它是一个普通的dict
)。这允许保留和使用类主体中名称绑定的 * 顺序 (而普通的dict
会失去顺序),当类必须具有某种特定顺序的“字段”时(例如,将1:1Map到Cstruct
、CSV文件或DB表中的行等),这有时是很好的--在Python 2. 中,这必须被冗余地指定(通常使用一个额外的class属性,该属性是一个序列,因此保持顺序),而Python 3元类的这个特性允许删除冗余。uqjltbpv2#
为您的编程增加额外的灵活性:
但是根据这个Metaclass programming in Python,你可能(还)不需要它们
9rygscc13#
我经常使用元类,它们是工具箱中非常强大的工具,有时使用元类会比不使用元类更优雅,代码更少。
我发现自己最常使用元类的地方是在创建类的过程中对类属性进行后处理,例如,在适当的地方设置对象的
name
属性(就像Django ORM可能的工作方式):如果你把它作为一个工具,并且你正在使用一个类,这个类必须知道它的
name
,然后才能do_something()
:它可以使代码的其余部分在以下方面有所不同:
还有这个
从而减少重复(以及变量名和赋值名可能不同步的机会)。
zu0ti5jz4#
如果您正在寻找使用元类机制的示例,可以阅读django.forms的源代码。
表单定义的声明性风格是通过元类实现的。
83qze16e5#
它们很少被需要,但是在您想要向对象的基本行为添加行为的地方非常有用--比较面向方面编程,或者在持久性框架(如Hibernate)中完成的插装。
例如,您可能需要一个类来持久化或记录每个新对象。
agxfikkp6#
也许没有什么东西可以专门用元类来完成,但是对于一些人(包括我的)来说,这是一个可以使用的有趣的工具,只是要小心不要滥用,因为它可能很棘手。
例如,我在最近的一个项目中使用了元编程,那是一个OpenOffice计算表,它使用一些pyUNO宏生成一些包含信息的文件,其中一个表向用户显示要填充的信息,其他的表可以用来描述元素的种类和属性,然后用户可以选择元素的数量和类型。并生成文件。宏将根据每张表上的配置通过元编程创建一个类。然后,用户可以示例化每个类并生成对象。
它可以不用元编程来完成,但对我来说,使用元编程功能来完成它似乎是很自然的。
idv4meu87#
看一下Django源代码--例如元类被用来生成模型。
http://code.djangoproject.com/wiki/DynamicModels
在内部,Django使用元类来创建基于源代码中提供的类的模型,这意味着Django接收类的描述,并使用类的描述来创建模型,而不是将类作为实际的模型。
b1uwtaje8#
第一个评论者说元类here的使用是帮助他追踪已经发生了(很多)天的意外错误的“一块宝石”。
a9wyjsp79#
有两件事要记住:
super().foo()
支持多重继承和方法链接,甚至可以在类中添加函数,在运行时绑定到类。所以,大多数时候元类都有些矫枉过正。
然而,我发现它们非常有用的一件事是从多个类的属性(* 而不仅仅是方法 *)组装行为。
例如,将多个需求放在一起的验证框架:
元类系统可以将所有这些需求组合在一起,这样您的单元测试只需要指定标题,并且类“知道”它必须抓取
<title>
的页面,检查内容类型并检查响应代码。这里有一个使用元类的例子。我们打印的信件有不同的页眉模板,这些模板被组装在一起。页脚可以使用相同的机制,但是使用mixins a la Python’s super() considered super!。这两种方法--组装类级属性和通过super链接
__init__
以外的方法--可以很好地一起工作。并且输出: