Python Introduction - OOP
There are two paradigms of programming
-
imperative (using statements, loops, and functions as subroutines)
-
functional (using pure functions, higher-order functions, and recursion)
And another very popular paradigm is object-oriented programming (OOP), so let’s explore it.
Throughout this tutorial, I’ll walk you through the creation of a basic python object-oriented programming.
It’ll consist of some parts:
- what is a class?
- what is inheirance?
- …
面向对象相关知识
-
三大支柱:封装、继承、多态
面向对象的设计原则:SOLID原则
面向对象的设计模式:GoF设计模式(单例、工厂、代理、策略、迭代器)
月薪结算系统 - 部门经理每月15000 程序员每小时200 销售员1800底薪加销售额5%提成
例子:工资结算系统。
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78"""
月薪结算系统 - 部门经理每月15000 程序员每小时200 销售员1800底薪加销售额5%提成
"""
from abc import ABCMeta, abstractmethod
class Employee(metaclass=ABCMeta):
"""员工(抽象类)"""
def __init__(self, name):
self.name = name
def get_salary(self):
"""结算月薪(抽象方法)"""
pass
class Manager(Employee):
"""部门经理"""
def get_salary(self):
return 15000.0
class Programmer(Employee):
"""程序员"""
def __init__(self, name, working_hour=0):
self.working_hour = working_hour
super().__init__(name)
def get_salary(self):
return 200.0 * self.working_hour
class Salesman(Employee):
"""销售员"""
def __init__(self, name, sales=0.0):
self.sales = sales
super().__init__(name)
def get_salary(self):
return 1800.0 + self.sales * 0.05
class EmployeeFactory():
"""创建员工的工厂(工厂模式 - 通过工厂实现对象使用者和对象之间的解耦合)"""
def create(emp_type, *args, **kwargs):
"""创建员工"""
emp_type = emp_type.upper()
emp = None
if emp_type == 'M':
emp = Manager(*args, **kwargs)
elif emp_type == 'P':
emp = Programmer(*args, **kwargs)
elif emp_type == 'S':
emp = Salesman(*args, **kwargs)
return emp
def main():
"""主函数"""
emps = [
EmployeeFactory.create('M', '曹操'),
EmployeeFactory.create('P', '荀彧', 120),
EmployeeFactory.create('P', '郭嘉', 85),
EmployeeFactory.create('S', '典韦', 123000),
]
for emp in emps:
print('%s: %.2f元' % (emp.name, emp.get_salary()))
if __name__ == '__main__':
main() -
类与类之间的关系
- is-a关系:继承
- has-a关系:关联 / 聚合 / 合成
- use-a关系:依赖
例子:扑克游戏。
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188"""
经验:符号常量总是优于字面常量,枚举类型是定义符号常量的最佳选择
枚举 - 一个变量的值只有有限个选择,最适合的类型就是枚举
通过枚举我们可以定义符号常量,符号常量优于字面常量
"""
from enum import Enum, unique
import random
class Suite(Enum):
"""花色"""
SPADE, HEART, CLUB, DIAMOND = range(4)
def __lt__(self, other):
return self.value < other.value
class Card():
"""牌"""
def __init__(self, suite, face):
"""初始化方法"""
self.suite = suite
self.face = face
def show(self):
"""显示牌面"""
suites = ['♠️', '♥️', '♣️', '♦️']
faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
return f'{suites[self.suite.value]} {faces[self.face]}'
def __str__(self):
return self.show()
def __repr__(self):
return self.show()
class Poker():
"""扑克"""
def __init__(self):
self.index = 0
self.cards = [Card(suite, face)
for suite in Suite
for face in range(1, 14)]
def shuffle(self):
"""洗牌(随机乱序)"""
random.shuffle(self.cards)
self.index = 0
def deal(self):
"""发牌"""
card = self.cards[self.index]
self.index += 1
return card
def has_more(self):
return self.index < len(self.cards)
class Player():
"""玩家"""
def __init__(self, name):
self.name = name
self.cards = []
def get_one(self, card):
"""摸一张牌"""
self.cards.append(card)
def sort(self, comp=lambda card: (card.suite, card.face)):
"""整理手上的牌"""
self.cards.sort(key=comp)
def main():
"""主函数"""
poker = Poker()
poker.shuffle()
players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
while poker.has_more:
for player in players:
player.get_one(poker.deal())
for player in players:
player.sort()
print(player.name, end=': ')
print(player.cards)
from enum import Enum, unique
import random
class Suite(Enum):
"""花色(枚举)"""
SPADE, HEART, CLUB, DIAMOND = range(4)
def __lt__(self, other):
return self.value < other.value
class Card():
"""牌"""
def __init__(self, suite, face):
self.suite = suite
self.face = face
def __repr__(self):
return self.__str__()
def __str__(self):
suites = ('♠️', '♥️', '♣️', '♦️')
faces = ('', 'A', '2', '3', '4', '5', '6',
'7', '8', '9', '10', 'J', 'Q', 'K')
return f'{suites[self.suite.value]} {faces[self.face]}'
class Poker():
"""扑克"""
def __init__(self):
self.index = 0
self.cards = [Card(suite, face)
for suite in Suite
for face in range(1, 14)]
def shuffle(self):
"""洗牌"""
self.index = 0
random.shuffle(self.cards)
def deal(self):
"""发牌"""
card = self.cards[self.index]
self.index += 1
return card
def has_more(self):
"""是否有更多的牌"""
return self.index < len(self.cards)
class Player():
"""玩家"""
def __init__(self, name):
self.name = name
self.cards = []
def get_card(self, card):
"""摸牌"""
self.cards.append(card)
def arrange(self):
"""整理手上的牌"""
self.cards.sort(key=lambda card: (card.suite, card.face))
def main():
"""主函数"""
poker = Poker()
poker.shuffle()
players = [
Player('东邪'), Player('西毒'),
Player('南帝'), Player('北丐')
]
while poker.has_more:
for player in players:
player.get_card(poker.deal())
for player in players:
player.arrange()
print(player.name, end=': ')
print(player.cards)
if __name__ == '__main__':
main()说明:上面的代码中使用了Emoji字符来表示扑克牌的四种花色,在某些不支持Emoji字符的系统上可能无法显示。
-
对象的复制(深复制/深拷贝/深度克隆和浅复制/浅拷贝/影子克隆)
垃圾回收、循环引用和弱引用
Python使用了自动化内存管理,这种管理机制以引用计数为基础,同时也引入了标记-清除和分代收集两种机制为辅的策略。
1 | typedef struct_object { |
1 | /* 增加引用计数的宏定义 */ |
导致引用计数+1的情况:
- 对象被创建,例如
a = 23 - 对象被引用,例如
b = a - 对象被作为参数,传入到一个函数中,例如
f(a) - 对象作为一个元素,存储在容器中,例如
list1 = [a, a]
导致引用计数-1的情况:
- 对象的别名被显式销毁,例如
del a - 对象的别名被赋予新的对象,例如
a = 24 - 一个对象离开它的作用域,例如f函数执行完毕时,f函数中的局部变量(全局变量不会)
- 对象所在的容器被销毁,或从容器中删除对象
引用计数可能会导致循环引用问题,而循环引用会导致内存泄露,如下面的代码所示。为了解决这个问题,Python中引入了“标记-清除”和“分代收集”。在创建一个对象的时候,对象被放在第一代中,如果在第一代的垃圾检查中对象存活了下来,该对象就会被放到第二代中,同理在第二代的垃圾检查中对象存活下来,该对象就会被放到第三代中。
1 | # 循环引用会导致内存泄露 - Python除了引用技术还引入了标记清理和分代回收 |
以下情况会导致垃圾回收:
- 调用
gc.collect() - gc模块的计数器达到阀值
- 程序退出
如果循环引用中两个对象都定义了__del__方法,gc模块不会销毁这些不可达对象,因为gc模块不知道应该先调用哪个对象的__del__方法,这个问题在Python 3.6中得到了解决。
也可以通过weakref模块构造弱引用的方式来解决循环引用的问题。
-
魔法属性和方法(请参考《Python魔法方法指南》)
有几个小问题请大家思考:
- 自定义的对象能不能使用运算符做运算?
- 自定义的对象能不能放到set中?能去重吗?
- 自定义的对象能不能作为dict的键?
- 自定义的对象能不能使用上下文语法?
-
混入(Mixin)
例子:自定义字典限制只有在指定的key不存在时才能在字典中设置键值对。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class SetOnceMappingMixin:
"""自定义混入类"""
__slots__ = ()
def __setitem__(self, key, value):
if key in self:
raise KeyError(str(key) + ' already set')
return super().__setitem__(key, value)
class SetOnceDict(SetOnceMappingMixin, dict):
"""自定义字典"""
pass
my_dict= SetOnceDict()
try:
my_dict['username'] = 'jackfrued'
my_dict['username'] = 'hellokitty'
except KeyError:
pass
print(my_dict) -
元编程和元类
例子:用元类实现单例模式。
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
37import threading
class SingletonMeta(type):
"""自定义元类"""
def __init__(cls, *args, **kwargs):
cls.__instance = None
cls.__lock = threading.Lock()
super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
if cls.__instance is None:
with cls.__lock:
if cls.__instance is None:
cls.__instance = super().__call__(*args, **kwargs)
return cls.__instance
class President(metaclass=SingletonMeta):
"""总统(单例类)"""
def __init__(self, name, country):
self.name = name
self.country = country
def __str__(self):
return f'{self.country}: {self.name}'
p1 = President('特朗普', '美国')
p2 = President('奥巴马', '美国')
p3 = President.__call__('克林顿', '美国')
print(p1 == p2)
print(p1 == p3)
print(p1, p2, p3, sep='\n') -
面向对象设计原则
- 单一职责原则 (SRP)- 一个类只做该做的事情(类的设计要高内聚)
- 开闭原则 (OCP)- 软件实体应该对扩展开发对修改关闭
- 依赖倒转原则(DIP)- 面向抽象编程(在弱类型语言中已经被弱化)
- 里氏替换原则(LSP) - 任何时候可以用子类对象替换掉父类对象
- 接口隔离原则(ISP)- 接口要小而专不要大而全(Python中没有接口的概念)
- 合成聚合复用原则(CARP) - 优先使用强关联关系而不是继承关系复用代码
- 最少知识原则(迪米特法则,LoD)- 不要给没有必然联系的对象发消息
说明:上面加粗的字母放在一起称为面向对象的SOLID原则。
-
GoF设计模式
- 创建型模式:单例、工厂、建造者、原型
- 结构型模式:适配器、门面(外观)、代理
- 行为型模式:迭代器、观察者、状态、策略
例子:可插拔的哈希算法。
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
26class StreamHasher():
"""哈希摘要生成器(策略模式)"""
def __init__(self, alg='md5', size=4096):
self.size = size
alg = alg.lower()
self.hasher = getattr(__import__('hashlib'), alg.lower())()
def __call__(self, stream):
return self.to_digest(stream)
def to_digest(self, stream):
"""生成十六进制形式的摘要"""
for buf in iter(lambda: stream.read(self.size), b''):
self.hasher.update(buf)
return self.hasher.hexdigest()
def main():
"""主函数"""
hasher1 = StreamHasher()
with open('Python-3.7.1.tgz', 'rb') as stream:
print(hasher1.to_digest(stream))
hasher2 = StreamHasher('sha1')
with open('Python-3.7.1.tgz', 'rb') as stream:
print(hasher2(stream))
Classes
Objects are created using classes, which are actually the focal point of OOP.
The class describes what the object will be, but is separate from the object itself. In other words, a class can be described as an object’s blueprint, description, or definition.
1 | class Animal: |
1 | cat1 = Animal("Mr.MidNight", "black", 2.0) |
1 | print(hasattr(cat1, 'color'), getattr(cat1, 'name', "None")) |
1 | Animal |
1 | Animal.__doc__: |
1 | class MyError(Exception): |
1 | My exception occurred, value: 22 |
Setter & Getter
1 | class Animal: |
Slots
That is to avoiding dynamically created attributes 😻
1 | class Dog(): |
Inheritance
Inheritance provides a way to share functionality between classes.
A class that inherits from another class is called a subclass.
A class that is inherited from is called a superclass.
If a class inherits from another with the same attributes or methods, it overrides them.
is-a、has-a、use-a
Inheritance
1 | """ |
Class Animal
Imagine several classes, Cat, Dog, Rabbit and so on.
Although they may differ in some ways (only Dog might have the method bark), they are likely to be similar in others (all having the attributes color and name). This similarity can be expressed by making them all inherit from a superclass Animal.
1 | class Animal: |
1 | ## somepig from Charlotte's Web |
Override & Superclass
If a class inherits from another with the same attributes or methods, it overrides(方法的覆盖或重写) them.
1 | big_husky = Dog("luna", "grey") |
Polymorphism
The fancy word for the ability of multiple object types to implement the same functionality is polymorphism.
1 | class Animal(): |
Magic methods
1 | class Person(object): |
1 | """ |
Magic methods are special methods which have double underscores at the beginning and end of their names.
They are also known as dunders.
One common use of them is operator overloading.
This means defining operators for custom classes that allow operators such as + and * to be used on them.
1 | 'ABC'.__len__() |
| Methods | Meaning |
|---|---|
| _sub_ | - |
| _mul_ | * |
| _truediv_ | / |
| _floordiv_ | // |
| _mod_ | % |
| _pow_ | ** |
| _and_ | & |
| _xor_ | ^ |
| _or_ | | |
| _lt_ | < |
| _le_ | <= |
| _eq_ | == |
| _ne_ | != |
| _gt_ | > |
| _ge_ | >= |
| _len_ | len() |
| _getitem_ | indexing |
| _setitem_ | assigning to indexed values |
| _delitem_ | deleting indexed values |
| _iter_ | iteration over objects (e.g., in loops) |
| _contains_ | in |
1 | import random |
1 | class Vector: |
Vector(2, 10) Destoryed!
Vector(5, -2) Destoryed!
Vector(9, -1) Destoryed!
Vector(7, 8) Destoryed!
Vector(-2, 9)
Vector(-2, 9) Destoryed!
1 | class SpecialString: |
Types & Type
Types
1 | import types |
1 | class cat(): |
1 | def hi(self, name = 'rabbit1'): |
MethodType
1 | from types import MethodType |
Class & Static Methods
Static Methods
1 | from math import sqrt |
Pizza
1 | class Pizza: |
1 | ingredients = ["cheese", "onions", "spam", "eggs","chicken breast"] |
1 | pizza.pineapple_allowed = True |
MetaClass
1 | class ListMetaclass(type): |
例子
假设小偷有一个背包,最多能装20公斤赃物,他闯入一户人家,发现如下表所示的物品。很显然,他不能把所有物品都装进背包,所以必须确定拿走哪些物品,留下哪些物品。
| 名称 | 价格(美元) | 重量(kg) |
|---|---|---|
| 电脑 | 200 | 20 |
| 收音机 | 20 | 4 |
| 钟 | 175 | 10 |
| 花瓶 | 50 | 2 |
| 书 | 10 | 1 |
| 油画 | 90 | 9 |
1 | """ |
1 | 20 6 |





