*args 和 **Kwargs
- *args 是用来发送一个非键值对的可变数量的参数列表给一个函数
- kwargs 允许你将不定长度的键值对, 作为参数传递给一个函数。 如果你想要在一个函 数里处理带名字的参数, 你应该使用kwargs。
1 | def test_args_kwargs(arg1, arg2, arg3): |
1 | # Test *args |
1 | # Test **kwargs |
Debugging模式
python提供的pdb功能可以在命令行模式下或者函数内部设置断点进行调试。
常见命令列表如下:
- c: 继续执行
- w: 显示当前正在执行的代码行的上下文信息
- a: 打印当前函数的参数列表
- s: 执行当前代码行,并停在第一个能停的地方(相当于单步进入)
- n: 继续执行到当前函数的下一行,或者当前行直接返回(单步跳过)
- 命令行模式
1 | $ python -m pdb my_script.py |
- 函数内部执行
1 | import pdb |
生成器:Generator
生成器也是一种迭代器,但只使用一次迭代。这是因为它们并没有把所有的值存在内存中,而是在运行时生成值。你通过遍历来使用它们,要么用一个“for”循环,要么将它们传递给任意可以进行迭代的函数和结构。大多数时候生成器是以函数来实现的。然而, 它们并不返回一个值,而是yield(暂且译作“生出”)一个值。
1 | def generator_function(): |
应用场景:不同一时间将所有计算出来的大量结果集分配到内存当中,特别是结果集里还包含循环。用这种方式,不必担心会大量使用资源。
1 | # 斐波那契数列的迭代器:会大量使用内存资源(在在计算很大的输入参数时) |
1 | # 斐波那契数列的生成器:不会大量使用内存资源 |
内置函数:next()
Python内置函数:next()。允许我们获取一个序列的下一个元素。
1 | def generator_function(): |
内置函数:iter()
一个可迭代对象并不是一个迭代器,如下:
1 | my_string = "Yasoob" |
iter。它将根据一个可迭代对象返回一个迭代器对象。
1 | my_string = "Yasoob" |
装饰器:Decorator
作用:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic。装饰器让你在一个函数的前后去执行代码。
“@decorator_name” is meaning of
f = decorator_name(f)
@wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表 等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。
1 | from functools import wraps |
容器 Collections
defaultdict
与dict类型不同,defaultdict不需要检查key是否存在
1 | import collections |
counter
Counter是一个计数器,它可以帮助我们针对某项数据进行计数。比如它可以用来计算每个人喜欢多少种颜色:
1 | from collections import Counter |
deque
deque提供了一个双端队列,你可以从头/尾两端添加或删除元素。
1 | from collections import deque |
也可以限制这个列表的大小,当超出设定的限制时,数据会从对队列另一端被挤出 去(pop)。
1 | d = deque(maxlen=30) |
还可以从任一端扩展这个队列中的数据
1 | d = deque([1,2,3,4,5]) |
namedtuple
命名元组把元组变成一个针对简单任务的容器。你不必使用整数索引来访问一个namedtuples的数据。你可以像字典(dict)一样访问namedtuples, 但namedtuples是不可变的。
- 命名元组(namedtuple)有两个必需的参数。它们是元组名称和字段名称。
下面例子中,我们的元组名称是Animal,字段名称是’name’,’age’和’type’。
1 | from collections import namedtuple |
- namedtuple的每个实例没有对象字典,所以它们很轻量,与普通的元组比,并不 需要更多的内存。这使得它们比字典更快。
- 命名元组向后兼容于普通的元组,这意味着可以既使用整数索引,也可以使用名称来访问namedtuple:
1 | from collections import namedtuple |
- 可以将一个命名元组转换为字典
1 | from collections import namedtuple |
enum.Enum
1 | from collections import namedtuple |
枚举 enumerate
enumerate()是在python2.3中引入的。它具有一定的惰性,只在需要的时候才会产生一个(index,value)。
enumerate(sequence, start = 0)
接受一个可迭代的对象作为输入,如list或者set,函数返回一个迭代器。可以使用next()方法获取下一个元素。枚举(enumerate)是Python内置函数,它允许我们遍历数据并自动计数。
1 | my_list = ['apple', 'banana', 'grapes', 'pear'] |
等价于
1 | my_list = ['apple', 'banana', 'grapes', 'pear'] |
1 | my_list = ['apple', 'banana', 'grapes', 'pear'] |
enumerate()的函数实现非常简单,实际相当于以下代码
1 | def enumerate(sequence, start=0): |
对象自省
自省(introspection),是指在运行时来判断一个对象的类型的能力。 它是Python的强项之一。
dir
dir返回一个列表,列出了一个对象所拥有的属性和方法。
1 | my_list = [1, 2, 3] |
type和id
- type函数返回一个对象的类型。
1 | print(type('')) |
- id()函数返回任意不同种类对象的唯一ID。
1 | name = "Yasoob" |
inspect模块
inspect模块也提供了许多有用的函数,来获取活跃对象的信息。
1 | import inspect |
lambda表达式
它们在其他语言中也被称为匿名函数。如果你不想在程序中对一个函数使用两次,你也许会想用lambda表达式。
lambda 参数:操作(参数)
1 | add = lambda x, y: x + y |
Map、Filter、Reduce
Map
Map()会将一个函数映射到一个输入列表的所有元素上。
map(function_to_apply, list_of_inputs)
1 | items = [1, 2, 3, 4, 5] |
1 | def multiply(x): |
Filter
filter()过滤列表中的元素,并且返回一个由所有符合要求的元素所构成的列表,符合要求即函数映射到该元素时返回值为True。filter()是一个内置函数,类似于for循环,但是执行会更快。
filter(function_to_apply, list_of_inputs)
1 | number_list = range(-5, 5) |
Reduce
Reduce()用于对一个列表进行一些计算并返回结果。
reduce(function_to_apply, list_of_inputs)
1 | from functools import reduce |
三元运算符
如果条件为真,返回真 否则返回假
condition_is_true if condition else condition_is_false
1 | is_fat = True |
另一个晦涩一点的用法比较少见,它使用了元组:
1 (if_test_is_false, if_test_is_true)[test]
1
2
3 fat = True
fitness = ("skinny", "fat")[fat] print("Ali is ", fitness)
#输出: Ali is fat
对象的可变( mutable)与不可变(immutable)
每当你将一个变量赋值为另一个可变类型的变量时,对这个数据的任意改动会同时反映到这两个变量上去。新变量只不过是 老变量的一个别名而已。这个情况只是针对可变数据类型。
1 | foo = ['hi'] print(foo) |
在Python中当函数被定义时,默认参数只会运算一次,而不是每次被调用时都会重新运算。
1 | def add_to(num, target=[]): target.append(num) return target |
现在每当你在调用这个函数不传入target参数的时候,一个新的列表会被创建。
1 | def add_to(element, target=None): |
__slot__
在Python中,每个类都有实例属性。默认情况下Python用一个字典来保存一个对象的实例属性 。然而,对于有着已知属性的小类来说,它可能是个瓶颈。这个字典浪费了很多内存。 Python不能在对象创建时直接分配一个固定量的内存来保存所有的属性。
不过还是有一个方法来规避这个问题。这个方法需要使用__slots__来告诉Python不要使用字典,而且只给一个固定集合的属性分配空间。
1 | class MyClass(object): |
第二段代码会为你的内存减轻负担。通过这个技巧,可以看到内存占用率几乎 40%~50%的减少。
1 | class MyClass(object): |
Comprehension推导式
推导式(又称解析式)是Python的一种独有特性。推导式是可以从一个数据序列构建另一个新的数据序列的结构体。 共有三种推导:
- 列表(list)推导式
- 字典(dict)推导式
- 集合(set)推导式
列表(list)推导式
列表推导式(又称列表解析式)提供了一种简明扼要的方法来创建列表。它的结构是在一个中括号里包含一个表达式,然后是一个for语句,然后是0个或多个for 或者if语句。
variable = [out_exp for out_exp in input_list if out_exp == 2]
1 | multiples = [i for i in range(30) if i % 3 is 0] |
字典(dict)推导式
字典推导和列表推导的使用方法是类似的。如快速对换一个字典的键和值。
1 | {v: k for k, v in some_dict.items()} |
集合(set)推导式
它跟列表推导式也是类似的。 唯一的区别在于它使用大括号{}。
1 | squared = {x**2 for x in [1, 1, 2]} |
异常
处理异常
- 处理一个异常
1 | try: |
- 处理多个异常:在你不知道你要捕捉什么异常时,非常有用
1 | try: |
Finally
包裹到finally从 句中的代码不管异常是否触发都将会被执行。
1 | try: |
try/else
else从句只会在没有异常的情况下执行,而且它会在finally语句之前执行。
1 | try: |
一行式
简易Web Server
通过网络快速共享文件,进入到共享文件的目录下并在命令行中运行下面的代码:
1 | python -m http.server |
漂亮的打印
Python REPL可漂亮的打印出列表和字典。
1 | from pprint import pprint |
脚本性能分析
cProfile是一个比profile更快的实现,因为它是用c写的。
1 | python -m cProfile my_script.py |
CSV转换为json
1 | python -c "import csv,json;print json.dumps(list(csv.reader(open('csv.csv')))) |
列表辗平
通过使用itertools包中的itertools.chain.from_iterable轻松快速的辗平一个列表。
1 | a_list = [[1, 2], [3, 4], [5, 6]] |
一行式的构造器
避免类初始化时大量重复的赋值语句
1 | class A(object): |
For-Else
for循环还有一个else从句,我们大多数人并不熟悉。这个else从句会在循环正常结束时执行。这意味着,循环没有遇到任何break。
有个常见的构造是跑一个循环,并查找一个元素。如果这个元素被找到了,我们使 用break来中断这个循环。有两个场景会让循环停下来。
- 第一个是当一个元素被找到,break被触发。
- 第二个场景是循环结束。
现在我们也许想知道其中哪一个,才是导致循环完成的原因。一个方法是先设置一个标记,然后在循环结束时打上标记。另一个是使用else从句。
简而言之:当循环自然终结(未调用break),else从句会被执行一次。
以下例子会找出2到10之间的数字的因子。附加的else语句块,用来抓住质数:
1 | for n in range(2, 10): |
使用C扩展
CPython使得Python可以轻松调用C代码。
ctypes
ctypes模块提供了和C语言 兼容的数据类型和函数来加载dll文件,因此在调用时不需对源文件做任何的修改。
- 实现两数求和的C代码,保存为add.c
1 |
|
- 接下来将C文件编译为.so文件(windows下为DLL)
1 | For Linux |
- 现在在Python代码中来调用它
1 | from ctypes import * |
这种方法虽然简单,清晰,但是却很受限。例如,并不能在C中对对象进行操作。
SWIG
SWIG是Simplified Wrapper and Interface Generator的缩写。是Python中调用C代码的另一种 方法。在这个方法中,开发人员必须编写一个额外的接又文件来作为SWIG(终端工具)的入口。Python开发者一般不会采用这种方法,因为大多数情况它会带来不必要的复杂。而当你有一个C/C++代码库需要被多种语言调用时,这将是个非常不错的选择。
1 | ```C |
编译它
1 | unix % swig -python example.i |
Python的调用
1 | import example |
Python/C API
Python/C API可能是被最广泛使用的方法。它不仅简单,而且可以在C代码中操作你的Python对象。这种方法需要以特定的方式来编写C代码以供Python去调用它。所有的Python对象都被表 示为一种叫做PyObject的结构体,并且Python.h头文件中提供了各种操作它的函数。
- 编写一个C扩展,添加所有元素到一个Python列表(所有元素都是数字)
1 | //Python.h has all the required function definitions to manipulate the Pyt |
Python.h头文件中包含了所有需要的类型(Python对象类型的表示)和函数定义(对 Python对象的操作)
接下来我们编写将要在Python调用的函数, 函数传统的命名方式由{模块名}_{函数 名}组成,所以我们将其命名为addList_add 然后填写想在模块内实现函数的相关信息表,每行一个函数,以空行作为结束 最后的模块初始化块签名为PyMODINIT_FUNC init{模块名}。
- 现在我们已经编写完C模块了。将下列代码保存为setup.py
1 | ``` |
运行,现在应该已经将我们的C文件编译安装到我们的Python模块中了。
1 | python setup.py install |
- 验证
1 | #module that talks to the C code |
CPython
Open()
1 | with open('photo.jpg', 'r+') as f: |
open的第一个参数是文件名。第二个(mode 打开模式)决定了这个文件如何被打开。
- 如果你想读取文件,传入r
- 如果你想读取并写入文件,传入r+
- 如果你想覆盖写入文件,传入w
- 如果你想在文件末尾附加内容,传入a
如果不传入任意编码,一个系统 - 以及Python -指定的默认选项将被选中。你也许会·去依赖这个默认选项,但这个默认选项经常是错误的,或者默认编码实际上不能表达文件里的所有字符(这将经常发生在Python 2.x和/或Windows)。
1 | import io |
Python 2+3
Future模块导入
Future模块可以帮你在Python2中导入 Python3的功能。
1 | from __future__ import print_function |
模块重命名
1 | try: |
协程
Python中的协程和生成器很相似但又稍有不同。主要区别在于:
生成器是数据的生产者
协程则是数据的消费者
1 | def grep(pattern): |
我们可以通过send()方法向它传值。
1 | search = grep('coroutine') |
发送的值会被yield接收。
next()方法启动一个协程。就像协程中包含的生成器并不是立刻执行,而是通过next()方法来响应send()方法。因此,你必须通过next()方法来执行yield表达式。
可以通过调用close()方法来关闭一个协程。
1 | search = grep('coroutine') |
函数缓存
在Python 3.2以后版本,lru_cache的装饰器,允许我们将一个函数的返回值快速地缓存或取消缓存。
实现一个斐波那契计算器,并使用lru_cache。
- maxsize参数是告诉lru_cache,最多缓存最近多少个返回值。
1 | from functools import lru_cache |
- 也可以轻松地对返回值清空缓存
1 | fib.cache_clear() |
上下文管理器
上下文管理器允许你在有需要的时候,精确地分配和释放资源。with确保我们的文件会被关闭,而不用关注嵌套代码如何退出。
1 | with open('some_file', 'w') as opened_file: |
等价于:
1 | file = open('some_file', 'w') |
自行实现上下文管理器 — 基于类的实现
1 | class File(object): |
通过定义enter和exit方法,我们可以在with语句里使用它。
1 | with File('demo.txt', 'w') as opened_file: |
exit函数接受三个参数。这些参数对于每个上下文管理器类中的exit方法都是必须的。
- with语句先暂存了File类的exit方法
- 然后它调用File类的enter方法
- enter__方法打开文件并返回给with语句
- 打开的文件句柄被传递给opened_file参数
我们使用.write()来写文件
with语句调用之前暂存的exit方法
exit方法关闭了文件
在4与6之前,如果发生了异常,Python会将异常的type,value和traceback传递给exit方法。它让exit方法来决定如何关闭文件以及是否需要其他步骤。
如果exit返回的是True,那么这个异常就被优雅地处理了。
- 如果exit返回的是True以外的任何东西,那么这个异常将被with语句抛出。
自行实现上下文管理器 — 基于生成器的实现
还可以使用一个生成器函数来实现一 个上下文管理器,而不是使用一个类。
- Python解释器遇到了yield关键字。因为这个缘故它创建了一个生成器而不是一个普通的函数。
- 因为这个装饰器,contextmanager会被调用并传入函数名(open_file)作为 参数。
- contextmanager函数返回一个以GeneratorContextManager对象封装过的 生成器。
- 这个GeneratorContextManager被赋值给open_file函数,我们实际上是在 调用GeneratorContextManager对象。
1 | from contextlib import contextmanager |
等价于:
1 | with open_file('some_file') as f: |
Pythonic的代码
print 字符串格式化
非常Pythonic的代码应该是这样:
1 | print('Hello %(name)s!'%{'name':'Tom'}) |
1 | print('{greet} from {language}.'.format(greet = 'Hello world',language = 'Python')) |
风格检查工具
风格检查工具提出了保持代码一致性细节的要求。包括代码布局、注释、命名规范等方面的要求。这种工具有PEP8、Pychecker、Pylint等。如PEP8的安装与使用如下:
- 安装:使用conda或者pip进行安装
1 | $ conda install -c anaconda pycodestyle |
or
1 | $ pip install -U pycodestyle |
- 简单的检测代码
1 | $ pycodestyle --first test.py |
- 还可以使用—show-source参数让PEP8显示每一个错误和警告对应的代码
1 | $ pycodestyle --show-source --show-pep8 test.py |
注释
- 函数注释:给外部可访问的函数与方法添加文档注释。注释要清楚的描述方法的功能,并对参数、返回值以及可能发生的异常进行说明。
1 | def FuncName(parameter1,parameter2): |
- 推荐在文件头中包含copyright申明、模块描述等,如有必要可以加入作者信息及变更记录。
1 | """ |
常量
Python的内建命名空间是支持一小部分常量的,如True、Flase、None等。只是Python没有提供常量的直接方式而已。
- 常量名所有字母应大写,用下划线连接各个单词,如:MAX_OVERFLOW
- 将常量集中到一个文件
1 | class _const: |
设置常量
1 | import const |
使用常量
1 | import constant import const |
类型检查
内建函数type() 用于返回当前对象的类型,但type()有时并不能准确返回结果。建议使用isinstance()来检测。
isinstance(object, classinfo)
classinfo为直接或者间接的类名,基本类型名称或者由它们组成的元组。
1 | isinstance(2, float) |
浮点计算
1 | print(1.1 + 1.1 + 1.1) |
如果对精度要求比较高,可以用Decimal来进行处理。
1 | from Decimal import * |
官方资源地址
https://docs.python.org/2/library/pdb.html
https://docs.python.org/3/library/pdb.html
参考书籍
- 《Intermediate Python》
- 《编写高质量代码 改善Python程序的91个建议》
- 《Python核心编程 第三版》