Python修养3
函数
基本说明
- 可以返回多个值
- 可以无参, 可以无返回
- 形参只拿到引用的副本
- 形参重新赋值, 不影响实参
- 形参和实参指向同一个对象, 修改本地同时生效( ⚠️不是赋值, 赋值改变地方)
- 若执行过程中改变了形参所指向地方的内容, 实参也相应改变 (⚠️地方不变)
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] ← 没变!更明显的范例:
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,没交换!
范式
def 函数名(参数1,参数2,...):
语句组(即"函数体")
# 也可无参数
参数传递
默认参数
实参可带名字
def 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传入元组def func(a,*b): #此处b将成为元组 print(b) c=[1,2,3] func(1,2,'ok',c)传入参数可以带
*来解”框”def 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传入字典def 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() 退出程序
跨文件引用函数和变量
t.py
def hello():
print('hello from t')
haha='ok'
a.py
from t import hello,haha
# 或者from t import *
hello() # hello from t
print('haha=',haha) # haha=ok
OOP
object类
所有类均自动由object类派生
成员函数
__init__构造函数__eq__等号__lt__< 即less than__gt__>__le__<=__ge__>=__ne__!=__str__强制转换成str__repr__强制转换为python可执行字符串__del__析构函数- …
范式
- 构造函数和析构函数都只能有一个
- 对象的成员变量可以随时添加
class A:
def __init__(self,x,y):
self.x=x
self.y=y
def func(self):
self.xx=9
def __del__(self):
print(self.x,"destructed")
def __eq__(self,b):
return self.x==b.x and self.y==b.y
#缺省情况下, 退回is, 比较id是否一样
a=A(1,3)
b=A(2,3)
a.n2=28
print(a.n2)
a.func()
print(a.xx)
#print(b.n2) #error
print(a.__eq__(b))
print(a==b)
# 均为False
a=8 # 1 destructed
所谓比较id指的是, “地址”
class A: def __init__(self,name): self.name=name a=A("hh") b=A("hh") print(a==b) #此处的==退化为is #False重载了
__eq__后, 自动将__hash__设置为None, 需要手动设置回来才可哈希, 并且确保相等的值哈希值相等关于
format的语法"({0.x},{0.y})".format(self)
符号 含义 {0}format()的第 0 个参数,也就是self{0.x}第 0 个参数的 .x属性,即self.x{0.y}第 0 个参数的 .y属性,即self.y关于字符串重载的写法
(重载目的是 方便print)
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}"
静态方法和类方法
class Student:
school = "PKU" # 类属性(全校统一)
def __init__(self, name):
self.name = name # 实例属性(每个人不同)
# ========== 普通方法 ==========
def introduce(self): # self = 具体的某个学生
print(f"我是{self.name},来自{self.school}")
# ========== 类方法 ==========
@classmethod
def change_school(cls, new_name): # cls = Student 这个类本身
cls.school = new_name
print(f"全校改名了,现在叫{new_name}")
# ========== 静态方法 ==========
@staticmethod
def calc_score(a, b): # 跟学生、学校都没关系,就是个工具
return (a + b) / 2
s = Student("Alice")
# 普通方法:必须有个具体的学生
s.introduce() # ✅ 我是Alice,来自PKU
Student.introduce(s) # ✅ 等价写法,手动传self
# 类方法:通过类或实例都能调,改的是全校
Student.change_school("THU") # ✅ 全校改名了
s.change_school("THU") # ✅ 也能调,但改的是类属性
# 静态方法:就是个普通函数,放类里只是为了归类
Student.calc_score(80, 90) # ✅ 85.0
s.calc_score(80, 90) # ✅ 也能调,但跟 s 没关系
类方法用于修改全类数据, 静态方法用于定义工具(如格式转换等), 普通方法用于操作某个具体对象的数据
参见表格
普通方法 类方法 静态方法 定义格式 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访问类属性, 属于查询机制, 先找实例对象,若无则查找类对象此外还可以用类方法创建实例
... @classmethod 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
class foo:
def __init__(self):
self.name='yoda'
self.work='master'
def get_person(self):
return self.name,self.work
def set_person(self,value):
self.name,self.work=value
person=property(get_person,set_person)
A3=foo()
print(A3.person) # ('yoda','master')
A3.person='skylaer','programmer'
print(A3.person) # ('skylaer','programmer')
way2(更简化)
使用@property将方法转化为属性
以及@属性名.setter和属性名.deleter来定义
class foo:
def __init__(self):
self.name='yoda'
self.work='master'
#将方法转换为属性
@property
def person(self):
return self.name,self.work
@person.setter #若不设置, 则为只读操作
def person(self,value):
self.name,self.work=value
A3=foo()
print(A3.person)
A3.person='skylaer','programmar'
print(A3.person)
用途
控制访问方式
class Person:
def __init__(self,name):
self._name=name
@property
def name(self):
return self._name
@name.setter
def name(self,value):
if not isinstance(value,str):
raise VauleError("Name must be a string")
self._name=value
p=Person("Alice")
p.name=123
动态计算属性
class Rectangle:
def __init__(self,width,height):
self.width=width
self.height=height
@property
def area(self):
return self.width*self.height
r=Rectangle(5,10)
print(r.area)
隐藏内部实现细节
以前的成员属性现在内部修改完, 外面也可以继续用
运算符重载
直接记忆!
一、算术运算符
| 运算符 | 普通 | 反向(对象在右) | 增量赋值(原地改) |
|---|---|---|---|
+ |
__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__ |
上下文管理器 |
老式迭代:
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循环内部有个备胎机制:for item in X: ...实际执行的大致逻辑:
# 第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 # 越界就停现代迭代:
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__ |
七、输出重载
字符串即可
class A:
def __str__(self): # print() 调用
return "我是str"
def __repr__(self): # 交互式命令行/repr()调用
return "我是repr"
a = A()
print(a) # 我是str
#常用,面向用户
print(repr(a)) # 我是repr
#开发者调试用
注:不重载lt也可以排序(用key=)
from operator import *
class Point:
def __init__(self,x=0,y=0):
self.x=x
self.y=y
def __str__(self):
return "("+str(self.x)+","+str(self.y)+")"
a=[Point(3,5),Point(2,1),Point(4,6),Point(9,0)]
a.sort(key=attrgetter('x','y')) #先按x,后按y
for x in a:
print(x,end='')
print('')
a.sort(key=lambda p:p.y)
#按y排序
for x in a:
print(x,end='')
注:
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): 派生类定义
class A:
def __init__(self,x,y):
self.x=x
self.y=y
def func(self):
print("A::func",self.x,self.y)
class B(A):
def __init__(self,x,y,z):
A.__init__(self,x,y) #调用基类的构造函数
self.z=z
def func(self):
A.func(self)
print("B::func",self.x,self.y,self.z)
a=A(1,2)
b=B(1,2,3)
a.func()
b.func()
调用父类函数的方式
- 类名
- super(type,obj)
- super()
class A:
def __init__(self,x,y):
self.x=x
self.y=y
def func(self):
print("A::func",self.x,self.y)
class B(A):
def __init__(self,x,y,z):
# ✨🌟
#A.__init__(self,x,y) #调用基类的构造函数
#super(B,self).__init__(x,y)
super().__init__(x,y)
self.z=z
def func(self):
A.func(self)
print("B::func",self.x,self.y,self.z)
a=A(1,2)
b=B(1,2,3)
a.func()
b.func()
super(B,b).func()
注:类外部使用super(type,obj)
注:
isinstance的用法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__才又可哈希
问题如下:
class 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 ,而非值
class P:
def __init__(self,x,y):
self.x=x
self.y=y
print(P(1,2) is P(1,2)) # False
a=P(1,2)
b=P(1,2)
d={a:'1', b:'ok',P(1,1):1.2}
print(d[a]) # 1
print(d[b]) # ok
print(d[P(1,1)]) # keyError 因为这是重新创建的地址, 找不到你存的那个地址了
对象的值作为key
重写__eq__和__hash__
class A:
def __init__(self,x):
self.x=x
def __eq__(self,other):
return self.x==other.x
def __hash__(self):
return 0
a=A(3)
b=A(3)
d={A(5):10, A(3):20, a:30, b:40}
print(d[a]) # 40
print(d[A(5)]) # 10