Python笔记

点进来复习

OpenCV

cv2.waitKey()

1
2
3
4
5
k = cv2.waitKey(30) & 0xff
if k == 27:
break
elif k == ord('s'):
cv2.imwrite('opticalfb.png', frame2)

cv2.waitKey()的参数(代表milliseconds)是0时,无返回值,否则返回这段时间内的按键ASCII码,超过时间无按键,返回 -1。

& 0xff防止在某些系统中键盘返回的不是ASCII(比如SCII),就提取最后一个字节。

python内置函数ord()返回ASCII码,27是Esc的ASCII码。

判断数据类型

判断变量a是否是列表:

1
2
if isinstance(a, list):
pass

判断变量b是否是数字:

1
2
3
4
import numbers

if isinstance(b, numbers.Number):
pass

也可以用if isinstance(b, float):,但是numbers.Number可以针对整数、小数、复数。

1
2
3
4
5
6
import numbers

a = 1+1j

if isinstance(a, numbers.Number):
pass

判断c是否是ndarray类型:

1
if isinstance(c, np.ndarray)

也可以用来判断是否是几种特定类型中的一个:

1
assert isinstance(output_size, (int, tuple))

判断output_size是否是int类型或者tuple类型。

解压操作

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# * 号解压
list1 = [1, 2, 3, 4, 5]
print(*list1) # 1 2 3 4 5 注意是5个值,不再是列表

# 可迭代对象可以通过一个简单的赋值语句解压并赋值给多个变量
p = (4, 5)
x, y = p # x:4 y:5
s = 'Hello'
a, b, c, d, e = s

# 只想解压一部分,丢弃其他的值
data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
_, shares, price, _ = data

# 星号解压语法,星号表达式在迭代元素为可变长元组的序列时是很有用的
list1 = [23, 45, 56, 39, 20]
a, *b, c = list1 # b:[45, 56, 39] *b: 45 56 39

# demo1
records = [
('foo', 1, 2),
('bar', 'hello'),
('foo', 3, 4),
]


def do_foo(x, y):
print('foo', x, y)


def do_bar(s):
print('bar', s)


for tag, *args in records:
if tag == 'foo':
do_foo(*args)
elif tag == 'bar':
do_bar(*args)

# demo2
line = 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false'
uname, *fields, homedir, sh = line.split(':')
# uname : 'nobody'
# homedir : '/var/empty'
# sh : '/usr/bin/false'

# demo3
record = ('ACME', 50, 123.45, (12, 18, 2012))
name, *_, (*_, year) = record
# name : 'ACME'
# year : 2012

# zip
list_a = [1, 2, 3, 4, 5, 6]
list_b = [6, 5, 4, 3, 2, 1]

for i, j in zip(list_a, list_b):
print(i, j)

# 1 6
# 2 5
# 3 4
# 4 3
# 5 2
# 6 1

deque

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from collections import deque

# demo1
# 使用 deque(maxlen=N) 构造函数会新建一个固定大小的队列。当新的元素加入并
# 且这个队列已满的时候,最老的元素会自动被移除掉。
q = deque(maxlen=3)
q.append(1)
q.append(2)
q.append(3)
# q : deque([1, 2, 3], maxlen=3)

q.append(4)
# q : deque([2, 3, 4], maxlen=3)
q.append(5)
# q : deque([3, 4, 5], maxlen=3)

# demo2
# 如果不设置deque最大队列大小,那么就会得到一个无限大小队列,可以在队列的两端执行添加和弹出元素的操作 (设置了队列大小也可以在两端添加)。
# 在队列两端插入或删除元素时间复杂度都是 O(1) ,区别于列表,在列表的开头插入或删除元素的时间复杂度为 O(N) 。
q = deque()
q.append(1)
q.append(2)
q.append(3)
# q : deque([1, 2, 3])
q.appendleft(4)
# q : deque([4, 1, 2, 3])
q.pop()
# q : deque([4, 1, 2])
q.popleft()
# q : deque([1, 2])

# demo3
# 在文件中匹配文本,显示最后一次匹配及其前N行文本
def search(lines, pattern, history=5):
previous_lines = deque(maxlen=history)
for line in lines:
if pattern in line:
yield line, previous_lines
previous_lines.append(line)

# Example use on a file
if __name__ == '__main__':
with open(r'a.txt') as f:
for line, prevlines in search(f, 'python', 3):
for pline in prevlines:
print(pline, end='')
print(line, end='')
print('-' * 20)

global和nonlocal关键字

同一个文件(模块)中,定义在函数外面的变量可以当做全局变量使用。

1
2
3
4
5
def test():
print(a)

a = 5
test()

会输出5,即使a的定义在函数定义后面也可以。

但是如果函数中出现了同名的变量定义,则函数外的变量定义失效:

1
2
3
4
5
6
7
a = 5

