zoukankan      html  css  js  c++  java
  • 【转载】Python 描述符简介

    来源:Alex Starostin  

    链接:www.ibm.com/developerworks/cn/opensource/os-pythondescriptors/

    关于Python@修饰符的文章可以看:https://my.oschina.net/shyl/blog/626490

    简介

    Python 2.2 引进了 Python 描述符,同时还引进了一些新的样式类,但是它们并没有得到广泛使用。Python 描述符是一种创建托管属性的方法。除了其他优点外,托管属性还用于保护属性不受修改,或自动更新某个依赖属性的值。

    描述符增加了对 Python 的理解,改善了编码技能。本文介绍了描述符协议,并演示了如何创建和使用描述符。

    描述符协议

     Python 描述符协议 只是一种在模型中引用属性时指定将要发生事件的方法。它允许编程人员轻松、有效地管理属性访问:

       1、set    2、get     3、delete

     在其他编程语言中,描述符被称作 setter 和 getter,而公共函数用于获得 (Get) 和设置 (Set) 一个私有变量。Python 没有私有变量的概念,而描述符协议可以作为一种 Python 的方式来实现与私有变量类似的功能。

    总的来说,描述符就是一个具有绑定行为的对象属性,其属性访问将由描述符协议中的方法覆盖。这些方法为 __get__、__set__ 和 __delete__。如果这些方法中的任何一个针对某个对象定义,那么它就被认为是一个描述符。通过 清单 1 进一步了解这些方法。

    清单 1. 描述符方法

     

    __get__(self, instance, owner)
    
    __set__(self, instance, value)
    
    __delete__(self, instance)      

    其中:

    __get__ 用于访问属性。它返回属性的值,或者在所请求的属性不存在的情况下出现 AttributeError 异常。

    __set__ 将在属性分配操作中调用。不会返回任何内容。

    __delete__ 控制删除操作。不会返回内容。

    需要注意,描述符被分配给一个类,而不是实例。修改此类,会覆盖或删除描述符本身,而不是触发它的代码

    需要使用描述符的情况

    考虑 email 属性。在向该属性分配值之前,需要对邮件格式进行检验。该描述符允许通过一个正则表达式处理电子邮件,然后对格式进行检验后将它分配给一个属性。

    在其他许多情况下,Python 协议描述符控制对属性的访问,如保护 name 属性。

    创建描述符

    您可以通过许多方式创建描述符:

    • 创建一个类并覆盖任意一个描述符方法:__set__、__ get__ 和 __delete__。当需要某个描述符跨多个不同的类和属性,例如类型验证,则使用该方法。

    • 使用属性类型,这种方法可以更加简单、灵活地创建描述符。

    • 使用属性描述符,它结合了属性类型方法和 Python 描述符。

    以下示例在其操作方面均相似。不同之处在于实现方法。

    使用类方法创建描述符

    class Descriptor(object):
     
        def __init__(self):
            self._name = ''
     
        def __get__(self, instance, owner):
            print "Getting: %s" % self._name
            return self._name
     
        def __set__(self, instance, name):
            print "Setting: %s" % name
            self._name = name.title()
     
        def __delete__(self, instance):
            print "Deleting: %s" %self._name
            del self._name
     
    class Person(object):
        name = Descriptor()
    

     使用这些代码并查看输出:

    >>> user = Person()
    >>> user.name = 'john smith'
    Setting: john smith
    >>> user.name
    Getting: John Smith
    'John Smith'
    >>> del user.name
    Deleting: John Smith
    

    通过以下方法覆盖父类的 __set__()、__get__() 和 __delete__() 方法,创建一个描述符类:

    • get 将输出 Getting

    • delete 将输出 Deleting

    • set 将输出 Setting

    并在分配之前将属性值修改为标题title(第一个字母大写,其他字母为小写)。这样做有助于存储和输出名称 

    大写转换同样可以移动到 __get__() 方法。_value 有一个初始值,并根据 get 请求转换为标题。

    使用属性类型创建描述符

     虽然上面中定义的描述符是有效的且可以正常使用,但是还可以使用属性类型的方法。通过使用 property(),可以轻松地为任意属性创建可用的描述符。创建 property() 的语法是 property(fget=None, fset=None, fdel=None, doc=None),其中:

    • fget:属性获取方法

    • fset:属性设置方法

    • fdel:属性删除方法

    • doc:docstring

    使用属性重写该例子,如 下所示。

    class Person(object):
        def __init__(self):
            self._name = ''
     
        def fget(self):
            print "Getting: %s" % self._name
            return self._name
        
        def fset(self, value):
            print "Setting: %s" % value
            self._name = value.title()
     
        def fdel(self):
            print "Deleting: %s" %self._name
            del self._name
        name = property(fget, fset, fdel, "I'm the property.")
    
    使用该代码并查看输出:
    
    >>> user = Person()
    >>> user.name = 'john smith'
    Setting: john smith
    >>> user.name
    Getting: John Smith
    'John Smith'
    >>> del user.name
    Deleting: John Smith
    

     

    显然,结果是相同的。注意,fget、fset 和 fdel 方法是可选的,但是如果没有指定这些方法,那么将在尝试各个操作时出现一个 AttributeError 异常。例如,声明 name 属性时,fset 被设置为 None,然后开发人员尝试向 name 属性分配值。这时将出现一个 AttributeError 异常。

    这种方法可以用于定义系统中的只读属性:

     

    name = property(fget, None, fdel, "I'm the property")
    user.name = 'john smith'
    
    输出为:
    
    Traceback (most recent call last):
    File stdin, line 21, in mоdule
    user.name = 'john smith'
    AttributeError: can't set attribute
    

    使用属性修饰符创建描述符

    可以使用 Python 修饰符创建描述符,如 下 所示。Python 修饰符是对 Python 语法的特定修改,能够更方便地更改函数和方法。在本例中,将修改属性管理方法。在 developerWorks 文章 Decorators make magic easy 中寻找更多有关应用 Python 修饰符的信息。

    class Person(object):
     
        def __init__(self):
            self._name = ''
     
        @property
        def name(self):
            print "Getting: %s" % self._name
            return self._name
     
        @name.setter
        def name(self, value):
            print "Setting: %s" % value
            self._name = value.title()
     
        @name.deleter
        def name(self):
            print ">Deleting: %s" % self._name
            del self._name
    

      

    在运行时创建描述符

     前面的所有例子都使用了 name 属性。该方法的局限性在于需要对各个属性分别覆盖 __set__()、__get__() 和 __delete__()。清单 5 提供了一个可能的解决方案,帮助开发人员在运行时添加 property 属性。该解决方案使用属性类型构建数据描述符。

    使用属性修饰符创建描述符

      

    class Person(object):
     
        def addProperty(self, attribute):
            # create local setter and getter with a particular attribute name 
            getter = lambda self: self._getProperty(attribute)
            setter = lambda self, value: self._setProperty(attribute, value)
     
            # construct property attribute and add it to the class
            setattr(self.__class__, attribute, property(fget=getter, 
                                                        fset=setter, 
                                                        doc="Auto-generated method"))
     
        def _setProperty(self, attribute, value):
            print "Setting: %s = %s" %(attribute, value)
            setattr(self, '_' + attribute, value.title())    
     
        def _getProperty(self, attribute):
            print "Getting: %s" %attribute
            return getattr(self, '_' + attribute)
    
    让我们运行这段代码:
    
    >>> user = Person()
    >>> user.addProperty('name')
    >>> user.addProperty('phone')
    >>> user.name = 'john smith'
    Setting: name = john smith
    >>> user.phone = '12345'
    Setting: phone = 12345
    >>> user.name
    Getting: name
    'John Smith'
    >>> user.__dict__
    {'_phone': '12345', '_name': 'John Smith'}
    

     

    这将在运行时创建 name 和 phone 属性。它们可以根据相应的名称进行访问,但是按照 _setProperty 方法中的定义,将在对象名称空间目录中存储为 _name 和 _phone。基本上,name 和 phone 是对内部的 _name 和 _phone 属性的访问符。

    当开发人员尝试添加 name property 属性时,您可能对系统中的 _name 属性存在疑问。实际上,它将用新的 property 属性覆盖现有的 _name 属性。这些代码允许控制如何在类内部处理属性。

     

      

  • 相关阅读:
    Codechef EDGEST 树套树 树状数组 线段树 LCA 卡常
    BZOJ4319 cerc2008 Suffix reconstruction 字符串 SA
    Codechef STMINCUT S-T Mincut (CodeChef May Challenge 2018) kruskal
    Codeforces 316G3 Good Substrings 字符串 SAM
    Codechef CHSIGN Change the Signs(May Challenge 2018) 动态规划
    BZOJ1396 识别子串 字符串 SAM 线段树
    CodeForces 516C Drazil and Park 线段树
    CodeForces 516B Drazil and Tiles 其他
    CodeForces 516A Drazil and Factorial 动态规划
    SPOJ LCS2
  • 原文地址:https://www.cnblogs.com/itdyb/p/6140075.html
Copyright © 2011-2022 走看看