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

Другие вопросы по тегам