序
欠4前年的一份笔记 ,献给今后的自己。
魔术方法
特殊属性

查看属性

如果dir(lobji)参数obj包含方法 dir(),该方法将被调用。如果参数obj不包含__dir__(),
该方法将最大限度地收集参数信息。
dir()对于不同类型的对象具有不同的行为:
如果对象是模块对象,返回的列表包含模块的属性名。
如果对象是类型或者类对象,返回的列表包含类的属性名,及它的基类的属性名。
否则,返回列表包含对象的属性名,它的类的属性名和类的基类的属性名。
animal. py
python
class Animal:
x = 123
def __init__(self, name):
self._name = name
self._age = 10
self.weight = 20
test.py
python
print('animal Modulel\'s names ={}'.format(dir())) # 模块的属性
# cat.py
import animal
from animal import Animal
class Cat(Animal):
x = 'cat'
y = 'abcd'
class Dog(Animal):
def _dir_(self):
return ['dog'] # 必须返回可迭代对象
print('-' * 30)
print('Current Modulel\'s names = {}'.format(dir())) # 模块名词空间内的属性
print('animal Modulel\'s names = {}'.format(dir(animal))) # 指定模块名词空间内的属性
print("object's __dict__ ={} ".format(sorted(object.__dict__.keys()))) # object的字典
print("Animal's dir() = {} ".format(dir(Animal))) # *Animal的dir()
print("Cat's dir() = {}".format(dir(Cat))) # CatHJdir()
print('~' * 50)
tom = Cat('tome')
print(sorted(dir(tom))) # 实例tom的属性、Cat类及所有祖先类的类属性
print(sorted(tom.__dict__)) # 同上
# dir()的等价 近似如下,_dict_字典中几乎包括了所有属性
print(sorted(set(tom.__dict__.keys()) | set(Cat.__dict__.keys()) | set(object.__dict__.keys())))
print("Dog's dir = {}".format(dir(Dog)))
dog = Dog('snoppy')
print(dir(dog))
print(dog.__dict__)
输出:
animal Modulel's names =['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
------------------------------
Current Modulel's names = ['Animal', 'Cat', 'Dog', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'animal']
animal Modulel's names = ['Animal', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
object's __dict__ =['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
Animal's dir() = ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'x']
Cat's dir() = ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'x', 'y']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', '_age', '_name', 'weight', 'x', 'y']
['_age', '_name', 'weight']
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '_age', '_name', 'weight', 'x', 'y']
Dog's dir = ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', '_dir_', 'x']
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', '_age', '_dir_', '_name', 'weight', 'x']
{'_name': 'snoppy', '_age': 10, 'weight': 20}
魔术方法**
- 分类:
1、创建、初始化与销毁
init 与 del
2、 hash
3、 bool
4、 可视化
5、 运算符重载
6、 容器和大小
7、 可调用对象
8、上下文管理
9、 反射
10、 描述器
11、 其他杂项
hash

python
class A:
def __init__(self, name, age=18):
self.name = name
def __hash__(self):
return 1
def __repr__(self):
return self.name
print(hash(A('tom'))) # 1
print((A('tom'), A('tom'))) #(tom, tom)
print([A('tom'), A('tom')]) # [tom, tom]
print('~'*30)
s = {A('tom'), A('tom')} # set
print(s) # 去重了吗 {tom, tom}
print({tuple('t'), tuple('t')}) # {('t',)}
print({('tom',), ('tom',)}) # {('tom',)}
print({'tom', 'tom'}) # {'tom'}
上例中set为什么不能剔除相同的key?
python
class A:
def __init__(self, name, age=18):
self.name = name
def __hash__(self):
return 1
def __repr__(self):
return self.name
def __eq__(self, other): # 这个函数作用?
return self.name == other.name
print(hash(A('tom'))) # 1
print((A('tom'), A('tom'))) # (tom, tom)
print([A('tom'), A('tom')]) # [tom, tom]
print('~' * 30)
s = {A('tom'), A('tom')} # set
print(s) # [去重了吗 ] diff {tom}
print({tuple('t'), tuple('t')}) # {('t',)}
print({('tom',), ('tom',)}) # {('tom',)}
print({'tom', 'tom'}) # {'tom'}

hash 方法只是返回一个hash值作为set的key,但是 去重,还需要__eq__来判断2个对象
是否相等。
hash值相等,只是hash冲突,不能说明两个对象是相等的。
因此,一般来说提供 hash 方法是为了作set或者dict的key,所以 去重 要同时提供
eq 方法。
不可hash对象isinstance(p1, collections.Hashable)一定为False。
去重 需要提供__eq__方法。
思考:
list类实例为什么不可hash?
练习
设计二维坐标类Point,使其成为可hash类型,并比较2个坐标的实例是否相等?
list类实例为什么不可hash
源码中有一句__hash__ =None,也就是如果调用 __hash__相当于None(),一定报错。
所有类都继承object ,而这个类是具有__hash__方法的,如果一个类不能被hash,就把
hash 设置为None。
练习参考
python
from collections import Hashable
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x, self.y))
def __eq__(self, other):
return self.x == other.x and self.y == other.y
p1 = Point(4, 5)
p2 = Point(4, 5)
print(hash(p1))
print(hash(p2))
print(p1 is p2)
print(p1 == p2) # True 使用_eq_
print(hex(id(p1)), hex(id(p2)))
print(set((p1, p2)))
print(isinstance(p1, Hashable))
bool
python
class A: pass
print(bool(A()))
if A():
print('Real A')
class B:
def __bool__(self):
return False
print(bool(B))
print(bool(B()))
if B:
print('Real B')
class C:
def _len_(self):
return 0
print(bool(C()))
if C():
print('Real C')
输出:
True
Real A
True
False
Real B
True
Real C
可视化

python
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __repr__(self):
return 'repr: {},{}'.format(self.name, self.age)
def __str__(self):
return 'str: {},{}'.format(self.name, self.age)
def __bytes__(self):
# return "(} is {}". format(self.name, self.age).encode()
import json
return json.dumps(self.__dict__).encode()
print(1, A('tom')) # print函数使用__str__
print('2', [A('tom')]) # []使用_str_,但其内部使用_repr_
print(3, ([str(A('tom'))])) # []使用_str_,str()函数也使用_str_
print(4, A('tom')) # print函数使用_str_
print(5, [A('tom')]) # []使用_str_,但其内部使用_repr_
print(6, ([str(A('tom'))])) # []使用_str ,str()函数也使用_str
print(7, 'str:a,1') # 字符串直接输出没有引号
s = '1'
print(8, s)
print(9, ['a'], (5,)) # 字符串在基本数据类型内部输出有引号
print(10, {s, 'a'})
print(11, bytes(A('tom')))
输出:
1 str: tom,18
2 [repr: tom,18]
3 ['str: tom,18']
4 str: tom,18
5 [repr: tom,18]
6 ['str: tom,18']
7 str:a,1
8 1
9 ['a'] (5,)
10 {'1', 'a'}
11 b'{"name": "tom", "age": 18}'
运算符重载
operator模块提供以下的特殊方法,可以将类的实例使用下面的操作符来操作

