在Python中导入后自动实例化类

时间:2018-01-02 11:05:14

标签: python python-2.7 metaclass

我想在导入模块后自动在Python中实例化一些类,因此不需要直接实例化调用。 我的设置可以拆分为以下代码:

class MetaTest(type):

    _instances = {}

    def __init__(cls, name, bases, attrs):
        super(MetaTest, cls).__init__(name, bases, attrs)
        MetaTest._instances[name] = cls()


class Foo(object):

    __metaclass__ = MetaTest

    def __init__(self):
        super(Foo, self).__init__()

class Bar(Foo):

    def __init__(self):
        super(Bar, self).__init__()

super上的实例化失败了:

    super(Foo, self).__init__()
NameError: global name 'Foo' is not defined

不管怎样,怎么可能有这样做?

3 个答案:

答案 0 :(得分:2)

首先注意:

对于这种代码,使用Python 3更容易。在第一个解决方案和答案结束时更多关于此内容。

因此,类本身Foo的名称显然不会在class Foo的主体内部可用:此名称仅在完成类实例化后才绑定。

在Python 3中,有super()调用的无参数版本,它可以将一个引导到超类,而不需要对类本身的显式引用。这是语言为其可预测的机制所做的少数例外之一。它在编译时将数据绑定到super调用。但是,即使在Python 3中,也无法调用一个使用super的方法,同时该类仍然被异步化 - 即在从元类__new____init__方法返回之前。无参数super所需的数据在此之后默默绑定。

然而,在Python 3.6+中,有一种机制甚至可以排除元类:在创建类之后,将在超类上调用名为__init_subclass__的类方法,它可以正常使用(无参数)超级。这适用于Python 3.6:

class Base:

   _instances = {}

   def __init_subclass__(cls):
        __class__._instances[cls.__name__] = cls()

   ...

class Foo(Base):

    def __init__(self):
        super().__init__()

此处,__class__(不是obj.__class__)是另一个神奇的名称,可与无参数super一起使用,它始终引用使用它的主体中的类,而不是子类。

