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

2024-03-29 13:36:54 发布

您现在位置: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

这是我的decorator方法,用来修饰其他类的property的setter方法,我想给这个方法设置一些属性。但这不允许我这么做。在

我试了一下

^{pr2}$

运行此时出现以下错误

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

我做错了什么?我如何为setter方法设置一些属性。在


Tags: to方法selfobjdefault属性valuedef
3条回答

访问__dbattr__有点棘手:

首先,需要获取property对象:

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

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

^{pr2}$

然后从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__,正如其他答案所指出的那样。在

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

首先,将__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上,我们就失去了对它的访问权(如果没有一些严重的黑客攻击__closure__访问)。相反,我们应该把数据放在validate。在

^{pr2}$

现在,u.Name.__dbattr__有用吗?不,您仍然会得到相同的错误,但是数据现在可以访问了。要找到它,我们需要理解python的descriptor protocol,它定义了属性如何工作。在

请阅读链接的文章以获得完整的解释,但实际上,@property的工作原理是使用__get____set__和{}方法创建一个附加的类,当您调用inst.property时,您实际要做的是调用inst.__class__.property.__get__(inst, inst.__class__)(和{}和{}()类似)。这些方法依次调用fgetfset和{}方法,它们是对类中定义的方法的引用。在

所以我们可以找到你的__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

这只在setter最终返回值时有效,但在您的例子中确实如此。在

# 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类包装器,它允许随机赋值)。在

因此,最终,您最初想要的东西是不可能的内置字符串,但使用自定义类允许您这样做。在

您需要在decorator类的call方法返回的包装器函数上设置属性:

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

但是请注意,它不是一个方法,因为类上有一个属性,它的实例只会返回一个字符串,即getter返回的字符串。要访问setter,必须通过类上的属性间接地执行此操作:

^{pr2}$

哪个打印:

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

相关问题 更多 >