Как получить атрибут метода setter свойства в python
Пожалуйста, рассмотрите код ниже
class DataMember():
def __init__(self, **args):
self.default = {"required" : False , "type" : "string" , "length": -1}
self.default.update(args)
def __call__(self , func):
#Here I want to set the attribute to method
setattr(func , "__dbattr__" , self.default)
def validate(obj , value):
#some other code
func(obj , value)
return validate
Это мой метод декоратора для украшения метода установки свойства другого класса, я хочу установить некоторый атрибут метода. но это не позволяет мне
Я пробовал как ниже
class User(DbObject):
def __init__(self):
super(User , self)
self._username = None
@property
def Name(self):
return self._username
@Name.setter
@DataMember(length=100)
def Name(self , value):
self._username = value
u = User()
u.Name = "usernameofapp"
print(u.Name)
print(u.Name.__dbattr__)
Получил приведенную ниже ошибку при запуске этого
Traceback (most recent call last):
File "datatypevalidator.py", line 41, in <module>
print(u.Name.__dbattr__)
AttributeError: 'str' object has no attribute '__dbattr__'
Что я делаю не так, и как я могу установить какой-либо атрибут для метода установки.
3 ответа
ОК, так что здесь есть три путаницы. Идентификация объекта, протоколы дескриптора и динамические атрибуты.
Во-первых, вы назначаете __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 вокруг строки, которая допускает случайное присваивание)
В конечном счете, если то, что вы изначально хотели, было в конечном итоге невозможно. Однако использование пользовательского строкового класса делает это так.
Вам нужно установить атрибут в функции- обертке, который возвращается методом вызова вашего класса декоратора:
class DataMember():
def __init__(self, **args):
self.default = {"required" : False , "type" : "string" , "length": -1}
self.default.update(args)
def __call__(self , func):
#Here I want to set the attribute to method
def validate(obj , value):
#some other code
func(obj , value)
setattr(validate , "__dbattr__" , self.default)
return validate
class DbObject: pass
class User(DbObject):
def __init__(self):
super(User , self)
self._username = None
@property
def Name(self):
return self._username
@Name.setter
@DataMember(length=100)
def Name(self , value):
self._username = value
Но обратите внимание, что это не метод, так как в классе есть свойство, его экземпляры всегда будут возвращать только строку, возвращенную получателем. Чтобы получить доступ к установщику, вы должны сделать это косвенно через свойство, которое находится в классе:
u = User()
u.Name = "usernameofapp"
print(u.Name)
print(User.Name.fset.__dbattr__)
который печатает:
usernameofapp
{'required': False, 'type': 'string', 'length': 100}
Доступ __dbattr__
немного сложнее:
Во-первых, вам нужно получить свойство объекта:
p = u.__class__.__dict__['Name']
затем вернуть объект функции setter, названный validate
, который определяется внутри DataMember.__call__
:
setter_func = p.fset
затем верните основной User.Name(self , value)
Функциональный объект от закрытия setter_func
:
ori_func = setter_func.__closure__[0].cell_contents
теперь вы можете получить доступ __dbattr__
:
>>> ori_func.__dbattr__
{'required': False, 'type': 'string', 'length': 100}
но полезно ли это? Я не знаю. может быть, вы могли бы просто установить __dbattr__
на validate
функциональный объект, который возвращается DataMember.__call__
, как указали другие ответы.