本文系统总结了 Python 面向对象编程的核心知识点,包括类与实例、静态方法与类方法、属性装饰器、父类调用以及上下文管理器等进阶技巧。
1. 面向对象
待解决
dataclass + __solts__的问题
__solts__ 使用slots一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在 __slots__ 中定义的那些属性名
使用__solts__,python实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似.Python的很多特性都依赖于普通的基于字典的实现。
另外,定义了slots后的类不再支持一些普通类特性了,比如多继承。
尽量不要使用 __solts__
基础
1 | class Color(): |
1 | class Color(): |
使用第三方插件修改一下
1 | from attr import attrs, attrib # dataclass 内置库可替代 |
声明和比较
1 | from attr import attrs, attrib # dataclass内置库可替代 |
使用python3.7 的内置库
1 | from dataclasses import dataclass |
1 | ## 默认init,repr,eq 为True, 其余为False |
1 | from dataclasses import dataclass |
1 | from dataclasses import dataclass |
__init__方法在返回前会调用__post_init__1
2
3
4
5
6
7
8
9
10
11
12from dataclasses import dataclass
@dataclass()
class Point():
x:int = 0
y:int = 0
def __post_init__(self):
self.x += 1
p1 = Point(1,2)
print(p1) # Point(x=2, y=2)
有关继承
1 | from dataclasses import dataclass |
super1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20from dataclasses import dataclass
@dataclass
class Point:
x:int = 0
y:int = 0
def __post_init__(self):
self.x += 1
@dataclass
class ThirdPonit(Point):
z:int =0
def __post_init__(self):
super().__post_init__() # 调用 父类的__post_init__()
self.x += 10
ppp = ThirdPonit(1,3,4)
print(ppp) # ThirdPonit(x=12, y=3, z=4)
复合初始化
1 | import random |
修改1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import random
from dataclasses import dataclass, field
def get_random_marks():
return [random.randint(1,10) for _ in range(5)]
@dataclass
class Student:
marks:int = field(default_factory= get_random_marks) # int 不建议,建议使用数据类型
# default_factory :如果在创建对象时没赋值,则使用该方法初始化该字段 (必须是可以调用的无参数方法)
# 使用 get_random_marks 方法 初始化 marks
a = Student()
print(a) # Student(marks=[7, 2, 4, 7, 4])
print(a.marks) # [7, 2, 4, 7, 4]
print(type(a.marks)) # <class 'list'>
b = Student('b') # 传参则不影响
print(b) # Student(marks='b')
print(b.marks) # b
print(type(b.marks)) # <class 'str'>
filed 更多操作
1 | from dataclasses import dataclass, field |
2. @staticmethod和@classmethod
一般来说,要使用某个类的方法,需要先实例化一个对象再调用方法。
而使用 @staticmethod或 @classmethod,就可以不需要实例化,直接类名.方法名()来调用。
1 | class A: |
如果在@staticmethod中要调用到这个类的一些属性方法,只能直接类名.属性名或类名.方法名。
而@classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等,避免硬编码
这两个方法的用法是类似的,大多数情况下,classmethod也可以通过staticmethod代替,在通过类调用时,这两者对于调用者来说是不可区分的。
这两者的区别在于,classmethod增加了一个对实际调用类的引用,这带来了很多方便的地方:
- 方法可以判断出自己是通过基类被调用,还是通过某个子类被调用
- 通过子类调用时,方法可以返回子类的实例而非基类的实例
- 通过子类调用时,方法可以调用子类的其他classmethod
3. 有关于@property
有关于@property
1. 将方法改为属性
这种类型的attributes并不会被实际的存储,而是在需要的时候计算出来
1 | import math |
2. 做限定
参考前文
3.
4. 调用父类方法
调用父类方法
super()
- 为了调用父类(超类)的一个方法,可以使用
super()函数
1 | class A: |
super()函数的一个常见用法是在__init__()方法中确保父类被正确的初始化了1
2
3
4
5
6
7
8
9
10
11class A:
def __init__(self):
self.x = 0
class B(A):
def __init__(self):
super().__init__()
self.y = 1
b = B()
print(b.x,b.y) # 0,1
使用dataclasses改写1
2
3
4
5
6
7
8
9
10
11
12from dataclasses import dataclass
@dataclass
class A:
x:int = 0
@dataclass
class B(A):
y:int = 1
b = B()
print(b) # B(x=0, y=1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Father:
def __init__(self, name):
self.name = name
print("init Father's name")
class Mather:
def __init__(self, age):
self.age = age
print("init Mather's age")
class Son(Father, Mather):
def __init__(self, name, age, sex):
super().__init__(name) # 先继承 Father 的 name
super(Father, self).__init__(age) # 继承后 再继承 Mother 的age
self.sex = sex # 最后 实例化自己的 sex
print("init Son's sex")
if __name__ == "__main__":
son = Son("Tom", 5, "Male")
print(Son.__mro__) # (<class '__main__.Son'>, <class '__main__.Father'>, <class '__main__.Mather'>, <class 'object'>)
print(son.name, son.age, son.sex)
1 | from dataclasses import dataclass |
MRO列表
Python会在MRO列表上继续搜索下一个类。
只要每个重定义的方法统一使用 super() 并只调用它一次,
那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次
super()有个令人吃惊的地方是它并不一定去查找某个类在MRO中下一个直接父类,
你甚至可以在一个没有直接父类的类中使用它
1 | class A: |
1 | class C(B,A): |
关于super()
由于 super() 可能会调用不是你想要的方法,你应该遵循一些通用原则。
首先,确保在继承体系中所有相同名字的方法拥有可兼容的参数签名(比如相同的参数个数和参数名称)。
这样可以确保 super() 调用一个非直接父类方法时不会出错。
其次,最好确保最顶层的类提供了这个方法的实现,这样的话在MRO上面的查找链肯定可以找到某个确定的方法。
在Python社区中对于 super() 的使用有时候会引来一些争议。
尽管如此,如果一切顺利的话,你应该在你最新代码中使用它。
Raymond Hettinger为此写了一篇非常好的文章
Python’s super() Considered Super
通过大量的例子向我们解释了为什么 super() 是极好的。
5. 格式化字符串
实例的字符串显示1
2
3
4
5
6
7
8
9
10
11
12
13
14class Pair:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f'({self.x}, {self.y})'
def __str__(self):
return f'Pair({self.x=}, {self.y=})'
p = Pair(3, 4)
print(p)
1 | from dataclasses import dataclass |
对象自定义格式化1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25_formats = {
'ymd': '{d.year}-{d.month}-{d.day}',
'mdy': '{d.month}/{d.day}/{d.year}',
'dmy': '{d.day}/{d.month}/{d.year}'
}
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __format__(self, code):
if code == '':
code = 'ymd'
fmt = _formats[code]
return fmt.format(d=self)
d = Date(2012, 12, 21)
print(d)
print(format(d, 'mdy'))
print('The date is {:ymd}'.format(d))
print('The date is {:mdy}'.format(d))
修改后1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25from dataclasses import dataclass
_formats = {
'ymd': '{d.year}-{d.month}-{d.day}',
'mdy': '{d.month}/{d.day}/{d.year}',
'dmy': '{d.day}/{d.month}/{d.year}'
}
@dataclass
class Date:
year:int
month:int
day:int
def __format__(self, code):
if code == '':
code = 'ymd'
fmt = _formats[code]
return fmt.format(d=self)
a = Date(2012, 12, 21)
print(a)
print(format(a, 'mdy'))
print('The date is {:ymd}'.format(a))
print('The date is {:mdy}'.format(a))
6. 上下文管理器
上下文管理器
让对象支持上下文管理器
对象需要定义 __enter__ 和 __exit__1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35from socket import socket, AF_INET, SOCK_STREAM
class LazyConnection:
def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
self.address = address
self.family = family
self.type = type
self.sock = None
def __enter__(self):
if self.sock is not None:
raise RuntimeError('Already connected')
self.sock = socket(self.family, self.type)
self.sock.connect(self.address)
return self.sock
def __exit__(self, exc_ty, exc_val, tb): # 异常类型,异常值和异常的trackback
self.sock.close()
self.sock = None
#return True 如果这里返回True 则代表不处理with中的异常
if __name__ == '__main__':
from functools import partial
conn = LazyConnection(('www.python.org', 80))
# Connection closed
with conn as s:
# conn.__enter__() executes: connection open
s.send(b'GET /index.html HTTP/1.0\r\n')
s.send(b'Host: www.python.org\r\n')
s.send(b'\r\n')
resp = b''.join(iter(partial(s.recv, 8192), b''))
print(resp)
# conn.__exit__() executes: connection closed
线程安全修改版1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40from socket import socket, AF_INET, SOCK_STREAM
import threading
class LazyConnection:
def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
self.address = address
self.family = AF_INET
self.type = SOCK_STREAM
self.local = threading.local()
def __enter__(self):
if hasattr(self.local, 'sock'):
raise RuntimeError('Already connected')
self.local.sock = socket(self.family, self.type)
self.local.sock.connect(self.address)
return self.local.sock
def __exit__(self, exc_ty, exc_val, tb):
self.local.sock.close()
del self.local.sock
from functools import partial
def test(conn):
with conn as s:
s.send(b'GET /index.html HTTP/1.0\r\n')
s.send(b'Host: www.huawei.com\r\n')
s.send(b'\r\n')
resp = b''.join(iter(partial(s.recv, 8192), b''))
print('Got {} bytes'.format(len(resp)))
if __name__ == '__main__':
conn = LazyConnection(('www.huawei.com', 80))
t1 = threading.Thread(target=test, args=(conn,))
t2 = threading.Thread(target=test, args=(conn,))
t1.start()
t2.start()
t1.join()
t2.join()
使用 contexlib 模块中的 @contextmanager装饰器
实现一个新的上下文管理器的最简单的方法
yield 之前的代码会在上下文管理器中作为 __enter__() 方法执行,
所有在 yield 之后的代码会作为 __exit__() 方法执行
先执行print('《', end=''),遇到yeild ,执行 print('挪威的森林',end='') , 最后执行 print('》', end='')1
2
3
4
5
6
7
8
9
10
11
12from contextlib import contextmanager
@contextmanager
def book_mark():
print('《', end='')
yield
print('》', end='')
with book_mark():
print('挪威的森林',end='')
## 《挪威的森林》
1 | import time |
使用from contextlib import contextmanager
1 | import time |
任何对列表的修改只有当所有代码运行完成并且不出现异常的情况下才会生效
1 |
|