Python`描述符`

时间:2012-05-12 09:19:51

标签: python descriptor

描述符类如下:

class Des(object):
    def __get__(self, instance, owner): ...
    def __set__(self, instance, value): ...
    def __delete__(self, instance): ...

class Sub(object):
    attr = Des()

X = sub()

问题

  1. 我没有看到owner存在的意义,我该如何使用它?

  2. 要使attr为只读,我们不应省略__set__,而是定义它以捕获赋值并引发异常。因此X.attr = 123会失败,但__set__的参数不包含owner,这意味着我仍然可以Sub.attr = 123,对吗?

6 个答案:

答案 0 :(得分:4)

请参阅http://docs.python.org/reference/datamodel.html#implementing-descriptors

  

owner始终是所有者类,而instance是访问该属性的实例,或者通过所有者访问该属性时为None

您将使用owner的情况是创建 classproperty

class _ContentQueryProperty(object):
    def __get__(self, inst, cls):
        return Content.query.filter_by(type=cls.TYPE)

答案 1 :(得分:2)

您可以尝试这个示例:

# the descriptor protocol defines 3 methods:
#    __get__()
#    __set__()
#    __delete__()

# any class implementing any of the above methods is a descriptor
# as in this class
class Trace(object):
    def __init__(self, name):
        self.name = name

    def __get__(self, obj, objtype):
        print "GET:" + self.name + " = " + str(obj.__dict__[self.name])
        return obj.__dict__[self.name]

    def __set__(self, obj, value):
        obj.__dict__[self.name] = value
        print "SET:" + self.name + " = " + str(obj.__dict__[self.name])

# define the attributes of your class (must derive from object)
#  to be references to instances of a descriptor

class Point(object):
# NOTES:
# 1. descriptor invoked by dotted attribute access:  A.x or a.x
# 2. descripor reference must be stored in the class dict, not the instance dict
# 3. descriptor not invoked by dictionary access: Point.__dict__['x']

    x = Trace("x")
    y = Trace("y")

    def __init__(self, x0, y0):
        self.x = x0
        self.y = y0

    def moveBy(self, dx, dy):
        self.x = self.x + dx     # attribute access does trigger descriptor
        self.y = self.y + dy


# trace all getters and setters    
p1 = Point(15, 25)
p1.x = 20
p1.y = 35
result = p1.x
p2 = Point(16, 26)
p2.x = 30
p2.moveBy(1, 1)

答案 2 :(得分:1)

我遇到类似的困惑时遇到了这个问题,在我自己回答之后,在这里报告我的研究结果似乎是值得谨慎的。

正如ThiefMaster已经指出的那样,“owner”参数可以构建像classproperty这样的结构。有时,您希望类将方法屏蔽为非方法属性,并使用owner参数允许您使用普通描述符执行此操作。

但这只是问题的一半。至于“只读”问题,这是我发现的:

我首先在这里找到答案:http://martyalchin.com/2007/nov/23/python-descriptors-part-1-of-2/。我起初并不理解它,我花了大约五分钟来绕过它。最终说服我的是一个例子。

考虑最常见的描述符:property。让我们使用一个简单的示例类,其中包含一个属性计数,它是访问变量计数的次数。

class MyClass(object):
    def __init__(self):
        self._count = 0
    @property
    def count(self):
        tmp = self._count
        self._count += 1
        return tmp
    @count.setter
    def setcount(self):
        raise AttributeError('read-only attribute')
    @count.deleter
    def delcount(self):
        raise AttributeError('read-only attribute')

正如我们已经建立的那样,owner函数的__get__参数意味着当您在类级别访问该属性时,__get__函数会拦截getattr调用。碰巧的是,property的代码在类级别访问时只是returns the property itself,但它可以做任何事情(比如返回一些静态值)。

现在,想象如果__set____del__以相同的方式工作会发生什么。除了实例级别之外,__set____del__方法将拦截类级别的所有setattrdelattr调用。

因此,这意味着MyClass的“count”属性实际上是不可修改的。如果您习惯于使用静态编译语言(如Java)进行编程,那么这似乎不太有趣,因为您无法修改应用程序代码中的类。但在Python中,你可以。类被视为对象,您可以动态分配它们的任何属性。例如,假设MyClass是第三方模块的一部分,MyClass几乎完全适用于我们的应用程序(我们假设除了count的代码之外还有其他代码)除了我们希望计数方法的工作方式略有不同。相反,我们希望它始终为每个实例返回10。我们可以做到以下几点:

>>> MyClass.count = 10
>>> myinstance = MyClass()
>>> myinstance.count
10

如果__set__拦截了对setattr(MyClass, 'count')的调用,则无法真正更改MyClass。相反,setcount的代码会拦截它,不能对它做任何事情。唯一的解决方案是编辑MyClass的源代码。 (我甚至不确定你是否可以在子类中覆盖它,因为我认为在子类中定义它仍然会调用setattr代码。但我不确定,因为我们已经在处理在这里反事实,我真的没有办法测试它。)

现在,您可能会说,“这正是我想要的!我故意不希望我的用户重新分配我班级的属性!”对此,我只能说你想要的是使用天真的描述符是不可能的,我会指导你进行上述推理。允许重新分配类属性更符合当前的Python习语。

如果你真的,真的 想要制作一个只读的类属性,我认为不能告诉你如何。但是如果有解决方案,则可能涉及使用元类并创建元类property或修改 setattr 的元类代码delattr 即可。但这是Deep Magic,远远超出了这个答案的范围(以及我自己的Python能力)。

答案 3 :(得分:1)

就只读属性而言(参见上面的讨论),以下示例显示了它的完成方式:

############################################################
#
#    descriptors
#
############################################################

# define a class where methods are invoked through properties
class Point(object):
    def getX(self):
    print "getting x"
    return self._x

    def setX(self, value):
    print "setting x"
    self._x = value

    def delX(self):
    print "deleting x"
    del self._x

    x = property(getX, setX, delX)

p = Point()
p.x = 55    # calls setX
a = p.x     # calls getX
del p.x     # calls delX

# using property decorator (read only attributes)
class Foo(object):
    def __init__(self, x0, y0):
    self.__dict__["myX"] = x0
    self.__dict__["myY"] = y0

    @property
    def x(self):
    return self.myX

f = Foo(4,6)
print f.x
try:
    f.x = 77        # fails: f.x is read-only
except Exception,e:
    print e

答案 4 :(得分:0)

所有者只是实例的类,为方便起见而提供。您始终可以从实例计算它:

owner = instance.__class__

答案 5 :(得分:0)

  1. __set__方法应该更改实例上的属性。但是,如果您想要更改所有实例共享的属性并因此存在于类中,例如,是一个类属性,该怎么办?只有在您有权访问类时才能执行此操作,因此可以访问所有者参数。

  2. 是的,如果通过类分配属性,则可以覆盖属性/描述符。这是设计,因为Python是一种动态语言。

  3. 希望能回答这个问题,尽管很久以前就被问过了。