python
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __sub__(self, other):
return self.age - other.age
def __isub__(self, other):
return A(self.name, self - other)
tom = A('tom')
jerry = A('jerry', 16)
print(1, tom - jerry) # 2
print(2, jerry - tom, jerry.__sub__(tom)) # -2 , -2 n
print(3, id(tom)) # 4378826064
tom -= jerry
print(4, tom.age, id(tom)) # 2 4381561616
输出:
1 2
2 -2 -2
3 4378826064
4 2 4381561616
练习:
完成Point类设计,实现判断点相等的方法,并完成向量的加法
在直角坐标系里面,定义原点为向量的起点.两个向量和与差的坐标分别等于这两个向量相应坐标
的和与差若向量的表示为(xy)形式,
A(X1, Y1) B(X2,Y2), JWJA+B= (X1+X2, Y1+Y2) , A-B= (X1-X2, Y1-Y2)
python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def add(self, other):
return (self.x + other.x, self.y + other.y)
def __str__(self):
return '<Point: {}, {}>'.format(self.x, self.y)
p1 = Point(1, 1)
p2 = Point(1, 1)
points = (p1, p2)
print(points[0].add(points[1])) # (2, 2)
# 运算符重载
print(points[0] + points[1]) # <Point: 2, 2>
print(p1 == p2) # True
运算符重载应用场景
往往是用面向对象实现的类,需要做大量的运算,而运算符是这种运算在数学上最常见的表达方式。例如,上例中的对+进行了运算符重载,实现了Point类的二元操作,重新定义为Point+Point。
提供运算符重载,比直接提供加法方法要更加适合该领域内使用者的习惯。int类,几乎实现了所有操作符,可以作为参考。
@functools.total_ordering 装饰器

python
from functools import total_ordering
@total_ordering
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.age == other.age
def __gt__(self, other):
return self.age > other.age
tom = Person('tom', 20)
jerry = Person('jerry', 16)
print(tom > jerry) # True
print(tom < jerry) # False
print(tom >= jerry) #True
print(tom <= jerry) # False
上例中大大简化代码,但是一般来说比较实现等于或者小于方法也就够了,其它可以不实现,所以这个装饰器只是看着很美好,且可能会带来性能问题,建议需要什么方法就自己创建,少用这个装饰器。
python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __eq__(self, other):
return self.age == other.age
def __gt__(self, other):
return self.age > other.age
def __ge__(self, other):
return self.age >= other.age
tom = Person('tom', 20)
jerry = Person('jerry', 16)
print(tom > jerry) # True
print(tom < jerry) # False
print(tom >= jerry) # True
print(tom <= jerry) # False
print(tom == jerry) # False
print(tom != jerry) # True
容器相关方法

python
class A(dict):
def __missing__(self, key):
print('Missing key : ', key)
return 0
a = A()
print(a['k'])
输出:
Missing key : k
0
思考
为什么空字典、空字符串、空元组、空集合、空列表等可以等效为False?
练习
将购物车类改造成方便操作的容器类
python
class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def additem(self, item):
self.items.append(item)
def __iter__(self):
return iter(self.items)
def __getitem__(self, index): # 索引访问
return self.items[index]
def __setitem__(self, key, value): # 索引赋值
self.items[key] = value
def __str__(self):
return str(self.items)
def __add__(self, other): # +
self.items.append(other)
return self
cart = Cart()
cart.additem(1)
cart.additem('abc')
cart.additem(3)
# 长度、bool
print(len(cart))
print(bool(cart))
# 迭代
for x in cart:
print(x)
# in
print(3 in cart)
print(2 in cart)
# 索引操作
print(cart[1])
cart[1] = 'xyz'
print(cart)
# # 链式编程实现加法
print(cart + 4 + 5 + 6)
print(cart.__add__(17).__add__(18))
输出:
3
True
1
abc
3
True
False
abc
[1, 'xyz', 3]
[1, 'xyz', 3, 4, 5, 6]
[1, 'xyz', 3, 4, 5, 6, 17, 18]
可调用对象
Python中一切皆对象,函数也不例外。
python
def foo():
print(foo.__module__, foo.__name__)
foo()
# 等价于
foo.__call__()
输出:
__main__ foo
__main__ foo
函数即对象,对象foo加上(),就是调用对象的__call__()方法
可调用对象
可调用对象:定义一个类,并实例化得到其实例,将实例像函数一样调用。
python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __call__(self, *args, **kwargs):
return "<Point {}: {}>".format(self.x, self.y)
p = Point(4, 5)
print(p) # <__main__.Point object at 0x104b7d550>
print(p()) # <Point 4: 5>
class Adder:
def __call__(self, *args):
ret = 0
for x in args:
ret += x
self.ret = ret
return ret
adder = Adder()
print(adder(4, 5, 6)) # 15
print(adder.ret) #15
练习:
定义一个斐波那契数列的类,方便调用,计算第n项
python
class Fib:
def __init__(self):
self.items = [0, 1, 1]
def __call__(self, index):
if index < 0:
raise IndexError('Wrong Index')
if index < len(self.items):
return self.items[index]
for i in range(3, index + 1):
self.items.append(self.items[i - 1] + self.items[i - 2])
return self.items[index]
print(Fib()(100)) # 354224848179261915075
上例中,增加迭代的方法、返回容器长度、支持索引的方法
python
class Fib:
def __init__(self):
self.items = [0, 1, 1]
def __call__(self, index):
return self[index]
def __iter__(self):
return iter(self.items)
def __len__(self):
return len(self.items)
def __getitem__(self, index):
if index < 0:
raise IndexError('Wrong Index')
if index < len(self.items):
return self.items[index]
for i in range(len(self), index + 1):
self.items.append(self.items[i - 1] + self.items[i - 2])
return self.items[index]
def __str__(self):
return str(self.items)
_repr_ = __str__
fib = Fib()
print(fib(5), len(fib)) # 全部计算
print(fib(10), len(fib)) # 部分计算
for x in fib:
print(x)
print(fib[5], fib[6]) # 索引访问,不计算
输出:
5 6
55 11
0
1
1
2
3
5
8
13
21
34
55
5 8
可以看出使用类来实现斐波那契数列也是非常好的实现,还可以缓存数据,便于检索。
上下文管理
文件IO操作可以对文件对象使用上下文管理,使用with...as语法。
python
with open('test') as f:
pass
仿照上例写一个自己的类,实现上下文管理
python
class Point:
pass
with Point() as p: # AttributeError: _exit_
pass
exit 提示属性错误,没有 ,看了需要这个属性
上下文管理对象
当一个对象同时实现了__enter__()和__exit__方法,它就属于上下文管理的对象