def test():
print(a)
a = 3

test()

报错UnboundLocalError: local variable 'a' referenced before assignment,如果函数中的a=3放在print(a)前面则输出3,很好理解。

再上一个例子中的函数最前面加一句global:

1
2
3
4
5
6
7
8
a = 5

def test():
global a
print(a)
a = 3

test()

就可以正常输出5(之后被改成了3).

所以,当函数中对全局变量进行了修改,最好加上global.

Python 3.x引入了nonlocal关键字,可以用于标识外部作用域的变量。

局部作用域里的代码可以读外部作用域(包括全局作用域)里的变量,但不能更改它。一旦进行更改,就会将其当成是局部变量。而如果在更改前又进行了读取操作,则会抛出异常。

nonlocal关键字用来在函数或其他作用域中使用外层(非全局)变量:

1
2
3
4
5
6
7
def make_counter(): 
count = 0
def counter():
nonlocal count
count += 1
return count
return counter

路径操作

os.path.sep: Linux下路径分隔符

os.path.altsep: Windows下路径分隔符

os.path.curdir: 当前目录,Linux下是‘ . ’,可以用os.path.abspath(os.path.curdir)

os.path.pardir: 父目录,Linux下是‘ .. ’,同样可以结合os.path.abspath(path)

os.path.abspath(path): 绝对路径

os.path.join(): 常用来链接路径

os.path.split(path): 把path分为目录和文件两个部分,以列表返回

os.path.basename: 返回最后一个/后面的目录或文件名

os.listdir(path): 获取文件夹路径下的所有目录及文件名,结果的文件顺序并不是按文件的显示顺序

join(os.path.join)

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
#对序列进行操作(分别使用' '与':'作为分隔符)
>>> seq1 = ['hello','good','boy','doiido']
>>> print(' '.join(seq1))
hello good boy doiido
>>> print(':'.join(seq1))
hello:good:boy:doiido

#对字符串进行操作
>>> seq2 = "hello good boy doiido"
>>> print(':'.join(seq2))
h:e:l:l:o: :g:o:o:d: :b:o:y: :d:o:i:i:d:o

#对元组进行操作
>>> seq3 = ('hello','good','boy','doiido')
>>> print(':'.join(seq3))
hello:good:boy:doiido

#对字典进行操作
>>> seq4 = {'hello':1,'good':2,'boy':3,'doiido':4}
>>> print(':'.join(seq4))
boy:good:doiido:hello

#合并目录
>>> import os
>>> os.path.join('/hello/','good/boy/','doiido')
'/hello/good/boy/doiido'
>>> os.path.join('/hello','good/boy','doiido') # 不加'/'也行
'/hello/good/boy/doiido'
>>> os.path.join('/hello','good/boy','/doiido') # 从最后一个 '/**'开始,在路径最前面加'/'易出错
'/doiido'

以上代码出自:https://www.cnblogs.com/jonm/p/8281032.html

python文件路径

test.py文件内容:

1
2
3
4
5
6
7
import os

base_path = os.path.dirname(os.path.abspath(__file__))
driver_path = os.path.abspath(__file__)

print(base_path)
print(driver_path)

输出:

1
2
C:\Test_framework\test
C:\Test_framework\test\test.py

os.path.dirname(x)是用来获取x所在的目录(x可以是文件,也可以是目录,目录即文件):

the os.path.dirname( ) function simply removes the last segment of a path.

但是由于运行py文件所处的位置不同,__file__可能也不同,所以想获得py文件所在目录,最好加上绝对路径:

1
os.path.dirname(os.path.abspath(__file__))

class

class中函数的第一个参数不一定非要是self,改成其他,只要保持统一就行。

lambda

对字典按value排序:

1
2
3
4
5
6
dict1 = {'x': 90, 'y': 98, 'z': 84, 'a': 78, 'b': 72, 'c': 96, 'd': 99}
dict2 = sorted(dict1.items(), key=lambda x: x[1])
print(dict2)

out:
[('b', 72), ('a', 78), ('z', 84), ('x', 90), ('c', 96), ('y', 98), ('d', 99)]

sorted的参数key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。

注意输出的结果已经不再是字典。

要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True

Numpy

np.where

先了解numpy.nonzero,对于文档中的这个例子:

1
2
3
4
5
6
7
>>> x = np.array([[1,0,0], [0,2,0], [1,1,0]])
>>> x
array([[1, 0, 0],
[0, 2, 0],
[1, 1, 0]])
>>> np.nonzero(x)
(array([0, 1, 2, 2]), array([0, 1, 0, 1]))

对应的两个输出表示的是两个维度,对应值组成一组索引。

再看np.where. 就能理解里面的例子了:

