0%

Python进阶

*args 和 **Kwargs

  • *args 是用来发送一个非键值对的可变数量的参数列表给一个函数
  • kwargs 允许你将不定长度的键值对, 作为参数传递给一个函数。 如果你想要在一个函 数里处理带名字的参数, 你应该使用kwargs。
1
2
3
4
def test_args_kwargs(arg1, arg2, arg3):
print("arg1:", arg1)
print("arg2:", arg2)
print("arg3:", arg3)
1
2
3
4
5
6
7
# Test *args
args = ("two", 3, 5)
test_args_kwargs(*args)

arg1: two
arg2: 3
arg3: 5
1
2
3
4
5
6
# Test **kwargs
kwargs = {"arg3": 3, "arg2": "two", "arg1": 5}
test_args_kwargs(**kwargs)
arg1: 5
arg2: two
arg3: 3

Debugging模式

python提供的pdb功能可以在命令行模式下或者函数内部设置断点进行调试。

常见命令列表如下:

  • c: 继续执行
  • w: 显示当前正在执行的代码行的上下文信息
  • a: 打印当前函数的参数列表
  • s: 执行当前代码行,并停在第一个能停的地方(相当于单步进入)
  • n: 继续执行到当前函数的下一行,或者当前行直接返回(单步跳过)
  • 命令行模式
1
$ python -m pdb my_script.py
  • 函数内部执行
1
2
3
import pdb
def make_bread(): pdb.set_trace()
return "This is pdb mode"

生成器:Generator

生成器也是一种迭代器,但只使用一次迭代。这是因为它们并没有把所有的值存在内存中,而是在运行时生成值。你通过遍历来使用它们,要么用一个“for”循环,要么将它们传递给任意可以进行迭代的函数和结构。大多数时候生成器是以函数来实现的。然而, 它们并不返回一个值,而是yield(暂且译作“生出”)一个值。

1
2
3
4
5
6
7
8
9
10
11
def generator_function():
for i in range(3):
yield i
for item in generator_function():
print(item)


# Output: 0
#1
#2
#3

应用场景:不同一时间将所有计算出来的大量结果集分配到内存当中,特别是结果集里还包含循环。用这种方式,不必担心会大量使用资源。

1
2
3
4
5
6
7
# 斐波那契数列的迭代器:会大量使用内存资源(在在计算很大的输入参数时)
def fibon(n): a=b=1
result = []
for i in range(n):
result.append(a)
a, b = b, a + b
return result
1
2
3
4
5
6
7
8
9
# 斐波那契数列的生成器:不会大量使用内存资源
def fibon(n):
a=b=1
for i in range(n):
yield a
a, b = b, a + b

for x in fibon(1000000):
print(x)

内置函数:next()

Python内置函数:next()。允许我们获取一个序列的下一个元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def generator_function():
for i in range(3):
yield i
gen = generator_function()
print(next(gen))
# Output: 0
print(next(gen))
# Output: 1
print(next(gen))
# Output: 2
print(next(gen))
# Output: Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# StopIteration

内置函数:iter()

一个可迭代对象并不是一个迭代器,如下:

1
2
3
4
5
my_string = "Yasoob"
next(my_string)
# Output: Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: str object is not an iterator

iter。它将根据一个可迭代对象返回一个迭代器对象。

1
2
3
4
my_string = "Yasoob" 
my_iter = iter(my_string)
next(my_iter)
# Output: 'Y'

装饰器:Decorator

  • 作用:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic。装饰器让你在一个函数的前后去执行代码。

  • “@decorator_name” is meaning of

    f = decorator_name(f)

  • @wraps接受一个函数来进行装饰,并加入了复制函数名称、注释文档、参数列表 等等的功能。这可以让我们在装饰器里面访问在装饰之前的函数的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from functools import wraps

def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated

@decorator_name
def func():
return("Function is running")

can_run = True
print(func())
# Output: Function is running

can_run = False
print(func())
# Output: Function will not run

容器 Collections

defaultdict

与dict类型不同,defaultdict不需要检查key是否存在

1
2
3
4
5
6
7
import collections
tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['colours']['favourite'] = "yellow"

print(json.dumps(some_dict))
## 输出: {"colours": {"favourite": "yellow"}}

counter

