Python修养3

Python修养3
flowwalkerPython修养3
目录
[TOC]
函数
基本说明
- 可以返回多个值
- 可以无参, 可以无返回
- 形参只拿到引用的副本
- 形参重新赋值, 不影响实参
- 形参和实参指向同一个对象, 修改本地同时生效( ⚠️不是赋值, 赋值改变地方)
- 若执行过程中改变了形参所指向地方的内容, 实参也相应改变 (⚠️地方不变)
1
2
3
4
5
6
7
8
9
10
11
12
13 def modify(lst):
lst.append(99) # ✅ 原地修改,外面看得见
def reassign(lst):
lst = [99] # ❌ 重新赋值,改变了指向的地方,外面看不见
a = [1, 2, 3]
modify(a)
print(a) # [1, 2, 3, 99]
b = [1, 2, 3]
reassign(b)
print(b) # [1, 2, 3] ← 没变!更明显的范例:
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 class A:
n = 0
class B:
n = 100
def Swap1(x, y):
tmp = x.n
x.n = y.n
y.n = tmp
def Swap2(x, y):
x, y = y, x
a = A()
a.n = 100 # 给实例 a 添加属性 n = 100
b = B()
b.n = 200 # 给实例 b 添加属性 n = 200
Swap1(a, b)
print(a.n, b.n) # 200 100,确实交换了
a = 6
b = 7
Swap2(a, b)
print(a, b) # 6 7,没交换!
范式
1 | def 函数名(参数1,参数2,...): |
参数传递
默认参数
实参可带名字
1
2
3
4
5
6def func(a,b=1,c=2):
print('a=',a,'b=',b,'c=',c)
func(10,20)
func(30,c=40)
func(c=50,a=60)参数个数可以不定,
*b传入元组1
2
3
4def func(a,*b): #此处b将成为元组
print(b)
c=[1,2,3]
func(1,2,'ok',c)传入参数可以带
*来解"框"1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16def func(*b):
print(b,end=' however: ')
for x in b:
print(x,end=' ')
func(1,2,3)
print()
func(*[1,2,3])
print()
func(*(1,2,3))
print()
func((1,2,3))
# (1, 2, 3) however: 1 2 3
# (1, 2, 3) however: 1 2 3
# (1, 2, 3) however: 1 2 3
# ((1, 2, 3),) however: (1, 2, 3)参数可以不定,
**b传入字典1
2
3
4
5
6
7
8def func(**b):
print(b)
func(p1=2,p2=3,p3=4)
# {'p1': 2, 'p2': 3, 'p3': 4}
a={'take':1,'back':'this'}
func(**a)
# {'take': 1, 'back': 'this'}函数中的变量, 不加声明均为局部变量
全局声明:
global x,y
python内置函数
- int(x)
- float(x)
- str(x)
- ord(x) 返回单个字符的Unicode 编码(整数)
- chr(x)
- abs(x)
- len(x)
- max(x) x是列表,下同
- min(x)
- max(x1,x2,…) 此处先视为列表后比较
- min(x1,x2,…)
- exit() 退出程序
跨文件引用函数和变量
1 | def hello(): |
1 | from t import hello,haha |
OOP
object类
所有类均自动由object类派生
成员函数
__init__构造函数__eq__等号__lt__< 即less than__gt__>__le__<=__ge__>=__ne__!=__str__强制转换成str__repr__强制转换为python可执行字符串__del__析构函数- …
范式
- 构造函数和析构函数都只能有一个
- 对象的成员变量可以随时添加
1 | class A: |
所谓比较id指的是, “地址”
1
2
3
4
5
6
7
8 class A:
def __init__(self,name):
self.name=name
a=A("hh")
b=A("hh")
print(a==b) #此处的==退化为is
#False重载了
__eq__后, 自动将__hash__设置为None, 需要手动设置回来才可哈希, 并且确保相等的值哈希值相等关于
format的语法
1 "({0.x},{0.y})".format(self)
符号 含义 {0}format()的第 0 个参数,也就是self{0.x}第 0 个参数的 .x属性,即self.x{0.y}第 0 个参数的 .y属性,即self.y关于字符串重载的写法
(重载目的是 方便print)
1
2
3
4
5
6
7
8
9
10
11
12 def __str__(self):
# 写法1:对象属性访问(你的代码)
return "({0.x},{0.y})".format(self)
# 写法2:拆开传(等价)
return "({},{})".format(self.x, self.y)
# 写法3:命名参数(等价)
return "({x},{y})".format(x=self.x, y=self.y)
# 写法4:f-string(最推荐)
return f"({self.x},{self.y})"python字符串的三种主要表示
写法 出现时间 示例 推荐度 %格式化古老 "%s is %d" % (name, age)⭐⭐ 能跑,但过时了 .format()Python 2.6+ "{} is {}".format(name, age)⭐⭐⭐ 兼容性好 f-string Python 3.6+ f"{name} is {age}"
静态方法和类方法
1 | class Student: |
类方法用于修改全类数据, 静态方法用于定义工具(如格式转换等), 普通方法用于操作某个具体对象的数据
参见表格
普通方法 类方法 静态方法 定义格式 def func(self)@classmethoddef func(cls)@staticmethoddef func()第一个参数 self(实例)
表示知道自己属于哪个对象cls(类本身)
表示知道自己属于哪个类没有 访问实例属性 self.name✅ 能 ❌ 不能 ❌ 不能 访问类属性 school能(通过 self.n或者类名A.n或者self.__class__.n)✅ 能(通过 cls)❌ 不能直接访问
(硬写类名可以,但不推荐)调用方式 a.func()A.func()或a.func()A.func()或a.func()用途 操作具体某个对象的数据 操作类本身的数据、
创建替代构造函数放在类里的工具函数
(跟类数据无关)注: 此处普通方法利用
self.n访问类属性, 属于查询机制, 先找实例对象,若无则查找类对象此外还可以用类方法创建实例
1
2
3
4
5
6
7
8...
def from_string(cls,string):
value=int(string)
return cls(value)
...
obj=MyClass.from_string("42")
print(obj.instance_attr)
访问范围
私有属性
以__开头的变量 (两个_)
注意:
_p表示开发者约定为私有变量,仍然可以直接使用;__ppython会将其改名为_类名__p以强化私有变量的意味, 我们仍然可以使用改名后的变量python奉行"成年人"的观念
- 名称改写机制防止子类意外覆盖父类的属性
- 私有属性的命名是一种提示, 表示不应该
- 通过限制对私有属性的直接访问, 类的设计者可以更自由地修改内部实现, 而不影响外部代码
property函数
概念
接收至多四个参数
fget获取属性值的方法fset设置属性值的方法fdel删除属性值的方法doc属性的文档字符串
property是python的一个内部函数, 用于将一个方法转化为属性
(方法伪装成属性, 调用时无需括号, 看起来像是数据一样,实际内部调用了函数)
解决的是私有后, 总是写获取/修改/设置/删除接口的麻烦
实现 属性封装 和 计算属性
使用方法
way1
1 | class foo: |
way2(更简化)
使用@property将方法转化为属性
以及@属性名.setter和属性名.deleter来定义
1 | class foo: |
用途
控制访问方式
1 | class Person: |
动态计算属性
1 | class Rectangle: |
隐藏内部实现细节
以前的成员属性现在内部修改完, 外面也可以继续用
运算符重载
直接记忆!
一、算术运算符
| 运算符 | 普通 | 反向(对象在右) | 增量赋值(原地改) |
|---|---|---|---|
+ | __add__ | __radd__ | __iadd__ |
- | __sub__ | __rsub__ | __isub__ |
* | __mul__ | __rmul__ | __imul__ |
/ | __truediv__ | __rtruediv__ | __itruediv__ |
// | __floordiv__ | __rfloordiv__ | __ifloordiv__ |
% | __mod__ | __rmod__ | __imod__ |
** | __pow__ | __rpow__ | __ipow__ |
@ | __matmul__ | __rmatmul__ | __imatmul__ |
二、一元运算符
| 表达式 | 方法 | 说明 |
|---|---|---|
-x | __neg__ | 负号 |
+x | __pos__ | 正号(一般返回自身) |
abs(x) | __abs__ | 绝对值 |
~x | __invert__ | 按位取反 |
三、比较运算符
| 运算符 | 方法 | 说明 |
|---|---|---|
== | __eq__ | 等于 |
!= | __ne__ | 不等于 |
< | __lt__ | 小于 |
<= | __le__ | 小于等于 |
> | __gt__ | 大于 |
>= | __ge__ | 大于等于 |
四、位运算符
| 运算符 | 普通 | 反向 | 增量 |
|---|---|---|---|
& | __and__ | __rand__ | __iand__ |
| | __or__ | __ror__ | __ior__ |
^ | __xor__ | __rxor__ | __ixor__ |
<< | __lshift__ | __rlshift__ | __ilshift__ |
>> | __rshift__ | __rrshift__ | __irshift__ |
五、容器/序列(最常用)
| 表达式 | 方法 | 说明 |
|---|---|---|
len(x) | __len__ | 长度 |
x[i] | __getitem__ | 取值 |
x[i] = v | __setitem__ | 赋值 |
del x[i] | __delitem__ | 删除 |
x in s | __contains__ | 成员判断 |
for i in x: | __iter__ | 迭代 |
next(x) | __next__ | 生成器/迭代器下一项 |
x() | __call__ | 把对象当函数调用, 相当于c++的operator() |
with x: | __enter__ / __exit__ | 上下文管理器 |
老式迭代:
1
2
3
4
5
6
7 class stepper:
def __getitem__(self, i):
return self.data[i]
X = stepper()
X.data = 'Spam'
for item in X: #call __getitem__ ,for 语句自带异常处理
print (item)
stepper虽然没写__iter__,for会自动按 i=0,1,2,3… 去调__getitem__,直到self.data[i]越界抛出IndexError,for内部捕获后结束循环。
__getitem__为什么能被for用Python 的
for循环内部有个备胎机制:
1
2 for item in X:
...实际执行的大致逻辑:
1
2
3
4
5
6
7
8
9
10
11
12 # 第1步:找 __iter__
try:
iterator = iter(X) # 调用 X.__iter__()
except TypeError:
# 第2步:如果没有 __iter__,就用 __getitem__ 硬凑
i = 0
while True:
try:
item = X[i] # 调用 X.__getitem__(i)
i += 1
except (IndexError, StopIteration):
break # 越界就停现代迭代:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 class stepper:
def __init__(self, data):
self.data = data
def __iter__(self): # 现代迭代协议
return iter(self.data) # 直接返回一个迭代器
# 或者更完整:
class stepper:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
result = self.data[self.index]
self.index += 1
return result
六、类型转换
| 函数 | 方法 |
|---|---|
str(x) | __str__ |
repr(x) | __repr__ |
int(x) | __int__ |
float(x) | __float__ |
bool(x) | __bool__ |
hash(x) | __hash__ |
七、输出重载
字符串即可
1 | class A: |
注:不重载lt也可以排序(用key=)
1 | from operator import * |
注:
itemgetter用[]取(列表/字典索引),attrgetter用.取(对象属性)。
工具 操作符 用于 等价 lambda itemgetter(0)x[0]列表、元组、字典 lambda x: x[0]itemgetter('name')x['name']字典 lambda x: x['name']attrgetter('x')obj.x对象的属性 lambda obj: obj.xattrgetter('x', 'y')(obj.x, obj.y)多属性 lambda obj: (obj.x, obj.y)
泛型
变量类型本就可变, 任何函数都相当于模板
继承和多态
基本内容
本来对象的类型就是运行时确定, 没有明显的多态
多态过于自然, 以至于感受不到
class B(A): 派生类定义
1 | class A: |
调用父类函数的方式
- 类名
- super(type,obj)
- super()
1 | class A: |
注:类外部使用super(type,obj)
注:
isinstance的用法
1
2
3
4
5
6
7
8
9
10 class A:
pass
class B(A):
pass
a=A()
b=B()
print(isinstance(a,A)) #True
print(isinstance(b,A)) #True⚠️⚠️
print(isinstance(a,B)) #False
print(isinstance(b,B)) #True
可哈希(hashable)
基本内容
不可变的内置对象, 可哈希 ( 能通过 hash映射为一个整数)
字典, 列表, 集合不可哈希
自定义对象默认可哈希
- 重载了
__eq__默认不可哈希( python自动将__hash__设为None) - 重载了
__eq__需要重载__hash__才又可哈希
问题如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class BadMutable:
def __init__(self, value):
self.value = value
def __eq__(self, other):
return self.value == other.value
# 如果没设置成None,等价于:
def __hash__(self):
return id(self)
obj1 = BadMutable(10)
obj2 = BadMutable(20)
d = {obj1: "ten", obj2: "twenty"}
print(len(d)) # 2
obj2.value = 10 # 修改 obj2 的值让它等于 obj1
print(len(d)) # 集合中它们被认为是不同的元素!- 重载了
关于哈希值/本值
字典和集合都是"哈希表"数据结构, 根据元素的哈希值为元素找存放的槽, 哈希值可视为槽编号
若
hash(a)!=hash(b)则a,b可处于同一个集合(同字典中不同元素的键)若
hash(a)==hash(b), 但a==b不成立, 也可以(python底层会自己处理)
对象作为key
实际是以对象的地址作为key ,而非值
1 | class P: |
对象的值作为key
重写__eq__和__hash__
1 | class A: |






