python描述符与实例属性_Python - 属性描述符和应用案例

什么是描述符(descriptor)描述符是一个特殊的Python对象,该对象定义了__get__、__set__、__delete__三个方法中的一个或多个;

描述符单独存在没有意义,一般描述符就是某个实例的属性,当访问该属性时,就会调用描述符的__get__方法,而修改会调用__set__方法,删除会调用__delete__方法;

描述符是对多个属性运用相同存取逻辑的一种方式。

描述符的种类

描述符分两种:覆盖型描述符和非覆盖型描述符,实现了__set__方法的就叫覆盖型描述符,没有实现就叫非覆盖型描述符;

这两种描述符的区别就是访问的优先级不同,访问属性的优先级如下:首先会先调用实例的 __getattribute__方法,然后依次按如下顺序去获取属性,获取到就返回:如果访问的属性是覆盖型描述符,则调用覆盖型描述符的__get__方法获取属性(由 __getattribute__调用)

从实例的__dict__字典获取属性

从实例所属类的__dict__字典获取属性

如果访问的属性是非覆盖型描述符,则调用非覆盖型描述符的__get__方法获取属性

从实例父类的__dict__字典获取属性

如果上述都未找到,则调用实例的 __getattr__方法

通过一个例子来验证下:

def cls_name(obj_or_cls):

cls = type(obj_or_cls)

if cls is type:

cls = obj_or_cls

return cls.__name__.split('.')[-1]

def display(obj):

cls = type(obj)

if cls is type:

return ''.format(obj.__name__)

elif cls in [type(None), int]:

return repr(obj)

else:

return '<{} object>'.format(cls_name(obj))

def print_args(name, *args):

pseudo_args = ', '.join(display(x) for x in args)

print('-> {}.__{}__({})'.format(cls_name(args[0]), name, pseudo_args))

class Overriding:

"""覆盖型描述符"""

def __init__(self, name):

self.name = name

def __get__(self, instance, owner): # self是描述符对象,instance是Managed的实例(也就是描述符对象所在类的实例),owner是Managed(也就是描述符对象所在的类)

print_args('get', self, instance, owner)

if instance is None:

return self

else:

return self.name

def __set__(self, instance, value):

print_args('set', self, instance, value)

self.name = value

class OverridingNoGet:

"""没有__get__方法的覆盖型描述符"""

def __init__(self, name):

self.name = name

def __set__(self, instance, value):

print_args('set', self, instance, value)

self.name = value

class NonOverriding:

"""非覆盖型描述符"""

def __init__(self, name):

self.name = name

def __get__(self, instance, owner):

print_args('get', self, instance, owner)

if instance is None:

return self

else:

return self.name

class Managed:

over = Overriding("init_over_value")

over_no_get = OverridingNoGet("init_over_no_get_value")

non_over = NonOverriding("init_non_over_value")

def spam(self):

print('-> Managed.spam({})'.format(display(self)))

obj = Managed()

覆盖型描述符测试:

>>> obj.__dict__['over'] = "instance_over_value1" # 添加实例属性

>>> obj.__dict__

{'over': 'instance_over_value1'}

>>> obj.over # 由于over属性是一个覆盖型描述符,所以优先调用了描述符对象的__get__方法获取属性

-> Overriding.__get__(, , )

'init_over_value'

>>> obj.over = "instance_over_value2" # 修改over属性,可以看到调用了描述符的__set__方法

-> Overriding.__set__(, , )

>>> obj.over

-> Overriding.__get__(, , )

'instance_over_value2'

没有__get__方法的覆盖型描述符测试:

>>> obj.__dict__['over_no_get'] = "instance_over_no_get_value1" # 添加实例属性

>>> obj.__dict__

{'over_no_get': 'instance_over_no_get_value1'}

>>> obj.over_no_get # 访问over_no_get属性,由于over_no_get描述符对象没有__get__方法,所以从实例的__dict__获取属性

'instance_over_no_get_value1'

非覆盖型描述符测试:

>>> obj.__dict__['non_over'] = "instance_non_over_value1"

>>> obj.__dict__

{'non_over': 'instance_non_over_value1'}

>>> obj.non_over # 可以看到访问non_over属性没有调用描述符对象的__get__方法,而是优先从实例的__dict__获取属性

'instance_non_over_value1'

类的属性访问测试:

>>> Managed.__dict__

mappingproxy({'over': , '__dict__':

s>, 'spam': , 'non_over': , '

__doc__': None, '__weakref__': , '__module__': 'descriptor', 'over_no_get':

ptor.OverridingNoGet object at 0x0000000002644C18>})

>>> Managed.over

-> Overriding.__get__(, None, )

>>> Managed.over = 1 # 属性重新赋值

>>> Managed.over

1

property

property是Python自带的描述符。

给类的方法加上property装饰器可以使方法变为一个描述符,该描述符可以实现对属性值的校验等功能,如下代码所示:

class Item:

@property # 等价于price = property(price),也就是实例化了一个描述符对象price

