Есть как минимум три способа сделать это:
Пусть метакласс проверяет все атрибуты и, если они являются одним из изменяемых (list
, dict
, set
и т. Д.), Заменяет атрибут дескриптором, который будет активирован при первом доступе и обновите экземпляр новой копией изменяемой версии.
Укажите дескриптор из (1) в качестве декоратора, который будет использоваться при написании класса.
- Пусть метакласс добавит свой собственный метод
__init__
в класс, который при запуске:
- называет оригинал
__init__
- затем проверяет наличие обязательных атрибутов
Минусы (по методике):
Дополнительные усилия требуются, если у класса есть изменяемый атрибут, который должен быть общим для всех экземпляров.
Атрибут в классе становится функцией в классе (возможно, деформация ума;)
- Переместить точку ошибки в экземпляр класса вместо определения класса.
Я предпочитаю (2), он дает полный контроль автору класса, упрощает те случаи, когда изменяемый атрибут уровня класса должен быть общим для всех экземпляров, и сохраняет ошибку при определении класса.
Вот дескриптор-декоратор:
class ReplaceMutable:
def __init__(self, func):
self.func = func
def __call__(self):
return self
def __get__(self, instance, owner):
if instance is None:
return self
result = self.func()
setattr(instance, self.func.__name__, result)
return result
и тестовый класс:
class Test:
@ReplaceMutable
def mutable():
return list()
Как это работает:
Так же, как property
, ReplaceMutable
является объектом дескриптора с тем же именем, что и атрибут, который он заменяет. В отличие от property
, он не определяет ни __set__
, ни __delete__
, поэтому, когда код пытается перепривязать имя (mutable
в приведенном выше тесте) в экземпляре Python, ему это разрешается. Эта же идея лежит в основе дескрипторов кэширования.
ReplaceMutable
украшает функцию (с именем требуемого атрибута), которая просто возвращает то, с чем должен быть инициализирован атрибут уровня экземпляра (пустой list
в примере выше). Поэтому при первом поиске атрибута в экземпляре он не будет найден в словаре экземпляров, и Python активирует дескриптор; дескриптор затем вызывает функцию для извлечения исходного объекта / данных / чего угодно, сохраняет его в экземпляре и затем возвращает его. При следующем обращении к этому атрибуту в этом экземпляре он будет в словаре экземпляра, и именно это будет использоваться.
Пример кода:
t1 = Test()
t2 = Test()
t1.mutable.append('one')
t2.mutable.append('two')
print(t1.mutable)
print(t2.mutable)