zoukankan      html  css  js  c++  java
  • Python动态属性和特性(二)

    内置的property经常用作装饰器,但它其实是一个类。在Python中,函数和类通常可以互换,因为二者都是可调用对象,而且没有实例化的new运算符,所以调用构造方法和调用工厂函数没有区别,只要能返回新的可调用对象,代替被装饰的函数,二者都可用作装饰器

    property初始化方法(__init__)的完整签名如下:

    property(fget=None, fset=None, fdel=None, doc=None)

    所有的参数都是可选的,如果没有把函数传给某个参数,那么得到的特性对象就不允许执行相应的操作

    class LineItem:
    
        def __init__(self, description, weight, price):
            self.description = description
            self.weight = weight
            self.price = price
    
        def subtotal(self):
            return self.weight * self.price
    
        def get_weight(self):  # <1>
            return self.__weight
    
        def set_weight(self, value):  # <2>
            if value > 0:
                self.__weight = value
            else:
                raise ValueError('value must be > 0')
    
        weight = property(get_weight, set_weight)  # <3>
    

      

    1. 普通的读值方法
    2. 普通的设值方法
    3. 构建property对象,然后赋值给公开的类属性

    测试LineItem这个类

    line = LineItem("Golden raisins", 10, 6.5)
    print("<1>:", line.subtotal())  # <1>
    line.weight = -9
    

      

    运行结果:

    <1>: 65.0
    Traceback (most recent call last):
    ……
    ValueError: value must be > 0
    

      

    当然,我们可以写成另外一种形式,也是可以通过方法来读取和设置公开属性的

    class LineItem:
    
        def __init__(self, description, weight, price):
            self.description = description
            self.weight = weight
            self.price = price
    
        def subtotal(self):
            return self.weight * self.price
    
        @property
        def weight(self):
            return self.__weight
    
        @weight.setter
        def weight(self, value):
            if value > 0:
                self.__weight = value
            else:
                raise ValueError('value must be > 0')
    

      

    特性都是类属性,但是特性管理其实是实例属性的存取,如果实例和所属的类有同名的属性,那么在获取实例读取属性时,实例属性会覆盖类属性

    class Class:
        data = "the class data attr"
    
        @property
        def prop(self):
            return "the prop value"
    
    
    obj = Class()
    print("<1>:", vars(obj))  # <1>
    print("<2>:", obj.data)  # <2>
    obj.data = "bar"  # <3>
    print("<4>:", vars(obj))  # <4>
    print("<5>:", obj.data)  # <5>
    print("<6>:", Class.data)  # <6>
    

      

    运行结果:

    <1>: {}
    <2>: the class data attr
    <4>: {'data': 'bar'}
    <5>: bar
    <6>: the class data attr
    

      

    1. vars()返回obj.__dict__属性,表明实例没有属性
    2. 读取obj.data,获取的是Class.data的值
    3. 为obj.data赋值,为实例创建一个属性
    4. 再次用vars()查看实例的属性
    5. 现在读取obj.data,实例属性覆盖类属性
    6. 打印类属性Class.data

    下面,尝试覆盖obj的prop特性

    print("<1>:", Class.prop)  # <1>
    print("<2>:", obj.prop)  # <2>
    obj.prop = "hello world"
    

        

    运行结果:

    <1>: <property object at 0x00000000004805E8>
    <2>: the prop value
    Traceback (most recent call last):
        obj.prop = "hello world"
    AttributeError: can't set attribute
    

      

    1. 直接从Class读取prop特性,获取的是特性对象本身,不会运行特性的读值方法
    2. 读取obj.prop会执行特性的读值方法
    3. 尝试设置pro实例属性,结果失败
    obj.__dict__["prop"] = "hello world"  # <1>
    print("<2>:", vars(obj))  # <2>
    print("<3>:", obj.prop)  # <3>
    Class.prop = "baz"  # <4>
    print("<5>:", obj.prop)  # <5>
    

      

    运行结果:

    <2>: {'prop': 'hello world'}
    <3>: the prop value
    <5>: hello world
    

      

    1. 上一个例子,通过设置obj.prop会抛出异常,于是我们尝试通过obj.__dict__设置实例属性
    2. 可以看到上一个步骤设置成功
    3. 然而,读取prop时仍会运行特性的读值方法,特性没被实例属性覆盖
    4. 覆盖Class.prop特性,销毁特性对象
    5. 现在,obj.prop读取的是实例属性。Class.prop不是特性了,因此不会再覆盖obj.prop

    定义一个特性工厂函数

    我们将定义一个名为quantity的特性函数,替我们管理之前LineItem这个类中的weight和price这两个属性,要求这两个属性都必须大于0 

    def quantity(storage_name):
        def qty_getter(instance):  # <3>
            return instance.__dict__[storage_name]
    
        def qty_setter(instance, value):  # <4>
            if value > 0:
                instance.__dict__[storage_name] = value
            else:
                raise ValueError('value must be > 0')
    
        return property(qty_getter, qty_setter)
    
    
    class LineItem:
        weight = quantity('weight')  # <1>
        price = quantity('price')
    
        def __init__(self, description, weight, price):
            self.description = description
            self.weight = weight  # <2>
            self.price = price
    
        def subtotal(self):
            return self.weight * self.price
    

        

    1. 使用工厂函数把weight和price定义为类属性
    2. 这里,特性已经激活,确保不能把weight设置为负数或零
    3. qty_getter函数的第一个参数可以命名为self,但是这样做很奇怪,因为qty_getter函数不在类的定义体中,instance指代LineItem实例,在qty_getter中,从LineItem实例的__dict__获取想要的属性,然后返回
    4. 定义qty_setter函数,第一个参数instance也是LineItem实例,然后将值存入instance.__dict__中,这也是为了跳过特性,避免递归循环最后栈溢出

    测试工厂函数

    nutmeg = LineItem("Moluccan nutmeg", 8, 13.95)
    print("<1>:", nutmeg.weight, nutmeg.price)  # <1>
    print("<2>:", sorted(vars(nutmeg).items()))  # <2>
    

      

    运行结果:

    <1>: 8 13.95
    <2>: [('description', 'Moluccan nutmeg'), ('price', 13.95), ('weight', 8)]
    

      

    定义特性时,可以使用@attr.deleter装饰器包装一个方法,负责删除特性管理的属性

    class BlackKnight:
    
        def __init__(self):
            self.members = ['an arm', 'another arm',
                            'a leg', 'another leg']
            self.phrases = ["'Tis but a scratch.",
                            "It's just a flesh wound.",
                            "I'm invincible!",
                            "All right, we'll call it a draw."]
    
        @property
        def member(self):
            print('next member is:')
            return self.members[0]
    
        @member.deleter
        def member(self):
            text = 'BLACK KNIGHT (loses {})
    -- {}'
            print(text.format(self.members.pop(0), self.phrases.pop(0)))
    

      

    测试删除特性

    b = BlackKnight()
    del b.member
    del b.member
    del b.member
    del b.member
    

      

    运行结果:

    BLACK KNIGHT (loses an arm)
    -- 'Tis but a scratch.
    BLACK KNIGHT (loses another arm)
    -- It's just a flesh wound.
    BLACK KNIGHT (loses a leg)
    -- I'm invincible!
    BLACK KNIGHT (loses another leg)
    -- All right, we'll call it a draw.
    

      

      

  • 相关阅读:
    SpringCloud----分布式事务
    ps----基础
    DVWA靶场之XSS(Stored)通关
    DVWA靶场之XSS(Reflected)通关
    DVWA靶场之SQL injection(blind)通关
    CentOS6与CentOS7的几点区别
    DVWA靶场之SQL Injection通关
    DVWA靶场之File Upload(文件上传)通关
    DVWA靶场之File Inclusion(文件包含)通关
    DVWA靶场之CSRF(跨站请求伪造)通关
  • 原文地址:https://www.cnblogs.com/beiluowuzheng/p/9222708.html
Copyright © 2011-2022 走看看