1
2
3
4
5
6
7
8
9
>>> x = np.arange(9.).reshape(3, 3)
>>> np.where( x > 5 )
(array([2, 2, 2]), array([0, 1, 2]))
>>> x[np.where( x > 3.0 )] # Note: result is 1D.
array([ 4., 5., 6., 7., 8.])
>>> np.where(x < 5, x, -1) # Note: broadcasting.
array([[ 0., 1., 2.],
[ 3., 4., -1.],
[-1., -1., -1.]])

对于上面最后一个例子,当输入是np.where(condition, x, y)时,当条件为True,生成对应值是x,否则是y。

np.newaxis

1
2
3
4
5
6
7
8
import numpy as np

a = np.array([1, 2, 3, 4])
b = a[:,np.newaxis]
c = a[np.newaxis, :]

print(b)
print(c)

输出:

1
2
3
4
5
6
[[1]
[2]
[3]
[4]]

[[1 2 3 4]]

np.full

1
2
3
np.full((2, 2), 10)
>> array([[10, 10],
[10, 10]])

np.array[1, …]

1
2
3
4
5
6
7
8
9
10
>>> x = np.array([[[1],[2],[3]], [[4],[5],[6]]])
>>> x[...,0] # shape (2, 3, 1) to shape (2, 3)
array([[1, 2, 3],
[4, 5, 6]])
>>> x[:,0]
array([[1],
[4]])
>>> x[:,:,0]
array([[1, 2, 3],
[4, 5, 6]])

省略所有的冒号来用...代替,x[:,:,0]x[...,0]一样。

np.clip

numpy.clip(a, a_min, a_max, out=None)

1
2
3
4
5
6
7
8
9
10
11
12
>>> a = np.arange(10)
>>> np.clip(a, 1, 8)
array([1, 1, 2, 3, 4, 5, 6, 7, 8, 8])
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> np.clip(a, 3, 6, out=a)
array([3, 3, 3, 3, 4, 5, 6, 6, 6, 6])
>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> np.clip(a, [3, 4, 1, 1, 1, 4, 4, 4, 4, 4], 8)
array([3, 4, 2, 3, 4, 5, 6, 7, 8, 8])

np.random

np.random.laplace

随机变量服从Laplace分布(又叫双指数函数分布/ double exponential distribution), probability density function:
$$
f(x;\mu,\lambda) = \frac{1}{2 \lambda} e^{-\frac{\vert x –\mu \vert}{\lambda}}
$$

The position, $ \mu$, of the distribution peak. Default is 0.

$\lambda$, the exponential decay. Default is 1.

The Laplace distribution is similar to the Gaussian/normal distribution, but is sharper at the peak and has fatter tails. It represents the difference between two independent, identically distributed exponential random variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import matplotlib.pyplot as plt
import numpy as np
import random

loc, scale = 0., 1.
s = np.random.laplace(loc, scale, 1000)

count, bins, ignored = plt.hist(s, 30, density=True)

x = np.arange(-8., 8., .01)
pdf = np.exp(-abs(x-loc)/scale)/(2.*scale)
l1, = plt.plot(x, pdf)

g = (1/(scale * np.sqrt(2 * np.pi)) *
np.exp(-(x - loc)**2 / (2 * scale**2)))
l2, = plt.plot(x,g, '-.')

plt.legend([l1, l2], ["Laplace distribution", "standard Gaussian distribution"], loc='upper left')

plt.show()

plt.hist(s, 30, density=True)30为显示的柱数,density=True是为了保证接下来画的两条线能够在同一张图上显示。

效果图:

直方图为随机的按Laplace分布的变量。

permutation vs shuffle

numpy.random.permutation

numpy.random.shuffle

二者区别是shuffle是in-place操作

np.prod

numpy.prod

计算乘积,可以按axis

np.squeeze

https://docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.squeeze.html

1
2
3
4
5
6
7
>>> x = np.array([[[0], [1], [2]]])
>>> x.shape
(1, 3, 1)
>>> np.squeeze(x).shape
(3,)
>>> np.squeeze(x, axis=(2,)).shape
(1, 3)

np.fliplr

https://docs.scipy.org/doc/numpy/reference/generated/numpy.fliplr.html

1
2
A = np.random.randn(2,3,5)
np.all(np.fliplr(A) == A[:,::-1,...])

注意,是在第二个维度进行翻转。

假设现在tImage图片数据的维度格式是[batch_size, time_steps, height, width, channel],想将所有图片进行左右翻转,可以借助np.fliplr:

1
tImage = np.fliplr(tImage.transpose(2,3,4,0,1)).transpose(3,4,0,1,2)

应该也可以用np.flip,可以指定axis参数。

BGR和RGB顺序转换

1
imageRGB = image[:,:,::-1]

将第三个维度进行转换(逆序),上面是numpy array类型,其实对list类型也适用(list类型逆序还可以用reverse)。

为什么这样可以做到逆序?看几个例子:

1
2
3
4
5
6
7
8
9
10
a = [1, 2, 3, 4, 5, 6, 7, 8]

b = a[1:7:2]

print(a)
print(b)

# 结果是:
[1, 2, 3, 4, 5, 6, 7, 8]
[2, 4, 6]

a[1:7:2]就是按索引取1-6,间隔为2。那么a[::-1]就很好理解了,第三个-1代表逆序间隔为1,第一个:两边什么都没有代表取全部内容。

还可以用OpenCV中的函数:

1
image_BGR = cv2.cvtColor(image_RGB, cv2.COLOR_RGB2BGR)

np.mean

重点是axis参数,如果是想求每个通道的均值(对于图片数据 H×W×3来说),就要写axis=(0, 1),这样的结果才会有3个数(均值)。

np.errstate

numpy.errstate, 参数参考numpy.seterr

作为上下文管理器,可以处理一些python不会提示的意外情况:

1
2
3
4
5
6
7
>>> np.arange(3) / 0.
array([ NaN, Inf, Inf])
>>> with np.errstate(divide='warn'):
... np.arange(3) / 0.
...
__main__:2: RuntimeWarning: divide by zero encountered in divide
array([ NaN, Inf, Inf])

np.unravel_index

unravel: 拆解

numpy.unravel_index

可以搭配argmax()来使用:

1
2
# A是一个二维矩阵
r_max, c_max = np.unravel_index(A.argmax(),A.shape)

由于A.argmax()是将A展开,再寻找最大值的索引,返回的是一个整型数。所以想要找到最大值对应于A的位置坐标(r_max, c_max),需要用unravel_index()进行转换.

还可针对更复杂的维度或者多个index进行转换,详见官方demo。

np.meshgrid

官方文档

相关解释

对于输入一维,输出二维情况,相当于是构建了一个坐标矩阵(两个返回值分别存放x和y坐标)。

collections — Container datatypes

官方文档

namedtuple

详细见collections.namedtuple

1
from collections import namedtuple

个人理解是这个东西类似与一个类(精简版),但是只用来存放变量,没有方法。也类似于字典,相当于一个字典模板,将里面存放的东西给定义好。

网上找的两个列子:

  1. 类似于字典,有”key”,通过’’.”访问(和访问类中的成员变量一样),注意这里直接用obj.name访问字符串'name'对应的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import collections

    MyTupleClass = collections.namedtuple('MyTupleClass',['name', 'age', 'job'])
    obj = MyTupleClass("Tomsom",12,'Cooker')
    print(obj.name)
    print(obj.age)
    print(obj.job)

    # 输出
    Tomsom
    12
    Cooker
  2. 更像类的一个例子,可以用这个模板来创建多个“对象”

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from collections import namedtuple

    Friend=namedtuple("Friend",['name','age','email'])

    f1=Friend('xiaowang',33,'xiaowang@163.com')
    print(f1)
    print(f1.age)
    print(f1.email)
    f2=Friend(name='xiaozhang',email='xiaozhang@sina.com',age=30)
    print(f2)

    name,age,email=f2
    print(name,age,email)

修改值

参考

1
2
3
4
5
6
7
8
import collections

Rectangle = collections.namedtuple('Rectangle', ['x', 'y', 'width', 'height'])

selection1 = Rectangle(0.0, 0.0, 0.0, 0.0)
selection2 = selection1._replace(x=33, y=22)

print(selection1,selection2)

注意:

  • selection1 的值未变,_replace()方法返回新的实例
  • x 不需要加引号

将字典转换为namedtuple

1
2
3
4
5
6
7
8
9
10
11
from collections import namedtuple

cfg = {
'initial_lr': 0.01,
'lr_decay': 0.8685113737513527,
'weight_decay': 5e-4,
'momentum': 0.9}

config = namedtuple('Config', cfg.keys())(**cfg)

print(config.initial_lr)

输出 0.01

排序函数

列表排序

list排序(存在子列表,也按子列表的0索引值排序):

1
2
3
list1 = [[3, 4, 2], [4, 1, 7], [2, 3, 7]]
list1.sort()
print(list1)

输出:

1
[[2, 3, 7], [3, 4, 2], [4, 1, 7]]

发现列表的sort操作有key参数,如果要按key排序,可以:

1
2
3
4
5
6
7
8
# 获取列表的第二个元素
def takeSecond(elem):
return elem[1]

random = [(2, 2), (3, 4), (4, 1), (1, 3)]

# 指定第二个元素排序
random.sort(key=takeSecond)

输出:

1
[(4, 1), (2, 2), (1, 3), (3, 4)]

ndarray排序

argsort()

参考:https://stackoverflow.com/questions/5047407/python-numpy-array-sorting

想进行如下排序:

1
2
3
4
5
6
7
8
9
array([[0, 2, 4, 7],
[9, 7, 3, 1],
[7, 3, 9, 5]])

-->

[[0 2 4 7]
[7 3 9 5]
[9 7 3 1]]

而不是在某个维度里单独排序 ndarray.sort(axis=0) ,结果就会变成下面这样(每一列都排序了):

1
2
3
[[0 2 3 1]
[7 3 4 5]
[9 7 9 7]]

可以这样操作:

1
ndarray[ndarray[:,0].argsort()]

lexsort()

参考numpy.lexsort:

1
2
3
4
5
>>> a = [1,5,1,4,3,4,4] # First column
>>> b = [9,4,0,4,0,2,1] # Second column
>>> ind = np.lexsort((b,a)) # Sort by a, then by b
>>> print(ind)
[2 0 4 6 5 3 1]

装饰器

装饰器 (decorator) 的本质——闭包 。

闭包

如果在一个内嵌函数里,对在外部函数内(但不是在全局作用域)的变量进行引用,那么内嵌函数就被认为是闭包(closure)。

1
2
3
4
5
6
> def outer():
> x = 1
> def inner():
> print x
> return inner
>

总结一下,创建一个闭包必须满足以下几点:

  • 必须有一个内嵌函数
  • 内嵌函数必须引用外部函数中的变量
  • 外部函数的返回值必须是内嵌函数

涉及到变量的生存周期,下面的outer( )就是一个装饰器

1
2
3
4
5
6
7
8
9
10
def outer(some_func):
def inner():
print "before some_func"
ret = some_func()
return ret + 1
return inner
def foo():
return 1
decorated = outer(foo)
decorated()

输出:

1
2
before some_func
2

增强foo( )函数:

1
foo1 = outer(foo)

原本的函数foo并没有发生改变.

@ 标识符

1
2
3
@outer
def foo():
return 1

这样写相当于:

1
foo = outer(foo)

上面的装饰器只能应用于某一个(某一类方法),如何设计一个适用于更多方法的装饰器(比如一个函数运行时的计数器)?

*args

网上例子:

1
2
3
4
5
6
7
8
9
10
def one(*args):
print args
one()
# output: ()
one(1, 2, 3)
# output: (1, 2, 3)
def two(x, y, *args):
print x, y, args
two('a', 'b', 'c')
# output: a b ('c',)

可以看出来,*args可以接受多个(不定长)参数,放入一个元组中。*本身是解压操作,参考前面笔记。

例子2:

1
2
3
4
5
6
7
def add(x, y):
return x + y
lst = [1,2]
add(lst[0], lst[1]) # 1
# output: 3
add(*lst) # 2
# output: 3

这个例子中两种调用方式相同

**kwargs

关键字参数

例1:

1
2
3
4
5
6
def foo(**kwargs):
print kwargs
foo()
# output: {}
foo(x=1, y=2)
# output: {'y': 2, 'x': 1}

例2:

1
2
3
4
5
dct = {'x': 1, 'y': 2}
def bar(x, y):
return x + y
bar(**dct)
# output: 3

*args 表示多个位置参数(positional arguments ),它本质是一个 tuple

**kwargs 表示关键字参数(keyword arguments ),它本质上是一个 dict

如果同时使用 *args 和 **kwargs 时,args 参数列要在 kwargs 之前,因为positional arguments必须位于keyword arguments之前 。

所以需要 *args 和 **kwargs 配合,才能同时接受所有参数(非关键字参数和关键字参数)。

举例:

1
2
3
4
5
6
def foo(*args, **kwargs):
print 'args = ', args
print 'kwargs = ', kwargs
foo('a', 1, None, a=1, b='2', c=3)
# output: args = ('a', 1, None)
# kwargs = {'a': 1, 'c': 3, 'b': '2'}

装饰器作用所有函数

1
2
3
4
5
def logger(func):
def inner(*args, **kwargs):
print "Arguments were: %s, %s" % (args, kwargs)
return func(*args, **kwargs)
return inner
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@logger
def foo1(x, y=1):
print x * y
@logger
def foo2():
print 2
foo1(5, 4)
# output: Arguments were: (5, 4), {}
# 20
foo1(1)
# output: Arguments were: (1,), {}
# 1
foo2()
# output: Arguments were: (), {}
# 2

为什么kwargs是空字典?注意了,如果将调用改成foo1(5, y=4),kwargs就不是空字典了!

一个计算函数运行时间的装饰器:

1
2
3
4
5
6
7
8
9
10
11
import time