def price(self):

return self._price

@price.setter # 使用描述符对象的setter方法对price进行装饰(price = price.setter(price)),这里就是将设置属性值的方法传入描述符内,修改price属性时就会通过__set__方法调用传入的方法

def price(self, value):

if value > 0: # 校验price

self._price = value

else:

raise ValueError("Valid value..")

item = Item()

item.price = 100

print(item.price)

item.price = -100 # 会抛出ValueError异常

property的实现原理:

# property2.py

class MyProperty(object):

def __init__(self, fget=None, fset=None, fdel=None, doc=None):

self.fget = fget

self.fset = fset

self.fdel = fdel

self.__doc__ = doc

def __get__(self, obj, objtype=None):

print("====>in __get__")

if obj is None:

return self

if self.fget is None:

raise AttributeError

return self.fget(obj)

def __set__(self, obj, value):

print("====>in __set__")

if self.fset is None:

raise AttributeError

self.fset(obj, value)

def __delete__(self, obj):

print("====>in __delete__")

if self.fdel is None:

raise AttributeError

self.fdel(obj)

def getter(self, fget):

pass

def setter(self, fset):

print("====>in setter")

return type(self)(self.fget, fset, self.fdel, self.__doc__)

def deleter(self, fdel):

print("====>in deleter")

return type(self)(self.fget, self.fset, fdel, self.__doc__)

class Item:

@MyProperty

def price(self):

return self._price

@price.setter

def price(self, value):

if value > 0:

self._price = value

else:

raise ValueError("Valid value..")

测试:

>>> from property2 import Item

====>in setter # 创建类的时候就开始装饰了

>>> item = Item()

>>> item.price = 100

====>in __set__

>>> item.price

====>in __get__

100

>>> item.price = -100

====>in __set__

Traceback (most recent call last):

File "", line 1, in

File "D:\mycode\localtest\property2.py", line 20, in __set__

self.fset(obj, value)

File "D:\mycode\localtest\property2.py", line 50, in price

raise ValueError("Valid value..")

ValueError: Valid value..

除了property,Python还自带了其他的描述符,如staticmethod和classmethod,有兴趣可以自行了解。

应用案例

1.利用描述符实现缓存和只读属性

class LazyProperty(object):

"""实现缓存(只计算一次,并将结果缓存)利用了 obj.__dict__访问优先级高于非覆盖型描述符的特性第一次调用 __get__ 以同名属性存于实例字典中,之后就不再调用 __get__"""

def __init__(self, fun):

self.fun = fun

def __get__(self, instance, owner):

if instance is None:

return self

value = self.fun(instance)

setattr(instance, self.fun.__name__, value)

return value

class ReadonlyNumber(object):

"""实现只读属性(实例属性初始化后无法被修改)利用了覆盖型描述符访问优先级高于 obj.__dict__ 的特性当试图对属性赋值时,总会先调用 __set__ 方法从而抛出异常"""

def __init__(self, value):

self.value = value

def __get__(self, instance, owner):

return self.value

def __set__(self, instance, value):

raise AttributeError(

"'%s' is not modifiable" % self.value

)

class Circle(object):

pi = ReadonlyNumber(3.14)

def __init__(self, radius):

self.radius = radius

@LazyProperty

def area(self):

print('====>Computing area')

return self.pi * self.radius ** 2

circle = Circle(5)

print(circle.area)

# 输出:

# ====>Computing area # 只计算一次

# 78.5

print(circle.area)

# 输出:

# 78.5

circle.pi = 3.1415

# 抛异常

2.简化代码

上面讲了property可以用来做属性校验,但使用property有个缺点就是如果一个类里有很多属性需要校验,而且校验逻辑都一样的话,使用property就会让代码不利于维护,如下所示:

# Item类里有两个属性price和weight需要进行属性校验

class Item:

def __init__(self, price, weight):

self.price = price

self.weight = weight

@property

def price(self):

return self._price

@price.setter

def price(self, value):

if value > 0:

self._price = value

else:

raise ValueError("Valid value..")

@property

def weight(self):

return self._weight

@weight.setter

def weight(self, value):

if value > 0:

self._weight = value

else:

raise ValueError("Valid value..")

item = Item(100, 10)

print(item.price)

print(item.weight)

item = Item(100, -10) # 抛异常

可以看到price和weight的校验逻辑是一样的,也就是说代码冗余了。我们可以通过描述符来简化代码,代码如下:

class CheckDescriptor:

"""用来进行属性校验的描述符"""

def __init__(self, storage_name):

self.storage_name = storage_name

def __set__(self, instance, value):

if value > 0:

instance.__dict__[self.storage_name] = value

else:

raise ValueError('Valid value..')

class Item:

weight = CheckDescriptor('weight')

price = CheckDescriptor('price')

def __init__(self, price, weight):

self.price = price

self.weight = weight

item = Item(100, 10)

print(item.price)

print(item.weight)

item = Item(100, -10) # 抛异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值