Как получить атрибут метода метода set в Python - PullRequest
0 голосов
/ 26 августа 2018

Пожалуйста, рассмотрите код ниже

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 ]

0 голосов
/ 28 августа 2018

доступ __dbattr__ немного сложнее:

сначала вам нужно получить объект свойства:

p = u.__class__.__dict__['Name']

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

0 голосов
/ 28 августа 2018

Вам необходимо установить атрибут для функции wrapper , которая возвращается методом вызова вашего класса декоратора:

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}
0 голосов
/ 28 августа 2018

ОК, так что здесь есть три путаницы. Идентификация объекта, протоколы дескриптора и динамические атрибуты.

Прежде всего, вы назначаете __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 вокруг строки, которая допускает случайное назначение).

Итак, в конечном счете, если то, что вы изначально хотели, было в конечном итоге невозможно. Однако использование пользовательского строкового класса делает это так.

...