def time_it(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(func.__name__, end_time - start_time)
return result
return wrapper

result是为了接func的返回值。

functools.wraps

将一个装饰器作用在某个函数上,这个函数的重要的元信息比如名字、文 档字符串、注解和参数签名都会丢失。对上面计算函数运行时间的装饰器来说,如果有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@time_it
def prime_number(number_range):
"""
find prime number
:param number_range:
:return:
"""
prime_number_list = [2, 3]

for i in range(2, number_range + 1):
length = math.ceil(math.sqrt(i))
for j in range(2, length):
if i % j == 0:
break
if j == length - 1:
prime_number_list.append(i)

print(prime_number_list)

help(prime_number)

输出的不是prime_number里的注释信息,而是装饰器中的函数信息:

1
2
3
Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

解决方法:使用 functools 库中的 @wraps 装饰器来注解底层包装函数。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
import time
from functools import wraps


def time_it(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(func.__name__, end_time - start_time)
return result
return wrapper

这样,再使用help(prime_number)的结果就看到了原函数的注释信息:

1
2
3
4
5
6
7
Help on function prime_number in module __main__:

"""
find prime number
:param number_range:
:return:
"""

解除一个装饰器

直接访问未包装的原始函数在调试、内省和其他函数操作时是很有用的。如果装饰器是通过 @wraps 来实现的,那么你可以通过访问 __wrapped__ 属性来访问原始函数:

1
2
3
4
5
@somedecorator
def add(x, y):
return x + y

orig_add = add.__wrapped__

参考资料:

python装饰器入门与提高

相见恨晚的技巧

raise

from here.

1
2
3
4
5
import sys

sys.stderr.write('fail!!!!')

raise SystemExit(1)

输出’fail!!!!’,结束程序。也可以直接写成:

1
2
3
import sys

raise SystemExit('fail!!!!')

同样效果,默认返回1。

1
raise Exception("This user is not allowed to get food")

在父类中,如果存在子类一定要重写的方法,可以这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A(object):
def __init__(self, ...):
...

def func1(self):
raise NotImplementedError()

def func2(self)
self.func1()

class B(A):
def __init__(self, ...):
...

def func1(self, ...):
...

这样,就可以保证调用B.func2()不会出错(如果B中没有重写func1(),就会报错)。

assert

1
assert condition

等同于

1
2
if not condition:
raise AssertionError()

添加异常解释:

1
assert expression [, arguments]

比如:

1
assert len(unique_ids) > 1, 'unique_ids must be at least 2 elements'

enumerate

enumerate(list_a, 1)可以指定index从1开始

文件的x模式

'w'覆盖写模式,文件不存在则创建,存在则完全覆盖;

'x'创建写模式,文件不存在则创建,存在则返回异常FileExistError

list.pop()

列表的pop方法会返回弹出的值:

1
a = list_1.pop()

defaultdict

详细见collections

更方便的构造dict

demo1 - value type is list

不必为每一个key重复创建空列表(默认每一个新添加的key对应的value为空list)

1
2
3
4
5
6
7
8
from collections import defaultdict

d = defaultdict(list)

d['numbers'].append(1)
d['numbers'].append(2)

print(d)

输出:

1
defaultdict(<class 'list'>, {'number': [1, 2]})

demo2 - value type is int

默认每一个新添加的key对应的value为整数 0 )

1
2
3
4
5
6
7
8
from collections import defaultdict

d = defaultdict(int)

d['count1'] += 1
d['count2'] -=1

print(d)

输出:

1
defaultdict(<class 'int'>, {'count1': 1, 'count2': -1})

默认空列表和整数0的原因是 defaultdict( ) 的参数是一个 mapping , list()int()返回的就是空列表和整数0。对于demo2,如果想指定别的数作为默认值,可以:

1
2
3
4
5
6
7
8
9
10
11
from collections import defaultdict

def set_default_value():
return 1

d = defaultdict(set_default_value)

d['count1'] += 1
d['count2'] -=1

print(d)

输出为:

1
defaultdict(<function set_value at 0x7fea5bb63f28>, {'count2': 0, 'count1': 2})

#往实例中添加方法

当创建了一个实例,想要往实例中添加额外的方法时,可以这样(法一):

1
2
3
4
5
6
7
8
9
10
11
def my_print(a):
print(a)


class Student:
pass

s = Student()
s.my_print = my_print

s.my_print('123')

也可以这样(法二):

1
2
3
4
5
6
7
8
>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25

法一简单,但是添加的函数无法调用self,法二可以。

只判断图片大小

出自Re3

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def get_image_size(fname):
import struct, imghdr, re
'''Determine the image type of fhandle and return its size.
from draco'''
# Only a loop so we can break. Should never run more than once.
while True:
with open(fname, 'rb') as fhandle:
head = fhandle.read(32)
if len(head) != 32:
break
if imghdr.what(fname) == 'png':
check = struct.unpack('>i', head[4:8])[0]
if check != 0x0d0a1a0a:
break
width, height = struct.unpack('>ii', head[16:24])
elif imghdr.what(fname) == 'gif':
width, height = struct.unpack('<HH', head[6:10])
elif imghdr.what(fname) == 'jpeg':
try:
fhandle.seek(0) # Read 0xff next
size = 2
ftype = 0
while not 0xc0 <= ftype <= 0xcf:
fhandle.seek(size, 1)
byte = fhandle.read(1)
while ord(byte) == 0xff:
byte = fhandle.read(1)
ftype = ord(byte)
size = struct.unpack('>H', fhandle.read(2))[0] - 2
# We are at a SOFn block
fhandle.seek(1, 1) # Skip `precision' byte.
height, width = struct.unpack('>HH', fhandle.read(4))
except Exception: #IGNORE:W0703
break
elif imghdr.what(fname) == 'pgm':
header, width, height, maxval = re.search(
b"(^P5\s(?:\s*#.*[\r\n])*"
b"(\d+)\s(?:\s*#.*[\r\n])*"
b"(\d+)\s(?:\s*#.*[\r\n])*"
b"(\d+)\s(?:\s*#.*[\r\n]\s)*)", head).groups()
width = int(width)
height = int(height)
elif imghdr.what(fname) == 'bmp':
_, width, height, depth = re.search(
b"((\d+)\sx\s"
b"(\d+)\sx\s"
b"(\d+))", str).groups()
width = int(width)
height = int(height)
else:
break
return width, height
imShape = cv2.imread(fname).shape
return imShape[1], imShape[0]

能不调用opencv,就不调用opencv,只读取前32字节,节省资源。

打印函数名

函数内部打印函数名

1
2
def func():
print(sys._getframe().f_code.co_name)

输出:

1
func

Python build-in functions

排列组合

itertools

1
2
3
4
5
6
7
8
9
10
11
import itertools

list1 = [1, 2, 3, 4, 5]

# 排列, r=2表示选取其中2个元素进行排列, r=None表示全排列
g_permutations = itertools.permutations(list1, r=None)
#组合
g_combinations = itertools.combinations(list1, r=3)

for i in g_combinations:
print(i)

容易产生bug的地方

用列表作默认参数

1
2
3
4
5
6
7
def func(a, b=[]):
b.append(a)
print(f'a: {a}')
print(f'b: {b}')

func(1)
func(2)

输出:

1
2
3
4
a: 1
b: [1]
a: 2
b: [1, 2]

第一次调用的结果仍然保留了。

for … else

1
2
3
4
5
for i in range (10):
if i == 6:
continue
else:
print('items are exhausted')

会执行else中的语句,当把continue改成break时,不会执行else中的语句。

即当没有break或者return打破for循环,就会执行else里的内容,注意两点:

  1. 在最后一次循环中使用了break,也不会执行else中语句。

  2. 可迭代内容为空时,仍会执行else语句,即使里面有break

    1
    2
    3
    4
    for i in []:
    break
    else:
    print('items are exhausted')

    会执行print语句。

对while循环同理。

循环+else可以节省一个flag。

格式化字符串

% 格式化字符串

C语言风格

1
2
3
print('%.1f' % 5.6)
print('%s' % 'hello world!')
print('%5d' % 3)

输出:

1
2
3
5.6
hello world!
3

面对更复杂的情况,这是一种冗余的方式。对于python中的结构,字典和列表的处理也不够好。

str.format

更强大的格式化函数,参数和demo参考:Format Specification Mini-Language

还是冗余,但是字典可以进行解压缩:.format(**dict)

f-string

python 3.6 + 环境中,PEP 498 – Literal String Interpolation

1
2
3
a = 'hello'
b = 3
print(f'{a}, {b}')

输出:hello, 3

1
2
3
4
5
6
def upper(s):
return s.upper()

a = 'hello'
b = 3
print(f'{upper(a)}, {b}')

输出:HELLO, 3

注意,引号不要重复了:

1
2
comedian = {'name': 'Eric Idle', 'age': 74}
f"The comedian is {comedian['name']}, aged {comedian['age']}."

内容中有单引号,外面要用双引号。

对于lambda表达式:

1
f'{lambda x: x*2 (3)}'

会产生错误,因为有:号,应该写成f'{(lambda x: x*2)(3)}',结果为'6'

sacred

Facilitates automated and reproducible experimental research

Docs

Python中的多任务

有关进程(process)线程(thread)的区别可以参考:

多进程

参考:

※ What’s the difference between ThreadPool vs Pool in Python multiprocessing module

多线程

参考:

目前(2019.03.05)仍缺少multiprocessing.pool.ThreadPool的文档,只能看源码

1
from multiprocessing.pool import ThreadPool

异步IO

参考:

super

在子类中调用父类的某个已经被覆盖的方法,可以:

1
2
3
4
5
6
7
8
class Person:
def fun(self):
print('A')

class Student(Person):
def fun(self):
print('B')
super().fun() # 调用父类

可以在__init__方法中使用super

1
2
3
4
5
6
7
8
class Animal:
def __init__(self, name):
self.name = name

class Cat(Animal):
def __init__(self, name, age): # 确保父类被正确的初始化
super().__init__(name) # Python2 的写法: super(Cat, self).__init__(name)
self.age = age

使用super的方法比下面这种更好:

1
2
3
4
class Cat(Animal):
def __init__(self, name, age):
Animal.__init__(self, name)
self.age = age

具体原因见python3 cookbook.

对于多继承中的super,可以参考:Python: super 没那么简单,注意MRO.

类的特殊方法

这一节基本是 廖雪峰的python教程 - 面向对象编程(高级) 中的内容

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:

1
2
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

__len__()

在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

1
2
3
4
>>> len('ABC')
3
>>> 'ABC'.__len__()
3

我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法:

1
2
3
4
5
6
7
>>> class MyDog(object):
... def __len__(self):
... return 100
...
>>> dog = MyDog()
>>> len(dog)
100

类似的还有__str__()

__slots__()

可以在类外给实例绑定成员变量和成员方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ---绑定变量---
class Student(object):
pass

>>> s = Student()
>>> s.name = 'Michael' # 动态给实例绑定一个变量
>>> print(s.name)
Michael

# ---绑定方法---
>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25

给一个实例绑定的方法,对另一个实例不起作用。为了给所有实例都绑定方法,可以给class绑定方法:

1
2
3
4
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score

给class绑定方法后,所有实例均可调用:

1
2
3
4
5
6
>>> s.set_score(100)
>>> s.score
100
>>> s2.set_score(99)
>>> s2.score
99

通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。

__slots__用来限制实例的属性。如只允许对Student实例添加nameage属性,可以在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

1
2
3
4
5
6
7
8
9
10
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

其他

__str__, __iter__/__next__, __getitem__, __getattr__, __call__,参考定制类

@property

使用@property

类方法和静态方法

类中的方法可以分为:

  • 实例方法:最常用的一种,因为其中往往要用到实例属性(变量)和实例方法,参数一定要带self
  • 类方法:方法用@classmethod装饰,参数不需要带self
  • 静态方法:方法用@staticmethod装饰,参数不需要带self

在类中的函数,如果没有self这样的参数,必须用@classmethod或者@staticmethod来修饰。

类方法

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

bar = 1

def __init__(self):
pass

@classmethod
def class_foo(cls):
print 'Hello, ', cls
print cls.bar

>>> A.class_foo() # 直接通过类来调用方法
Hello, <class '__main__.A'>
1

注意,类方法class_foo的参数不是self,用来代表类,调用时也不需要实际传入,可通过类名直接调用。

静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A(object):

bar = 1

def __init__(self):
pass

@staticmethod
def static_foo():
print 'Hello, ', A.bar

>>> a = A()
>>> a.static_foo()
Hello, 1
>>> A.static_foo()
Hello, 1

静态方法可以被类调用,也可以被实例调用。

静态方法没有 self 和 cls 参数,相当于一个普通的函数。

PyCharm

功能

对选中的代码按Ctrl+Alt+T可以进行分块操作。

设置

当在a.py中导入同目录下的b.py文件时:

1
from . import b

需要在目录下创建一个__init.py文件

如果直接写成:

1
import b

也是可以,但是在PyCharm编辑界面中会出现错误提示,实际上是可以运行的,涉及一个环境变量的问题。

  • 如果在命令行直接运行a.py,系统会默认当前目录已经在环境变量中
  • 在PyCharm中,需要将当前目录添加到环境变量中(右键make_directory as–>sources path将当前工作的文件夹加入source_path),编辑时才不会提示错误(运行时不会提示错误)。

搭配git

project栏目中文件名的含义:

绿色,已经加入版本控制暂未提交;
红色,未加入版本控制;
蓝色,加入版本控制,已提交,有改动;
白色,加入版本控制,已提交,无改动;
灰色:版本控制已忽略文件。

路径问题

当在终端里可以正确运行,在 pycharm 中提示找不到某个脚本文件,一般是 working directory 不对。

Run -> Edit Configuration -> working directory 设置和终端相同的目录。

一些错误

无返回值

print(list.reverse())输出None,因为list.reverse()不返回任何值,应该写成如下形式

1
2
list.reverse()
print(list)

图像显示错误

问题一:

显示全白图像,但是数据不都是255。

错误原因:数据类型错误,cv2.imshow()接收的ndarray类型是np.uint8!

解决方法:

1
2
# float32 -> uint8:
img = img.astype(np.uint8)

问题二:

RGB顺序颠倒。

解决方法:上面有(image[:,:,::-1]).

导入包错误

1
from ../utils import Calc

显示错误,其实不需要/,应该写成:

1
from ..utils import Calc

----------over----------