Python修养3

Python修养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
2
3
def 函数名(参数1,参数2,...):
语句组(即"函数体")
# 也可无参数

参数传递

  • 默认参数

  • 实参可带名字

    1
    2
    3
    4
    5
    6
    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传入元组

    1
    2
    3
    4
    def 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
    16
    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传入字典

    1
    2
    3
    4
    5
    6
    7
    8
    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

1
2
3
def hello():
print('hello from t')
haha='ok'

a.py

1
2
3
4
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__ 析构函数

范式

  • 构造函数和析构函数都只能有一个
  • 对象的成员变量可以随时添加
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
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指的是, “地址”

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-stringPython 3.6+f"{name} is {age}"

静态方法和类方法

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
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)@classmethod
    def func(cls)
    @staticmethod
    def 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
    ...
    @classmethod
    def from_string(cls,string):
    value=int(string)
    return cls(value)
    ...
    obj=MyClass.from_string("42")
    print(obj.instance_attr)

访问范围

私有属性

__开头的变量 (两个_)

注意: _p表示开发者约定为私有变量,仍然可以直接使用; __p python会将其改名_类名__p强化私有变量的意味, 我们仍然可以使用改名后的变量

python奉行"成年人"的观念

  • 名称改写机制防止子类意外覆盖父类的属性
  • 私有属性的命名是一种提示, 表示不应该
  • 通过限制对私有属性的直接访问, 类的设计者可以更自由地修改内部实现, 而不影响外部代码

property函数

概念

接收至多四个参数

  • fget 获取属性值的方法
  • fset 设置属性值的方法
  • fdel 删除属性值的方法
  • doc 属性的文档字符串

property是python的一个内部函数, 用于将一个方法转化为属性

(方法伪装成属性, 调用时无需括号, 看起来像是数据一样,实际内部调用了函数)

解决的是私有后, 总是写获取/修改/设置/删除接口的麻烦

实现 属性封装 和 计算属性

使用方法

way1
1
2
3
4
5
6
7
8
9
10
11
12
13
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来定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

用途

控制访问方式
1
2
3
4
5
6
7
8
9
10
11
12
13
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
动态计算属性
1
2
3
4
5
6
7
8
9
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__上下文管理器

老式迭代:

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] 越界抛出 IndexErrorfor 内部捕获后结束循环。

__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
2
3
4
5
6
7
8
9
10
11
12
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=)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.x
attrgetter('x', 'y')(obj.x, obj.y)多属性lambda obj: (obj.x, obj.y)

泛型

变量类型本就可变, 任何函数都相当于模板

继承和多态

基本内容

本来对象的类型就是运行时确定, 没有明显的多态

多态过于自然, 以至于感受不到

class B(A): 派生类定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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的用法

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
    19
    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 ,而非值

1
2
3
4
5
6
7
8
9
10
11
12
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__

1
2
3
4
5
6
7
8
9
10
11
12
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