Python中的Descriptor

Python中的描述符

描述符的定义:

通常情况下,我们可以认为"假设对象的某个属性被绑定了(__get__, __set__, __delete__)这三个方法中的任意一个方法",那么我们称该属性为"描述符"
class Foo(object):
    def init(self, name, age):
        self.name = name
        self.age = age

foo = Foo("pizza", 18)
我们不能称 foo.name, foo.age 这两个属性为描述符,因为它们都没有绑定上面三个方法。
默认情况下, 对象的属性访问是通过get, set, delete这三个方法访问属性的字典__dict__来实现的。
比如说,a.x会首先查找a.__dict__[‘x‘], 如果没有找到则查找type(a).__dict__[‘x‘], 然后不断的往上查找直到metaclass(不包括metaclass)。
如下代码所示:
class Foo(object):
    country = "China"
    def __init__(self, name, age):
        self.name = name
        self.age = age

foo = Foo("pizza", 18)
print(foo.__dict__)    # {‘name‘: ‘pizza‘, ‘age‘: 18}
print(type(foo).__dict__)    # {‘__module__‘: ‘__main__‘, ‘country‘: ‘China‘, ‘__init__‘: <function Foo.__init__ at 0x103802488>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘Foo‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘Foo‘ objects>, ‘__doc__‘: None}
上面的代码中,如果print(foo.name)或者print(foo.age)或查找foo.__dict__, 如果print(foo.country)则会查找type(foo)既Foo.__dict__
如果查找过程中遇到描述符,那么Python解释器就会用描述符中的方法来替代查找顺序,到底是先查找对象的__dict__还是描述符,取决于描述符类型,我们将在下面的小节中演示。

描述符协议

descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
descr.__delete__(self, obj) --> None
如果定义了以上三个方法中的任意一个,那么,我们就可以认为该对象是一个描述符对象,它会覆盖对象属性的查找顺序。
如下代码所示:
class Bar(object):
    def __get__(self, instance, owner):
        print("__get__")

    def __set__(self, instance, value):
        print("__set__")

    def __delete__(self, instance, value):
        print("__delete__")

class Foo(object):
    bar = Bar()

foo = Foo()
以上代码中,foo的bar属性就被认为是一个描述符。
上文提到了描述符类型,描述符分为,Data Descriptor和Non-data Descriptor。
如果一个对象定义了__get__()和__set__()这两个方法,那么我们认为该对象是一个Data Descriptor。如果只定义了__get__()方法,那就是Non-data Descriptor,如下代码所示:

Data Descriptor

class Bar(object):
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("__set__")

    def __delete__(self, instance, value):
        print("__delete__")

class Foo(object):
    bar = Bar()

foo = Foo()
以上代码中,foo的bar属性就被认为是一个描述符,而且是Data Descriptor。

Non-data Descriptor

class Bar(object):
    def __get__(self, instance, owner):
        print("__get__")

class Foo(object):
    bar = Bar()

foo = Foo()
以上代码中,foo的bar属性就被认为是一个描述符,而且是Non-data Descriptor。
Data and non-data descriptors的不同点在于访问对象属性的方式。
如果对象的字典__dict__中有一个跟Data Descriptor同名的属性,那么,Data Descriptor会覆盖__dict__的查找,如下代码所示:
class Bar(object):
    def __get__(self, instance, owner):
        print("__get__")

    def __set__(self, obj, value):
        print("__set__")

class Foo(object):
    bar = Bar()
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.bar = "bar"

foo = Foo("pizza", 18)
foo.bar    # __get__
以上代码中,foo对象的bar属性查找会执行对象的__get__方法。因为,Data Descriptor会覆盖__dict__的查找。
如果对象的字典__dict__中有一个跟Non-data Descriptor同名的属性,那么,对象的__dict__查找会覆盖Non-data Descriptor,如下代码所示:
class Bar(object):
    def __get__(self, instance, owner):
        print("__get__")

class Foo(object):
    bar = Bar()
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.bar = "bar"

