После небольшого количества экспериментов я нашел способ (в основном) делать то, что я хочу.
class DeferredAttribute:
""" A single attribute that has had its resolution deferred """
def __init__(self, fn):
"""fn - when this attribute is resolved, it will be set to fn() """
self.fn = fn
def __set_name__(self, owner, name):
DeferredAttribute.DEFERRED_ATTRIBUTES.add((owner, name, self))
@classmethod
def resolve_all(cls):
""" Resolves all deferred attributes """
for owner, name, da in cls.DEFERRED_ATTRIBUTES:
setattr(owner, name, da.fn())
cls.DEFERRED_ATTRIBUTES.clear()
Идиомы, используемые для этого:
class A:
@DeferredAttribute
def b():
return B()
class B:
a = A()
DeferredAttribute.resolve_all()
И это приводит кклассы A
и B
, точно такие же, как если бы вы запустили код
class A:
pass
class B:
a = A()
A.b = B()
Вывод: С другой стороны, это помогает организации кода, избегая повторения и локализации связанныхкод.
С другой стороны, это не оправдывает некоторые ожидания динамического программирования;пока не будет вызван resolve_deferred_attributes
, значение A.b
будет специальным значением, а не экземпляром B
.Кажется возможным частично исправить это, добавив соответствующие методы к DeferredAttribute
, но я не вижу способа сделать его идеальным.
Примечание редактора: Приведенный выше код заставляет мою IDE (PyCharm) кричать на меня с ошибкой, говоря, что def b():
должен принимать параметр (хотя он работает нормально).Если вы хотите, вы можете изменить ошибку на предупреждение, изменив код:
In the resolve_all method, change:
setattr(owner, name, da.fn())
->
fn = da.fn
if isinstance(fn, staticmethod):
setattr(owner, name, fn.__func__())
else:
setattr(owner, name, fn())
And in the use code, change:
@defer_attribute
def b():
...
->
@defer_attribute
@staticmethod
def b():
...
Я не нашел способа полностью исключить предупреждения, кроме их отключения.