分类
1 | class Point: |
2 | def __init__(self,x,y): |
3 | self.x = x |
4 | self.y = y |
5 | |
6 | def __add__(self,other): |
7 | return Point(self.x + other.x, self.y + other.y) |
8 | |
9 | def __sub__(self,other): |
10 | return Point(self.x - other.x, self.y - other.y) |
11 | |
12 | p = Point(1,2) + Point(2,3) |
13 | p.x |
14 | 3 |
15 | p.y |
16 | 5 |
hash
使用内置函数hash对某个对象hash值时,会调用对象的__hash__
方法
hash方法必须返回int
1 | class Point: |
2 | def __hash__(self): |
3 | return None |
4 | |
5 | set([Point(),]) |
6 | TypeError: __hash__ method should return an integer |
7 | |
8 | class Point: |
9 | __hash__ = None |
10 | |
11 | set([Point(),]) |
12 | TypeError: unhashable type: 'Point' |
13 | |
14 | class Point: |
15 | pass |
16 | |
17 | set([Point(),]) |
18 | {<__main__.Point at 0x1057ee5f8>} |
可hash的对象就是具有hash方法的对象
- set类去重
1 | class Point: |
2 | def __init__(self,x,y): |
3 | self.x = x |
4 | self.y = y |
5 | |
6 | p1 = Point(1,2) |
7 | p2 = Point(1,2) |
8 | set([p1,p2]) |
9 | {<__main__.Point at 0x1068961d0>, <__main__.Point at 0x1068972e8>} |
10 | |
11 | # 我们看下hash |
12 | hash(p1) |
13 | 275289629 |
14 | hash(p2) |
15 | -9223372036579485906 |
16 | |
17 | # 我们自己定义hash |
18 | class Point: |
19 | def __init__(self,x,y): |
20 | self.x = x |
21 | self.y = y |
22 | def __hash__(self): |
23 | return hash('{}:{}'.format(self.x,self.y)) |
24 | |
25 | p1 = Point(1,2) |
26 | p2 = Point(1,2) |
27 | set([p1,p2]) |
28 | {<__main__.Point at 0x1056fe4a8>, <__main__.Point at 0x1056fe240>} |
29 | |
30 | # 还是没法去重,在看下hash |
31 | hash(p1) |
32 | -2893477644897778412 |
33 | hash(p2) |
34 | -2893477644897778412 |
35 | hash(p1) == hash(p2) # hash 相等了 |
36 | True |
37 | p1 == p2 # 实例对象却不相等了 |
38 | False |
39 | |
40 | class Point: |
41 | def __init__(self,x,y): |
42 | self.x = x |
43 | self.y = y |
44 | def __hash__(self): |
45 | return hash('{}:{}'.format(self.x,self.y)) |
46 | def __eq__(self,other): |
47 | return self.x == other.x and self.y == other.y |
48 | |
49 | p1 = Point(1,2) |
50 | p2 = Point(1,2) |
51 | set([p1,p2]) |
52 | {<__main__.Point at 0x10574b4e0>} # 去重了 |
所以我们可以得出的结论:
- 如果没有重写
__hash__
方法的话,hash方法通常不相等 - 通常
__hash__
和__eq__
一起使用,因为解释器通常判断hash是否相等及实例是否相等
定义类大小
1 | class Sized: |
2 | def __len__(self): |
3 | return 10 |
4 | |
5 | len(Sized()) |
6 | 10 |
当对象实现len方法的时候,可以使用内置方法len求对象的长度,__len__
方法必须返回非负整数
定义布尔
定义类bool
1 | class F: |
2 | def __bool__(self): |
3 | return False |
4 | |
5 | bool(F()) |
- 当对象o定义了
__bool__
方法时,bool(o)
返回值为o.__bool__()
当对象o没有实现__bool__
方法时, bool(o)永远返回True- 当对象o没有实现
__bool__
方法的时候,如果o实现了__len__
方法,bool(o)
返回值为len(o) != 0
- 当对象o既没有实现
__bool__
方法,也没有实现__len__
方法的时候,bool(o)
返回True - 当同时实现
__bool__
与__len__
方法的时候,__bool__
的优先级更高 __bool__
方法必须返回bool
对象可视化
1 | class Point: |
2 | def __init__(self,x,y): |
3 | self.x = x |
4 | self.y = y |
5 | |
6 | def __str__(self): |
7 | return 'Point <{}, {}>'.format(self.x, self.y) |
8 | |
9 | def __repr__(self): |
10 | return 'Point <{}, {}>'.format(self.x, self.y) |
11 | |
12 | p = Point(1,2) |
callable 对象
1 | class Fn: |
2 | def __call__(self): |
3 | print('{} called'.format(self)) |
4 | |
5 | f= Fn() |
6 | f() |
7 | |
8 | callable(f) |
9 | True |
一个对象,只要实现了__call__
方法,就可以通过小括号来调用,这一类对象,称为可调用对象
上下文管理
1 | with open('test.txt') as f: |
2 | pass |
ope函数返回一个文件对象,而文件对象是上下文管理的
__enter__
与__exit__
魔术方法实现上文管理
1 | class Context: |
2 | def __enter__(self): |
3 | print('enter context') |
4 | |
5 | def __exit__(self): |
6 | print('exit context') |
7 | |
8 | with Context(): |
9 | print('do something') |
即使with块抛出异常,__enter__
和__exit__
也会被执行,所以上下文管理是安全的。
as子句可以获得__enter__
的返回值
1 | class Context: |
2 | def __enter__(self,*args,**kwargs): |
3 | print('enter context') |
4 | print(args) |
5 | print(kwargs) |
6 | self.txt = 'hello world' |
7 | return self |
8 | |
9 | def __exit__(self,*args,**kwargs): |
10 | print('exit context') |
11 | |
12 | with Context() as c: |
13 | print(c.txt) |
14 | enter context |
15 | () |
16 | {} |
17 | hello world |
18 | exit context |
__enter__
除self之外,不带任何参数。
1 | class Context: |
2 | def __enter__(self): |
3 | print('enter context') |
4 | |
5 | def __exit__(self,*args,**kwargs): |
6 | print('exit context') |
7 | print(args) |
8 | print(kwargs) |
9 | return self |
10 | |
11 | with Context(): |
12 | pass |
13 | enter context |
14 | exit context |
15 | (None, None, None) |
16 | {} |
17 | |
18 | with Context(): |
19 | raise Exception('raise') |
20 | enter context |
21 | exit context |
22 | (<class 'Exception'>, Exception('raise',), <traceback object at 0x1056e2488>) |
23 | {} |
__exit__
的返回值没法获取,如果with块中抛出异常,__exit__
返回False的时候,会向上抛出异常,返回True,会屏蔽异常。__exit__
的三个参数 异常类型 异常 traceback
上下文管理应用
计时
1
from functools import wraps
2
from datetime import datetime
3
4
def timeit(fn):
5
6
def wrap(*args,**kwargs):
7
start = datetime.now()
8
ret = fn(*args,**kwargs)
9
cost = datetime.now() - start
10
print(cost)
11
return ret
12
return wrap
13
14
15
def add(x,y):
16
return x+y
1
class timeit:
2
def __init__(self,fn=None):
3
wraps(fn)(self)
4
5
def __call__(self,*args,**kwargs):
6
start = datetime.now()
7
ret = self.__wrapped__(*args,**kwargs)
8
cost = datetime.now() - start
9
print(cost)
10
return ret
11
12
def __enter__(self):
13
self.start = datetime.now()
14
15
def __exit__(self,*args):
16
cost = datetime.now() - self.start
17
print(cost)
18
19
with timeit():
20
z=3+8
21
22
23
def add(x,y):
24
return x+y
凡是要在代码块前后插入代码的场景统统适用
- 资源管理
- 权限验证
1 | import contextlib |
2 | |
3 |
|
4 | def context(): |
5 | print('enter context') # 初始化部分 相当于__enter__ 方法 |
6 | try: |
7 | yield 'haha' # 相当于__enter__的返回值 |
8 | finally: |
9 | print('exit context') # 清理部分,相当于__exit__方法 |