foo = Foo("pizza", 18)
foo.bar    # "bar"
以上代码中,foo对象的bar属性查找会打印“bar”,因为,对象的__dict__查找会覆盖Non-data Descriptor。

Python中默认的property

在Python面向对象的设计中,有一个非常重要的知识点,叫做property,它的实现方式有多种,我们通过下面的代码演示其中一种:
class Foo(object):
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

foo = Foo("Pizza")
print(foo._name)    # "Pizza"
print(foo.name)    # "Pizza"
在上面的代码示例中,我们使用foo._name能够找到该属性的值”Pizza“,我们也可以通过foo.name找到该属性的值”Pizza“(因为该属性返回self._name)。
我们通过在Foo类中定义一个name方法,然后通过默认的装饰器property实现访问方法时,不进行常用的函数调用方式。
在这个过程中,存在一个疑问,既然希望通过属性的方式访问对象的方法,且返回值就是某个属性,那么为什么不直接在__init__里面定义一个属性。
其实,属性的更多的时候,是动态的获取某个值,并保留属性的访问方式,而不是简单的返回已存在的属性的值,比如:
class Foo(object):
    def __init__(self, name):
        self._name = name

    @property
    def stock(self):
        return 100 + 100

foo = Foo("Pizza")
print(foo._name)    # "Pizza"
print(foo.stock)    # 200
我们在前面的章节中提到过,Python中的property,static method,class method的实现都依赖于descriptor的机制来实现。
那么,接下来,我们来自定义一个property。

使用Descriptor自定义property

从上一小节中,我们可以看到,将类中的方法修改为一个property,就是利用了装饰器@property。我们知道装饰器语法糖@decorator,等价于 func = decorator(func),如下代码所示:
class Foo(object):
    def stock(self):
        return 100 + 100
    print(stock)    # <function Foo.stock at 0x101a57048>

class Foo(object):
    @property
    def stock(self):
        return 100 + 100
    print(stock)    # <property object at 0x103811c78>
上面的代码示例中,print(stock)的打印结果是<property object at 0x103811c78><function Foo.stock at 0x101a57048>,既,在stock方法上面加上@property之后,stock这个方法变为了property的对象,与第一个print(stock)的<function Foo.stock at 0x101a57048>不同。
接下来,我们的目的是通过descriptor来实现自定义property。
在实现自定义property之前,我们先假设有一个类,如下代码所示:
class Foo(object):
    def stock(self):
        return 100 + 100

foo = Foo()
foo.stock
我们已知如下几点:
  • 装饰器语法糖 @property等价于 stock = property(stock);
  • 描述符是一个类的实例化对象,如 bar = Bar(),然后在Bar这个类中定义了__get__, __set__, __delete__
我们的目的是,通过类似属性访问的方式(foo.stock)而非方法调用的方式(foo.stock()),获得返回值200。首选,我们通过描述符的方式,来实现简单的属性访问,如下代码所示:
class Stock(object):
    def __get__(self, instance, owner):
        print("__get__")
        return 100 + 100

class Foo(object):
    stock = Stock()

foo = Foo()
foo.stock
此时,通过访问foo.stock会先打印__get__, 然后显示200。那么,如果我们将stock变为类中的一个方法呢?如下代码所示:
class Stock(object):
    def __get__(self, instance, owner):
        print("__get__")
        return 100 + 100

class Foo(object):
    def stock(self):
        print("stock")
如果能将stock方法变为一个descriptor,那么我们就可以通过foo.stock访问该descriptor的__get__方法,然后获取其返回值,既,200。
我们知道,将属性变为descriptor,直接通过给该属性绑定__get__方法即可,如:stock = Stock(),但是,如何利用装饰器语法糖呢?我们知道,装饰器语法糖@Stock等价于stock = Stock(stock),因此,我们需要,在Stock这个类中定义一个__init__方法,并定义一个形参来接收Stock类实例化时传入的stock函数,如下代码所示:
class Stock(object):
    def __init__(self, stock):
        self.stock = stock

    def __get__(self, instance, owner):
        print("__get__")
        return 100 + 100

