描述符descriptor(property原理)
-
定义:将某种特殊类的实例指派给另一个类的属性
-
特殊类:至少实现以下三个方法中的一个(描述符属性方法)
__get__(self, instance, owner)
– 用于访问属性和返回它的值
__set__(self, instance, value)
– 将在属性分配时调用,不返回任何值
__delete__(self, instance)
– 控制删除操作,不返回任何值
-
重写property
""" write a property class """ class Myproperty: def __init__(self, fget=None, fset=None, fdel=None): self.fget = fget self.fset = fset self.fdel = fdel def __get__(self, instance, owner): print("geting...", self, ",", instance, ",", owner) return self.fget(instance) def __set__(self, instance, value): print("setting...", self, ",", instance, ",", value) self.fset(instance, value) def __delete__(self, instance): print("deleting...", self, ",", instance) self.fdel(instance) class C: def __init__(self, _x=1): self._x = _x def getX(self): return self._x def setX(self, value): self._x = value def delX(self): del self._x x = Myproperty(getX, setX, delX)
2) 摄氏华氏度转换,celsius x 1.8 + 32 = fahrenheit
''' celsius 2 fahrenheit via descriptor'''
class Celsius:
def __init__(self, temp=26.0):
self.temp = float(temp)
def __get__(self, instance, owner):
return self.temp
def __set__(self, instance, value):
self.temp = float(value)
class Fahrenheit:
def __get__(self, instance, owner):
return instance.cel * 1.8 + 32
def __set__(self, instance, value):
instance.cel = (float(value) -32) / 1.8
class Temperature:
cel = Celsius()
fah = Fahrenheit()
t = Temp()
# t.cel = 100
t.fah = 100
print(t.cel)
print(t.fah)
# 计算属性赋值的次数
class Counter:
k = []
def __init__(self):
self.counter = 0
def __setattr__(self, name, value):
if name != "counter":
if name not in self.k:
self.counter += 1
self.k.append(name)
super().__setattr__(name, value)
def __delattr__(self, name):
self.counter -= 1
self.k.remove(name)
super().__delattr__(name)
魔法方法之描述符
- 什么是描述符——descriptor以及相关的一系列定义
(1)描述符:某个类,只要是内部定义了方法 get, set, delete 中的一个或多个,就可以称为描述符,描述符的本质是一个类。
(2)描述符协议:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()
中的一个,这些魔法方法也被称为描述符协议.
(3)非数据描述符:一个类,如果只定义了 __get__() 或者是__delete__()
方法,而没有定义 __set__()
方法,则认为是非数据描述符
(4)数据描述符:一个类,不仅定义了 __get__() 方法,还定义 __set__(), __delete__()
方法,则认为是数据描述符
(5)描述符对象:描述符(即一个类,因为描述符的本质是类)的一个对象,一般是作为其他类对象的属性而存在.
- *绑定行为:**所谓的绑定行为,是指在属性的访问、赋值、删除时还绑定发生了其他的事情。
托管属性:python描述符是一种创建“托管属性”的方法,即通过描述符(类)去托管另一个类的相关属性,也可以说是类的属性的一个代理。其实就是专门用一个类去装饰某一个属性,我可以把这个属性定义成任何我想要的样子,所谓的*“一对一定制属性”**。一个类属性本质上就是属性描述类的一个实例对象.
描述符三个函数的定义形式:
def __get__(self, instance, owner)
self:指的是描述符类的实例
instance:指的是使用描述符的那个类的实例
owner:指的是使用描述符的那个类
def __set__(self, instance, value)
def __delete__(self, instance)
总结:对于类属性描述符**,如果解析器发现属性property是一个描述符的话,它能把Class.x转换成Class.dict[‘property’].get(None, Class)来访问。
类属性描述符
总结:
(1)对于类装饰器属性,只要出现属性访问(不管是通过对象访问还是类名访问),都会优先调用装饰器的__get__
方法;
(2)对于类装饰器属性,若出现属性修改(不管是通过对象访问还是类名访问),都会优先调用装饰器的__set__
方法;
(3)对于类装饰器属性,若出现属性删除(不管是通过对象访问还是类名访问),都会优先调用装饰器的__delete__
方法;
实例属性描述符
*总结:*描述符是一个类属性,必须定义在类的层次上, 而不能单纯的定义为对象属性。
Eg:
>>> class MyDes:
def __init__(self, value = None):
self.val = value
def __get__(self, instance, owner):
return self.val ** 2
>>> class Test:
def __init__(self):
self.x = MyDes(3)
>>> test = Test()
>>> test.x
<__main__.MyDes object at 0x1058e6f60>
##访问实例层次上的描述符 x,只会返回描述符本身。为了让描述符能够正常工作,它们必须定义在类的层次上。如果你不这么做,那么 Python 无法自动为你调用 __get__ 和 __set__ 方法
''' use pickle to store the attribute of a var, used with descriptor '''
import os
import pickle
class Mydes:
saved = []
def __init__(self, var=None):
self.var = var
self.f_name = self.var + '.pkl'
def __get__(self, instance, owner):
if self.var not in Mydes.saved:
raise AttributeError('No value set for %s, wahaha' % self.var)
with open(self.f_name, 'rb') as f:
value = pickle.load(f)
return value
def __set__(self, instance, value):
with open(self.f_name, 'wb') as f:
pickle.dump(value, f)
Mydes.saved.append(self.var)
def __delete__(self, instance):
os.remove('%s.pkl' %self.var)
Mydes.saved.remove(self.var)
class Test:
x = Mydes('x')
y = Mydes('y')
test = Test()
魔法方法之定制序列定制容器
Protocols 与其他编程语言中的接口很相似,规定了那些方法必须要定义。然鹅在Python中协议显得不那么重视,更像是一种指南/建议。
容器类型的协议:
容器类型 | |
---|---|
__len__(self) | 定义当被 len() 调用时的行为(返回容器中元素的个数) |
__getitem__(self, key) | 定义获取容器中指定元素的行为,相当于 self[key] |
__setitem__(self, key, value) | 定义设置容器中指定元素的行为,相当于 self[key] = value |
__delitem__(self, key) | 定义删除容器中指定元素的行为,相当于 del self[key] |
__iter__(self) | 定义当迭代容器中的元素的行为 |
__reversed__(self) | 定义当被 reversed() 调用时的行为 |
__contains__(self, item) | 定义当使用成员测试运算符(in 或 not in)时的行为 |
- 希望定制的容器是不可变的,需要定义
__len__()
和__getitem__()
方法, eg,元组,字符串 - 如果希望定制容器可变的话,除了
__len__()
和__getitem__()
外还需定义__setitem__()
和__delitem__()
方法,eg. 列表list, 字典dict的赋值、删除等
Eg. 定制不可变列表并可以记录每个元素被访问的次数
class Mylist:
def __init__(self, *args):
self.values = [x for x in args]
self.count = {}.fromkeys(range(len(self.values)), 0) # 每个参数都做成了一个dict,value都是0
def __len__(self):
return len(self.values)
def __getitem__(self, key):
self.count[key] += 1
return self.values[key]
Eg:
定制一个列表,同样要求记录列表中每个元素被访问的次数。这一次我们希望定制的列表功能更加全面一些,比如支持 append()、pop()、extend() 原生列表所拥有的方法。你应该如何修改呢?
要求1:实现获取、设置和删除一个元素的行为(删除一个元素的时候对应的计数器也会被删除)
要求2:增加 counter(index) 方法,返回 index 参数所指定的元素记录的访问次数
要求3:实现 append()、pop()、remove()、insert()、clear() 和 reverse() 方法(重写这些方法的时候注意考虑计数器的对应改变)
class CountList(list):
def __init__(self, *args):
super().__init__(args)
self.count = []
for i in args:
self.count.append(0) # 初始化参数的计数器
def __len__(self):
return len(self.count)
def __getitem__(self, key):
self.count[key] += 1
return super().__getitem__(key)
def __setitem__(self, key, value):
self.count[key] += 1
super().__setitem__(key, value)
def __delitem__(self, key):
del self.count[key]
super().__delitem__(key)
def counter(self, key):
return self.count[key]
def append(self, value):
self.count.append(0)
super().append(value)
def pop(self, key=-1):
del self.count[key]
return super().pop(key)
def remove(self, value):
key = super().index(value)
del self.count[key]
super().remove(value)
def insert(self, key, value):
self.count.insert(key, 0)
super().insert(key, value)
def clear(self):
self.count.clear()
super().clear()
def reverse(self):
self.count.reverse()
super().reverse()
魔法方法之迭代器
内置BIF:
- Iter() — 制作迭代器
iter() – return itself - Next() – 读取下一个元素,没有元素可以列举时报 StopIteration 异常
next() — 重点,决定了如何得到下一个元素
实现了__iter__
和 __next__
魔法方法的类就可以作为迭代器
Eg1: 使用while实现for 循环
a = iter(range(10)) #range是一种range object,仅实现了__iter__但是没有__next__,不能作为迭代器,iter将他变成了迭代器
while True:
try:
each = next(a)
except StopIteration:
break
print(each)
Eg2: 斐波那契数列
class Fibs:
def __init__(self, n=10):
self.a,self.b = 0, 1
self.n = n
def __iter__(self):
return self
def __next__(self):
self.a, self.b = self.b, self.a + self.b
return self.a
fibs = Fibs(100)
for each in fibs:
if each < fibs.n:
print(each)
else:
Break
Eg3: 打印出闰年(迭代器)
import datetime as dt
class LeapYear:
def __init__(self):
self.now = dt.date.today().year
def isLeapYear(self, year):
if (year%4 == 0 and year%100 != 0) or (year%400 == 0):
return True
else:
return False
def __iter__(self):
return self
def __next__(self):
while not self.isLeapYear(self.now):
self.now -= 1
temp = self.now
self.now -= 1
return temp
Eg4: 迭代器实现reversed
class MyRev:
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]