python
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
with Point() as f:
print('do sth.')
实例化对象的时候,并不会调用enter,进入with语句块调用__enter__ 方法,然后执行语句体,最后离开with语句块的时候,调用__exit__方法。with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。
上下文管理的安全性
看看异常对上下文的影响。
python
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
with Point() as f:
raise Exception('error')
print('do sth.')
输出:
Traceback (most recent call last):
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 13, in <module>
raise Exception('error')
Exception: error
init
enter
exit
可以看出在enter和exit照样执行,上下文管理是安全的。
极端的例子
调用sys.exit(),它会退出当前解释器。
打开Python解释器,在里面敲入sys.exit(),窗口直接关闭了。也就是说碰到这一句,Python运行环境直接退出了。
python
import sys
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
with Point() as f:
sys.exit(-100)
print('do sth.')
print('outer')
输出:
init
enter
exit
从执行结果来看,依然执行了__exit__函数,哪怕是退出Python运行环境。说明上下文管理很安全。
with语句
python
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
def __exit__(self, exc_type, exc_val, exc_tb):
print('exit')
p = Point()
with p as f:
print(p == f) # 为什么不相等
print('do sth. ')
输出:
init
enter
False
exit
do sth.
问题在于 __enter__方法上,它将自己的返回值赋给f。修改上例
python
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
return self
def __exit__(self, exc_type, exc_val, ex_tb):
print('exit')
p = Point()
with p as f:
print(p == f)
print('do sth.')
输出:
init
enter
True
exit
do sth.
enter 方法返回值就是上下文中使用的对象,with语法会把它的返回值赋给as子句的变量。
方法和 exit 方法的参数 exit 方法参数
enter 方法没有其他参数。
exit 方法有3个参数:
exit(self, exc_type, exc_value, traceback)
这三个参数都与异常有关。
如果该上下文退出时没有异常,这3个参数都为None。
如果有异常,参数意义如下
exc_type,异常类型
exc_value,异常的值
traceback,异常的追踪信息
exit 方法返回一个等效True的值,则压制异常;否则,继续抛出异常
python
class Point:
def __init__(self):
print('init')
def __enter__(self):
print('enter')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(exc_type)
print(exc_val)
print(exc_tb)
print('exit')
return "abc"
p = Point()
with p as f:
raise Exception('New Error')
print('do sth.')
print('outer')
输出:
init
enter
<class 'Exception'>
New Error
<traceback object at 0x1028b7e00>
exit
do sth.
outer
练习
为加法函数计时
方法1、使用装饰器显示该函数的执行时长
方法2、使用上下文管理方法来显示该函数的执行时长
python
import time
def add(x, y):
time.sleep(2)
return x + y
装饰器实现
python
import datetime
from functools import wraps
import time
def timeit(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
delta = (datetime.datetime.now() - start).total_seconds()
print('{} took {}s'.format(fn.__name__, delta))
return ret
return wrapper
@timeit
def add(x, y):
time.sleep(2)
return x + y
print(add(4, 5))
输出:
add took 2.000806s
9
用上下文实现
python
import datetime
from functools import wraps
import time
def add(x, y):
time.sleep(2)
return x + y
class Timeit:
def __init__(self, fn):
self.fn = fn
def __enter__(self):
self.start = datetime.datetime.now()
return self.fn
def __exit__(self, exc_type, exc_val, exc_tb):
delta = (datetime.datetime.now() - self.start).total_seconds()
print("{} took {}s".format(self.fn.__name__, delta))
with Timeit(add) as fn:
print(fn(4, 6))
print('~' * 80)
print(add(4, 5))
另一种实现,使用可调用对象实现。
python
import datetime
from functools import wraps
import time
def add(x, y):
time.sleep(2)
return x + y
class Timeit:
def __init__(self, fn):
self.fn = fn
def __enter__(self):
self.start = datetime.datetime.now()
return self.fn
def __exit__(self, exc_type, exc_val, exc_tb):
delta = (datetime.datetime.now() - self.start).total_seconds()
print("{} took {}s".format(self.fn.__name__, delta))
def __call__(self, x, y):
print(x, y)
return self.fn(x, y)
with Timeit(add) as fn:
print(fn(4, 5))
输出:
9
add took 2.005119s
根据上面的代码,能不能把类当做装饰器用?
python
import datetime
from functools import wraps
import time
class Timeit:
def __init__(self, fn):
self.fn = fn
def __enter__(self):
self.start = datetime.datetime.now()
return self.fn
def __exit__(self, exc_type, exc_val, exc_tb):
delta = (datetime.datetime.now() - self.start).total_seconds()
print("__exit__ {} took {}s".format(self.fn.__name__, delta))
pass
def __call__(self, *args, **kwargs):
self.start = datetime.datetime.now()
ret = self.fn(*args, **kwargs)
self.delta = (datetime.datetime.now() - self.start).total_seconds()
print('__call__ {} took {}s. call'.format(self.fn.__name__, self.delta))
return ret
@Timeit
def add(x, y):
"""This is add function."""
time.sleep(2)
return x + y
add(1, 2)
print(add.__doc__)
输出:
__call__ add took 2.00509s. call
None
思考
如何解决文档字符串问题?
方法一
doc 直接修改
python
class Timeit:
def __init__(self, fn):
self.fn = fn
#把函数对象的文档字符串赋给类
self.__doc__ = fn.__doc__
方法二
使用functools.wraps函数
python
import datetime
from functools import wraps
import time
class Timeit:
"""This is A Class"""
def __init__(self, fn):
self.fn = fn
# 把函数对象的文档字符串赋给类
# self._doc_ = fn._doc_
# update_wrapper(self, fn)
wraps(fn)(self)
def __enter__(self):
self.start = datetime.datetime.now()
return self.fn
def __exit__(self, exc_type, exc_val, exc_tb):
delta = (datetime.datetime.now() - self.start).total_seconds()
print("__exit__ {} took {}s".format(self.fn.__name__, delta))
pass
def __call__(self, *args, **kwargs):
self.start = datetime.datetime.now()
ret = self.fn(*args, **kwargs)
self.delta = (datetime.datetime.now() - self.start).total_seconds()
print('__call__ {} took {}s. call'.format(self.fn.__name__, self.delta))
return ret
@Timeit
def add(x, y):
"""This is add function."""
time.sleep(2)
return x + y
add(1, 2)
print(add.__doc__)
print(Timeit(add).__doc__)
输出:
__call__ add took 2.004217s. call
This is add function.
This is add function.
上面的类即可以用在上下文管理,又可以用做装饰器
上下文应用场景
1.增强功能
在代码执行的前后增加代码,以增强其功能。类似装饰器的功能。
- 资源管理
打开了资源需要关闭,例如文件对象、网络连接、数据库连接等
- 权限验证
在执行代码之前,做权限的验证,在__enter__中处理
contextlib.contextmanager
它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现 __enter__和
__exit__方法。
对下面的函数有要求,必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。
也就是这个装饰器接收一个生成器对象作为参数。
python
import contextlib
@contextlib.contextmanager
def foo(): #
print('enter') # 相当于_enter_() 求学院
yield # yield 5, yield的值只能有一个,作为_enter_方法的返回值
print('exit') # 相当于_exit_() 人的高
with foo() as f:
# raise Exception()
print(f)
输出:
enter
None
exit
f接收yield语句的返回值。
上面的程序看似不错但是,增加一个异常试一试,发现不能保证exit的执行,怎么办?
try finally 。
python
import contextlib
@contextlib.contextmanager
def foo():
print('enter')
try:
yield # yield 5,yield的值只能有一个,作为__enter_方法的返回值
finally:
print('exit')
with foo() as f:
raise Exception()
print(f)
输出:
enter
exit
Traceback (most recent call last):
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 15, in <module>
raise Exception()
Exception
上例这么做有什么意义呢?
当yield发生处为生成器函数增加了上下文管理。这是为函数增加上下文机制的方式。
- 把yield之前的当做__enter__方法执行
- 把yield之后的当做__exit__方法执行
- 把yield的值作__enter__的返回值
总结
如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方式,如果业务复杂,用类的
方式加__enter__和__exit__ 方法方便。
python
import contextlib
import datetime
import time
@contextlib.contextmanager
def add(x, y): # 为生成器函数增加了上下文管理
start = datetime.datetime.now()
try:
yield x + y # yield 5, yield的值只能有一个,作__enter__方法的返回值
finally:
delta = (datetime.datetime.now() - start).total_seconds()
print(delta)
with add(4, 5) as f:
# raise Exception()
time.sleep(2)
print(f)
输出:
9
2.005053
总结
如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方式,如果业务复杂,用类的
方式加 enter 和 exit 方法方便。
反射
概述
运行时,区别于编译时,指的是程序被加载到内存中执行的时候。
反射,reflection,指的是运行时获取类型定义信息。
一个对象能够在运行时,像照镜了一样,反射出其类型信息。
简单说,在Python中,能够通过一个对象,找出其type、class. attribute或method的能力,称为反射或者自省。
具有反射能力的函数有:type()、isinstance()、callable()、dir()、getattr()
反射相关的函数和方法
需求
有一个Point类,查看它实例的属性,并修改它。动态为实例增加属性
python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return "Point({}, {})".format(self.x, self.y)
def show(self):
print(self.x, self.y)
p = Point(4, 5)
print(p) #Point(4, 5)
print(p.__dict__) #{'x': 4, 'y': 5}
p.__dict__['y'] = 16
print(p.__dict__) # {'x': 4, 'y': 16}
p.z = 10
print(p.__dict__) #{'x': 4, 'y': 16, 'z': 10}
print(dir(p)) # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'show', 'x', 'y', 'z']
print(p.__dir__()) # ['x', 'y', 'z', '__module__', '__firstlineno__', '__init__', '__str__', 'show', '__static_attributes__', '__dict__', '__weakref__', '__doc__', '__new__', '__repr__', '__hash__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__reduce_ex__', '__reduce__', '__getstate__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
上例通过属性字典__dict__来访问对象的属性,本质上也是利用的反射的能力。
但是,上面的例子中,访问的方式不优雅,Python提供了内置的函数。

用上面的方法来修改上例的代码
python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return "Point({}, {})".format(self.x, self.y)
def show(self):
print(self)
p1 = Point(4, 5)
p2 = Point(10, 10)
print(1, repr(p1), repr(p2), sep=',') # <__main__.Point object at 0x103099550>,<__main__.Point object at 0x103295450>
print(p1.__dict__) # {'x': 4, 'y': 5}
setattr(p1, 'y', 16)
setattr(p1, 'z', 10)
print(2, getattr(p1, '__dict__')) # {'x': 4, 'y': 16, 'z': 10}
# 动态调用方法
if hasattr(p1, 'show'):
getattr(p1, 'show')() # Point(4, 16)
# 动态增加方法
# 为类增加方法
if not hasattr(Point, 'add'):
setattr(Point, 'add', lambda self, other: Point(self.x + other.x, self.y + other.y))
print(3, Point.add) # <function <lambda> at 0x1003244a0>
print(p1.add) # <bound method <lambda> of <__main__.Point object at 0x100485550>>
print(5,p1.add(p2)) # 绑定 # Point(14, 26)
# 为实例增加方法,未绑定
if not hasattr(p1, 'sub'):
setattr(p1, 'sub', lambda self, other: Point(self.x - other.x, self.y - other.y))
print(6,p1.sub(p1, p1)) #Point(0, 0)
print(p1.sub) # <function <lambda> at 0x100ddd080>
# add在谁里面,sub在谁里面
print(p1.__dict__) # {'x': 4, 'y': 16, 'z': 10, 'sub': <function <lambda> at 0x100ddd080>}
print(Point.__dict__) #{'__module__': '__main__', '__firstlineno__': 1, '__init__': <function Point.__init__ at 0x100b8d440>, '__str__': <function Point.__str__ at 0x100c98040>, 'show': <function Point.show at 0x100d5b9c0>, '__static_attributes__': ('x', 'y'), '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None, 'add': <function <lambda> at 0x100c544a0>}
涔吮 思考
这种动态增加属性的方式和装饰器修饰一个类、Mixin方式的差异?
这种动态增删属性的方式是运行时改变类或者实例的方式,但是装饰器或Mixin都是定义时就决定了,因此反射能力具有更大的灵活性。
练习
命令分发器,通过名称找对应的函数执行。
思路:名称找对象的方法
python
class Dispatcher:
def __init__(self):
self._run()
def cmd1(self):
print("I'm cmd1")
def cmd2(self):
print("I'm cmd2")
def _run(self):
while True:
cmd = input('Plz input a command: ').strip()
if cmd == 'quit':
break
getattr(self, cmd, lambda: print('Unknown Command {}'.format(cmd)))()
Dispatcher()
输出
Plz input a command: cmd1
I'm cmd1
Plz input a command: cmd2
I'm cmd2
Plz input a command:
上例中使用getatt 方法找到对象的属性的方式,比自己维护一个字典来建立名称和函数之间的关
系的方式好多了。
反射相关的魔术方法
getattr(), setattr(),delattr()这三个魔术方法,分别测试
getattr()
python
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self, x, y):
self.x = x
self.y = y
def show(self):
print(self.x, self.y)
def __getattr__(self, item):
return "missing {}".format(item)
p1 = Point(4, 5)
print(p1.x) # 4
print(p1.z) # 6
print(p1.n) # 0
print(p1.t) # missing t
一个类的属性会按照继承关系找,如果找不到,就会执行 getattr()方法,如果没有这个方法,就会抛出AttributeError异常表示找不到属性。
查找属性顺序为:
instance. dict -->instance. class.dict -->继承的祖先类(直到object)的__dict__
---找不到-->调用 getattr()
setattr()
python
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self, x, y):
self.x = x
self.y = y
def show(self):
print(self.x, self.y)
def __getattr__(self, item):
return "missing {}".format(item)
def __setattr__(self, key, value):
print("setattr {}={}".format(key, value))
p1 = Point(4, 5)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.t) # missing
p1.x = 50
print(p1.__dict__)
p1.__dict__['x'] = 60
print(p1.__dict__)
print(p1.x)
输出:
setattr x=4
setattr y=5
missing x
6
0
missing t
setattr x=50
{}
{'x': 60}
60
实例通过.点设置属性,如同self.x =x,就会调用 setattr(),属性要加到实例的__dict__中,就需要自己完成。
python
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self, x, y):
self.x = x
self.y = y
def show(self):
print(self.x, self.y)
def __getattr__(self, item):
return "missing (}".format(item)
def __setattr__(self, key, value):
print("setattr {}={}".format(key, value))
self.__dict__[key] = value
setattr()方法,可以拦截对实例属性的增加、修改操作,如果要设置生效,需要自己操作实例的__dict__。
delattr()
python
class Point:
z = 5
def __init__(self, x, y):
self.x = x
self.y = y
def __delattr__(self, item):
print('Can not del {}'.format(item))
p = Point(14, 5)
del p.x # Can not del x
p.z = 15
del p.z # Can not del z
del p.z # Can not del z
print(Point.__dict__) # {'__module__': '__main__', '__firstlineno__': 1, 'z': 5, '__init__': <function Point.__init__ at 0x1048a8040>, '__delattr__': <function Point.__delattr__ at 0x1047ef2e0>, '__static_attributes__': ('x', 'y'), '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
print(p.__dict__) # {'x': 14, 'y': 5, 'z': 15}
del Point.z
print(Point.__dict__) # {'__module__': '__main__', '__firstlineno__': 1, '__init__': <function Point.__init__ at 0x1048a8040>, '__delattr__': <function Point.__delattr__ at 0x1047ef2e0>, '__static_attributes__': ('x', 'y'), '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
可以阻止通过实例删除属性的操作。但是通过类依然可以删除属性。
getattribute
python
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self, x, y):
self.x = x
self.y = y
def __getattr__(self, item):
return "missing {}".format(item)
def __getattribute__(self, item):
return item
p1 = Point(4, 5)
print(p1.__dict__) # __dict__
print(p1.x) # x
print(p1.z) # z
print(p1.n) # n
print(p1.t) # t
print(
Point.__dict__) # {'__module__': '__main__', '__firstlineno__': 7, 'z': 6, '__init__': <function Point.__init__ at 0x100880040>, '__getattr__': <function Point.__getattr__ at 0x1009479c0>, '__getattribute__': <function Point.__getattribute__ at 0x100d21080>, '__static_attributes__': ('x', 'y'), '__doc__': None}
print(Point.z) # 6
实例的所有的属性访问,第一个都会调用 getattribute_ 方法,它阻止了属性的查找,该方法应该返回(计算后的)值或者抛出一个AttributeError异常。它的return值将作为属性查找的结果。如果抛出AttributeError异常,则会直接调用 __getattr__方法,因沩表示属性没有找到。
python
class Base:
n = 0
class Point(Base):
z = 6
def __init__(self, x, y):
self.x = x
self.y = y
def __getattr__(self, item):
return "missing {}".format(item)
def __getattribute__(self, item):
# raise AttributeError ("Not Found")
# pass
# return self.__dict__[item]
return object.__getattribute__(self, item)
p1 = Point(4, 5)
print(p1.__dict__) # {'x': 4, 'y': 5}
print(p1.x) # 4
print(p1.z) # 6
print(p1.n) # 0
print(p1.t) # missing t
print(
Point.__dict__) # {'__module__': '__main__', '__firstlineno__': 5, 'z': 6, '__init__': <function Point.__init__ at 0x100eb0040>, '__getattr__': <function Point.__getattr__ at 0x100f739c0>, '__getattribute__': <function Point.__getattribute__ at 0x100ff5080>, '__static_attributes__': ('x', 'y'), '__doc__': None}
print(Point.z) # 6
getattribute 方法中为了避免在该方法中无限的递归,它的实现应该永远调用基类的同名方法以访问需要的任何属性,例如 object.getattribute(self,name)。
注意,除非你明确地知道 getattribute 方法用来做什么,否则不要使用它。
描述器 Descriptors
描述器的表现
方法签名如下
object.get(self, instance, owner)
object.set(self, instance, value)
object.delete(self, instance)
self指代当前实例,调用者
instance 是owner的实例
owner 是属性的所属的类
请思考下面程序的执行流程是什么?
python
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
class B:
x = A()
def __init__(self):
print('B.init')
print('-' * 20)
print(B.x.a1)
print('=' * 20)
b = B()
print(b.x.a1)
输出:
A.init
--------------------
a1
====================
B.init
a1
可以看出执行的先后顺序吧?
类加载的时候,类变量需要先生成,而类B的x属性是类A的实例,所以类A先初始化,所以打印
A.init。
然后执行到打印B.x.a1。
然后实例化并初始化B的实例b。
打印b.x.a1,会查找类属性b.x,指向A的实例,所以返回A实例的属性a1的值。
看懂执行流程了,再看下面的程序,对类A做一些改造。
如果在类A中实现 get 方法,看看变化
python
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
def __get__(self, instance, owner):
print("A.__get__{} {} {}".format(self, instance, owner))
class B:
x = A()
def __init__(self):
print('B.init')
print(' - ' * 20)
print(B.x)
# print(B.x.al) # #Uf#AttributeError: 'NoneType' object has no attribute 'al'
print('=' * 20)
b = B()
print(b.x)
输出:
A.init
- - - - - - - - - - - - - - - - - - - -
A.__get__<__main__.A object at 0x102f61550> None <class '__main__.B'>
None
====================
B.init
A.__get__<__main__.A object at 0x102f61550> <__main__.B object at 0x103307620> <class '__main__.B'>
None
因为定义了__get__ 方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A的实例的访问,就会调用 __get__方法
如何解决上例中访问报错的问题,问题应该来自 get 方法。
self, instance,owner这这三个参数,是什么意思?
<main.A object at 0x0000000000B84E48> None
<main.A object at 0x0000000000B84E48><main.B object at 0x0000000000B84F28>
self都是A的实例
owner都是B类
instance说明
python
- None表示是没有B类的实例,对应调用B.x
- <__main__.B object at 0x0000000000B84F28>表示是B的实例,对应调用B().×
使用返回值解决。返回self,就是A的实例,该实例有a1属性,返回正常。
python
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
def __get__(self, instance, owner):
print("A.__get__{} {} {}".format(self, instance, owner))
return self # 解决 返回 None 的问题
class B:
x = A()
def __init__(self):
print('B.init')
print(' - ' * 20)
print(B.x)
print(B.x.a1)
print('=' * 20)
b = B()
print(b.x)
print(b.x.a1)
输出:
A.init
- - - - - - - - - - - - - - - - - - - -
A.__get__<__main__.A object at 0x10084d550> None <class '__main__.B'>
<__main__.A object at 0x10084d550>
A.__get__<__main__.A object at 0x10084d550> None <class '__main__.B'>
a1
====================
B.init
A.__get__<__main__.A object at 0x10084d550> <__main__.B object at 0x100af7620> <class '__main__.B'>
<__main__.A object at 0x10084d550>
A.__get__<__main__.A object at 0x10084d550> <__main__.B object at 0x100af7620> <class '__main__.B'>
a1
那么类B的实例属性也可以?
python
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
def __get__(self, instance, owner):
print("A.__get__{} {} {}".format(self, instance, owner))
return self # 解决 返回 None 的问题
class B:
x = A()
def __init__(self):
print('B.init')
self.b = A()
print(' - ' * 20)
print(B.x)
print(B.x.a1)
print('=' * 20)
b = B()
print(b.x)
print(b.x.a1)
print('-----------------')
print(b.b)
输出:
A.init
- - - - - - - - - - - - - - - - - - - -
A.__get__<__main__.A object at 0x105301550> None <class '__main__.B'>
<__main__.A object at 0x105301550>
A.__get__<__main__.A object at 0x105301550> None <class '__main__.B'>
a1
====================
B.init
A.init
A.__get__<__main__.A object at 0x105301550> <__main__.B object at 0x105527620> <class '__main__.B'>
<__main__.A object at 0x105301550>
A.__get__<__main__.A object at 0x105301550> <__main__.B object at 0x105527620> <class '__main__.B'>
a1
-----------------
<__main__.A object at 0x1055bd1d0>
从运行结果可以看出,只有类属性是类的实例才行。
描述器定义
Python中,一个类实现了 get、set,delete 三个方法中的任何一个方法,就是描述器。
如果仅实现了__get__,就是非数据描述符 non-data descriptor ;
同时实现了__get__、set 就是数据描述符 data descriptor。
如果一个类的类属性设置为描述器,那么它被称为owner属主。
属性的访问顺序
为上例中的类B增加实例属性x
python
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
def __get__(self, instance, owner):
print("A.__get__{} {} {}".format(self, instance, owner))
return self # 解决 返回 None 的问题
class B:
x = A()
def __init__(self):
print('B.init')
self.x = 'b.x' # 增加实例属性x
print(' - ' * 20)
print(B.x)
print(B.x.a1)
print('=' * 20)
b = B()
print(b.x)
# Traceback (most recent call last):
# File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 30, in <module>
# print(b.x.a1)
# ^^^^^^
# AttributeError: 'str' object has no attribute 'a1'
# print(b.x.a1)
输出:
A.init
- - - - - - - - - - - - - - - - - - - -
A.__get__<__main__.A object at 0x104e49550> None <class '__main__.B'>
<__main__.A object at 0x104e49550>
A.__get__<__main__.A object at 0x104e49550> None <class '__main__.B'>
a1
====================
B.init
b.x
b.x访问到了实例的属性,而不是描述器。
继续修改代码,为类A增加 set 方法。
python
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
def __get__(self, instance, owner):
print("A.__get__{} {} {}".format(self, instance, owner))
return self # 解决 返回 None 的问题
def __set__(self, instance, value):
print('A._ set_ {} {} {} '. format(self, instance, value))
self.data = value
class B:
x = A()
def __init__(self):
print('B.init')
self.x = 'b.x' # 增加实例属性x
print(' - ' * 20)
print(1,B.x) # 1 <__main__.A object at 0x1006f5550>
print(2,B.x.a1) # a1
print('=' * 20)
b = B()
print(10,b.x) # <__main__.A object at 0x102cd9550>
print(b.x.a1) # a1
输出:
A.init
- - - - - - - - - - - - - - - - - - - -
A.__get__<__main__.A object at 0x1006f5550> None <class '__main__.B'>
1 <__main__.A object at 0x1006f5550>
A.__get__<__main__.A object at 0x1006f5550> None <class '__main__.B'>
2 a1
====================
B.init
A._ set_ <__main__.A object at 0x1006f5550> <__main__.B object at 0x1008af620> b.x
A.__get__<__main__.A object at 0x1006f5550> <__main__.B object at 0x1008af620> <class '__main__.B'>
10 <__main__.A object at 0x1006f5550>
A.__get__<__main__.A object at 0x1006f5550> <__main__.B object at 0x1008af620> <class '__main__.B'>
a1
返回变成了a1,访问到了描述器的数据。
属性查找顺序
实例的__dict__ 优先于非数据描述器
数据描述器 优先于实例的 dict
delete 方法有同样的效果,有了这个方法,就是数据描述器。
尝试着增加下面的代码,看看字典的变化
b.x = 500
B.x = 600
b.x= 500,这是调用数据描述器的__set__ 方法,或调用非数据描述器的实例覆盖。
B.X=600,赋值即定义,这是覆盖类属性。
本质(进阶)
Python真的会做的这么复杂吗,再来一套属性查找顺序规则?看看非数据描述器和数据描述器,
类B及其 __dict__的变化。
屏蔽和不屏蔽 __set__方法,看看变化。
python
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init')
def __get__(self, instance, owner):
print("A.__get__{} {} {}".format(self, instance, owner))
return self # 解决 返回 None 的问题
#def __set__(self, instance, value):
# print('A._ set_ {} {} {} '. format(self, instance, value))
# self.data = value
class B:
x = A()
def __init__(self):
print('B.init')
self.x = 'b.x' # 增加实例属性x
self.y = 'b.y'
print(' - ' * 20)
print(1, B.x) # 1 <__main__.A object at 0x1006f5550>
print(2, B.x.a1) # a1
print('=' * 20)
b = B()
print(b.y) # a1
print('字典')
print(11, b.__dict__)
print(12, B.__dict__)
不删除 __set__ 方法结果如下 :
def __set__(self, instance, value):
print('A._ set_ {} {} {} '. format(self, instance, value))
self.data = value
A.init
- - - - - - - - - - - - - - - - - - - -
A.__get__<__main__.A object at 0x103141550> None <class '__main__.B'>
1 <__main__.A object at 0x103141550>
A.__get__<__main__.A object at 0x103141550> None <class '__main__.B'>
2 a1
====================
B.init
A._ set_ <__main__.A object at 0x103141550> <__main__.B object at 0x10377b620> b.x
b.y
字典
11 {'y': 'b.y'}
12 {'__module__': '__main__', '__firstlineno__': 16, 'x': <__main__.A object at 0x103141550>, '__init__': <function B.__init__ at 0x103321300>, '__static_attributes__': ('x', 'y'), '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
删除 __set__ 方法结果如下
A.init
- - - - - - - - - - - - - - - - - - - -
A.__get__<__main__.A object at 0x1024cd550> None <class '__main__.B'>
1 <__main__.A object at 0x1024cd550>
A.__get__<__main__.A object at 0x1024cd550> None <class '__main__.B'>
2 a1
====================
B.init
b.y
字典
11 {'x': 'b.x', 'y': 'b.y'}
12 {'__module__': '__main__', '__firstlineno__': 16, 'x': <__main__.A object at 0x1024cd550>, '__init__': <function B.__init__ at 0x1024f9080>, '__static_attributes__': ('x', 'y'), '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}
原来不是什么 数据描述器 优先级高,而是把实例的属性从 dict 中给去除掉了,造成了该属性如果是数据描述器优先访问的假象。说到底,属性访问顺序从来就没有变过。
Python中的描述器
描述器在Python中应用非常广泛。
Python的方法(包括staticmethod () 和classmethod()都实现为非数据描述器。因此,实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为。property(函数实现为一个数据描述器。因此,实例不能覆盖属性的行为。
python
class A:
@classmethod
def foo(cls): # 非数据描述器
pass
@staticmethod # 非数据描述器
def bar():
pass
@property # 数据描述器
def z(self):
return 5
def getfoo(self): # 非数据描述器
return self.foo
def __init__(self): # 非数据描述器
self.foo = 100
self.bar = 200
# self.z = 300
a = A()
print(a.__dict__) # {'foo': 100, 'bar': 200}
print(A.__dict__) # {'__module__': '__main__', '__firstlineno__': 1, 'foo': <classmethod(<function A.foo at 0x102235440>)>, 'bar': <staticmethod(<function A.bar at 0x102440040>)>, 'z': <property object at 0x102447f10>, 'getfoo': <function A.getfoo at 0x102585080>, '__init__': <function A.__init__ at 0x102585300>, '__static_attributes__': ('bar', 'foo'), '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
foo、bar都可以在实例中覆盖,但是z不可以。
练习
- 实现StaticMethod装饰器,完成staticmethod装饰器的功能
python
# 类staticmethod装饰器
class StaticMethod: # 怕冲突改名
def __init__(self, fn):
self._fn = fn
def __get__(self, instance, owner):
return self._fn
class A:
@StaticMethod
# stmtd = StaticMethod(stmtd)
def stmtd():
print('static method')
A.stmtd()
A().stmtd()
输出:
static method
static method
- 实现ClassMethod装饰器,完成classmethod装饰器的功能
python
from functools import partial
# 类classmethod装饰器
class ClassMethod: # 怕冲突改名
def __init__(self, fn):
self._fn = fn
def __get__(self, instance, owner):
ret = self._fn(owner)
return ret
class A:
@ClassMethod
# clsmtd = ClassMethod(clsmtd)
# 调用A.clsmtd()或者 A().c1smtd()
def clsmtd(cls):
print(cls.__name__)
print(A.__dict__)
A.clsmtd
print('~' * 80)
A.clsmtd()
输出:
{'__module__': '__main__', '__firstlineno__': 14, 'clsmtd': <__main__.ClassMethod object at 0x104a35550>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
A
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A
Traceback (most recent call last):
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 26, in <module>
A.clsmtd()
~~~~~~~~^^
TypeError: 'NoneType' object is not callable
A.cIsmtd()的意思就是None(),一定报错。怎么修改?
A.clsmtd()其实就应该是A.clsmtd(cls)(),应该怎么处理?
A.clsmeth = A.clsmtd (cls)
应该用partial函数
python
from functools import partial
# 类classmethod装饰器
class ClassMethod: # 怕冲突改名
def __init__(self, fn):
self._fn = fn
def __get__(self, instance, cls):
ret = partial(self._fn, cls)
return ret
class A:
@ClassMethod
# clsmtd = ClassMethod(clsmtd)
# 调用A.clsmtd()或者 A().c1smtd()
def clsmtd(cls):
print(cls.__name__)
print(A.__dict__)
print(A.clsmtd)
print('~' * 80)
print(A.clsmtd())
输出:
{'__module__': '__main__', '__firstlineno__': 14, 'clsmtd': <__main__.ClassMethod object at 0x104941550>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
functools.partial(<function A.clsmtd at 0x1048eb9c0>, <class '__main__.A'>)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A
None
- 对实例的数据进行校验
python
class Person:
def __init__(self, name:str, age:int):
self.name = name
self.age = age
对上面的类的实例的属性name、age进行数据校验
思路
- 写函数,在 init 中先检查,如果不合格,直接抛异常
- 装饰器,使用inspect模块完成
- 描述器
python
# 写函数检查
class Person:
def __init__(self, name: str, age: int):
params = ((name, str), (age, int))
if not self.checkdata(params):
raise TypeError
self.name = name
self.age = age
def checkdata(self, params):
for p, t in params:
if not isinstance(p, t):
return False
return True
p = Person('tom', '20')
输出:
Traceback (most recent call last):
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 19, in <module>
p = Person('tom', '20')
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 6, in __init__
raise TypeError
TypeError
这种方法耦合度太高。
装饰器的方式,前面写过类似的,这里不再赘述。
描述器方式
需要使用数据描述器,写入实例属性的时候做检查
python
class Typed:
def __init__(self, name, type):
self.name = name
self.type = type
def __get__(self, instance, owner):
if instance is not None:
return instance.__dict__[self.name]
return self
def __set__(self, instance, value):
if not isinstance(value, self.type):
raise TypeError(value)
instance.__dict__[self.name] = value
class Person:
name = Typed('name', str) # 不优雅
age = Typed('age', int) # 不优雅
def __init__(self, name: str, age: int):
self.name = name
self.age = age
p = Person('tom', '20')
输出:
Traceback (most recent call last):
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 26, in <module>
p = Person('tom', '20')
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 23, in __init__
self.age = age
^^^^^^^^
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 13, in __set__
raise TypeError(value)
TypeError: 20
代码看似不错,但是有硬编码,能否直接获取形参类型,使用inspect模块
先做个实验
python
params = inspect.signature(Person).parameters
OrderedDict({'name': <Parameter "name: str">, 'age': <Parameter "age: int">})
看看返回什么结果
完整代码如下
python
class Typed:
def __init__(self, name, type):
self.name = name
self.type = type
def __get__(self, instance, owner):
if instance is not None:
return instance.__dict__[self.name]
return self
def __set__(self, instance, value):
if not isinstance(value, self.type):
raise TypeError(value)
instance.__dict__[self.name] = value
import inspect
def typeassert(cls):
params = inspect.signature(cls).parameters
print(params)
for name, param in params.items():
print(param.name, param.annotation)
if param.annotation != param.empty: # 注入类属性
setattr(cls, name, Typed(name, param.annotation))
return cls
@typeassert
class Person:
# name = Typed('name',str) # 装饰器注入
# age = Typed('age', int)
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def __repr__(self):
return "{} is {}".format(self.name, self.age)
p = Person('tom', 20)
print(p)
print('~' * 100)
p = Person('tom', '20')
输出:
OrderedDict({'name': <Parameter "name: str">, 'age': <Parameter "age: int">})
name <class 'str'>
age <class 'int'>
tom is 20
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Traceback (most recent call last):
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 47, in <module>
p = Person('tom', '20')
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 37, in __init__
self.age = age
^^^^^^^^
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 13, in __set__
raise TypeError(value)
TypeError: 20
可以把上面的函数装饰器改为类装饰器,如何写?
python
class Typed:
def __init__(self, type):
self.type = type
def __get__(self, instance, owner):
pass
def __set__(self, instance, value):
print('T.set', self, instance, value)
if not isinstance(value, self.type):
raise ValueError(value)
import inspect
class TypeAssert:
def __init__(self, cls):
self.cls = cls # 记录着被包装的Person类新算业
params = inspect.signature(self.cls).parameters
print(params)
for name, param in params.items():
print(name, param.annotation)
if param.annotation != param.empty:
setattr(self.cls, name, Typed(param.annotation)) # 注入类属性
print(self.cls.__dict__)
def __call__(self, name, age):
p = self.cls(name, age) # 重新构建一个新的Person对象
return p
@TypeAssert
class Person: # Person = TypeAssert(Person)
# name = Typed (str)
# age = Typed (int)
def __init__(self, name: str, age: int):
self.name = name
self.age = age
p1 = Person('tom', 18)
print(id(p1))
print('='*100)
p2 = Person('tom', 20)
print(id(p2))
print('~' * 100 )
p3 = Person('tom', '20')
输出:
OrderedDict({'name': <Parameter "name: str">, 'age': <Parameter "age: int">})
name <class 'str'>
age <class 'int'>
{'__module__': '__main__', '__firstlineno__': 33, '__init__': <function Person.__init__ at 0x1045c5580>, '__static_attributes__': ('age', 'name'), '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'name': <__main__.Typed object at 0x103d55550>, 'age': <__main__.Typed object at 0x103b65d10>}
T.set <__main__.Typed object at 0x103d55550> <__main__.Person object at 0x103d556a0> tom
T.set <__main__.Typed object at 0x103b65d10> <__main__.Person object at 0x103d556a0> 18
4359280288
====================================================================================================
T.set <__main__.Typed object at 0x103d55550> <__main__.Person object at 0x103b65e50> tom
T.set <__main__.Typed object at 0x103b65d10> <__main__.Person object at 0x103b65e50> 20
4357250640
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
T.set <__main__.Typed object at 0x103d55550> <__main__.Person object at 0x103b66490> tom
T.set <__main__.Typed object at 0x103b65d10> <__main__.Person object at 0x103b66490> 20
Traceback (most recent call last):
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 50, in <module>
p3 = Person('tom', '20')
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 29, in __call__
p = self.cls(name, age) # 重新构建一个新的Person对象
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 40, in __init__
self.age = age
^^^^^^^^
File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 11, in __set__
raise ValueError(value)
ValueError: 20
练习
有一个无序序列[37,99, 73,48,47,40,40,25,99,511,请先排序并打印输出。
分别尝试插入20、40、41到这个序列中合适的位置,保证其有序。
思路
排序后二分查找到适当位置插入数值。
排序使用sorted解决,假设升序输出。
查找插入点,使用二分查找完成。
假设全长为n,首先在大致的中点元素开始和待插入数比较,如果大则和右边的区域的中点继续
比较,如果小则和左边的区域的中点进行比较,以此类推。直到中点就是
python
def insert_sort(orderlist, i):
ret = orderlist[:]
low = 0
high = len(orderlist) - 1
while low < high:
mid = (low + high) // 2
if orderlist[mid] < i: #
low = mid + 1 # 说明主大,右边,限制下限
else:
high = mid # 说明主不大手,左边,限制上限
print(low) # 1ow为插入点
ret.insert(low, i)
return ret
# 测试
r = []
for x in (40, 20, 41):
r = insert_sort(r, x)
print(r)
输出:
0
0
1
[20, 41, 40]
看似上面代码不错,请测试插入100。
问题来了,100插入的位置不对,为什么?
python
def insert_sort(orderlist, i):
ret = orderlist[:]
low = 0
high = len(orderlist) # 去掉-1
while low < high:
mid = (low + high) // 2
if orderlist[mid] < i: #
low = mid + 1 # 说明i大,右边,限制下限
else:
high = mid # 说明i不大于,左边,限制上限
print(low) # 1ow为插入点
ret.insert(low, i)
return ret
# 测试
r = []
for x in (40, 20, 41):
r = insert_sort(r, x)
print(r)
输出:
0
0
2
[20, 40, 41]
high = len(orderlist),去掉减1,不影响整除2,但影响下一行判断。
while low < high 这一句low索引可以取到length-1了,原来只能取到length-2,所以一旦插入元
素到尾部就出现问题了。算法的核心,就是折半至重合为止。
二分
二分前提是有序,否则不可以二分。
二分查找算法的时间复杂度o(logn)
bisect模块
Bisect模块提供的函数有:
- bisect.bisect_left(a,x, lo=0, hi=len(a)) :
查找在有序列表a中插入x的index。lo和hi用于指定列表的区间,默认是使用整个列表。如果x已经存在,在其左边插入。返回值为index。 - bisect.bisect_right(a,x, lo=0, hi=len(a)) 或 bisect.bisect(a, x,lo=0, hi=len(a))和bisect_left类似,但如果x已经存在,在其右边插入。
- bisect.insort_left(a,x, lo=0, hi=len(a))在有序列表a中插入x。等同于a.insert(bisect.bisect_left(a,x,lo,hi),x)。
函数可以分2类:
bisect系,用于查找index。
Insort系,用于实际插入。
默认重复时从右边插入。
python
import bisect
lst = [37, 99, 73, 48, 47, 40, 40, 25, 99, 51, 100]
newlst = sorted(lst) # 升序
print(newlst) # [25, 37, 40, 40, 47, 48, 51, 73, 99, 99, 100]
print(list(enumerate(
newlst))) # [(0, 25), (1, 37), (2, 40), (3, 40), (4, 47), (5, 48), (6, 51), (7, 73), (8, 99), (9, 99), (10, 100)]
print(20, bisect.bisect(newlst, 20)) # 0
print(30, bisect.bisect(newlst, 30)) # 1
print(40, bisect.bisect(newlst, 40)) # 4
print(20, bisect.bisect_left(newlst, 20)) # 0
print(30, bisect.bisect_left(newlst, 30)) # 1
print(50, bisect.bisect_left(newlst, 40)) # 2
for x in (20, 30, 40, 100):
bisect.insort_left(newlst, x)
print(newlst) # [20, 25, 30, 37, 40, 40, 40, 47, 48, 51, 73, 99, 99, 100, 100]
应用

python
import bisect
def get_grade(score):
breakpoints = [160, 70, 80, 90]
grades = 'EDCBA'
return grades[bisect.bisect(breakpoints, score)]
for x in (91, 82, 77, 65, 50, 60, 70):
print('{} => {}'.format(x, get_grade(x)))
输出:
91 => A
82 => B
77 => C
65 => E
50 => E
60 => E
70 => C
将前面的链表,封装成容器
1、提供__getitem__、iter、__setitem__方法
2、使用一个列表,辅助完成上面的方法
3、进阶:不使用列表,完成上面的方法
进阶题
实现类property装饰器,类名称为Property。
基本结构如下,是一个数据描述器
python
class Property: # 数据描述器
def _init_(self):
pass
def _get_(self, instance, owner):
pass
def _set_(self, instance, value):
pass
class A:
def __init__(self, data):
self._data = data
@Property
def data(self):
return self._data
@data.setter
def data(self, value):
self._data = value