class Foo(object):
    @Stock    # stock = Stock(stock)
    def stock(self):
        print("stock")
通过以上,代码,我们就将Foo类中的stock方法,成功的变成了一个descriptor,接下来我们可以通过Foo类的实例化对象来访问stock方法,并且使用普通的属性调用方法,因为此时Foo类中的stock方法已经是一个descriptor了。
foo = Foo()
foo.stock
以上代码会先打印__get__, 然后显示200。
事实上,细心的同学会发现,如果采用这种实现方式,我们实现了自定义的property,但是,与官方正版的property还有差距,这个差距在于,访问foo.stock的时候,Foo类中的stock并没有被执行,而正版的property中的属性是被执行了的,也就是说,我们最后需要获取的值,是直接从该属性中计算来获得的,如下代码所示:
class Foo(object):
    @property
    def stock(self):
        return 100 + 100

foo = Foo()
foo.stock
在上面的代码示例中,我们通过foo.stock获取到的结果200,是通过Foo类中的stock这个方法来计算获得的。如果我希望在自定义的property中也采用同样的方式,该如何做呢?
我们知道,在定义__get__方法时,它接受三个参数,第一个self表示descriptor,下面我们,分别print第二个和第三个参数,看看它们分别表示什么:
class Stock(object):
    def __init__(self, stock):
        self.stock = stock

    def __get__(self, instance, owner):
        print("__get__")
        print("instance: ", instance)
        print("owner:", owner)
        return 100 + 100

class Foo(object):
    @Stock
    def stock(self):
        print("stock")

foo = Foo()
foo.stock
以上代码的执行结果如下:
__get__
instance:  <__main__.Foo object at 0x106c2b9b0>
owner: <class ‘__main__.Foo‘>
200
从以上代码的执行结果可以看出,instance和owner这两个形参,分别被传入了foo和Foo这两个对象,一个是Foo类的实例化对象,一个是Foo类本身,那么,我们是否可以使用foo或者Foo在__get__方法中,调用stock呢?
答案是否定的,因为此时的stock已经是一个descriptor了,如果在__get__方法中调用,那么就进入死循环了,一直重复的执行__get__方法。
最原始的那个Foo类中的stock方法,在进行@Stock时,被传入了Stock类中的__init__方法进行初始化,因此,此时我们只能通过如下代码示例中使用的方式,进行调用:
class Stock(object):
    def __init__(self, stock):
        self.stock = stock

    def __get__(self, instance, owner):
        return self.stock(instance)

class Foo(object):
    @Stock
    def stock(self):
        return 100 + 100

foo = Foo()
foo.stock
以上代码的执行结果如下:
200
通过结果我们可以看出,与之前的执行结果是一致的。简单修改为更能理解的代码示例,如下所示:
class myproperty(object):
    def __init__(self, stock):
        self.stock = stock

    def __get__(self, instance, owner):
        return self.stock(instance)

class Foo(object):
    @myproperty
    def stock(self):
        return 100 + 100

foo = Foo()
foo.stock
至此,我们实现了自定义的property。

原文地址:https://www.cnblogs.com/paulwhw/p/9445301.html

时间: 2024-10-13 08:39:37

Python中的Descriptor的相关文章

python中基于descriptor的一些概念(上)

@python中基于descriptor的一些概念(上) python中基于descriptor的一些概念(上) 1. 前言 2. 新式类与经典类 2.1 内置的object对象 2.2 类的方法 2.2.1 静态方法 2.2.2 类方法 2.3 新式类(new-style class) 2.3.1 __init__方法 2.3.2 __new__静态方法 2.4. 新式类的实例 2.4.1 Property 2.4.2 __slots__属性 2.4.3 __getattribute__方法

python中基于descriptor的一些概念(下)

