摘要
七天假期最后一天,假期账户余额已严重不足,那就总结下这两天的学习内容吧。本文主要讲解Python中另一种高级特性描述器,学习如何自定义描述器,和几个内置的描述器,包括函数、property、静态方法和类方法。并运用我们所学的知识,用纯Python实现上述方法。最后介绍描述器的几个运用场景,并给出真实的例子说明。
定义
描述器单独出现是无意义的,它总是和其他类的类属性一起出现,事实上,描述器描述了类属性的访问、赋值和删除行为,所以它被叫做描述器
描述器是怎么工作的
描述器仅仅是一个对象,跟其他对象相比并没有内在的特殊之处。就像Python其他强大特性一样,它们非常的简单。想要深入了解描述器协议,有三个条件需要搞清楚:
- 你有一个新式的类
- 它有类属性
- 这个类属性有描述器的几个方法
上面所说的描述器协议,包括以下几个方法: -
__get__(self, instance, ower) --> value
获得对象的属性obj.description
相当于description.__get__(obj, OwerClass)
获得类的属性OwnerClass.descriptor
相当于descriptor.__get__(None, OwnerClass)
-
__set__(self, instance, value) --> None
给对象赋值obj.descriptor = 5
相当于descriptor.__set__(obj, 5)
-
__delete__(self, instance)
删除对象属性
del obj.description
相当于description.__delete__(obj)
这边有个易混淆的地方:描述器属性被触发是因为对象的属性(只有描述器对象是其他类的类属性时候才起作用),但是在上述三个方法中self
是描述器本身,而不是对象
简单的描述器
class Int:
def __init__(self, name):
print('Int init')
self.name = name
def __get__(self, instance, cls):
print('access')
print('{!r} {!r} {!r}'.format(self, instance, cls))
if instance is None:
return self
print(instance.__dict__)
return instance.__dict__[self.name]
def __set__(self, instance, value):
print('set')
print('{!r} {!r} {!r}'.format(self, instance, value))
instance.__dict__[self.name] = value
print(instance.__dict__)
def __delete__(self, instance):
pass
class A:
x = Int('x')
print(x)
def __init__(self, x):
print('A init')
self.x = x
# out: 在定义类A的时候就会生出类属性x
Int init
<__main__.Int object at 0x7fcb38f916a0>
a = A(4)
# out:
A init
set
<__main__.Int object at 0x7fcb38f916a0> <__main__.A object at 0x7fcb38ff5a20> 4
{'x': 4}
a.x
# out:
access
<__main__.Int object at 0x7fcb3874aeb8> <__main__.A object at 0x7fcb386b12e8> <class '__main__.A'>
{'x': 4}
4
描述器协议非常的简单,用途十分广泛,以至于他们被打包成独立的函数。像属性(property)、静态方法和类方法都是基于描述器协议的。
属性(property)
下面是一个纯Python实现的property
:
class Property:
def __init__(self, fget=None, fset=None, fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self, instance, cls):
print('get')
print(instance)
print(self.fget)
print(cls)
if instance is None:
return self
if not callable(self.fget):
raise AttributeError()
print(self.fget(instance))
return self.fget(instance)
def __set__(self, instance, value):
print('set')
if not callable(self.fset):
raise AttributeError()
print(self.fset)
self.fset(instance, value)
def __delete__(self, instance):
print('delete')
if not callable(self.fdel):
raise AttributeError()
self.fdel(instance)
def setter(self, fset):
print('setter')
print(fset)
return Property(self.fget, fset, self.fdel)
def deleter(self, fdel):
return Property(self.fget, self.fset, fdel)
class A:
def __init__(self, x):
self.__x = x
@Property
def x(self): # x = Property(A.x)
return self.__x
@x.setter # x = x.setter(A.x) = Property(x.fget, A.x, x.fdel)
def x(self, value):
self.__x = value
# out:
setter
<function A.x at 0x7fcb38f9fc80>
a = A(10)
a.x
# out:
get
<__main__.A object at 0x7fcb387439b0>
<function A.x at 0x7fcb3873bd08>
<class '__main__.A'>
10
a.x = 11
# out:
set
<function A.x at 0x7fcb3873bc80>
上述代码中我加了很多打印,能够帮助大家理解,如果觉得繁琐,可以删除上述打印。
-
Property
对象是描述器 -
Property.setter
和Property.deleter
都是装饰器,他们返回的是Property()
对象,不同的是@Property
设置的是fget
,setter
和deleter
分别设置fset
和fdel
类方法
纯Python实现类方法,代码如下:
from functools import wraps, partial
class Classmethod:
def __init__(self, method):
wraps(method)(self)
def __get__(self, instance, cls):
return partial(self.__wrapped__, cls)
class C:
@Classmethod
def method(cls):
print(cls)
def method2(self, x):
print(cls)
print(x)
C.method()
# out:
class method
<class '__main__.C'>
上述代码完美的实现了类方法装饰器,有一个函数wraps()
需要着重讲解一下
wraps(fn)(g)
把fn
的一些属性(比如__module__, __name__, __qualname__, __doc__,__annotations__
),复制给g
(主要是更新g
的__dict__
),g
就有一个属性叫做__wrapped__
,g.__wrapped__
就等价于fn
描述器的一个应用
用描述器做类型检查
from inspect import signature
class Typed:
def __init__(self, name, required_type):
self.name = name
self.required_type = required_type
def __get__(self, instance, cls):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, self.required_type):
raise TypeError('{} required {}'.format(self.name, self.required_type))
instance.__dict__[self.name] = value
def typeassert(cls):
sig = signature(cls)
for k, v in sig.parameters.items():
setattr(cls, k, Typed(k, v.annotation))
return cls
@typeassert
class Person:
def __init__(self, name: str, age: int): # Python3.5最新类型提示
self.name = name
self.age = age
p = Person(18, 'xus')
# out:
TypeError: name required <class 'str'>
结语
希望大家对Python描述器有了一个全新的认识,加油吧骚年!
网友评论