Автоматическое создание экземпляра класса после импорта в Python
Я хочу автоматически создавать экземпляры некоторых классов в 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 ответа:
Решение 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
Библиотекаfuture предлагает as
super()
для Python 2, которая работает так же, как и для 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>}
Первое Примечание:
Для такого рода кода проще использовать Python 3. Подробнее об этом на первом решении и в конце ответа.
Таким образом, имя самого класса
Foo
, очевидно, не будет доступно внутри телаclass Foo
: это имя связывается только после того, как экземпляр класса полностью выполнен.В Python 3 Существует беспараметрическая версия вызова
super()
, которая может направить его в суперкласс без необходимости явного ссылка на сам класс. Это одно из немногих исключений, которые язык делает для своих очень предсказуемых механизмов. Он связывает данные с вызовомsuper
во время компиляции. Однако даже в Python 3 невозможно вызвать метод, который используетsuper
, пока класс все еще создается - то есть до возвращения из метакласса__new__
и__init__
методов. После этого данные, необходимые параметру без параметровsuper
, автоматически связываются.Однако в Python 3.6+ есть механизм это даже исключает метакласс: метод класса с именем
__init_subclass__
будет вызван на суперклассах после создания класса, и он может нормально использовать (без параметров) super. Это будет работать с 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()
Еще раз: вызов этой функции "register" должен происходить в последней строке каждого из ваших модулей.
В "мета" вещь здесь-это интроспекция фрейма самой вызывающей функции, чтобы автоматически получить ее глобальные переменные. Вы можете избавиться от него, сделав
globals()
необходимым явным параметром для функции register и сохранив код более легким для понимания третьими сторонами.Почему Python 3
На сегодняшний день Python 2 находится в 2 годах от конца строки. И за последние 8 лет в языке произошло много улучшений, включая функции в классе и метаклассе модели. Я думаю, что многие люди, похоже, придерживаются python 2, потому что, поскольку "python" связан с Python 2 в их установках Linux, они думают, что это по умолчанию. Это не так: он просто существует для обратной совместимости-все больше и больше Linux install используют python3 в качестве Python по умолчанию, обычно устанавливаемого вместе с Python 2. А для ваших проектов лучше использовать изолированную среду, созданную с помощью virtuaelnv.
Что сказал 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'>
Однако, если вы попытаетесь дать либо
Foo
, либоBar
__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 использовать функцию регистрации-хороший компромисс.