Python高级编程
星号的用法
*
和**
的用法函数定义
def foo(*args, **kwargs): pass
参数传递
def foo(x, y, z, a, b): print(x) print(y) print(z) print(a) print(b) lst = [1, 2, 3] dic = {'a': 22, 'b': 77} foo(*lst, **dic)
import 语法,默认是调用的all里面的内容,如果all里面没有也是调用不了的
from xyz import * __all__ = ('a', 'e', '_d') a = 123 b = 456 c = 'asdfghjkl' _d = [1,2,3,4,5,6] e = (9,8,7,6,5,4)
强制命名参数,*后面的那个形参必须使用关键字命名,不能使用位置命名
def foo(a, *, b, c=123): pass
1 | a = 123 |
- 解包语法:
a, b, *ignored, c = [1, 2, 3, 4, 5, 6, 7]
1 | L = [1,2,3,4,5,6,7,8,9,0] |
赋值和引用
Python 的赋值和引用
==, is
:==
判断的是值,is
判断的是内存地址 (即对象的id)- 小整数对象: [-5, 256],缓存
copy, deepcopy
的区别copy
: 只拷贝表层元素deepcopy
: 在内存中重新创建所有子元素
函数的形式参数为可变的时候,调用的时候不会重新被置空,而是在原来的基础上操作
def extendList(val, lst=[]): lst.append(val) return lst list1 = extendList(10) list2 = extendList(123, []) list3 = extendList('a')
1 | 结果: |
- 浅复制和深复制
from copy import copy, deepcopy
from pickle import dumps, loads
a = ['x', 'y', 'z']
b = [a] * 3
c = copy(b)
d = deepcopy(b)
# 第二个参数指的是调用了第几种算法,性能有区别,底层是定义好的
e = loads(dumps(b, 4))
b[1].append(999)
c[1].append(999)
d[1].append(999)
e[1].append(999)
1 | 结果 |
- 自定义 deepcopy:
my_deepcopy = lambda item: loads(dumps(item, 4))
,使用自定义的这个深复制比Python自带的这个deepcopy效率要高,虽然说,Python的底层也是这个原理实现的,一般对性能要求高的时候,可以采用自定义的这个。 - Python的可变类型:列表,字典(这里的可变不可变指的是内存中)
- python的不可变类型:数字,字符串,元组
迭代器,生成器
迭代器, 生成器
class Range: def __init__(self, start, end, step): self.start = start - step self.end = end self.step = step def __iter__(self): return self def __next__(self): current = self.start + self.step if current < self.end: self.start = current return current else: raise StopIteration()
iterator: 任何实现了
__iter__
和__next__
(python2中是next()
) 方法的对象都是迭代器.__iter__
返回迭代器自身__next__
返回容器中的下一个值- 如果容器中没有更多元素, 则抛出StopIteration异常
generator: 生成器其实是一种特殊的迭代器, 不需要自定义
__iter__
和__next__
- 生成器函数 (yield)
- 生成器表达式
练习1: 定义一个随机数迭代器, 随机范围为 [1, 50], 最大迭代次数 30
import random class RandomIter: def __init__(self, start, end, times): self.start = start self.end = end self.max_times = times self.count = 0 def __iter__(self): return self def __next__(self): self.count += 1 if self.count <= self.max_times: return random.randint(self.start, self.end) else: raise StopIteration()
练习2: 自定义一个迭代器, 实现斐波那契数列
class Fib: def __init__(self, max_value): self.prev = 0 self.curr = 1 self.max_value = max_value def __iter__(self): return self def __next__(self): if self.curr < self.max_value: res = self.curr self.prev, self.curr = self.curr, self.prev + self.curr return res else: raise StopIteration()
练习3: 自定义一个生成器函数, 实现斐波那契数列
def fib(max_value): prev = 0 curr = 1 while curr < max_value: yield curr prev, curr = curr, curr + prev
迭代器、生成器有什么好处?
- 节省内存
- 惰性求值
itertools
- 无限迭代
count(start=0, step=1)
等同于(start + step * i for i in count())
cycle(iterable)
repeat(object [,times])
创建一个迭代器,重复生成object,times(如果已提供)指定重复计数,如果未提供times,将无止尽返回该对象。
- 有限迭代
chain(*iterables)
将多个迭代器作为参数, 但只返回单个迭代器, 它产生所有参数迭代器的内容, 就好像他们是来自于一个单一的序列# chain('ABC', 'DEF') --> A B C D E F
- 排列组合
product(*iterables, repeat=1)
笛卡尔积permutations(iterable[, r-length])
全排列combinations(iterable, r-length)
组合
- 无限迭代
各种推导式
- 列表:
[i for i in range(5)]
- 字典:
{i: i + 3 for i in range(5)}
- 集合:
{i for i in range(5)}
- 列表:
装饰器
装饰器
最简装饰器,原理中使用了闭包
def deco(func): def wrap(*args, **kwargs): return func(*args, **kwargs) return wrap @deco def foo(a, b): return a ** b
原理
- 手动执行装饰的过程
- 对比被装饰前后的
foo.__name__
和foo.__doc__
1 | def check(func): |
结果:1
2<function bar at 0x0000023AE62D3158> 2451993080152
<function check.<locals>.wrap at 0x0000023AE62D31E0> 2451993080288
注意:locals指的是局部变量
查看装饰过程中的变化:
1 | def foo(): |
结果:1
2foo
i am foo
加上装饰器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def deco(func):
'''i am deco'''
# @wraps(func)
def wrap(*args, **kwargs):
'''i am wrap'''
return func(*args, **kwargs)
return wrap
@deco
def foo():
'''i am foo'''
pass
print(foo.__name__)
print(foo.__doc__)
结果:1
2wrap
i am wrap
装饰器内使用装饰器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17from functools import wraps
def deco(func):
'''i am deco'''
@wraps(func)
def wrap(*args, **kwargs):
'''i am wrap'''
return func(*args, **kwargs)
return wrap
# 语法糖
@deco
def foo():
'''i am foo'''
pass
print(foo.__name__)
print(foo.__doc__)
结果:1
2foo
i am foo
简单过程
fn = deco(func) foo = fn foo(*args, **kwargs)
多个装饰器调用过程
1 | print('**************定义多个装饰器**********************') |
执行结果:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18**************定义多个装饰器**********************
enter deco3
exit deco3
enter deco2
exit deco2
enter deco1
exit deco1
**************使用多个装饰器**********************
enter wrap1
Func is: wrap2
enter wrap2
Func is: wrap3
enter wrap3
Func is: foo
exit wrap3
exit wrap2
exit wrap1
9
- 装饰器定义的时候,就近原则独立完成装饰器的定义,
装饰器使用的时候,从距离函数远的开始,一层一层的进入,然后再一层一层的出去
装饰器的过程拆解
deco1( deco2( deco3(foo) ) )(3, 4)
1 | def bar(x, y): |
带参数的装饰器
def deco(n): def wrap1(func): def wrap2(*args, **kwargs): return func(*args, **kwargs) return wrap2 return wrap1 #调用过程 wrap1 = deco(n) wrap2 = wrap1(foo) foo = wrap2 foo() #单行形式 check_result(30)(bar)(4,8)
1 | def check_result(n): |
结果:1
230
33
装饰器类和
__call__
,能够执行的都是因为有__call__
,函数被调用的过程,其实就是__call__
被调用的过程,callable()可调用的class Deco: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): return self.func(*args, **kwargs) @Deco def foo(x, y): return x ** y # 过程拆解 fn = Deco(foo) foo = fn foo(12, 34)
1 | class CheckResult: |
使用场景
* 参数、结果检查 * 缓存、计数 * 日志、统计 * 权限管理 * 重试 * 其他
写一个 timer 装饰器, 计算出被装饰函数调用一次花多长时间, 并把时间打印出来
import time from functools import wraps def timer(func): @wraps(func) # 修正 docstring def wrap(*args, **kwargs): time0 = time.time() result = func(*args, **kwargs) time1 = time.time() print(time1 - time0) return result return wrap
写一个 Retry 装饰器,重试
import time class retry(object): def __init__(self, max_retries=3, wait=0, exceptions=(Exception,)): self.max_retries = max_retries self.exceptions = exceptions self.wait = wait def __call__(self, f): def wrapper(*args, **kwargs): for i in range(self.max_retries + 1): try: result = f(*args, **kwargs) except self.exceptions: time.sleep(self.wait) continue else: return result return wrapper
闭包
引用了自由变量的函数即是一个闭包,这个被引用的自由变量和这个函数一同存在,即使已经离开了创造他的环境也不例外,装饰器本身就是一个闭包,但是闭包不一定就是装饰器
说出下面函数返回值
def foo(): l = [] def bar(i): l.append(i) return l return bar f1 = foo() f2 = foo() # 说出下列语句执行结果 f1(1) f1(2) f2(3)
作用域
- global 声明全局变量
- nonlocal 声明非本层的局部变量
- globals() 查看全局变量
- locals() 查看局部变量
vars() 查看变量,不传参数相当于locals(),传入对象后,会得到
object.__dict__
local namespace | V global namespace | V builtin namespace 内置的命名空间
更深入一点:
__closure__
,装饰器中原本的那个函数改变了,那么要想找到原来的那个函数,就需要和这个属性,- Python中dis.dis(bar)可以查看Python解释器中的语句
方法
method
,classmethod
和staticmethod
method
: 通过实例调用时, 可以引用类内部的任何属性和方法classmethod
: 无需实例化, 可以调用类属性和类方法, 无法取到普通的成员属性和方法staticmethod
: 无论用类调用还是用实例调用, 都无法取到类内部的属性和方法, 完全独立的一个方法
1 | class Test(object): |
继承
继承、多继承、多态、Mixin、super
- 继承
多态,一个对象有多种形态
class Animal: pass class Cat(Animal): pass class Tom(Cat): pass tom = Tom() isinstance(tom, Cat) isinstance(tom, Animal)
多继承
Cls.mro()
菱形继承问题
# 继承关系示意 # # A.foo() # / \ # B C.foo() # \ / # D
1 | class Animal: |
结果:1
2aaaa
[<class '__main__.New'>, <class '__main__.Cat'>, <class '__main__.Dog'>, <class '__main__.Animal'>, <class 'object'>]
- Mixin,混入式继承,专门用来为其他类提供功能,只写他需要的功能,通过单纯的Mixin类完成功能的组合
- Super,super是根据继承的顺序执行了,
Cls.mro()
,这个继承的顺序是根据C3算法得到的,通过距离优先算法去查找
手动执行构造函数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
28class A:
def __init__(self):
print('enter A')
self.x = 111
print('exit A')
class B(A):
def __init__(self):
print('enter B')
A.__init__(self)
# super().__init__()
print('exit B')
class C(A):
def __init__(self):
print('enter C')
A.__init__(self)
# super().__init__()
print('exit C')
class D(B, C):
def __init__(self):
print('enter D')
B.__init__(self)
C.__init__(self)
# super().__init__()
print('exit D')
d = D()
执行结果:执行了2次A,如果是连接数据库的操作,或者文件操作的时候,就会出现问题1
2
3
4
5
6
7
8
9
10enter D
enter B
enter A
exit A
exit B
enter C
enter A
exit A
exit C
exit D
使用super改进: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
33class A:
def __init__(self):
print('enter A')
self.x = 111
print('exit A')
class B(A):
def __init__(self):
print('enter B')
# A.__init__(self)
super().__init__()
print('exit B')
class C(A):
def __init__(self):
print('enter C')
# A.__init__(self)
super().__init__()
print('exit C')
class D(B, C):
def __init__(self):
print('enter D')
# B.__init__(self)
# C.__init__(self)
super().__init__()
# super(D,self).__init__()
print('exit D')
d = D()
执行结果:1
2
3
4
5
6
7
8enter D
enter B
enter C
enter A
exit A
exit C
exit B
exit D
魔术方法
__str__
控制对象格式化的输出,对应str(),__repr__
有个repr(),一般配合着eval()还原字符串中的内容- repr和str这两个方法都是用于显示的,str是面向用户的,而repr面向程序员。
打印操作会首先尝试str和str内置函数(print运行的内部等价形式),它通常应该返回一个友好的显示。
repr用于所有其他的环境中:用于交互模式下提示回应以及repr函数,如果没有使用str,会使用print和str。它通常应该返回一个编码字符串,可以用来重新创建对象,或者给开发者详细的显示。
当我们想所有环境下都统一显示的话,可以重构repr方法;当我们想在不同环境下支持不同的显示,例如终端用户显示使用str,而程序员在开发期间则使用底层的repr来显示,实际上str只是覆盖了repr以得到更友好的用户显示。
__init__
和__new__
,__new__()
__new__
返回一个对象的实例, 实例不带有任何的属性,可以动态添加,__init__
初始化,无返回值__new__
是一个类方法__init__
初始化的数据都保存在__dict__
里面- 注意在和None进行判断的时候使用is效率更高
- 单例模式
class A(object):
'''单例模式'''
obj = None
def __new__(cls, *args, **kwargs):
if cls.obj is None:
cls.obj = object.__new__(cls)
return cls.obj
比较运算、数学运算
- update()方法,在字典中使用,更新
运算符重载
+
:__add__(value)
列表,元组(不同长度的不可以做加法),字符串,int,float类型都可以做加法,集合可以做& | -
运算-
:__sub__(value)
*
:__mul__(value)
列表,字符串,元组可以相乘/
:__truediv__(value)
(Python 3.x),__div__(value)
(Python 2.x)//
:__floordiv__(value)
向下取整,math库中的ceil()向上取整%
:__mod__(value)
&
:__and__(value)
|
:__or__(value)
实现字典的
__add__
方法, 作用相当于 d.update(other)class Dict(dict): def __add__(self, other): if isinstance(other, dict): new_dict = {} new_dict.update(self) new_dict.update(other) return new_dict else: raise TypeError('not a dict')
- 比较运算符的重载
==
:__eq__(value)
!=
:__ne__(value)
>
:__gt__(value)
>=
:__ge__(value)
<
:__lt__(value)
<=
:__le__(value)
1 | 实现数学上无穷大的概念 |
容器方法,列表,元组,字符串,字典,集合都是容器类型,注意所有的不可变的都可以作为字典的key,可变的不可以作为key,因为得不到一个具体的哈希值
__len__
-> len__iter__
-> for__contains__
-> in__getitem__
对string, list, tuple, dict
有效,通过【】方括号取值的时候会默认执行,__setitem__
对list, dict
有效__missing__
对dict
有效, 字典的预留接口, dict 自身并没有实现class Dict(dict): def __missing__(self, key): self[key] = None # 当检查到 Key 缺失时, 可以做任何默认行为
- 可执行对象:
__call__
- with是一个关键字
__enter__
进入with
代码块前的准备操作__exit__
退出时的善后操作- 打开文件时,有colsed属性,可以查看文件是否关闭了
1 | class A: |
执行结果:1
2
3111
222 None
33333 None None None
如果执行错误的,结果就是这样的1
2111
33333 <class 'TypeError'> 'NoneType' object is not subscriptable <traceback object at 0x000001BAFB6C7888>
注意:有错误的时候,exit后面的三个参数就会分别打印出错误类型,详细的错误信息,traceback对象,回溯,跟踪错误
__setattr__, __getattribute__, __getattr__, __dict__
对应于setattr(),getattr(),hasattr(),dict(),
方法__getattribute__
对应着所有的点操作- 类的方法属性保存在类的
__dict__
__getattr__
用来处理取不到属性方法的常用来做属性监听,监听属性的变化,钩子
class A: '''TestClass''' z = [7,8,9] def __init__(self): self.x = 123 self.y = 'abc' def __setattr__(self, name, value): print('set %s to %s' % (name, value)) object.__setattr__(self, name, value) def __getattribute__(self, name): print('get %s' % name) return object.__getattribute__(self, name) def __getattr__(self, name): print('not has %s' % name) return -1 def foo(self, x, y): return x ** y # 对比 a = A() print(A.__dict__) print(a.__dict__)
槽:
__slots__
- 固定类所具有的属性
- 实例不会分配
__dict__
- 实例无法动态添加属性
优化内存分配
class A: __slots__ = ('x', 'y')
垃圾收集
- Garbage Collection (GC)
- 引用计数
- 优点: 简单、实时性高
- 缺点: 消耗资源、循环引用无法处理
- 标记-清除, 分代收集
- 引用计数
lst1 = [3, 4] # lst1->ref_count 1
lst2 = [8, 9] # lst2->ref_count 1
# lst1 -> [3, 4, lst2]
lst1.append(lst2) # lst2->ref_count 2
# lst2 -> [8, 9, lst1]
lst2.append(lst1) # lst1->ref_count 2
del lst1 # lst1->ref_count 1
del lst2 # lst2->ref_count 1
产生垃圾
性能
动态语言性能上和静态语言无法抗衡,
静态语言:C, C++, GO C#,Java
动态语言:Python,JavaScript,Ruby,PHP,Lua 【脚本语言】
Python 性能之困
- 计算密集型
- CPU 长时间满负荷运行, 如图像处理、大数据运算、圆周率计算等
- 计算密集型: 用 C 语言补充
- Profile, timeit
- I/O 密集型: 网络 IO, 文件 IO, 设备 IO 等
- 多任务处理: 进程 / 线程 / 协程
- 阻塞 -> 非阻塞
- 同步 -> 异步
- GIL 全局解释器锁
- 它确保任何时候都只有一个Python线程执行。
- 它确保任何时候都只有一个Python线程执行。
- 什么是进程、线程、协程?
- 进程: 资1. 源消耗大, 系统整体开销大, 数据通信不方便
- 线程: 资源消耗小, 可共享数据。上下文开销大。按时间片强制切换, 不够灵活
- 协程: 内存开销更小, 上下文切换开销更小。可根据事件切换, 更加有效的利用 CPU,stackless/greenlets/Gevent利用的第三方完成的协成,tornado、asyncio使用Python原生的代码实现的协成,Twisted使用的是回调机制
- 进程、线程、协程调度的过程叫做上下文切换
- 什么是同步、异步、阻塞、非阻塞?
- 同步, 异步: 客户端调用服务器接口时
- 阻塞, 非阻塞: 服务端发生等待
- 同步,是有顺序的,一个接一个的进行
- 处理并发就是构建异步非阻塞程序
- 事件驱动 + 多路复用
- 轮询: select, poll
- 事件驱动: epoll 有效轮询
greenlet / gevent | tornado / asyncio
import asyncio async def foo(n): for i in range(10): print('wait %s s' % n) await asyncio.sleep(n) return i task1 = foo(1) task2 = foo(1.5) tasks = [asyncio.ensure_future(task1), asyncio.ensure_future(task2)] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks))
线程安全, 锁
- 获得锁之后, 一定要释放, 避免死锁
- 获得锁之后, 执行的语句, 只跟被锁资源有关
- 区分普通锁 Lock, 可重入锁 RLock
- 线程之间的数据交互尽量使用 Queue
- gevent
- monkey.patch
- gevent.sleep 非阻塞式等待
- Queue 协程间数据交互, 避免竞争
- 计算密集型
技巧
一些技巧和误区
- 格式化打印
- json.dumps(data, indent=4, sort_keys=True, ensure_ascii=False)
- json 压缩:
json.dumps(obj, separators=[',',':'])
- pprint
- 确保能取到有效值
d.get(k, default)
d.setdefault
defaultdict
a or b
可以省去很多if判断,短路x = a if foo() else b
- try…except… 的滥用
- 不要把所有东西全都包住, 程序错误需要报出来
- 使用
try...except
要指明具体错误,try
结构不是用来隐藏错误的, 而是用来有方向的处理错误的
利用 dict 做模式匹配
def do1(): print('i am do1') def do2(): print('i am do2') def do3(): print('i am do3') def do4(): print('i am do4') mapping = {1: do1, 2: do2, 3: do3, 4: do4} mod = random.randint(1, 10) func = mapping.get(mod, do4) func()
inf, -inf, nan
- venv, pyenv, 命名空间
- venv: 创建虚拟环境, 做环境隔离, venv 目录直接放到项目的目录里
- pyenv: 管理 Python 版本
property: 把一个方法属性化
class C(object): @property def x(self): "I am the 'x' property." return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x
else 子句:
if, for, while, try
- collections 模块
- defaultdict
- OrderedDict
- Counter 计数器
- namedtuple 根据元组里面的内容定义类
- 格式化打印