Counter是一个计数器,它可以帮助我们针对某项数据进行计数。比如它可以用来计算每个人喜欢多少种颜色:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from collections import Counter
colours = (
('Yasoob', 'Yellow'),
('Ali', 'Blue'),
('Arham', 'Green'),
('Ali', 'Black'),
('Yasoob', 'Red'),
('Ahmed', 'Silver'),
)
favs = Counter(name for name, colour in colours)
print(favs)

## 输出:
## Counter({
## 'Yasoob': 2,
## 'Ali': 2,
## 'Arham': 1,
## 'Ahmed': 1
## })

deque

deque提供了一个双端队列,你可以从头/尾两端添加或删除元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
from collections import deque
d = deque()
d.append('1')
d.append('2')
d.append('3')

print(d[0])
## 输出: '1'
print(d[-1])
## 输出: '3'

d.popleft() ## 输出: 1
d.pop() ## 输出: 3

也可以限制这个列表的大小,当超出设定的限制时,数据会从对队列另一端被挤出 去(pop)。

1
d = deque(maxlen=30)

还可以从任一端扩展这个队列中的数据

1
2
3
4
5
d = deque([1,2,3,4,5]) 
d.extendleft([0])
d.extend([6,7,8])
print(d)
## 输出: deque([0, 1, 2, 3, 4, 5, 6, 7, 8])

namedtuple

命名元组把元组变成一个针对简单任务的容器。你不必使用整数索引来访问一个namedtuples的数据。你可以像字典(dict)一样访问namedtuples, 但namedtuples是不可变的。

  • 命名元组(namedtuple)有两个必需的参数。它们是元组名称和字段名称。

下面例子中,我们的元组名称是Animal,字段名称是’name’,’age’和’type’。

1
2
3
4
5
6
7
8
from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="perry", age=31, type="cat")
print(perry)
## 输出: Animal(name='perry', age=31, type='cat')

print(perry.name)
## 输出: 'perry'
  • namedtuple的每个实例没有对象字典,所以它们很轻量,与普通的元组比,并不 需要更多的内存。这使得它们比字典更快。
  • 命名元组向后兼容于普通的元组,这意味着可以既使用整数索引,也可以使用名称来访问namedtuple:
1
2
3
4
5
from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="perry", age=31, type="cat")
print(perry[0])
## 输出: perry
  • 可以将一个命名元组转换为字典
1
2
3
4
5
from collections import namedtuple
Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="Perry", age=31, type="cat")
print(perry._asdict())
## 输出: OrderedDict([('name', 'Perry'), ('age', 31), ...

enum.Enum

1
2
3
4
5
6
7
8
from collections import namedtuple
from enum import Enum
class Species(Enum):
cat = 1
dog = 2

Animal = namedtuple('Animal', 'name age type')
tom = Animal(name="Tom", age=75, type=Species.cat)

枚举 enumerate

enumerate()是在python2.3中引入的。它具有一定的惰性,只在需要的时候才会产生一个(index,value)。

enumerate(sequence, start = 0)

接受一个可迭代的对象作为输入,如list或者set,函数返回一个迭代器。可以使用next()方法获取下一个元素。枚举(enumerate)是Python内置函数,它允许我们遍历数据并自动计数。

1
2
3
my_list = ['apple', 'banana', 'grapes', 'pear'] 
for index,value in zip(len(my_list),mylist)
print(index,value)

等价于

1
2
3
4
5
6
7
8
my_list = ['apple', 'banana', 'grapes', 'pear'] 
for index, value in enumerate(my_list, 1):
print(index, value)
# 输出:
(1, 'apple')
(2, 'banana')
(3, 'grapes')
(4, 'pear')
1
2
3
4
5
6
my_list = ['apple', 'banana', 'grapes', 'pear'] 
e = enumerate(my_list)
e.next()

# 输出:
(0, 'apple')

enumerate()的函数实现非常简单,实际相当于以下代码

1
2
3
4
5
def enumerate(sequence, start=0):
n = start
for elem in sequence:
yield n,elem
n += 1

对象自省

自省(introspection),是指在运行时来判断一个对象的类型的能力。 它是Python的强项之一。

dir

dir返回一个列表,列出了一个对象所拥有的属性和方法。

