Python学习笔记

本文最后更新于 2024年7月31日 上午

1. Python解释器

Python语言从规范到解释器都是开源,确实存在多种Python解释器。

CPython、IPython、PyPy、Jython、IronPython

2. Print()遇到逗号“,”会输出一个空格

3. Python提供ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符

1
2
3
4
5
6
7
8
>>> ord('A')
65
>>> ord('中')
20013
>>> chr(66)
'B'
>>> chr(25991)
'文'

4. 另一种格式化字符串的方法是使用字符串的format()方法

1
2
>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'

5. list.pop() 删除末尾一个 list.pop(i)删除指定位置

6. dict.pop(key)删除一个key,对应的value也会从dict中删除

7. set

可以看成数学意义上的无序和无重复元素的集合, 通过remove(key)方法可以删除元素 !不可以放入可变对象

8. 定义默认参数要牢记一点

默认参数必须指向不变对象

9. 和关键字参数*kw不同,命名关键字参数需要一个特殊分隔符,*后面的参数被视为命名关键字参数。

1
2
3
4
def person(name, age, *, city, job):
print(name, age, city, job)
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer

10. 可变参数和关键字参数

可变参数既可以直接传入:

1
func(1, 2, 3)

又可以先组装list或tuple,再通过*args传入:

1
func(*(1, 2, 3))

关键字参数既可以直接传入:

1
func(a=1, b=2)

又可以先组装dict,再通过**kw传入:

1
func(**{'a': 1, 'b': 2})

11. 创建一个generator

  • 第一种方法很简单,只要把一个列表生成式的[]改成()

1
2
3
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>
  • 如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'

>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>
def triangles():
L = [1]
while True:
yield L
L = [1] + [L[i] +L[i+1] for i in range(len(L) - 1)] + [1]

12. Iterable和Iterator不同

list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象

13. map()、reduce()、filter、sorted函数

  • map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回
1
2
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
  • reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
1
2
3
4
5
6
7
8
9
10
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def char2num(s):
return DIGITS[s]

def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
  • filter()用于过滤序列,接收一个函数和一个序列。把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
1
2
3
4
def is_odd(n):
return n % 2 == 1

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
  • sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:
1
2
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

14. 闭包

当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。

15. lambda

1
>>> f = lambda x: x * x

16. 装饰器 Decorator

functools.wraps 的作用是将原函数对象的指定属性复制给包装函数对象, 默认有 module、name、doc

1
2
3
4
5
6
7
8
import functools

def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper

同时支持@log和@log(‘something’)的装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def log(param):
if not callable(param):
text = param
def decorator(func):
@functools.wraps(func)
def warps(*args, **kwargs):
print('{} {}'.format(func.__name__, text))
return func(*args, **kwargs)
return warps
return decorator
else:
func = param
@functools.wraps(func)
def warps(*args, **kwargs):
print('{}'.format(func.__name__))
return func(*args, **kwargs)
return warps

17. Class私有变量

不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name

18. 继承-》多态

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

19. 实例属性和类属性

千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性

20. 使用__slots__

但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。

为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

1
2
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

21. 使用@property 和@score.setter

Python内置的@property装饰器就是负责把一个方法变成属性调用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student(object):

@property
def score(self):
return self._score

@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value

把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:

1
2
3
4
5
6
7
8
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!

22. 多重继承和MixIn

在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich继承自Bird。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承Runnable。这种设计通常称之为MixIn。

1
2
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
pass

23. 特殊用途的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__len__()方法我们也知道是为了能让class作用于len()函数。
__str__()定制化打印object
__repr__()与__str__()区别在于:
__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。

class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
要表现得像list那样按照下标取出元素,需要实现__getitem__()方法
如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
写一个__getattr__()方法,动态返回一个属性

24. 错误处理

try…except…finally…
就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# err_reraise.py

def foo(s):
n = int(s)
if n==0:
raise ValueError('invalid value: %s' % s)
return 10 / n

def bar():
try:
foo('0')
except ValueError as e:
print('ValueError!')
raise

bar()

在bar()函数中,我们明明已经捕获了错误,但是,打印一个ValueError!后,又把错误通过raise语句抛出去了,这不有病么?

其实这种错误处理方式不但没病,而且相当常见。捕获错误目的只是记录一下,便于后续追踪。但是,由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。好比一个员工处理不了一个问题时,就把问题抛给他的老板,如果他的老板也处理不了,就一直往上抛,最终会抛给CEO去处理。

25. 文件读写

1
2
with open('/path/to/file', 'r') as f:
print(f.read())

代码更佳简洁,并且不必调用f.close()方法。

26. 序列化

Python提供了pickle模块来实现序列化。

1
2
3
4
>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d) # dump对文件
b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'

python的dumps也可以进行序列化,对于非dict可指定转换函数进行序列化。

1
2
>>> print(json.dumps(s, default=student2dict))
{"age": 20, "name": "Bob", "score": 88}

27. 线程是最小的执行单元,而进程由至少一个线程组成

28. 进程线程

multiprocessing

模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from multiprocessing import Process
import os

# 子进程要执行的代码
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))

if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',))
print('Child process will start.')
p.start()
p.join()
print('Child process end.')
执行结果如下:

Parent process 928.
Process will start.
Run child process test (929)...
Process end.

如果要启动大量的子进程,可以用进程池的方式批量创建子进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from multiprocessing import Pool
import os, time, random

def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')

进程间通信

Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据。

多线程

线程锁 Lock()进行线程同步
当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。

Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。

ThreadLocal

一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。

29. 多进程VS多线程

多进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。
多进程模式的缺点是创建进程的代价大,如果有几千个进程同时运行,操作系统连调度都会成问题。
多线程模式通常比多进程快一点。

要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。

30. 协程

31. 正则表达式


Python学习笔记
http://spiderx.cc/2024/07/29/Python学习笔记/
作者
Li Pengbo
发布于
2024年7月29日
许可协议