как получить атрибут метода 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
Это мой метод декоратора, чтобы украсить метод setter свойства другого класса, я хочу установить некоторый атрибут метода. но мне это не позволяет.
Я попытался, как показано ниже
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__
to 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
, мы теряем доступ к нему (ну без каких-то серьезных hacky __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.__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'}
вам, вероятно, интересно, почему я использовал пользовательский класс 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 вокруг строки, которая позволяет случайное назначение).
Итак, в конечном счете, если то, что вы изначально хотели, было в конечном счете невозможно. Однако использование пользовательского класса string делает это так.
вам нужно установить атрибут фантик функция, которая возвращается методом вызова вашего класса декоратора:
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__
на the validate
объект функции, возвращаемый DataMember.__call__
, как указывали другие ответы.