@python中基于descriptor的一些概念(下) 3. Descriptor介绍 3.1 Descriptor代码示例 3.2 定义 3.3 Descriptor Protocol(协议) 3.4 Descriptor调用方法 4. 基于Descriptor实现的功能 4.1 property 4.2 函数和方法,绑定与非绑定 4.3 super 5. 结尾 3. Descriptor介绍 3.1 Descriptor代码示例 class RevealAccess(object):   

python 中关于descriptor的一些知识问题

这个问题从早上日常扫segmentfault上问题开始 有个问题是 class C(object): @classmethod def m(): pass m()是类方法,调用代码如下: C.m() 但我想当成属性的方式调用,像这样: C.m 请问该怎么弄呢? 请最好提供个简单的例子, 多谢! 这里我开始误会了他的意思,以为他是想直接使用C().m调用这个方法,如果是这样,直接将装饰器@classmathod改成@property就可以达到效果了. 但是这里他想要达到的效果是C.m 也就是说在不

python中的 descriptor

学好和用好python, descriptor是必须跨越过去的一个点,现在虽然Python书籍花样百出,但是似乎都是在介绍一些Python库而已,对Python语言本身的关注很少,或者即使关注了,但是能够介绍把 dscriptor介绍清楚的,是很少的,到目前,我自己还没有见到过. 一个attr能被称为descriptor,除了需要定义 descriptor protocol 规定的方法外,这个attr必须是属于某个class的,不能是属于某个instance 一.Python中的descript

Python中常见的文件对象内建函数

文件对象内建方法列表 文件对象的方法 操作 file.close() 关闭文件 file.fileno() 返回文件的描述符(file descriptor,FD,整数值) file.flush() 刷新文件的内部缓冲区 file.isatty() 判断file是否是一个类设tty备 file.next() 返回文件的下一行,或在没有其它行时引发StopIteration异常 file.read(size=-1) 从文件读取size个字节,当未给定size或给定负值时读取剩余的所有字节,然后作为

Python中的socket如何使用?

本文和大家分享的主要是python 中socket相关内容,一起来看看吧,希望对大家 学习python有所帮助. 一. socket模块 socket ,俗称套接字,其实就是一个 ip 地址和端口的组合.类似于这样的形式 (ip,  port), 其中 ip 代表的是某个主机, port 代表的是某个应用,我们可以通过 socket 和另外的一台主机进行通信. 关于socket 源码的解析在 tarnado 系列文章中,正在写中..... 1. 通信的方式 tcp 通信 udp 通信 基于uni

python描述符descriptor(二)

python内置的描述符 python有些内置的描述符对象,property.staticmethod.classmethod,python实现如下: class Property(object): def __init__(self,getf,setf,delf,doc): self.getf=getf self.setf=setf self.delf=delf self.doc=doc def __set__(self,instance,own=None): if instance is N

python2.7高级编程 笔记二(Python中的描述符)

Python中包含了许多内建的语言特性,它们使得代码简洁且易于理解.这些特性包括列表/集合/字典推导式,属性(property).以及装饰器(decorator).对于大部分特性来说,这些"中级"的语言特性有着完善的文档,并且易于学习. 但是这里有个例外,那就是描述符.至少对于我来说,描述符是Python语言核心中困扰我时间最长的一个特性.这里有几点原因如下: 有关描述符的官方文档相当难懂,而且没有包含优秀的示例告诉你为什么需要编写描述符(我得为Raymond Hettinger辩护一

python中os.system()的返回值

最近遇到os.system()执行系统命令的情况,上网搜集了一下资料,整理如下,以备不时之需,同时也希望能帮到某些人. 一.python中的 os.system(cmd)的返回值与linux命令返回值(具体参见本文附加内容)的关系 大家都习惯用os.systemv()函数执行linux命令,该函数的返回值十进制数(分别对应一个16位的二进制数).该函数的返回值与 linux命令返回值两者的转换关系为:该函数的返回值(十进制)转化成16二进制数,截取其高八位(如果低位数是0的情况下,有关操作系统的