这里我只是简单介绍一下Python数据结构的一些常用内容,详细内容请参考:Python 3.13.0 文档 。
数据类型
Python为我们提供了type()
、isinstance()
等方法来帮助我们查看变量的数据类型。执行如下代码我们得到了Python中的基本数据类型。这些基本类型都是由Python的标准库模块builtins
提供。
Python中的基本数据类型可以大致分为:可变数据类型 | Mutable Type
、不可变数据类型 | Immutable Type
。可变数据在定义后可以进行修改;而不可变数据在定义后不可以进行修改,当我们对值为不可变数据类型的变量进行修改时,实际上是删除原有数据后重新赋值而已。
- 不可变数据类型:
int
、float
、bool
、complex
、str
、tuple
、bytes
。 - 可变数据类型:
list
、dict
、set
、bytearray
。
1 | t1=1 |
我们详细介绍一下上面提到的数据类型:
1. 数值类型
Python的数值类型有:int
、bool
、float
、complex
:
int
&bool
:Python 3
的整型是没有限制大小的,可以当作Long
类型使用,所以Python 3
没有Python 2
的Long
类型。Python 3
的布尔类型是整型的子类,需要注意的是布尔值首字母需要大写True
。由于的布尔类型是整型的子类,在使用==
比较时有以下登上成立:1
2print(1==True) # True
print(0==False) # Truefloat
:浮点型由整数部分与小数部分组成,浮点型也可以使用科学计数法表示,
2.5e5
即2.5*10^5
。1
2
3f1=250000.0
f2=2.5e5
print(f1==f2) # Truecomplex
:即复数类型,由实部与虚部组成,可以使用以下两种方式生成。
1
2
3
4c1=3+4j
c2=complex(3, 4)
print(c1==c2) # True
2. 序列类型
Python 的序列类型有:list
、tuple
、set
、dict
。这四者的异同主要体现在三方面:元素的可变性、唯一性、有序性。
可变性 | 唯一性 | 有序性 | |
---|---|---|---|
列表 | ✅ | ❌ | ✅ |
元组 | ❌ | ❌ | ✅ |
集合 | ✅ | ✅ | ❌ |
字典 | ✅ | ✔️ | Unique Key |
✔️ | python 3.7 |
可变性:元素在定义后是否可以进行修改。
唯一性:元素在同一序列中是否可以重复。
这些可以有元素唯一性特性的数据类型在判断内部元素是否具有唯一性时,是调用元素的
__eq__()
、__hash__()
方法来判断的,这里需要注意的是:对于dict
而言,是dict
的唯一性是对于键而言的。我们将这类可以判断唯一性的数据类型称为 Hashable 类型。以下是常用类型的唯一性判断标准:
Immutable Type
大多数都是 可哈希 的;Mutable Container
都 不可哈希 的,例如list
、dict
;Immutable Container
仅当它们的元素均为可哈希时才是 可哈希 的,例如tuple
、frozenset
。
用户定义类的实例对象默认是可哈希的,它们在比较时一定不相同(除非是与自己比较),它们的哈希值是使用
id()
获取的。id(object)
返回对象的 标识值 。该值是一个整数,在此对象的生命周期中保证是唯一且恒定的。两个生命期不重叠的对象可能具有相同的id()
值。如果我们想为自己的类实例定制化
hash(ins)
的输出结果,可以重写__hash__()
、__eq__()
这两个魔术方法,以下是示例:1
2
3
4
5
6
7
8
9
10
11
12
13class HashableClass:
def __init__(self, id):
self._id = id
def __hash__(self):
# Get hash, make sure using same id with __eq__()
return hash(self._id)
def __eq__(self, other):
# Check self equals to other
if isinstance(other, HashableClass):
return self._id == other._id
return False有序性:元素的存储顺序是否有序。
列表与元组的有序性显而易见,这里我们着重讨论一下字典的有序性。
字典在
Python 3.7
之前是随机存储键值对的,我们使用dict.popitem()
弹出的是一个随机的键值对。在Python 3.7
之后,字典采用 后进先出|LIFO
的策略管理键值对,故我们使用dict.popitem()
弹出的始终是最近进入字典的键值对。1
2
3
4
5
6
7d1={1: 'None',2: 'None',3: 'None', 4: "None"}
d1.popitem()
print(d1) # {1: 'None', 2: 'None', 3: 'None'}
d1[4]=123
d1[2]=123
d1.popitem()
print(d1) # {1: 'None', 2: 123, 3: 'None'}需要注意的是,只有新增的键才被视为进,如果使用
dict.update()
、dict[key]=value
来更新字典内容不会使该键值对重新进入字典。1
2
3
4
5
6
7d1={1: 'None',2: 'None',3: 'None', 4: "None"}
d1.popitem()
print(d1) # {1: 'None', 2: 'None', 3: 'None'}
d1[4]=123
d1.update({1:1})
d1.popitem()
print(d1) # {1: 'None', 2: 123, 3: 'None'}
🔧 公共方法
以下是这四种类型的公共方法:
len()
、max()
、min()
:builtins
提供了列表、元组和集合的通用函数:len()
、max()
、min()
。1
2
3
4
5
6
7
8
9
10
11
12targets = {
'list': [1, 2, 2],
'tuple': (1, 2, 2),
'set': {1, 2},
}
functions = [len, max, min]
for key in targets:
print(key)
for f in functions:
print(f"\t{f.__name__}({key}) => {f(targets[key])}")
print()字典也可以使用上述通用函数。对于
len()
所有字典都可以使用,对于max()
、min()
则只有当作为键的类型实现了关于比较的方法后才能使用max()
、min()
。需要实现的魔术方法有:__eq__()
、__lt__()
、__le__()
、__gt__()
、__ge__()
等。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22dict1={True: 1, 2: 2, 3.14:3}
dict2={True: 1, 2: 2, "3.14":3}
dicts={
'dict1': dict1,
'dict2': dict2,
}
functions = [len, max, min]
for dict_name in dicts:
for f in functions:
try:
print(f"{f.__name__}({dict_name}) = {f(dicts[dict_name])}")
except TypeError as err:
print(f"{f.__name__}({dict_name}) => {err}")
# # Result:
# # len(dict1) = 3
# # max(dict1) = 3.14
# # min(dict1) = True
# # len(dict2) = 3
# # max(dict2) => '>' not supported between instances of 'str' and 'int'
# # min(dict2) => '<' not supported between instances of 'str' and 'bool'切片与删除:
列表、元组等有序序列都支持切片,需要注意的是虽然 字典 也可以看作有序序列 ,但其却并不支持切片操作。
切片遵循左闭右开的裁切方式。左
index
大于右index
,左右index
大于len(list)
都不会报错,请在裁切时做好防御性编程。以下是切片实验代码,运行他们以便你更好的了解切片:1
2
3
4
5
6
7
8
9
10
11
12
13l1=[1,2,3,4,5]
l2=l1[:]
l3=l1[0:]
l4=l1[0:-1]
l5=l1[4:3] # Wont report error
l6=l1[7:8] # Wont report error
print(l1) # [1, 2, 3, 4, 5]
print(l2) # [1, 2, 3, 4, 5]
print(l3) # [1, 2, 3, 4, 5]
print(l4) # [1, 2, 3, 4]
print(l5) # []
print(l6) # []列表、元组、字典等有序序列都支持使用
del
关键字进行删除操作,集合 仅支持使用del
删除集合本身:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16l1=[1,2,3] # list
d1={1: 1, '1': 1} # dict
t1=(1,2,3) # tuple
del l1[1]
del d1['1']
print(l1) # [1, 3]
print(d1) # {1: 1}
del l1
del d1
del t1
s1={1,2,3}
del s1
🧰 实例方法
上述四种类型的使用以及实例方法如下:
list
常用实例方法:方法 说明 list.count(obj)
统计某个元素在列表中出现的次数 list.clear()
清空列表 list.append(obj)
在列表末尾添加新的对象 list.remove(obj)
移除列表中某个值的第一个匹配项 list.insert(index, obj)
从列表中找出某个值第一个匹配项的索引位置 list.extend(seq)
在列表末尾一次性追加另一个序列中的多个值 list.sort(key=None, reverse=False)
对原列表进行排序 list.reverse()
反转列表中元素 list.copy()=>list
复制列表 list.pop(index=-1)=>obj
移除列表中的一个元素(默认最后一个元素),并且返回该元素的值 set
常用实例方法 :方法 说明 set.add(hashable_el)
向集合添加一个 Hashable 元素 set.clear()
移除集合中的所有元素 set.copy() => set
返回集合的浅拷贝 # 删除操作 : set.pop()
从集合中移除并返回任意一个元素。 如果集合为空则会引发 KeyError
set.remove(hashable_el)
从集合中移除元素 hashable_el。 如果 hashable_el 不存在于集合中则会引发 KeyError
set.discard(hashable_el)
如果元素 hashable_el 存在于集合中则将其移除 # 原集合不变返回新集合 : set.isdisjoint(other)=>bool
判断 set 是否与 other 无交集 set.issubset(other)=>bool
判断 set 是否为 other 的子集 set.issupperset(other)=>bool
判断 set 是否为 other 的超集 # 原集合不变返回新集合 : set.union(other)=>set
返回两个集合的合集 set.difference(other)=>set
返回两个集合的差集 set.intersection(other)=>set
返回两个集合的交集 set.symmetric_difference(other)=>set
返回两个集合中不重复的元素集合 # 在原集合上更新 : set.update(other)
为集合添加来自 others 中的所有元素 set.difference_update(other)
set.intersection_update(other)
set.symmetric_difference_update(other)
注意!
set
元素必须为 Hashable 元素,如果不是则会报错KeyError
。dict
:我们可以使用多种类型实例作为字典的键,但需要注意的是,该类型实例必须为 Hashable 元素。即实现了
__hash__()
、__eq__()
方法。当我们使用
str
类型作为字典的键时,不可以省略‘’
,这一点不同于JavaScript
。1
2
3
4
5
6
7dict1={
'a': 100,
'b': True
}
# Realize iterable
for i in dict1: # a 100
print(i, dict1[i]) # b True我们可以通过字典键来管理字典内容:
1
2
3
4
5
6
7dict1={}
dict1['key1']=1
dict1[2]=2
print(dict1) # {'key1': 1, 2: 2}
del dict1[2]
print(dict1) # {'key1': 1}字典的常用实例方法如下:
方法 说明 fromkeys(iterable, value=None)=>dict
使用来自 iterable 的键创建一个新字典,并将键值设为 value。 fromkeys()
是dict
类型的静态方法,使用过程如下:1
2
3iter=[1,2,3]
dict1=dict.fromkeys(iter, 'hello')
print(dict1) # {1: 'hello', 2: 'hello', 3: 'hello'}方法 说明 dict.clear()
移除字典中的所有元素 dict.copy()
返回原字典的浅拷贝 # 字典值获取方法 : dict.get(key, default=None)
如果 key 存在于字典中则返回 key 的值,否则返回 default 。注意 default 初始值为 None
因此不会为报KeyError
dict.pop(key[,default])=>value
如果 key 存在于字典中则将其移除并返回其值,否则返回 default。 如果 default 未给出且 key 不存在于字典中,则会引发 KeyError
# 字典键值获取方法 : dict.popitem()=>(key, value)
从字典中移除并返回一个 (key, value)
对,键值对会按 LIFO 的顺序被返回;Python 3.7
采用 LIFO 策略即stack
,管理字典的键值对。在Python 3.7
之前此方法会移除并返回一个随机键值对。# 字典键值更新方法 : dict.update(other)
使用 other 中的键值对更新 dict ,也可以使用 dict.update(key1=value1, ...)
的方式更新。注意使用dict.update(key1=value1)
时,字典会自动将 key1 转化为‘key1’
字符串作为键1
2
3d1={1: 'None'}
d1.update(key='None')
print(d1) # {1: 'None', 'key': 'None'}方法 说明 dict.setdefault(key, default=None)
如果字典存在键 key ,返回它的值。如果不存在,插入值为 default 的键 key ,并返回 default # 字典输出方法 : dict.items()
返回由字典项 (key, value)
组成的一个新视图,可以视作一个 iterable_tuplesdict.keys()
返回由字典键组成的一个新视图 dict.values()
返回由字典值组成的一个新视图 tuple
:元组中只包含一个元素时,需要在元素后面添加逗号
,
,否则括号会被当作运算符使用:1
2
3
4
5t1=(50)
t2=(50, )
print(type(t1)) # <class 'int'>
print(type(t2)) # <class 'tuple'>元组之间可以使用运算符进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的元组。
1
2
3
4
5
6
7t1=(50, 10, 5)
t2=(50, 20, 30)
t3=t1+t2
t4=t1*2
print(t3) # (50, 10, 5, 50, 20, 30)
print(t4) # (50, 10, 5, 50, 10, 5)
3. 字符串
在Python中我们可以使用'
、"
、"""
创建字符串,其中单引号与双引号用于创建普通字符串,三引号可以用于创建多行字符串与多行注释。以下是字符串的简单使用样例:
1 | str1='123' |
下面我们从以下方面来学习字符串:
✏️ 常用转义字符
常用模板字符串有:换行符 | \n
、 回车符 | \r
、 制表符 | \t
、 单引号 | \'
、 双引号 | \"
、 续行符 | \
、 反斜杠 | \\
。
1 | print("a\nb") # a |
➕ 字符串运算符
常用的字符串运算符有:
运算符 | 说明 |
---|---|
+ |
用于字符串拼接,当使用字符串拼接类型实例时,会调用实例对象的__str__ 方法生成对应字符串 |
* |
用于字符串重复输出 |
in 、not in |
用于判断字符串中是否包含指定字符串 |
r'' |
原始字符串,不能输出转义字符 |
f'' |
模板字符串,等同于‘’.format(*args) |
''%() |
格式化字符串,用于格式化输出 |
+
用于字符串拼接,当使用字符串拼接类型实例时,会调用实例对象的__str__
方法生成对应字符串;*
用于字符串重复输出。
1 | s="123" |
in
与not in
这两个成员运算符作用于字符串时不同于列表、元组与字典。当这两个成员运算符作用于字符串时更像是执行了bmp
算法,而当它们作用于列表等容器时是将比较对象看作子元素而不是子集。
1 | s1="123" |
当我们不想在字符串中使用转义字符时,可以使用原始字符串r''
。
1 | print(r'\n\r\t\'\"\\') # \n\r\t\'\"\\ |
🧩 模板字符串
当我们需要拼接多个字符串与对象时,可以使用模板字符串进行拼接。Python的模板字符串有以下两种使用方式:
“{0}, {1}”.format(*args)
:我们使用{index}
来为format(*args)
中对应的对象占位,当输出字符串时会自动调用对象的__str__()
方法,将生成的字符串插入到对应的{index}
中;f“{a1}, {a2}”
:我们也可以使用类似JavaScript
中生成模板字符串的方式,给我们的模板字符串插入对应对象调用__str__()
方法返回的字符串;
🧵 字符串格式化
Python支持格式化字符串的输出。需要注意的是Python的字符串格式化不止服务于print
方法,这是属于字符串的格式化方法。以下是Python字符串格式化符号:
符号 | 描述 |
---|---|
%c |
格式化字符及其ASCII 码 |
%s |
格式化字符串 |
%d |
格式化整数 |
%u |
格式化无符号整型 |
%o |
格式化无符号八进制数 |
%x |
格式化无符号十六进制数 |
%X |
格式化无符号十六进制数(大写) |
%f |
格式化浮点数字,可指定小数点后的精度 |
%e |
用科学计数法格式化浮点数 |
%E |
作用同%e ,用科学计数法格式化浮点数 |
%g |
%f 和%e 的简写 |
%G |
%f 和%E 的简写 |
%p |
用十六进制数格式化变量的地址 |
以下是字符串格式化的辅助符号:
符号 | 说明 |
---|---|
* |
用于动态指定宽度或精度(如%*.*f`) |
- |
左对齐输出内容 |
+ |
在正数前显示加号(如+3.2f ) |
<sp> |
正数前显示空格(负数仍显示负号) |
# |
启用进制前缀(如0 、0x 、0X )用于八进制或十六进制 |
0 |
使用零填充输出的空白部分(如08d 表示宽度为8,前补0) |
%% |
输出一个% 字符 |
(*args) |
使用元组或列表映射多个格式变量 |
m.n |
m 表示最小总宽度,n 表示小数点后的位数(如%6.2f ) |
1 | s="%d\n%6.1f\n% d\n%%"%(1, 6.111111, 1) |
4. 字节数组类型
Python的字节数组类型有:bytes
、bytearray
。其中,bytes
是二进制形式的不可变序列,bytearray
是二进制形式的可变序列。由于Python 3
的自带字符默认使用utf-8
格式编码和显示,故这两种字节数组类型的默认编码格式是utf-8
。
关于bytes
与bytearray
需要注意的地方有:
🧱 字节数组与hashable
bytes
的实现了__hash__()
、__eq__()
这两个魔术方法,故其实例对象可以作为字典键与集合元素,而bytearray
则不行。
1 | s={1, 2} |
🔣 字节数组与int
bytes
与bytearray
的底层是int
类型,取值 与 切片 的结果如下:
1 | b=b"123" |
🛠️ 创建字节数组
我们执行help(bytearray)
可以看到bytearray
的构造函数用法如下。
1 | bytearray(iterable_of_ints) # -> bytearray |
我们执行help(bytes)
则可以看到bytes
的构造函数可以用如下方法使用:
1 | bytes(iterable_of_ints) # -> bytes |
我们发现这里help
告诉我们的整型序列不是用的list
、tuple
这些关键词,而是用 iterable 描述的整型序列。如果想要我们的类支持通过bytes
或者bytearray
实现二进制序列化,则需要实现__iter__()
这一魔术方法。关于此方法的说明和使用请参考下一节面向对象编程。
1 | class MyCollection: |
字节数组构造函数的调用规律如下:
可以使用可遍历的
int
序列创建,例如:(1,2,3)
、{1,2,3}
等。1
2
3
4
5
6
7
8
9b1=bytes((1,2,3))
b2=bytes([1,2,3])
b3=bytes({1,2,3})
b4=bytes({1: '123'}) # Int-keys of dict is iterable
print(b1) # b'\x01\x02\x03'
print(b2) # b'\x01\x02\x03'
print(b3) # b'\x01\x02\x03'
print(b4) # b'\x01'可以使用
string
值创建,在创建时需要指定编码方式,默认是utf-8
。可以使用
bytes
或者buffer
创建。可以使用
int
值创建,根据int
值生成对应长度的字节数组。1
2
3
4b1=bytes(1)
for i in b1:
print(i) # 0可以创建空的
bytes
或者bytearray
。
Python为我们提供了类型转换工具,这有助于我们更好的处理数据。我们常用type_name(other_type_ins)
的方式进行类型转换,这实际上就是调用了对于类型的构造函数进行转换。Python的基本数据类型的类型转换规则如下:
数值类型间可以相互转换:
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
27raw_list=[1, 1.0, 3+4j, True]
trans_functions=[int, float, complex, bool]
for raw in raw_list:
for trans in trans_functions:
try:
print(f"{trans.__name__}({raw}) => {trans(raw)}")
except TypeError as err:
print(f"{trans.__name__}({raw}) | {err}")
# # Result:
# # int(1) => 1
# # float(1) => 1.0
# # complex(1) => (1+0j)
# # bool(1) => True
# # int(1.0) => 1
# # float(1.0) => 1.0
# # complex(1.0) => (1+0j)
# # bool(1.0) => True
# # int((3+4j)) | can't convert complex to int
# # float((3+4j)) | can't convert complex to float
# # complex((3+4j)) => (3+4j)
# # bool((3+4j)) => True
# # int(True) => 1
# # float(True) => 1.0
# # complex(True) => (1+0j)
# # bool(True) => True执行上述代码,我们可以发现只有
complex
的转换成其他类型时有些限制,其他类型之间都可以相互转换。list
、set
、tuple
与字节数组类型间可以相互转换:稍微更改上一节的代码并执行可以发现,
list
、set
、tuple
之间可以相互转换。当这三者的元素数据类型只有int
或者bool
时,可以通过调用bytes()
、bytearray()
转换为字节数组。1
2
3
4
5
6
7
8
9
10
11
12
13list1=[1,2,True]
tuple1=(1,2,3.1)
set1={1,2,'3'}
raw_list=[list1, tuple1, set1]
trans_functions=[list, tuple, set, bytes, bytearray]
for raw in raw_list:
for trans in trans_functions:
try:
print(f"{trans.__name__}({raw}) => {trans(raw)}")
except TypeError as err:
print(f"{trans.__name__}({raw}) | {err}")dict
与list
、set
、tuple
、bytes
、bytearray
的转换规则:字典在使用
list()
、set()
、tuple()
转换为相应类型时,是将字典值数组作为可遍历序列进行转换的。使用bytes()
、bytearray()
进行转换时,值类型也要满足上小节提到的要求。其他可遍历类型若想转化为
dict
,可以自己通过遍历实现或者采用使用dict
的静态方法fromkeys(iterable, value=None)
实现。bool()
还支持转换其他数据类型为bool
。str
类型可以被看作字符数组与list
、tuple
、set
进行类型转换。如果需要我们的类支持向这些基本数据类型进行转换,需要支持编写相应的魔术方法。目前支持的魔术方法有:
__bool__()
、__int__()
、__float__()
、__complex__()
等。魔术方法详见下一节面向对象编程。
面向对象编程
Python是一门面向对象的语言,所有基本数据类型对应的类都可以在builtins
中找到。在本节中我们将从以下方面了解Python的面向对象编程:
1.class
基础
我们使用class
关键字定义一个类,以下是定义类时需注意的事项:
🧬 属性与方法
关于类的属性与方法有如下实现细节:
私有字段:我们可以使用
__
作为属性名或者方法名的前缀,这样定义的方法就不能通过类的实例对象访问。静态属性:当我们用
ClassName.static_attr
调用属性时,得到的就是该类型的静态属性。静态属性不能通过实例对象进行修改,只能直接修改。静态方法:我们可以使用
@staticmethod
装饰器来装饰类的 实例方法 ,装饰后的方法应当没有入参self
,我们可以用ClassName.static_func()
来调用静态方法。抽象类方法:我们可以使用
@abstractmethod
装饰器来装饰类的 抽象方法 ,在定义类的抽象方法时,我们只需要给出方法的入参即可,方法的函数体与返回值使用pass
代替。当有子类继承了该类,需要实现该抽象方法。1
2
3
4
5
6
7
8class Test:
def __abstract_func(self):
pass
class SubTest:
def __abstract_func(self):
print("Writing implement here")类方法:我们可以使用
@classmethod
装饰器来装饰类的 类方法。与实例方法不同,类的类方法用于处理类本身即
cls
对象,我们可以通过cls
对象访问类中的静态方法或者属性,需要注意的是类的静态属性与静态方法实际上就是类的元类实例即cls
对象的实例方法与属性。1
2
3
4class Test:
def class_func(cls, *args, **kwargs):
pass属性代理方法:我们可以使用
@property
、@xxx.getter
、@xxx.setter
、@xxx.deleter
来代理类实例的属性或者私有属性。需要注意的是
@property
、@xxx.getter
实现的效果都是代理属性的获取功能,但是为了代码的可读性考虑,当我们需要对代理属性进行 删、改、查 操作时优先考虑后三者,当我们只需要 查 代理属性时优先考虑@property
。对于
@property
而言,def property():
是它实际的函数形式。我们除了在类定义时使用装饰器语法,还可以在元类中为类的方法添加@property
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class ImmutableMeta(type):
def __new__(cls, name, bases, dct):
for key, value in dct.items():
print(key, value)
if not key.startswith('__'):
dct[key] = property(value)
return super().__new__(cls, name, bases, dct)
class MyClass(metaclass=ImmutableMeta):
def __init__(self, value):
self._value = value
def get_value(self):
return self._value
obj = MyClass(10)
print(obj.get_value)
👨👩👦 类的继承
Python实现了 多继承 ,我们在编写类时支持通过class ClassName(FatherClass1, FatherClass2)
的方式继承父类的属性与方法。Python的继承的注意事项有:
- 子类实例对象访问父类的属性或者方法时,Python将 从左往右 依次查找父类中是否有对应方法。
- 子类可以对父类的方法进行重写,
super()
是Python提供的用于子类调用父类属性或方法的一个方法。 - 我们使用
super()
在子类中访问父类属性或方法时,不能访问父类的私有属性或方法。可以被看做在使用父类的一个实例对象。
2.class
进阶
Python的所有类型都是由builtins
的object
类型继承而来,这意味着我们可以在所有类型中使用object
的方法(object
中没有属性)。我们执行help(object)
得到object
的内置方法如下:
1 | Help on class object in module builtins: |
其中形如__xxx__()
、__xxx__
的属性或者方法我们称之为 魔术属性 或者 魔术方法 。在编程语言中,我们常用 魔术数字 | magic number
指代那些在直接硬编码的数字,它们通常没有一个明确的名称或含义。Python中的魔术字段也是硬编码在Python基础库或者解释器里面,它们各自服务于Python类型实例生命周期的不同片段。需要说明的一点是,
Python会因不同的PEP(Python Enhancement Proposal)
会不断添加删除魔术方法,故下面分类列出的魔术方法具有时效性,请在使用时查看相应Python文档或者使用help()
工具。魔术方法按功能可分为如下几类:
🛠️ 创建实例
方法 | 说明 |
---|---|
__init__(self, *args, **kwargs) |
用于初始化对象,在使用ClassName() 时自动调用。这里的self 对象是由__new__() 创建的 |
__new__(cls, *args, **kwargs) |
在__init__() 之前调用,用于创建self 对象。cls 代表类本身 |
__del__(self) |
在对象被垃圾回收时执行特定的操作,如关闭socket ,文件描述符等。因为当Python解释器退出时,对象有可能依然未被释放,因此__del__() 中定义册操作不一定能被执行,故在实际中应避免使用__del__() |
🎮 实例属性
方法 | 说明 |
---|---|
__getattr__(self, item) |
拦截访问实例不存在的属性,即在访问不存在实例的__dict__ 中的key 时,进行调用拦截 |
__getattribute__(self, item) |
拦截所有的实例属性访问操作,应当在函数体内避免使用self.key 这会造成死循环 |
__setattr__(self, key, value) |
拦截所有的实例属性赋值操作,应当在函数体内避免使用self.key=value 这会造成死循环 |
__delattr__(self, item) |
拦截所有的实例属性清除操作,应当在函数体内避免使用del self.key 这会造成死循环 |
🧭 描述实例
__str__(self)
:Python 的toString()
,使类型实例支持print()
。__repr__(self)
:使类型实例支持repr()
,repr()
方法用于获取对象的详细信息包括存储地址等。__str__(self)
中的信息一般是__repr__()
的一部分__format__(self, format_spec)
:使类型实例支持“{}”.format(obj, key)
或者f"{obj}"
、f"{obj:key}"
。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
32class EnableFormat:
value=None
default='default'
def __init__(self, value):
self.value = value
def __format__(self, format_spec):
raw_out=''
if format_spec == 'value':
raw_out=self.value
elif format_spec == 'default':
raw_out=self.default
elif format_spec == '':
raw_out=f"EnableFormat(value={self.value}, \
default={self.default})"
else:
raw_out=f"EnableFormat(value={self.value}, \
default={self.default})"
return raw_out
test=EnableFormat('enter')
print(f"{test}")
print("{}".format(test))
print(f"{test:value}")
print("{:default}".format(test))
# # Result:
# # EnableFormat(value=enter, default=default)
# # enter
# # EnableFormat(value=enter, default=default)
# # default__hash__(self)
:用于 Hashable 类型定义,这前面已经介绍过了。__dir__(self)
:调用dir()
方法时对象的返回值,一般不需要重写该方法,但在定义__getattr__
时有可能需要重写,以打印动态添加的属性。__sizeof__(self)
:定义sys.getsizeof()
方法调用时的返回,指类的实例的大小,单位是字节,在使用 C 扩展编写Python
类时比较有用。
🧪 实例间计算
在这里主要介绍关于 比较计数 的魔术方法:
方法 | 说明 |
---|---|
__eq__(self, obj) |
使类型实例支持== |
__ne__(self, obj) |
使类型实例支持!= |
__le__(self, obj) |
使类型实例支持<= |
__ge__(self, obj) |
使类型实例支持>= |
__lt__(self, obj) |
使类型实例支持< |
__gt__(self, obj) |
使类型实例支持> |
此外还有许多计算相关的魔术方法,在这里就不一一列举了(因为用不到)。这里的l
、g
、eq
分别是less
、greater
、equal
的缩写,le
、lt
则是less equal
、less than
的缩写。
🧷 序列化实例
定义一个不可变容器,只需要实现__len__
和__getitem__
,可变容器在此基础上还需实现__setitem__
和__delitem__
,如果希望容器是可迭代的,还需实现__iter__
方法。
方法 | 说明 |
---|---|
# 不可变容器魔术方法 : | |
__len__(self) |
返回序列的长度,使类型实例支持len(instance) |
# 可变容器魔术方法 : | |
__getitem__(self, key) |
使类型实例支持self[key] |
__setitem__(self, key, value) |
使类型实例支持self[key]=value |
__delitem__(self, key) |
使类型实例支持del self[key] |
# 可迭代容器魔术方法 : | |
__iter__(self) |
使类型实例支持for item in self 。通常执行__iter__() 返回的实例对象需要实现__next__() 方法__next__() 方法用于获取迭代器的下一个元素 |
__reversed__(self) |
使类型实例支持reversed(instance) |
__contains__(self, val) |
使类型实例支持val in instance |
# 字典子类魔术方法 : | |
__missing__(self) :。 |
当定制序列是 dict 的子类 时,使用dict[key] 读取元素而key 没有定义时调用 |
1 | class MyDict(dict): |
🧼 可调用化实例
__call__(self, *args, **kwargs)
:使类型 实例像函数一样调用 。需要注意的是当我们在元类中书写该方法时有些许不同,关于元类的详见下一小节 type
与元类 。
1 | class FunctionGenerator: |
♻️ 复制实例
copy
模块是Python的基础库之一。其中,copy.copy()
是浅复制,常用于 不可变数据 的复制,当对 可变数据类型 进行浅复制时,只是复制了对象本身与对象包含的引用,不是复制了指向的对象。copy.deepcopy()
是深复制,它会递归复制需要复制的对象。
__copy__(self)
:执行copy.copy()
时调用。__deepcopy__(self, memodict={})
:执行copy.deepcopy()
时调用。memodict
用于缓存目前已经复制的对象,以下是参考代码:1
2
3
4
5
6
7
8
9
10class Copy:
def __init__(self, value):
self.value=value
def __deepcopy(self, memodict={}):
# Copy part code, base on your class data structure
new=Copy(self.value)
memodict[id(new)]=new
return new
🪜 上下文管理
Python 中使用with
语句执行上下文管理,处理内存块被创建时和内存块执行结束时上下文应该执行的操作,上下文即程序执行操作的环境,包括异常处理,文件开闭。有两个魔术方法负责处理上下文管理。
__enter__(self)
:上下文创建时执行的操作,该方法返回as
后面的对象。__exit__(self, exc_type, exc_val, exc_tb)
:上下文结束时执行的操作。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Closer:
def __init__(self, obj):
self.obj = obj
def __enter__(self):
print('__enter__ called')
return self.obj # bound to target
def __exit__(self, exception_type, exception_val, trace):
print('__exit__ called')
try:
self.obj.close()
except AttributeError:
print('Not closable. here')
return True
with Closer(open("1.txt", "w")) as f:
f.write("1")
🧱 描述器
描述器更像是JavaScript
中的Proxy
,我们直接可以直接对描述器实例对象就行取值、赋值、删除操作,而这些操作均会被__get__()
、__set__()
、__delete__()
拦截并代理。与JavaScript
不同的是,描述器类型的实例只有在被赋值给属性后,才能进行拦截。
__get__(self, instance, owner)
:查找描述器属性时调用。__set__(self, instance, value)
:给描述器属性赋值时调用。__delete__(self, instance)
:移除描述器属性时调用。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 AttrProxy:
value=None
def __get__(self, instance, owner):
print("in __get__()")
return self.value
def __set__(self, instance, value):
print("in __set__()")
self.value = value
class Attach:
data=AttrProxy()
def __init__(self, value):
self.data = value
def print(self):
print(self.data)
test=Attach(10)
# in __set__()
test.print()
# in __get__()
# 10
print(test.data)
# in __get__()
# 10
🧨 super()
Python 提供的内置函数中有两个是与面向对象编程密切相关的:一个是staticmethod()
装饰器,它 用于声明类的静态方法 ;另一个是super()
,它用于在子类中 调用父类的方法与属性 。除了我们常用的super().func()
的调用方法,super()
还有其他调用方式。我们使用help(super)
来查看Python给我们提供的解释:
1 | class super(object) |
对于super
我们的关注点主要是在super()
构造函数。观察上述运行结果,我们发现,super()
总是接受 super(type, other)
的参数形式,这里的type
实际上就是指type
元类的实例对象即类。
super()
:该方式调用的同等效果参考上述描述。在一般情况下第一个参数有两种cls
、self
。super(type)
:使用该方式调用会返回一个未绑定的type
实例。注意,由于返回的实例对象未绑定到
self
或者cls
,对这返回对象的处理不会影响到self
。1
2
3
4
5
6
7
8
9
10class Base:
def hello(self):
print("Hello from Base")
class Derived(Base):
def hello(self):
super(Base).hello(self) # Hello from Base
derived = Derived()
derived.hello()super(type, obj)
:使用该方法调用会将返回的super
实例对象绑定到obj
上,这是我们一般调用方式的实际调用方式。super(type1, type2)
:这个涉及到了Python的 方法解析顺序|MRO
,后续会开专题解析。
3. type
与元类
Python是一门面向对象的语言,几乎所有的一切都是基于类实现的,连类本身也不例外。当我们执行以下代码便可以得知用class
声明的类本身也是type
类的实例。
1 | class MyClass: |
我们将用于创建一般类的类称为 元类| metaclass
:
- 静态字段:普通类的静态字段实际上就是使用元类生成的元类实例的实例方法与属性。
- 默认元类
type
:在定义类时使用class MyClass(*args, metaclass=MyMetaClass)
为指定元类。如果没有指定元类,Python则会指定默认元类type
作为该类的元类。Python的内置类的元类都是type
。
为了更好的了解元类,我们可以详细学习下type()
方法。我们执行help(type)
有如下结果:
1 | class type(object) |
我们主要关注type
的构造函数与type
类型实例的实例方法,即类的自带静态方法。
type()
:根据上述描述可知,该方法的的用法除了用于判断一个实例的类,还可以用type(name, bases, dct)
创建一个类。- 实例方法:类的自带静态方法实际上就是类的魔术方法,我们可以通过重写类的魔术方法为更好的处理类实例的生命周期以及使用。
我们也可以定制自己的元类。我们在创建类时继承type
便创建了一个元类。元类中的某些魔术方法实现方式也与普通类不一样。
__new__(cls, name, bases, dict)
:用于创建元类的实例对象即元类为该类的类。需要注意的是,这里的cls
是实际上的类对象。这个cls
无论是普通类还是元类都是这样的。1
2
3
4
5
6
7class MetaClass(type):
def __new__(cls, name, bases, dct):
print(cls) # <class '__main__.MetaClass'>
return super().__new__(cls, name, bases, dict)
class MyClass(metaclass=MetaClass):
pass下面我们解释一下
name
、bases
、dct
分别指什么:name
:使用该元类创建类时的类名。bases
:使用该元类创建类时指定的父类。dct
:使用字典表示用该元类创建的类的主体。
1
2
3
4
5
6
7
8
9
10
11class MetaClass(type):
def __new__(cls, *args):
name, bases, dct=args
print(f"Create class: {name}")
print(f"Extends class: {bases}")
print(dct)
return super().__new__(cls, name, bases, dct)
class MyClass(metaclass=MetaClass):
def __init__(self):
pass__init__(cls, name, bases, dict)
:用于初始化元类实例对象。__call__(cls, *args, **kwargs)
:__call__
在普通类中是用于类实例的可调用化,即用使用函数的方式使用类实例。在这里是用于元类实例的可调用化,即处理类的构造函数。1
2
3
4
5
6
7
8
9class MetaClass(type):
def __call__(self, *args, **kwargs):
print('In meta __call__')
class MyClass(metaclass=MetaClass):
def __init__(self):
pass
test=MyClass() # In meta __call__