如何在定义类时自动注册一个类

我希望在定义类时注册一个类实例。理想情况下,下面的代码可以解决问题。
registry = {}

def register( cls ):
   registry[cls.__name__] = cls() #problem here
   return cls

@register
class MyClass( Base ):
   def __init__(self):
      super( MyClass, self ).__init__() 
不幸的是,此代码生成错误
NameError: global name 'MyClass' is not defined
。 发生了什么是在
#problem here
线我试图实例化一个
MyClass
但装饰者还没有返回,所以它不存在。 是不是在使用元类或类似的东西?     
已邀请:
是的,元类可以做到这一点。元类'
__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
上一个示例适用于Python 2.x.在Python 3.x中,
MyClass
的定义略有不同(而
MetaClass
没有显示,因为它没有变化 - 除非
super(MetaClass, cls)
可以变成
super()
如果你想要的话):
#Python 3.x

class MyClass(metaclass=MetaClass):
    pass
从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
[编辑:修复缺失
cls
参数为
super().__new__()
] [编辑:添加了Python 3.x示例] [编辑:修正了args到super()的顺序,并改进了3.x差异的描述] [编辑:添加Python 3.6
__init_subclass__
示例]     
从python 3.6开始,你不需要元类来解决这个问题 在python 3.6中引入了更简单的类创建定制(PEP 487)。   一个
__init_subclass__
钩子,用于初始化给定类的所有子类。 提案包括以下子类注册示例
class PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)
  在这个例子中,
PluginBase.subclasses
将包含一个简单的列表   整个继承树中的所有子类。应该注意到这一点   这也很适合作为mixin类。     
问题实际上并不是由您指定的线引起的,而是由
__init__
方法中的
super
调用引起的。如果您使用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__()
在这两种情况下问题都是一样的;在创建类对象之后,但在绑定到名称之前,调用
register
函数(由元类或直接作为装饰器)。这就是
super
变得粗糙的地方(在Python 2.x中),因为它要求你在
super
调用中引用类,你只能通过使用全局名称并相信它将被绑定到该名称来合理地做到这一点。到调用
super
调用时。在这种情况下,这种信任是错误的。 我认为元类是错误的解决方案。元类用于创建具有一些共同的自定义行为的类族,正如类用于创建具有一些共同的自定义行为的实例族。你所做的只是在一个类上调用一个函数。您不会定义一个类来调用字符串上的函数,也不应该定义一个元类来调用类上的函数。 因此,问题是:(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
这是如何工作的: 我们首先检查
__init__
是否在
cls.__dict__
(而不是它是否具有
__init__
属性,这将始终为真)。如果它从另一个类继承了一个
__init__
方法,我们可能会很好(因为超类已经以通常的方式绑定到它的名字),而我们即将做的魔法不适用于
object.__init__
所以我们想要如果班级使用默认值
__init__
,请避免尝试。 我们查找
__init__
方法并抓住它的
func_globals
字典,这是全局查找(例如查找
super
调用中提到的类)的地方。这通常是最初定义
__init__
方法的模块的全局字典。这样的字典即将在
register
返回时插入
cls.__name__
,所以我们只是提前插入它。 我们最终创建一个实例并将其插入到注册表中。这是在try / finally块中,以确保我们删除我们创建的绑定,无论创建实例是否抛出异常;这是不太可能的必要(因为99.999%的时间名称即将反弹),但最好保持像这样的奇怪魔法尽可能绝缘,以尽量减少有一天其他一些奇怪的魔法与之交互不良的可能性它。 这个版本的
register
无论是作为装饰器调用还是由元类调用(我仍然认为它不能很好地利用元类)。有一些不起眼的情况会失败但是: 我可以想象一个奇怪的类没有
__init__
方法但是继承了一个叫
self.someMethod
的方法,并且
someMethod
在被定义的类中被覆盖并且进行了
super
调用。可能不太可能。
__init__
方法可能最初在另一个模块中定义,然后通过在类块中执行
__init__ = externally_defined_function
在类中使用。但是,另一个模块的
func_globals
属性,这意味着我们的临时绑定会破坏该模块中该类名称的任何定义(oops)。再一次,不太可能 可能是其他我没有想到的奇怪案例。 在这些情况下,您可以尝试添加更多黑客以使其更加强大,但Python的本质是这些黑客都是可能的,并且不可能使它们绝对是防弹。     
直接调用Base类应该有效(而不是使用super()):
  def __init__(self):
        Base.__init__(self)
    
这里的答案在python3中对我不起作用,因为
__metaclass__
不起作用。 这是我的代码在定义时注册类的所有子类:
registered_models = set()

class RegisteredModel(type):
    def __new__(cls, clsname, superclasses, attributedict):
        newclass = type.__new__(cls, clsname, superclasses, attributedict)
        # condition to prevent base class registration
        if superclasses:
            registered_models.add(newclass)
        return newclass

class CustomDBModel(metaclass=RegisteredModel):
    pass

class BlogpostModel(CustomDBModel):
    pass

class CommentModel(CustomDBModel):
   pass

# prints out {<class '__main__.BlogpostModel'>, <class '__main__.CommentModel'>}
print(registered_models)
    

要回复问题请先登录注册