как получить атрибут метода 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__, как указывали другие ответы.