现在,关于其他方法的一些注意事项:元类本身中没有代码可以用于实例化一个类,并且需要在其名称中引用该类的类方法没有帮助。类装饰器也不起作用,因为名称仅在从装饰器返回后绑定。如果您在具有事件循环的框架内对系统进行编码,则可以在元类__init__上安排一个事件来创建一个类实例 - 这样可以工作,但是您需要在这样的上下文中进行编码( gobject,Qt,django,sqlalchemy,zope / pyramid,Python asyncIO / tulip是具有可以进行所需回调的事件系统的框架示例。

现在,如果您真的需要在Python 2中使用它,我认为最简单的方法是创建一个函数,在每个模块的页脚中调用,以“注册”最近创建的类。 一些事情:

class MetaTest(type):

    _instances = {}

def register():
    import sys
    module_namespace = sys._getframe(1).f_globals

    for name, obj in f_globals.items():
        if isinstance(obj, MetaTest):
            MetaTest._instances[obj.__name__] = obj()

再次:对这个“寄存器”函数的调用应该出现在每个模块的最后一行。

这里的“元”事物是内省调用函数本身的框架以自动获取其全局变量。您可以通过使globals()成为寄存器函数的必需显式参数,并使第三方更容易理解代码来摆脱它。

为什么选择Python 3

截至今天,Python 2距离End of Line还有2年。在过去的8年中,语言已经发生了很多改进,包括类和元类模型中的特性。我似乎很多人似乎坚持使用python 2,因为“python”在他们的Linux安装中链接到Python 2,他们认为这是默认设置。事实并非如此:它只是为了向后兼容 - 越来越多的Linux安装使用python3作为默认Python,通常与Python 2一起安装。对于您的项目,使用virtuaelnv创建的隔离环境更好。

答案 1 :(得分:2)

解决方案1:修改sys.modules

这是Python 2版本。 将类添加到其模块是有效的,因为它解决了当您使用super()时类仍未退出的问题:

import sys

class MetaTest(type):

    _instances = {}

    def __init__(cls, name, bases, attrs):
        setattr(sys.modules[cls.__module__], cls.__name__, cls)
        super(MetaTest, cls).__init__(name, bases, attrs)
        MetaTest._instances[name] = cls()


class Foo(object):

    __metaclass__ = MetaTest

    def __init__(self):
        print('init Foo')
        super(Foo, self).__init__()

class Bar(Foo):

    def __init__(self):
        print('init Bar')
        super(Bar, self).__init__()

print(MetaTest._instances)

输出:

init Foo
init Bar
init Foo
{'Foo': <__main__.Foo object at 0x10cef90d0>, 'Bar': <__main__.Bar object at 0x10cef9110>}

解决方案2:使用future

{2}库为Python 2提供了super(),其工作方式与Python 3相似,即没有参数。 这有助于:

from builtins import super

class MetaTest(type):

    _instances = {}

    def __init__(cls, name, bases, attrs):
        super(MetaTest, cls).__init__(name, bases, attrs)
        MetaTest._instances[name] = cls()


class Foo(object):

    __metaclass__ = MetaTest

    def __init__(self):
        print('init Foo')
        super().__init__()

class Bar(Foo):

    def __init__(self):
        print('init Bar')
        super().__init__()

print(MetaTest._instances)

输出:

init Foo
init Bar
init Foo
{'Foo': <__main__.Foo object at 0x1066b39d0>, 'Bar': <__main__.Bar object at 0x1066b3b50>}

答案 2 :(得分:0)

jsbueno说的是什么。我一直在努力在Python 2中完成这项工作,但没有取得多大成功。这是我最接近的:

class MetaTest(type):
    _instances = {}

    def __init__(cls, name, bases, attrs):
        super(MetaTest, cls).__init__(name, bases, attrs)
        MetaTest._instances[name] = cls()

class Foo(object):
    __metaclass__ = MetaTest

class Bar(Foo):
    pass

for name, obj in Bar._instances.items():
    print name, obj, type(obj)

<强>输出

Foo <__main__.Foo object at 0xb74626ec> <class '__main__.Foo'>
Bar <__main__.Bar object at 0xb746272c> <class '__main__.Bar'>

但是,如果您尝试提供FooBar __init__方法,那么由于jsbueno给出的原因,这一切都会崩溃。

这是一个Python 3.6版本,使用了更为成功的super无形式:

class MetaTest(type):
    _instances = {}

    def __init__(cls, name, bases, attrs):
        print('cls: {}\nname: {}\nbases: {}\nattrs: {}\n'.format(cls, name, bases, attrs))
        super().__init__(name, bases, attrs)
        MetaTest._instances[name] = cls()

class Foo(metaclass=MetaTest):
    def __init__(self):
        super().__init__()

class Bar(Foo):
    def __init__(self):
        super().__init__()

for name, obj in Bar._instances.items():
    print(name, obj, type(obj))

<强>输出

cls: <class '__main__.Foo'>
name: Foo
bases: ()
attrs: {'__module__': '__main__', '__qualname__': 'Foo', '__init__': <function Foo.__init__ at 0xb7192dac>, '__classcell__': <cell at 0xb718c2b4: MetaTest object at 0xb718d3cc>}

cls: <class '__main__.Bar'>
name: Bar
bases: (<class '__main__.Foo'>,)
attrs: {'__module__': '__main__', '__qualname__': 'Bar', '__init__': <function Bar.__init__ at 0xb7192d64>, '__classcell__': <cell at 0xb718c2fc: MetaTest object at 0xb718d59c>}

Foo <__main__.Foo object at 0xb712514c> <class '__main__.Foo'>
Bar <__main__.Bar object at 0xb71251ac> <class '__main__.Bar'>

我必须承认,我对自动创建自己的实例的整个概念不太满意:“明确比隐含更好”。我认为jsbueno建议使用注册功能是一个很好的妥协。