如何获取 python 中 属性 的 setter 方法的属性

how to get the attribute of setter method of property in 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 的 属性 的 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__)

当 运行 this

时出现以下错误
Traceback (most recent call last):
File "datatypevalidator.py", line 41, in <module>
print(u.Name.__dbattr__)
AttributeError: 'str' object has no attribute '__dbattr__'

我做错了什么,我怎样才能将某些属性设置为 setter 方法。

好的,所以这里有三点混淆。对象标识、描述符协议和动态属性。

首先,您将 __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 的成员持有,而 validate 又替换了 class 中的 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 的 descriptor protocol,它定义了属性如何工作。

阅读链接的文章以获得完整的解释,但实际上,@property 通过使用 __get____set____del__ 方法制作额外的 class 来工作当你调用 inst.property 时,你实际做的是调用 inst.__class__.property.__get__(inst, inst.__class__)(和 inst.property = value --> __set__del inst.property --> __del__() 类似。它们中的每一个依次调用 fget , fsetfdel 方法是对您在 class.

中定义的方法的引用

所以我们可以找到你的 __dbattr__ 不在 u.Name 上(这是 User.Name.__get__(u, User) 的结果,而是在 User.Name.fset 方法本身!如果你考虑一下(hard),这个有道理,这是你放上去的方法,你没放上结果的值!

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

这仅在最终 setter returns 值时有效,但在您的情况下确实有效。

# 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'}

您可能想知道为什么我使用自定义字符串 class。好吧,如果你使用基本字符串,你就会看到问题

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 classes (collections.UserString是 python class 字符串包装器,允许随机分配)。

所以最终,你最初想要的是内置字符串是不可能的,但使用自定义 class 可以让你做到。

访问__dbattr__有点棘手:

首先,您需要获取 属性 对象:

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

然后取回setter函数对象,命名为validate,定义在DataMember.__call__:

setter_func = p.fset

然后从setter_func的闭包中取回底层的User.Name(self , value)函数对象:

ori_func = setter_func.__closure__[0].cell_contents

现在您可以访问 __dbattr__:

>>> ori_func.__dbattr__
{'required': False, 'type': 'string', 'length': 100}

但这有用吗?我不知道。正如其他答案所指出的,也许您可​​以在 DataMember.__call__ 返回的 validate 函数对象上设置 __dbattr__

您需要在装饰器 class 的调用方法 return 编辑的 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

但是请注意,它不是一种方法,因为 class 上有一个 属性,它的实例只会 return 一个字符串,第一个 return 由 getter 编辑。要访问 setter,您必须通过 属性 间接访问 class:

u = User()
u.Name = "usernameofapp"
print(u.Name)
print(User.Name.fset.__dbattr__)

打印:

usernameofapp
{'required': False, 'type': 'string', 'length': 100}