def*_*ode 39 python oop design-patterns metaclass decorator
我希望在定义类时注册一个类实例.理想情况下,下面的代码可以解决问题.
registry = {}
def register( cls ):
registry[cls.__name__] = cls() #problem here
return cls
@register
class MyClass( Base ):
def __init__(self):
super( MyClass, self ).__init__()
Run Code Online (Sandbox Code Playgroud)
不幸的是,此代码会生成错误NameError: global name 'MyClass' is not defined.
#problem here我正在尝试实例化的一行是什么,MyClass但装饰者还没有返回,所以它不存在.
是不是在使用元类或类似的东西?
dap*_*wit 45
是的,元类可以做到这一点.元类' __new__方法返回类,所以只需在返回之前注册该类.
class MetaClass(type):
def __new__(cls, clsname, bases, attrs):
newclass = super(MetaClass, cls).__new__(cls, clsname, bases, attrs)
register(newclass) # here is your register function
return newclass
class MyClass(object):
__metaclass__ = MetaClass
Run Code Online (Sandbox Code Playgroud)
上一个示例适用于Python 2.x. 在Python 3.x中,定义MyClass略有不同(虽然MetaClass没有显示,因为它没有改变 - 除非你想要的话super(MetaClass, cls)可以成为super():)
#Python 3.x
class MyClass(metaclass=MetaClass):
pass
Run Code Online (Sandbox Code Playgroud)
从Python 3.6开始,还有一种新__init_subclass__方法(参见PEP 487)可以用来代替元类(感谢@matusko的下面的答案):
class ParentClass:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
register(cls)
class MyClass(ParentClass):
pass
Run Code Online (Sandbox Code Playgroud)
[编辑:修复缺失cls参数super().__new__()]
[编辑:添加了Python 3.x示例]
[编辑:修正了args到super()的顺序,并改进了3.x差异的描述]
[编辑:添加Python 3.6 __init_subclass__示例]
mat*_*sko 17
在python 3.6中引入了更简单的类创建定制(PEP 487).
一个
__init_subclass__初始化给定类的所有子类的钩子.
提案包括以下子类注册示例
class PluginBase:
subclasses = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.subclasses.append(cls)
Run Code Online (Sandbox Code Playgroud)
在此示例中,
PluginBase.subclasses将包含整个继承树中所有子类的普通列表.应该注意的是,这也适用于mixin类.
Ben*_*Ben 11
问题实际上并不是由您指定的行引起的,而是由方法中的super调用引起的__init__.如果您使用dappawit建议的元类,问题仍然存在; 这个答案的例子之所以如此,那就是dappawit通过省略Base类和super调用来简化你的例子.在以下示例中,既不工作ClassWithMeta也不DecoratedClass工作:
registry = {}
def register(cls):
registry[cls.__name__] = cls()
return cls
class MetaClass(type):
def __new__(cls, clsname, bases, attrs):
newclass = super(cls, MetaClass).__new__(cls, clsname, bases, attrs)
register(newclass) # here is your register function
return newclass
class Base(object):
pass
class ClassWithMeta(Base):
__metaclass__ = MetaClass
def __init__(self):
super(ClassWithMeta, self).__init__()
@register
class DecoratedClass(Base):
def __init__(self):
super(DecoratedClass, self).__init__()
Run Code Online (Sandbox Code Playgroud)
在这两种情况下问题都是一样的; 在创建类对象register之后,但在绑定到名称之前,调用该函数(由元类或直接作为装饰器).这是gnarly(在Python 2.x中)的地方,因为它要求你引用调用中的类,你只能通过使用全局名称并相信它将被绑定到该名称来合理地执行.调用调用的时间.在这种情况下,这种信任是错误的.supersupersuper
我认为元类是错误的解决方案.元类用于创建具有一些共同的自定义行为的类系列,正如类用于创建具有一些共同的自定义行为的实例族.你所做的只是在一个类上调用一个函数.您不会定义一个类来调用字符串上的函数,也不应该定义一个元类来调用类上的函数.
因此,问题是以下几者之间的基本不兼容性:(1)在类创建过程中使用钩子来创建类的实例,以及(2)使用super.
解决此问题的一种方法是不使用super.super解决了一个难题,但它引入了其他人(这是其中之一).如果你使用的是复杂的多重继承方案,那么super问题就比不使用的问题要好super,如果你继续使用那些使用的第三方类,super那么你必须使用它super.如果这两个条件都不成立,那么super用直接基类调用替换你的调用实际上可能是一个合理的解决方案.
另一种方法是不挂钩register创建类.register(MyClass)在每个类定义之后添加相当于@register在它们之前添加或__metaclass__ = Registered(或者你称之为元类的任何东西)添加到它们中.尽管如此,底部的一行不如自我记录的那么好,所以这感觉不太好,但实际上它可能是一个合理的解决方案.
最后,你可以转向令人不愉快的黑客,但可能会奏效.问题是在模块绑定到模块的全局范围之前正在查找名称.所以你可以作弊,如下:
def register(cls):
name = cls.__name__
force_bound = False
if '__init__' in cls.__dict__:
cls.__init__.func_globals[name] = cls
force_bound = True
try:
registry[name] = cls()
finally:
if force_bound:
del cls.__init__.func_globals[name]
return cls
Run Code Online (Sandbox Code Playgroud)
这是如何工作的:
__init__存在cls.__dict__(而不是它是否具有__init__属性,这将始终为真).如果它继承的__init__方法从另一个类,我们很可能罚款(因为超将已经以通常的方式绑定到它的名字),我们想说的话不起作用的神奇object.__init__,所以我们要避免试图如果该类使用默认值__init__.__init__方法并获取它的func_globals字典,这是全局查找(例如查找super调用中引用的类)的地方.这通常__init__是最初定义方法的模块的全局字典.这样一本词典是大约有cls.__name__只要插入它register的回报,所以我们只是早将其插入自己.register无论是作为装饰器调用还是通过元类调用(我仍然认为它不能很好地使用元类),这个版本都可以工作.有一些不起眼的情况会失败但是:
__init__方法,但继承了一个调用self.someMethod,并someMethod在课堂上被覆盖被定义,使一个super电话.可能不太可能.__init__方法可能最初在另一个模块中定义,然后通过__init__ = externally_defined_function在类块中进行在类中使用.func_globals但是,另一个模块的属性,这意味着我们的临时绑定将破坏该模块中该类名称的任何定义(oops).再一次,不太可能在这些情况下,您可以尝试添加更多黑客以使其更加强大,但Python的本质是这些黑客是可能的,并且不可能使它们绝对是防弹.
| 归档时间: |
|
| 查看次数: |
20747 次 |
| 最近记录: |