1
2
3
my_list = [1, 2, 3]
dir(my_list)
# Output: ['__add__', '__class__', '__contains__', '__delattr__', '__delit # '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattri # '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul # '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne_ # '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '_ # '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', # '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop # 'remove', 'reverse', 'sort']

type和id

  • type函数返回一个对象的类型。
1
2
3
4
print(type(''))
# Output: <type 'str'>
print(type([]))
# Output: <type 'list'>
  • id()函数返回任意不同种类对象的唯一ID。
1
2
3
name = "Yasoob" 
print(id(name))
# Output: 139972439030304

inspect模块

inspect模块也提供了许多有用的函数,来获取活跃对象的信息。

1
2
3
import inspect
print(inspect.getmembers(str))
# Output: [('__add__', <slot wrapper '__add__' of ... ...

lambda表达式

它们在其他语言中也被称为匿名函数。如果你不想在程序中对一个函数使用两次,你也许会想用lambda表达式。

lambda 参数:操作(参数)

1
2
3
add = lambda x, y: x + y 
print(add(3, 5))
# Output: 8

Map、Filter、Reduce

Map

Map()会将一个函数映射到一个输入列表的所有元素上。

map(function_to_apply, list_of_inputs)​

1
2
items = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, items))
1
2
3
4
5
6
7
8
9
10
11
12
13
def multiply(x):
return (x*x)
def add(x):
return (x+x)
funcs = [multiply, add]
for i in range(3):
value = map(lambda x: x(i), funcs)
print(list(value))

# Output:
# [0, 0]
# [1, 2]
# [4, 4]

Filter

filter()过滤列表中的元素,并且返回一个由所有符合要求的元素所构成的列表,符合要求即函数映射到该元素时返回值为True。filter()是一个内置函数,类似于for循环,但是执行会更快。

filter(function_to_apply, list_of_inputs)

1
2
3
4
5
number_list = range(-5, 5)
less_than_zero = filter(lambda x: x < 0, number_list)
print(list(less_than_zero))

# Output: [-5, -4, -3, -2, -1]

Reduce

Reduce()用于对一个列表进行一些计算并返回结果。

reduce(function_to_apply, list_of_inputs)

1
2
3
4
from functools import reduce
product = reduce((lambda x, y: x * y), [1, 2, 3, 4] )

# 24

三元运算符

如果条件为真,返回真 否则返回假

condition_is_true if condition else condition_is_false

1
2
is_fat = True
state = "fat" if is_fat else "not fat"

另一个晦涩一点的用法比较少见,它使用了元组:

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
2
3
4
5
6
foo = ['hi'] print(foo)
# Output: ['hi']

bar = foo
bar += ['bye'] print(foo)
# Output: ['hi', 'bye']

在Python中当函数被定义时,默认参数只会运算一次,而不是每次被调用时都会重新运算。
1
2
3
4
5
6
def add_to(num, target=[]): target.append(num) return target
add_to(1)
# Output: [1]

add_to(2)
# Output: [1, 2]

现在每当你在调用这个函数不传入target参数的时候,一个新的列表会被创建。

1
2
3
4
5
6
7
8
9
10
11
def add_to(element, target=None): 
if target is None:
target = []
target.append(element)
return target

add_to(42)
# Output: [42]

add_to(42)
# Output: [42]

__slot__

在Python中,每个类都有实例属性。默认情况下Python用一个字典来保存一个对象的实例属性 。然而,对于有着已知属性的小类来说,它可能是个瓶颈。这个字典浪费了很多内存。 Python不能在对象创建时直接分配一个固定量的内存来保存所有的属性。

不过还是有一个方法来规避这个问题。这个方法需要使用__slots__来告诉Python不要使用字典,而且只给一个固定集合的属性分配空间

1
2
3
4
5
class MyClass(object):
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
self.set_up()

第二段代码会为你的内存减轻负担。通过这个技巧,可以看到内存占用率几乎 40%~50%的减少。

1
2
3
4
5
6
class MyClass(object):
__slots__ = ['name', 'identifier']
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
self.set_up()

Comprehension推导式

推导式(又称解析式)是Python的一种独有特性。推导式是可以从一个数据序列构建另一个新的数据序列的结构体。 共有三种推导:

  • 列表(list)推导式
  • 字典(dict)推导式
  • 集合(set)推导式

