• 【转载】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 属性。这些代码允许控制如何在类内部处理属性。

     

      

  • 相关阅读:
    小试阿里云容器服务
    .NET跨平台:再见dnx,你好dotnet cli
    使用Let's Encrypt手动创建https证书
    Chrome以https访问gitlab的问题:Your connection is not private
    [ASP.NET 5]终于解决:Unable to load DLL 'api-ms-win-core-localization-obsolete-l1-2-0.dll'
    Mac上远程桌面连接Windows Server 2012 R2
    解决JSON.stringify()在IE10下无法使用的问题
    解决阿里云SLB无法添加https证书的问题
    “状态机”学习笔记
    HttpClient读取ASP.NET Web API错误信息的简单方法
  • 原文地址:https://www.cnblogs.com/itdyb/p/6140075.html
Copyright © 2020-2023  润新知