美文网首页
python 动态属性和property特性

python 动态属性和property特性

作者: 落羽归尘 | 来源:发表于2019-09-20 21:29 被阅读0次

简介

特性至关重要的地方在于,特性的存在使得开发者可以非常安全并且确定可行地将公共数据属性作为类的公共接口的一部分开放出来。python中,对象中的属性和方法都称为属性,方法是可调用的属性。另外还可以创建特性property,不改变接口前提下修改属性。

通过属性的方式获取数据

比如有一个字典或者json,想通过.attr的方式获取对于的值。关键是实现__getattr__方法,对于一个key-value形式的数据结构,想通过点的方式获取value,对于一个对象来讲,由于key不是对象的属性,会触发__getattr__方法,因此我们可以实现这个方法。下面是实现过程:

from collections import abc
class Test(object):
    def __init__(self, mapping):
        self.__data = dict(mapping)
    def __getattr__(self, name):
        if hasattr(self.__data, name):
            return getattr(self.__data, name)
        else:
            return Test.build(self.__data[name]) 
    @classmethod
    def build(cls, obj): 
        if isinstance(obj, abc.Mapping): 
            return cls(obj)
        elif isinstance(obj, abc.MutableSequence): 
            return [cls.build(item) for item in obj]
        else: 
            return obj

d={'a':2,'b':{'c':'d'}}
t = Test(d)
print(t.a)
print(t.b.c)
输出结果:
2
d
  • 当访问t.a时,发现a不是t的属性,就会触发__getattr__方法
  • hasattr(self.__data, name)这里判断key是否是字典的属性,比如item就是字典的属性,是为了防止覆盖字典原本的特性。
  • 否则调用build,如果得到的值还是字典,那递归即可,如果是序列,每个值递归调用build,否则返回这个值。

属性名为关键字时

上面那个例子不能处理属性名为关键字的,比如

d={'class':2,'b':{'c':'d'}}
t = Test(d)
print(t.class)

会报错的。可以在初始化init时,判断是否是关键字,给关键字后加_。

__new__方法

__new__方法是一个类方法,用于创建对象的,会返回一个实例,传给__init__方法,如果返回其他类的实例,就不会调用__init__方法了。下面用__new__方法代替build,能达到相同的效果。

from collections import abc
class Test(object):
    def __new__(cls, obj): 
        if isinstance(obj, abc.Mapping): 
            return super().__new__(cls)
        elif isinstance(obj, abc.MutableSequence): 
            return [cls(item) for item in obj]
        else: 
            return obj
    def __init__(self, mapping):
        self.__data = dict(mapping)
    def __getattr__(self, name):
        if hasattr(self.__data, name):
            return getattr(self.__data, name)
        else:
            return Test(self.__data[name]) 


d={'class':2,'b':{'c':'d'}}
t = Test(d)

print(t.b.c)

@property 装饰器

特性经常用于把公开的属性变成使用读值和设置值进行管理,@property 装饰器装饰的方法,可以将方法当做属性来用。

  • 通过property 实现属性的只读
class LineItem:
    def __init__(self, weight):
        self.__weight = weight 

    @property 
    def weight(self): 
        print(1)
        return self.__weight
   
l=LineItem(-1)
print(l.weight)

将weight看成一个成员属性,只可读取,不可更改,若使用l.weight=x进行改值时,会报错。

  • 使用property setter 实现属性控制
    比如下面实现weight属性不能为小于等于0的值
class LineItem:
    def __init__(self, weight):
        self.weight = weight 

    @property 
    def weight(self): 
        print(1)
        return self.__weight
    
    @weight.setter 
    def weight(self, value):
        print(2)
        if value > 0:
            self.__weight = value 
        else:
            raise ValueError('value must be > 0') 

l=LineItem(-1)
print(l.weight)
  • 当实例化时LineItem(-1),就会自动调用 @weight.setter装饰的方法上,进行值得校验,-1时会抛出异常。
  • 注意__init__方法中执行self.weight = weight时,会触发在@weight.setter装饰的方法,已达到对值value进行的校验。

当然,也可以使用@property装饰器装饰一个方法,用于数据,逻辑处理后的返回值。

property装饰器其实是一个类,在python中,类和函数一方面是相似的,都是可调用对象。

property(fget=None, fset=None, fdel=None, doc=None)
class LineItem:
    def __init__(self, weight):
        self.weight = weight 

    def get_weight(self): 
        print(1)
        return self.__weight1

    def set_weight(self, value):
        print(2)
        if value > 0:
            self.__weight1 = value 
            print(self.weight,self.__weight1)
        else:
            raise ValueError('value must be > 0') 
    
    weight = property(get_weight, set_weight)

l=LineItem(-4)
  • 不使用property装饰器,使用原始取值设置值方式
  • 将取值,设置值得方法传给property(get_weight, set_weight),weight 是类属性,当加载类的时候会执行 property(get_weight, set_weight),进而执行set_weight,判断value的值,LineItem(-4)会抛出异常。

property特性会覆盖实例属性

特性都是类属性,对特性取值赋值是通过实例完成的

class LineItem:
    def __init__(self, weight):
        self.weight = weight 

    @property 
    def weight(self): 
        print(1)
        return self.__weight
    
    @weight.setter 
    def weight(self, value):
        print(2)
        if value > 0:
            self.__weight = value 
            print(self.weight,self.__weight)
        else:
            raise ValueError('value must be > 0') 
print(LineItem.weight)
l=LineItem(4)
print(l.weight)
输出结果:
<property object at 0x00AE8420>
2
1
4 4
1
4
  • 当print(LineItem.weight)时,可以看出weight时类属性,是一个类的特性。
  • 而取weight值和设置值时,是通过实例调用完成的。
class LineItem:
    def __init__(self, weight):
        self.weight = weight 

    @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') 
print(LineItem.weight)
l=LineItem(4)
l.__dict__['weight']=9
print(l.weight)
print(LineItem.weight)
输出结果:
<property object at 0x01C28360>
4
<property object at 0x01C28360>
  • l.__dict__['weight']=9在对象l中增加属性weight,名字与特性一样
  • 打印出4,说明property特性会覆盖实例属性
  • LineItem.weight,并且类的weight特性还是不变的

特性工厂函数

def quantity(name):
    def get_(obj): 
        return obj.__dict__[name]

    def set_(obj, value):
        if value > 0:
            obj.__dict__[name] = value 
        else:
            raise ValueError('value must be > 0') 
    
    return property(get_, set_)

class LineItem:
    weight = quantity("weight")
    price = quantity("price")

    def __init__(self, weight, price):
        self.weight = weight
        self.price = price

LineItem(-4,-5)
  • 特性工厂函数起到一个作用,为输入值进行正负值校验
  • 实例化LineItem(-4,-5)时,会先执行两个quantity函数,将类属性设置为property,
  • 接着执行init方法,为self.weight和 self.price设置值,这时会触发工厂特产property特性的set_函数,进行值校验
  • property(get_, set_)传入的函数默认第一个参数是LineItem实例。

补充

class A(object):
    x = 1

    def __init__(self):
        A.f(self)

    def f(self):
        print("f")
        return self.x

    def f1(self, v):
        self.x = v
        print("f1")

    y = property(f,f1)
    del f, f1
    

a=A()
a.y
a.y=6
  • property(getf,setf,delf,doc)
  • property作为函数来讲,当访问a.y时,会调用getf,设置值时会调用setf

相关文章

网友评论

      本文标题:python 动态属性和property特性

      本文链接:https://www.haomeiwen.com/subject/rapvectx.html