列表(list)推导式

列表推导式(又称列表解析式)提供了一种简明扼要的方法来创建列表。它的结构是在一个中括号里包含一个表达式,然后是一个for语句,然后是0个或多个for 或者if语句。

variable = [out_exp for out_exp in input_list if out_exp == 2]

1
2
3
multiples = [i for i in range(30) if i % 3 is 0] 
print(multiples)
# Output: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]

字典(dict)推导式

字典推导和列表推导的使用方法是类似的。如快速对换一个字典的键和值。

1
{v: k for k, v in some_dict.items()}

集合(set)推导式

它跟列表推导式也是类似的。 唯一的区别在于它使用大括号{}。

1
2
3
squared = {x**2 for x in [1, 1, 2]}
print(squared)
# Output: {1, 4}

异常

处理异常

  • 处理一个异常
1
2
3
4
try:
file = open('test.txt', 'rb')
except EOFError as e:
print("An EOF error occurred.") raise e
  • 处理多个异常:在你不知道你要捕捉什么异常时,非常有用
1
2
3
4
5
try:
file = open('test.txt', 'rb')
except Exception:
# 打印一些异常日志,如果你想要的话
raise

Finally

包裹到finally从 句中的代码不管异常是否触发都将会被执行。

1
2
3
4
5
6
7
8
try:
file = open('test.txt', 'rb')
except IOError as e:
print('An IOError occurred. {}'.format(e.args[-1]))
finally:
print("This would be printed whether or not an exception occurred!"
# Output: An IOError occurred. No such file or directory
# This would be printed whether or not an exception occurred!

try/else

else从句只会在没有异常的情况下执行,而且它会在finally语句之前执行。

1
2
3
4
5
6
7
8
9
10
11
12
try:
print('I am sure no exception is going to occur!')
except Exception:
print('exception')
else:
# 这里的代码只会在try语句里没有触发异常时运行,
# 但是这里的异常将 *不会* 被捕获
print('This would only run if no exception occurs. And an error here '
'would NOT be caught.')
print('This would be printed in every case.')
# Output: I am sure no exception is going to occur! # This would only run if no exception occurs.
# This would be printed in every case.

一行式

简易Web Server

通过网络快速共享文件,进入到共享文件的目录下并在命令行中运行下面的代码:

1
python -m http.server

漂亮的打印

Python REPL可漂亮的打印出列表和字典

1
2
3
from pprint import pprint
my_dict = {'name': 'Yasoob', 'age': 'undefined'}
pprint(my_dict)

脚本性能分析

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
2
3
4
5
6
7
a_list = [[1, 2], [3, 4], [5, 6]]
print(list(itertools.chain.from_iterable(a_list)))
# Output: [1, 2, 3, 4, 5, 6]

# or
print(list(itertools.chain(*a_list)))
# Output: [1, 2, 3, 4, 5, 6]

一行式的构造器

避免类初始化时大量重复的赋值语句

1
2
3
class A(object):
def __init__(self, a, b, c, d, e, f):
self.__dict__.update({k: v for k, v in locals().items()})

For-Else

for循环还有一个else从句,我们大多数人并不熟悉。这个else从句会在循环正常结束时执行。这意味着,循环没有遇到任何break。

有个常见的构造是跑一个循环,并查找一个元素。如果这个元素被找到了,我们使 用break来中断这个循环。有两个场景会让循环停下来。

  • 第一个是当一个元素被找到,break被触发。
  • 第二个场景是循环结束。

现在我们也许想知道其中哪一个,才是导致循环完成的原因。一个方法是先设置一个标记,然后在循环结束时打上标记。另一个是使用else从句。

简而言之:当循环自然终结(未调用break),else从句会被执行一次。

以下例子会找出2到10之间的数字的因子。附加的else语句块,用来抓住质数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print( n, 'equals', x, '*', n/x) break
else:
# loop fell through without finding a factor
print(n, 'is a prime number')

# output
2 is a prime number
3 is a prime number
4 equals 2 * 2.0
5 is a prime number
6 equals 2 * 3.0
7 is a prime number
8 equals 2 * 4.0
9 equals 3 * 3.0

使用C扩展

CPython使得Python可以轻松调用C代码。

ctypes

ctypes模块提供了和C语言 兼容的数据类型和函数来加载dll文件,因此在调用时不需对源文件做任何的修改。

  1. 实现两数求和的C代码,保存为add.c
1
2
3
4
5
6
7
8
9
#include <stdio.h>
int add_int(int, int);
float add_float(float, float);
int add_int(int num1, int num2){
return num1 + num2;
}
float add_float(float num1, float num2){
return num1 + num2;
}
  1. 接下来将C文件编译为.so文件(windows下为DLL)
1
2
3
4
#For Linux
$ gcc -shared -Wl,-soname,adder -o adder.so -fPIC add.c
#For Mac
$ gcc -shared -Wl,-install_name,adder.so -o adder.so -fPIC add.c
  1. 现在在Python代码中来调用它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from ctypes import *

#load the shared object file
adder = CDLL('./adder.so')

#Find sum of integers
res_int = adder.add_int(4,5)
print "Sum of 4 and 5 = " + str(res_int)

#Find sum of floats
a = c_float(5.5)
b = c_float(4.1)
add_float = adder.add_float
add_float.restype = c_float
print "Sum of 5.5 and 4.1 = ", str(add_float(a, b))

Sum of 4 and 5 = 9
Sum of 5.5 and 4.1 = 9.60000038147

这种方法虽然简单,清晰,但是却很受限。例如,并不能在C中对对象进行操作。

SWIG

SWIG是Simplified Wrapper and Interface Generator的缩写。是Python中调用C代码的另一种 方法。在这个方法中,开发人员必须编写一个额外的接又文件来作为SWIG(终端工具)的入口。Python开发者一般不会采用这种方法,因为大多数情况它会带来不必要的复杂。而当你有一个C/C++代码库需要被多种语言调用时,这将是个非常不错的选择。

1
2
3
4
5
6
​```C
#include
double My_variable = 3.0;
int fact(int n) {
......
}

编译它

1
2
3
4
unix % swig -python example.i
unix % gcc -c example.c example_wrap.c \
-I/usr/local/include/python2.1
unix % ld -shared example.o example_wrap.o -o _example.so

Python的调用

1
2
3
4
5
6
7
>>> import example
>>> example.fact(5)
120
>>> example.my_mod(7,3)
1
>>> example.get_time()
'Sun Feb 11 23:01:07 1996'

Python/C API

Python/C API可能是被最广泛使用的方法。它不仅简单,而且可以在C代码中操作你的Python对象。这种方法需要以特定的方式来编写C代码以供Python去调用它。所有的Python对象都被表 示为一种叫做PyObject的结构体,并且Python.h头文件中提供了各种操作它的函数。

  1. 编写一个C扩展,添加所有元素到一个Python列表(所有元素都是数字)
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
//Python.h has all the required function definitions to manipulate the Pyt
#include <Python.h>
//This is the function that is called from your python code
static PyObject* addList_add(PyObject* self, PyObject* args){
PyObject * listObj;
if (! PyArg_ParseTuple( args, "O", &listObj ))
return NULL;
long length = PyList_Size(listObj); //iterate over all the elements
int i, sum =0;
for (i = 0; i < length; i++) {
sum += elem;
}
}

static char addList_docs[] =
"add( ): add all elements of the list\n";

static PyMethodDef addList_funcs[] = {
{"add", (PyCFunction)addList_add, METH_VARARGS, addList_docs}, {NULL, NULL, 0, NULL}
};

PyMODINIT_FUNC initaddList(void){
Py_InitModule3("addList", addList_funcs,
"Add all ze lists");
}

Python.h头文件中包含了所有需要的类型(Python对象类型的表示)和函数定义(对 Python对象的操作)
接下来我们编写将要在Python调用的函数, 函数传统的命名方式由{模块名}_{函数 名}组成,所以我们将其命名为addList_add 然后填写想在模块内实现函数的相关信息表,每行一个函数,以空行作为结束 最后的模块初始化块签名为PyMODINIT_FUNC init{模块名}。

  1. 现在我们已经编写完C模块了。将下列代码保存为setup.py
1
2
3
4
5
6
7
​```
#build the modules
​```

from distutils.core import setup, Extension

setup(name='addList', version='1.0', \ ext_modules=[Extension('addList', ['adder.c'])])

运行,现在应该已经将我们的C文件编译安装到我们的Python模块中了。

1
python setup.py install
  1. 验证
1
2
3
4
5
6
#module that talks to the C code
import addList l = [1,2,3,4,5]
print "Sum of List - " + str(l) + " = " + str(addList.add(l))

# output
Sum of List - [1, 2, 3, 4, 5] = 15

CPython

Open()

1
2
with open('photo.jpg', 'r+') as f: 
jpgdata = f.read()

open的第一个参数是文件名。第二个(mode 打开模式)决定了这个文件如何被打开。

  • 如果你想读取文件,传入r
  • 如果你想读取并写入文件,传入r+
  • 如果你想覆盖写入文件,传入w
  • 如果你想在文件末尾附加内容,传入a

如果不传入任意编码,一个系统 - 以及Python -指定的默认选项将被选中。你也许会·去依赖这个默认选项,但这个默认选项经常是错误的,或者默认编码实际上不能表达文件里的所有字符(这将经常发生在Python 2.x和/或Windows)。

1
2
3
4
5
6
7
8
9
import io
with open('photo.jpg', 'rb') as inf:
jpgdata = inf.read()
if jpgdata.startswith(b'\xff\xd8'):
text = u'This is a JPEG file (%d bytes long)\n'
else:
text = u'This is a random file (%d bytes long)\n'
with io.open('summary.txt', 'w', encoding='utf-8') as outf:
outf.write(text % len(jpgdata))

Python 2+3

Future模块导入

Future模块可以帮你在Python2中导入 Python3的功能。

1
2
3
from __future__ import print_function 
print(print)
# Output: <built-in function print>

模块重命名

1
2
3
4
try:
import urllib.request as urllib_request # for Python 3
except ImportError:
import urllib2 as urllib_request # for Python 2

协程

Python中的协程和生成器很相似但又稍有不同。主要区别在于:

  • 生成器是数据的生产者

  • 协程则是数据的消费者

1
2
3
4
5
6
def grep(pattern):
print("Searching for", pattern)
while True:
line = (yield)
if pattern in line:
print(line)

我们可以通过send()方法向它传值。

1
2
3
4
5
6
7
search = grep('coroutine')
next(search)
#output: Searching for coroutine
search.send("I love you")
search.send("Don't you love me?")
search.send("I love coroutine instead!")
#output: I love coroutine instead!

发送的值会被yield接收。

next()方法启动一个协程。就像协程中包含的生成器并不是立刻执行,而是通过next()方法来响应send()方法。因此,你必须通过next()方法来执行yield表达式。

可以通过调用close()方法来关闭一个协程。

1
2
search = grep('coroutine')
search.close()

函数缓存

在Python 3.2以后版本,lru_cache的装饰器,允许我们将一个函数的返回值快速地缓存或取消缓存。

实现一个斐波那契计算器,并使用lru_cache。

  • maxsize参数是告诉lru_cache,最多缓存最近多少个返回值。
1
2
3
4
5
6
7
8
from functools import lru_cache
@lru_cache(maxsize=32)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
>>> print([fib(n) for n in range(10)])
# Output: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
  • 也可以轻松地对返回值清空缓存
1
fib.cache_clear()

上下文管理器

上下文管理器允许你在有需要的时候,精确地分配和释放资源。with确保我们的文件会被关闭,而不用关注嵌套代码如何退出。

1
2
with open('some_file', 'w') as opened_file: 
opened_file.write('Hola!')

等价于:

1
2
3
4
5
file = open('some_file', 'w') 
try:
file.write('Hola!')
finally:
file.close()

自行实现上下文管理器 — 基于类的实现

1
2
3
4
5
6
7
8
9
class File(object):
def __init__(self, file_name, method):
self.file_obj = open(file_name, method)
def __enter__(self):
return self.file_obj
def __exit__(self, type, value, traceback):
print("Exception has been handled")
self.file_obj.close()
return True

通过定义enter和exit方法,我们可以在with语句里使用它。

1
2
with File('demo.txt', 'w') as opened_file:
opened_file.write('Hola!')

exit函数接受三个参数。这些参数对于每个上下文管理器类中的exit方法都是必须的。

  1. with语句先暂存了File类的exit方法
  2. 然后它调用File类的enter方法
  3. enter__方法打开文件并返回给with语句
  4. 打开的文件句柄被传递给opened_file参数
  5. 我们使用.write()来写文件

  6. with语句调用之前暂存的exit方法

  7. exit方法关闭了文件

    在4与6之前,如果发生了异常,Python会将异常的type,value和traceback传递给exit方法。它让exit方法来决定如何关闭文件以及是否需要其他步骤。

  8. 如果exit返回的是True,那么这个异常就被优雅地处理了。

  9. 如果exit返回的是True以外的任何东西,那么这个异常将被with语句抛出。

自行实现上下文管理器 — 基于生成器的实现

还可以使用一个生成器函数来实现一 个上下文管理器,而不是使用一个类。

  1. Python解释器遇到了yield关键字。因为这个缘故它创建了一个生成器而不是一个普通的函数。
  2. 因为这个装饰器,contextmanager会被调用并传入函数名(open_file)作为 参数。
  3. contextmanager函数返回一个以GeneratorContextManager对象封装过的 生成器。
  4. 这个GeneratorContextManager被赋值给open_file函数,我们实际上是在 调用GeneratorContextManager对象。
1
2
3
4
5
6
from contextlib import contextmanager
@contextmanager
def open_file(name):
f = open(name, 'w')
yield f
f.close()

等价于:

1
2
with open_file('some_file') as f: 
f.write('hola!')

Pythonic的代码

非常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. 函数注释:给外部可访问的函数与方法添加文档注释。注释要清楚的描述方法的功能,并对参数、返回值以及可能发生的异常进行说明。
1
2
3
4
5
6
7
8
9
10
11
def FuncName(parameter1,parameter2):
"""Describe what this Function does:
# such as "Find wheter the special string is in the queue or not"
Args:
parameter1:parameter type,what is the parameter used for.
parameter2:parameter type,what is the parameter used for.
return type, return value.
"""
function body
...
...
  1. 推荐在文件头中包含copyright申明、模块描述等,如有必要可以加入作者信息及变更记录。
1
2
3
4
5
6
7
8
9
10
11
"""
Licensed Materials - Property of CorpA
(C) Copyright A Corp.1999,2011 All Right Reserved
CopyRight statement and purpose...
-------------------------------------------------------------
# @Time: 2020/2/27 12:08
# @Author: GraceKoo
# @File: 55_jump-game.py
# @Desc:https://leetcode-cn.com/problems/jump-game/
-------------------------------------------------------------
"""

常量

Python的内建命名空间是支持一小部分常量的,如True、Flase、None等。只是Python没有提供常量的直接方式而已。

  • 常量名所有字母应大写,用下划线连接各个单词,如:MAX_OVERFLOW
  • 将常量集中到一个文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class _const:
class ConstError(TypeError):pass
class ConstCaseError(constError):pass

def _setattr_(self, name, value):
if self.__dict__.has_key(name):
raise self.ConstError, "can not change const" %name
if not name.isupper():
raise self.ConstCaseError,\
'const name "%s" is not all uppercase' %name
self.__dict__[name] = value


import sys
sys.modules[__name__]=_const()

设置常量

1
2
import const
const.COMPANY = "IBM"

使用常量

1
2
import constant import const
print(const.COMPANY)

类型检查

内建函数type() 用于返回当前对象的类型,但type()有时并不能准确返回结果。建议使用isinstance()来检测。

isinstance(object, classinfo)

classinfo为直接或者间接的类名,基本类型名称或者由它们组成的元组。

1
2
3
isinstance(2, float)
isinstance("a", (str.unicode))
isinstance((2,3), (str, list, tuple))

浮点计算

1
2
3
print(1.1 + 1.1 + 1.1)

0.30000000000003

如果对精度要求比较高,可以用Decimal来进行处理。

1
2
3
4
5
from Decimal import *
getcontext().prec = 6
Decimal(1) / Decimal(7) # Decimal('0.142857') 保留6个有效数字

Decimal('50.5679').quantize(Decimal('0.00')) # Decimal('50.57') 四舍五入保留两位小数

官方资源地址

https://docs.python.org/2/library/pdb.html

https://docs.python.org/3/library/pdb.html

参考书籍

  • 《Intermediate Python》
  • 《编写高质量代码 改善Python程序的91个建议》
  • 《Python核心编程 第三版》