【问题标题】:How can I use descriptors for non-static methods?如何将描述符用于非静态方法?
【发布时间】:2016-09-23 19:06:06
【问题描述】:

我知道我可以使用描述符来更改静态属性,就像它是普通属性一样。但是,当我尝试对普通类属性使用描述符时,我最终会更改它引用的对象,而不是对象中的值。

如果我有

正常使用,考虑到method(param)返回一个对象

class SomeClass():
    property = method(param)

我可以这样做:

instance = SomeClass()
instance.property = 3

并且能够由属性为实例的类处理该设置。

现在,如果我有

class SomeClass():
    def__init__(self):
        self.property = method(param)

我愿意:

instance = SomeClass()
instance.property = 3

该代码不起作用,我用 3 覆盖对由 method(param) 创建的对象的引用,而不是由描述符处理该设置。

有没有一种方法可以在没有静态方法的情况下使用描述符?本质上,我需要能够创建该类的多个实例,每个实例都有自己独特的属性,可以使用方便的描述符方法进行更改。这可能吗?

Python 版本:2.7

谢谢!

【问题讨论】:

    标签: python python-2.7


    【解决方案1】:

    描述符提供了一种简单的机制,可以将通常的绑定函数模式更改为方法。

    回顾一下,函数有一个__get__() 方法,因此当作为属性访问时它们可以转换为方法。非数据描述符将obj.f(*args) 调用转换为f(obj, *args)。调用 klass.f(*args) 变为 f(*args)

    此图表总结了绑定及其两个最有用的变体:

    Transformation  Called from an Object   Called from a Class
    function            f(obj, *args)           f(*args)
    staticmethod        f(*args)                f(*args)
    classmethod         f(type(obj), *args)     f(klass, *args)
    

    静态方法返回底层函数而不作任何更改。调用 c.f 或 C.f 相当于直接查找

    object.__getattribute__(c, "f") or object.__getattribute__(C, "f"). 因此,该函数可以从对象或类中以相同方式访问。

    静态方法的良好候选者是不引用 self 变量的方法。

    class RevealAccess(object):
        """A data descriptor that sets and returns values
           normally and prints a message logging their access.
        """
        def __init__(self, initval=None, name='var'):
            self.val = initval
            self.name = name
    
        def __get__(self, obj, objtype):
            print 'Retrieving', self.name
            return self.val
    
        def __set__(self, obj, val):
            print 'Updating', self.name
            self.val = val
    
    >>> class MyClass(object):
    ...     x = RevealAccess(10, 'var "x"')
    ...     y = 5
    ...
    >>> m = MyClass()
    >>> m.x
    Retrieving var "x"
    10
    >>> m.x = 20
    Updating var "x"
    >>> m.x
    Retrieving var "x"
    20
    >>> m.y
    5
    

    【讨论】:

    • 谢谢!我了解您演示的此应用程序。我只是想知道,例如,x 和 y(在您的代码中)是否可以是对象的属性(可通过 self. 访问),而不是类的静态变量。你说什么?
    【解决方案2】:

    考虑到method(param) 返回一个描述符,您可以使用如下属性手动调用它:

    class SomeClass(object):
    
        def __init__(self):
            self._descriptor = method(param)
    
        @property
        def my_attribute(self):
            return self._descriptor.__get__(self, self.__class__)
    
        @my_attribute.setter
        def my_attribute(self, value):
            self._descriptor.__set__(self, value)
    

    这将允许您创建特定于实例的描述符而不是类级描述符。

    我仍然看不出你这样做的原因,因为描述符通常应该是类级别的。这就是他们的观点。否则它们只是您可以使用属性调用的常规对象。

    【讨论】:

    • 基本上,我使用 selenium 和页面对象来为页面建模。页面对象模块是第三方的,使用具有静态属性的描述符(页面元素作为描述符,用户可以使用= 运算符将文本插入文本框)。但我需要能够同时实例化多个页面对象(2 个浏览器导航同一页面),每个对象都有自己的组成页面的元素实例。所以我需要进行调整以利用已经构建的模块。谢谢,今天晚些时候我会试试你的代码
    【解决方案3】:

    属性是“计算属性”。属性是使用描述符实现的。如果查找对象的属性并找到描述符对象,则描述符的 getter 函数将计算该值。

    此特殊规则仅在班级级别有效

    这就是为什么:

    class SomeClass():
        property = <a property object here>
    

    为所有SomeClass 实例定义一个属性(计算属性),但是:

    class SomeClass():
        def__init__(self):
            self.property = <a property object here>
    

    定义了一个普通的属性,它恰好是属性对象类型,但没有以任何方式进行特殊处理。它的 getter 和 setter 被忽略(当然,除非用户代码明确调用)。


    没有其他方法可以使用描述符和属性作为在类中而不是在实例中设置它们。

    更新 - 示例:

    小例子:每次都实例化一个新的子类,而不是创建几个实例,每个实例都有自己的描述符(这种方法不起作用)。

    class BaseClass:
        pass
    
    _class_number = 0 
    def class_factory(prop):
        global _class_number
        _class_number += 1
        return type('Class' + str(_class_number), (BaseClass,), dict(prop=prop))
    
    c1 = class_factory(property(fget=lambda self: "property1"))()
    print(c1.prop)
    c2 = class_factory(property(fget=lambda self: "property2"))()
    print(c2.prop)
    

    【讨论】:

      【解决方案4】:

      伙计们,

      感谢您的帮助。在许多很好的建议中,我决定包装 setget 方法,这样就失去了描述符的实用性,同时保持了为我完全编程的模块的实用性。我只是扩展了“PageElement”类并创建了两个新方法 get() 和 set(),它们调用了它们对应的实现:

      def get(self):
          return self.__get__(self.owner_page, self.owner_page.__class__)
      
      def set(self, value):
          self.__set__(self.owner_page, value) 
      

      感谢您的帮助!我睁大了眼睛,用我仍然不知道的语言看到了很多细节。我会继续挖掘。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-02
        • 2020-07-13
        • 2023-03-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多