ОК, так что здесь есть три путаницы. Идентификация объекта, протоколы дескриптора и динамические атрибуты.
Прежде всего, вы назначаете __dbattr__
на func
.
def __call__(self , func):
func.__dbattr__ = self.default # you don't need setattr
def validate(obj , value):
func(obj , value)
return validate
Но это присвоение атрибута func
, который затем сохраняется только как член validate
, что, в свою очередь, заменяет func
в классе (это то, что в конечном итоге делают декораторы, заменяя одну функцию другой) , Таким образом, помещая эти данные в func
, мы теряем доступ к ним (ну без какого-либо серьезного хакерского доступа __closure__
). Вместо этого мы должны поместить данные в validate
.
def __call__(self , func):
def validate(obj , value):
# other code
func(obj , value)
validate.__dbattr__ = self.default
return validate
Теперь, u.Name.__dbattr__
работает? Нет, вы все еще получаете ту же ошибку, но данные теперь доступны. Чтобы найти его, нам нужно понять протокол дескриптора Python , который определяет, как работают свойства.
Прочитайте связанную статью для полного объяснения, но эффективно, @property
работает, создавая дополнительный класс с __get__
, __set__
и __del__
методами, которые, когда вы вызываете inst.property
, вы на самом деле вызываете inst.__class__.property.__get__(inst, inst.__class__)
(и аналогично для inst.property = value --> __set__
и del inst.property --> __del__
(). Каждый из них, в свою очередь, вызывает методы fget
, fset
и fdel
, которые являются ссылками на методы, которые вы определили в классе.
Таким образом, мы можем найти ваше __dbattr__
не на u.Name
(что является результатом User.Name.__get__(u, User)
, а на самом методе User.Name.fset
! Если подумать (трудно), это имеет смысл. метод вы надели его. Вы не указали значение результата!
User.Name.fset.__dbattr__
Out[223]: {'length': 100, 'required': False, 'type': 'string'}
Правильно, поэтому мы можем видеть, что эти данные существуют, но они не относятся к объекту, который мы хотим. Как мы можем получить это на этот объект? Ну, это на самом деле довольно просто.
def __call__(self , func):
def validate(obj , value):
# Set the attribute on the *value* we're going to pass to the setter
value.__dbattr__ = self.default
func(obj , value)
return validate
Это работает, только если в конечном итоге установщик возвращает значение, но в вашем случае это происходит.
# Using a custom string class (will explain later)
from collections import UserString
u = User()
u.Name = UserString('hello')
u.Name # --> 'hello'
u.Name.__dbattr__ # -->{'length': 100, 'required': False, 'type': 'string'}
Вам, наверное, интересно, почему я использовал пользовательский класс строк. Хорошо, если вы используете основную строку, вы увидите проблему
u.Name = 'hello'
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-238-1feeee60651f> in <module>()
----> 1 u.Name = 'hello'
<ipython-input-232-8529bc6984c8> in validate(obj, value)
6
7 def validate(obj , value):
----> 8 value.__dbattr__ = self.default
9 func(obj , value)
10 return validate
AttributeError: 'str' object has no attribute '__dbattr__'
str
объекты, как и большинство встроенных типов в python, не допускают случайного назначения атрибутов, как пользовательские классы python (collections.UserString
- это оболочка класса python вокруг строки, которая допускает случайное назначение).
Итак, в конечном счете, если то, что вы изначально хотели, было в конечном итоге невозможно. Однако использование пользовательского строкового класса делает это так.