0%

向下取整(//)和取余(%)

两个数相除,传统的除法会得到一个浮点数,向下取整会得到一个整数。

1
2
3
4
5
6
7
>>> minutes = 105
>>> minutes / 60
1.75
>>> minutes // 60
1
>>> minutes % 60
45

取余可以用来获取一个数后一位或者后几位数字,例如 x % 10 可以得到个位数字,x % 100 可以得到后两位数字;还可以检验是否能够被一个数整除。

在 Python 2 中,除法操作符(/)左右的操作对象都是整数时,进行的是向下取整操作,而有一个数是浮点数时,进行的就是浮点数操作。

1
2
3
4
>>> 65 / 3
21
>>> 65.0 / 3
21.666666666666668

无限递归的避免

函数调用自己也是一种合法行为,我们通常称为递归,但是如果一个递归永远达不到退出条件,就会无限的递归下去。

1
2
def recurse():
recurse()

因此写递归函数的时候,一定要加上一个退出的条件,如:

1
2
3
4
5
6
def countdown(n):
if n <= 0:
print('Bye!')
else:
print(n)
countdown(n-1)

该函数会在 n <= 0 的时候退出。

增量开发与函数的简化

通常在编写一个函数的时候会经常遇到输出中间值来验证程序的运行,这些中间值是便于我们调试而产生的,因此这个过程称为增量开发。

例如计算圆的面积:

1
2
3
4
5
def circle_area(xc, yc, xp, yp):
radius = distance(dc, yc, xp, yp) # 计算圆的半径的函数
result = area(radius) # 计算圆的面积
print(radius, result)
return result

这里面,radius 和 result 都是为了方便调试而设置的中间值,因此可以简化该函数:

1
2
def circle_area(xc, yc, xp, yp):
return area(distance(xc, yc, xp, yp))

再比如判断一个数能否被整除:

1
2
3
4
5
def is_divisible(x, y):
if x % y == 0:
return True
else:
return False

可以直接简化为:

1
2
def is_divisible(x, y):
return x % y == 0

函数代码是不是简洁了许多?当然这种情况只适用于这些值不需要的情况,在大型程序中如果这些值还会用到,就不必简化了,那样反而会使程序太过复杂。

首先介绍一个库,Python 中有一个模块叫 turtle,是一个图形库,可以用来画一些简单的形状。我将基于这个图形库教会大家如何做接口设计。

先来创建一个 turtle 对象。

1
2
3
import turtle
bob = turtle.Turtle()
turtle.mainloop()

这段代码就会新建一个窗口,里面包含一个小箭头,这个小箭头就是画图的起点。

下面介绍一下怎么用 turtle 来画图,以下是常用的方法,记住这些就够了。

1
2
3
4
5
6
7
8
9
import turtle
bob = turtle.Turtle() # 创建 turtle 对象
bob.fd(100) # 向前移动 100 个像素
bob.bk(100) # 向后移动 100 个像素
bob.lt(90) # 左转 90 度
bob.rt(90) # 右转 90 度
bob.pu() # 笔尖朝上
bob.pd() # 笔尖朝下
turtle.mainloop()

第一个任务,画一个正方形

当然可以用简单重复的办法来画,事实上新手也是这么做的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import turtle
bob = turtle.Turtle()

bob.fd(100)

bob.rt(90)
bob.fd(100)

bob.rt(90)
bob.fd(100)

bob.rt(90)
bob.fd(100)

turtle.mainloop()

更进一步你想到了用 for 循环:

1
2
3
4
5
6
7
8
import turtle
bob = turtle.Turtle()

for i in range(4):
bob.fd(100)
bob.rt(90)

turtle.mainloop()

(细心的同学可能会发现箭头方向变了)

这时,为了提高代码的可读性和重用性,一般会将代码封装起来,成为一个函数:

1
2
3
4
5
6
def square(t):
for i in range(4):
bob.fd(100)
bob.rt(90)

square(bob)

然后你可能会想了,这正方形的边长都是固定的,我想画一个不一样的怎么办?

第二个任务,画一个多边形

这个时候我们需要泛化

1
2
3
4
5
6
def square(t, length):
for i in range(4):
bob.fd(length)
bob.rt(90)

square(bob, 100)

更进一步,想要任意边数的多边形:

1
2
3
4
5
6
7
8
def polygon(t, n, length):
angle = 360/n
for i in range(n):
bob.fd(length)
bob.rt(angle)

polygon(bob, 7, 70)
polygon(bob, n=7, length=70) # 关键字参数

现在你就知道了,泛化就是给函数添加参数的过程,当参数比较多的时候可以用关键字参数。

第三个任务,画一个圆

这一步要写一个 circle 函数,接受形参 r,表示圆的半径,下面是一个例子:

1
2
3
4
5
6
7
def circle(t, r):
circumference = 2 * math.pi * r # math 是一个数学模块,math.pi 指 π 的值
n = 50
length = circumference / n
polygon(t, n, length)

circle(bob, 70)

这个方案的问题在于,n 是一个定值,我们是通过画很多个边,来近似成一个圆的,我们可以加上一个形参,给用户更多的选择,但是,这样接口就不太整洁。

函数的接口是如何使用函数的说明:有哪些参数?这个函数做什么?返回值是什么?

说一个接口整洁,是说它能够让调用者完成想要完成的事情,而不用关注细节,这里面 n 就属于画圆的细节,下面是另一个例子:

1
2
3
4
5
def circle(t, r):
circumference = 2 * math.pi * r # math 是一个数学模块,math.pi 指 π 的值
n = int(circumference / 3) + 1
length = circumference / n
polygon(t, n, length)

这里面 n 的值是一个接近 circumference / 3 的整数,所以每个边长都近似是 3,已经小到足够画出好看的圆了。

第四个任务,画一个圆弧

写 circle 函数的时候,可以复用 polygon,因为边数很多的正方形就是圆。但是圆弧则不太好办,因此需要修改 polygon 函数:

1
2
3
4
5
6
7
8
9
def arc(t, r, angle):
arc_length = 2 * math.pi * r * angle / 360
n = int(arc_length / 3) + 1
step_length = arc_length / n
step_angle = angle / n

for i in range(n):
t.fd(step_length)
t.lt(step_angle)

你会发现,这个函数下面的部分很像 polygon 函数,但是如果不修改 polygon 的接口,就没办法直接用,我们也可以泛化 polygon 函数,但是那样就不叫 polygon(多边形)了,因此将更泛化的名字叫 polyline(多边线):

1
2
3
4
def polyline(t, n, length, angle):
for i in range(n):
t.fd(length)
t.rt(angle)

现在就可以重写 polygon 和 arc 了:

1
2
3
4
5
6
7
8
9
10
def polygon(t, n, length):
angle = 360 / n
polyline(t, n, length, angle)

def arc(t, r, angle):
arc_length = 2 * math.pi * r * angle / 360
n = int(arc_length / 3) + 1
step_length = arc_length / n
step_angle = angle / n
polyline(t, n, step_length, step_angle)

最后,可以重写 circle,调用 arc:

1
2
def circle(t, r):
arc(t, r, 360)

这个过程,叫重构

而一个设计良好的接口,文档字符串是必不可少的,一般用三引号括起来:

1
2
3
4
5
6
7
8
def polyline(t, n, length, angle):
"""
Draws n line segments with the given length and angle between them. t is a turtle.

"""
for i in range(n):
t.fd(length)
t.rt(angle)

下面我们总结一下:

我们做一个开发计划一般有以下步骤:

  1. 最开始写一些小程序,不需要函数定义;
  2. 将部分功能封装成函数;
  3. 泛化函数,添加合适的形参;
  4. 重复 1 到 3;
  5. 重构程序,如果有相似的代码的话,就做成一个更通用的函数。

今天学到了几个概念,一并总结一下:

  1. 封装就是将部分功能组成一个函数;
  2. 泛化就是给函数添加形参;
  3. 接口需要整洁,形参中不要有关于函数内部细节的参数;
  4. 重构相似的代码,使代码更为通用。

通常在我们在编写 Python 程序的时候,需要什么库我们就直接用 pip 安装了,这种情况下是全局式的安装。我们在创建一个 Python 项目的时候,例如有的项目需要 Python3,有的需要 Python2,为了避免项目依赖的互相影响以及去除不需要的库,通常会通过虚拟环境来解决。

这个虚拟环境其实相当于另外独立出一个 Python 环境,有自己的独立的库,独立的依赖关系,是在开发项目时候的利器。话不多说,下面讲一下使用方法。

阅读全文 »

数据结构

如何将列表,字典等进行合并

列表

  • 使用 +
  • extend
  • 切片
1
2
3
4
5
6
l1 =[1,2,3]
l2 = [4,5,6]
l1 + l2 # 会创建第三个列表
Out[22]: [1, 2, 3, 4, 5, 6]
l1.extend(l2) # 会修改 l1
l1[len(l1):len(l2)] = l2 # 会修改 l1

字典

1
2
3
4
5
6
7
8
9
10
11
12
# 
context = {}
context.update(defaults)
context.update(user)
#
context = defaults.copy()
context.update(user)
#
context = dict(defaults)
context.update(user)
# python 3.5
context = {**defaults, **user}

如何筛选列表、集合、字典中的数据

列表

  • filter 函数
  • 列表解析

列表解析更好,时间更短。

1
2
3
4
5
6
7
8
9
10
11
12
13
# coding:utf-8

from random import randint

data = [randint(-10, 10) for x in xrange(10)]

# filter 函数
data2 = filter(lambda i: i > 0, data)

# 列表解析
data3 = [x for x in data if x > 0]

print data, data2, data3

字典

  • 字典解析
1
2
dictdata = {x: randint(0, 100) for x in xrange(0, 50)}
dictdata2 = {k: v for k, v in dictdata.iteritems() if v >= 60}

集合

  • 集合解析
1
2
setdata = set(data)
setdata2 = {x for x in setdata if x > 0}

如何根据字典中值的大小,对字典中的项排序

  • 使用内置函数 sorted
    1. 使用 zip 将字典转换为元组(将值放在第一个)
    2. 使用 sorted 排序
  • 使用 sorted 函数的 key 参数
1
2
3
4
5
from random import randint
d = {x:randint(60,100) for x in 'xyzabc'}
sorted(zip(d.itervalues(),d.iterkeys()))
# Out[7]: [(65, 'a'), (68, 'x'), (88, 'z'), (91, 'y'), (96, 'b'), (98, 'c')]
sorted(d.iteritems(),key=lambda x:x[1])

如何为元组中的每个元素命名,提高程序可读性

  • 定义类似于其他语言中的枚举类型,也就是定义一系列数值常量
  • 使用标准库中的 collections.namedtuple
1
2
3
4
5
6
7
from collections import namedtuple
student = namedtuple('student',['name','age'])
s1 = student('jim',16) # 位置参数,也可使用关键字参数
s2 = student(name='tom',age=12)
# 可以使用类对象的方式访问数据
s1.name
# Out[19]: 'jim'

如何统计序列中元素的出现频度

问题:

  1. 找到某随机序列中出现次数最高的三个元素
  2. 对某英文文章的单词进行词频统计,找到出现次数最高的 10 个单词
  • 构造字典
  • 使用 collections 下的 Counter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from random import randint
data = [randint(0, 10) for x in xrange(20)]
# 第一种方式
c = dict.fromkeys(data,0)
for x in data:
c[x] += 1
# 然后回到之前的问题,根据字典的值,对字典的键排序
sorted(c.iteritems(),key=lambda x:x[1])

# 第二种方式
from collections import Counter
c2 = Counter(data)
# c2
# Out[33]: Counter({0: 4, 1: 1, 2: 1, 3: 3, 4: 2, 5: 1, 6: 3, 8: 1, 9: 3, 10: 1})
c2.most_common(3) # 输出出现频度最高的三个单词
# Out[8]: [(3, 3), (4, 3), (7, 3)]

如何快速找到多个字典中的公共键

  • 使用字典的 viewkeys 方法
1
2
3
4
5
6
7
from random import randint, sample
s1 = {x: randint(1,4) for x in sample('abcdefg', randint(3,6))}
s2 = {x: randint(1,4) for x in sample('abcdefg', randint(3,6))}
# 取交集
s2.viewkeys() & s1.viewkeys()
# map, reduce
reduce(lambda a, b: a & b, map(dict.viewkeys, [s1, s2]))

如何让字典保持有序

问题:按照创建元素的顺序创建字典

  • 使用 collections 下的 OrderedDict
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 一个简单的竞赛排名系统
from random import randint
from time import time
from collections import OrderedDict

d = OrderedDict()
players = list('ABCDEFGH')
start = time()

for i in xrange(8):
raw_input()
p = players.pop(randint(0, 7 - i))
end = time()
print i + 1, p, end - start
d[p] = (i + 1, end - start)
print
print ' ' * 20
for k in d:
print k, d[k]

如何实现用户的历史记录功能(最多 n 条)

  • 使用 collections 中的 deque(双端循环队列)实现
  • 使用 pickle 序列化保存数据
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
from random import randint
from collections import deque

N = randint(0, 100)
history = deque([], 5)

def guess_digit(k):
if k == N:
print 'right'
return True
if k < N:
print '%s is less than N' % k
else:
print '%s is greater than N' % k
return False

while True:
line = raw_input('please input a number')
if line.isdigit():
k = int(line)
history.append(k)
if guess_digit(k):
break
elif line == 'history':
print list(history)

# 使用 pickle 序列化对象
import pickle
pickle.dump(history,open('history','w'))
pickle.load('history')

迭代器与生成器

参考:https://nvie.com/posts/iterators-vs-generators/

可迭代对象需要通过 iter () 方法实现为迭代器,然后才能通过 next() 迭代。

生成器是特殊(优雅)的迭代器(通过 yield 方法实现迭代器)。

一句话就是,只有迭代器对象才可以迭代,可迭代对象的意思就是可以实现为迭代器对象,然后迭代,生成器就是迭代器。

如何实现可迭代对象和迭代器对象

可迭代对象接口:__iter__(),__getitem__(),__next__()

迭代器对象接口:next()

  • 实现一个迭代器对象,next() 方法
  • 实现一个可迭代对象,__iter__() 方法返回一个迭代器对象
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
from collections import Iterable, Iterator
import requests


class WeatherIterator(Iterator): # 迭代器对象
def __init__(self, cities):
self.cities = cities
self.index = 0

def get_weather(self, city):
r = requests.get(u'http://wthrcdn.etouch.cn/weather_mini?city=' + city)
data = r.json()['data']['forecast'][0]
return '%s: %s, %s ' % (city, data['low'], data['high'])

def next(self):
if self.index == len(self.cities):
raise StopIteration
city = self.cities[self.index]
self.index += 1
return self.get_weather(city)


class WeatherIterable(Iterable): # 可迭代对象
def __init__(self, cities):
self.cities = cities

def __iter__(self):
return WeatherIterator(self.cities)


for x in WeatherIterable([u'北京', u'上海']):
print x

如何使用生成器函数实现可迭代对象

  • 将 __iter__() 方法实现为 yield() 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class PrimeNumbers:  # 生成器实现可迭代对象
def __init__(self, start, end):
self.start = start
self.end = end

def is_primenum(self, k):
if k < 2:
return False
for i in xrange(2, k):
if k % i == 0:
return False
return True

def __iter__(self):
for k in xrange(self.start, self.end + 1):
if self.is_primenum(k):
yield k


for x in PrimeNumbers(1, 100):
print x

如何进行反向迭代以及如何实现反向迭代

问题:实现一个浮点数发生器,根据范围和步长迭代

  • 使用 reversed() 方法

引子:如何将列表反向?

1
2
3
4
5
l = [1, 2, 3, 4, 5]
l.reverse() # 改变了原列表
l[::-1] # 得到了一个新列表
reversed(l) # 反向迭代器
iter(l) # 正向迭代器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class FloatFange:
def __init__(self, start, end, step=0.1):
self.start = start
self.end = end
self.step = step

def __iter__(self):
t = self.start
while t <= self.end:
yield t
t += self.step

def __reversed__(self):
t = self.end
while t >= self.start:
yield t
t -= self.step


for x in reversed(FloatFange(2, 4)): # 反向迭代器
print x

如何对迭代器做切片操作

问题:有某个文本文件,我们想读取其中某范围的内容,如 100-300 行的内容

  • 使用标准库中的 itertools.islice,它能返回一个迭代对象切片的生成器
1
2
3
4
5
6
7
from itertools import islice

f = open('test')
islice(f, 100, 300) # 100-300 行
islice(f, 500) # 前 500 行
islice(f, 100, None) # 100 行到最后
# islice 会消耗迭代对象。也就是其实是都迭代了,但是只不过把不符合要求的抛弃了

如何在一个 for 语句中迭代多个可迭代对象

问题:

  • 例如有两科成绩,想要算出总成绩
  • 例如有三个班的成绩,想要找出大于 90 分的成绩
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from random import randint
from itertools import chain

math = [randint(0, 100) for x in xrange(40)]

english = [randint(0, 100) for x in xrange(40)]

for m, e in zip(math, english): # zip 函数可以迭代多个对象
sum = m + e
# print sum

for s in chain(math,english): # chain 函数可以将多个串联起来
if s > 90:
print s

字符串处理

如何拆分含有多种分隔符的字符串

问题:s = ‘ab;cd,|enf|def,kdjk;kdjfk\dd’ 有多种分隔符,如何处理

  • 连续使用 str.split() 方法,每次处理一种分隔符
  • 使用正则表达式的 re.split() 方法,一次性拆分字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import re

s = 'ab;cd,|enf|def,kdjk;kdjfk sd'


def my_split(s, ds): # 字符串原始的分隔方法,需要循环
res = [s]
print res
for d in ds:
t = []
map(lambda x: t.extend(x.split(d)), res)
res = t

return [x for x in res if x]


print my_split(s, ';, |')

split_pattern = '[,; |]' # 使用正则表达式来分隔
print [x for x in re.split(split_pattern,s) if x]

如何判断字符串 a 是否以字符串 b 开头或者结尾

问题:某文件目录下有一系列文件,例如 .c,.py,.js

  • 使用 str.startwith(),str.endwith(),如果有多个结尾的话,需要用元组传入进去
1
2
3
4
5
6
7
8
import os, stat

[name for name in os.listdir('.') if name.endswith(('.sh', '.py'))]

os.stat('e.py').st_mode # 获取用户权限
stat.S_IXUSR # 用户权限

os.chmod('e.py', os.stat('e.py').st_mode | stat.S_IXUSR) # 修改文件权限

如何调整字符串中文本的格式

问题:例如需要更改 log 文件中的日期表示形式

  • 使用正则表达式 re.sub() 方法做字符串替换,利用正则表达式的捕获组,捕获每个部分的内容,在替换字符串中调整各个捕获组的顺序
1
2
3
4
import re

re.sub('(\d{4})-(\d{2})-(\d{2})', r'\2/\3/\1', file) # 将某一个日志文件中的日期格式修改,1,2,3 代表捕获组的序号
re.sub('(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', r'\g<month>/\g<day>/\g<year>', file) # 第二种方式

如何将多个小字符串拼接成一个大的字符串

问题:

  • 迭代列表,使用 + 拼接字符串。(有巨大的开销浪费,拼接过的字符串只是一个临时的)
  • 使用 join 方法
1
2
3
4
5
6
7
8
9
10
11
s1 = 'ddjfaljfl;'
s2 = 'ddj1222'

s1 + s2 # 运算符重载 + == __add__

l = ['dd', 'kjdlaj', 'kke']
l2 = ''.join(l)
l1 = ['dd', 123, 'dd', 45]
print ['dd', 'kjdlaj', 'kke']
l3 = [str(x) for x in l1] # 如果列表很长,开销巨大
l3 = (str(x) for x in l1) # 推荐使用生成器表达式

如何对字符串进行左,右,居中对齐

问题:

  • 使用字符串的 str.ljust(),str.rjust(),str.center() 对齐
  • 使用 format 方法,传递类似 ‘<20’,’>20’,’^20’ 参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
s.ljust(20)
# Out[12]: 'abc '
s.rjust(20)
# Out[13]: ' abc'
s.center(20)
# Out[14]: ' abc '
format(s,'<20')
# Out[15]: 'abc '
format(s,'>20')
# Out[16]: ' abc'
format(s,'^20')
# Out[17]: ' abc '

w = max(map(len, d.key())) # 找出字典中最长的键
for k in d:
print k.ljust(w), ':', d[k]

如何去掉字符串中不需要的字符

  • 字符串 strip(),lstrip(),rstrip() 方法取掉字符串两端字符
  • 删除单个固定位置的字符,可以使用切片+拼接的方式
  • 字符串的 replace() 方法或正则表达式 re.sub() 删除任意位置字符
  • 字符串 translate() 方法,可以同时删除多种不同字符
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
# 去掉字符串两端的字符
s = ' abc '
s.strip() # 空白
# Out[19]: 'abc'
s = '---dbc+++'
s.strip('-+')
# Out[21]: 'dbc'

# 去掉固定位置字符
s = 'dbc:ddd'
s[:3] + s[4:]
# Out[23]: 'dbcddd'

# 去掉任意位置
s = '/4kdlkj/4'
s.replace('/4','')
# Out[25]: 'kdlkj'
# 或者用正则表达式的 re.sub

# translate 方法,unicode 也有一个
s = 'abc123456xyz'
import string
string.maketrans('abcxyz','xyzabc') # 建立映射表
s.translate(string.maketrans('abcxyz','xyzabc'))
# Out[30]: 'xyz123456abc'
s.translate(None,'ax') # 第一个为空,表示删除字符
# Out[33]: 'bc123456yz'

unicode.translate() # 映射表为一个字典

文件 I/O 操作

如何读写文本文件

关于 python 的编解码,看这一篇就够了:https://foofish.net/why-python-encoding-is-tricky.html

1
2
3
4
5
6
7
8
9
10
11
12
13
s = u'你好'
# python 2
f = open('filename','w')
f.write(s.encode('utf-8')) # 写入
f.read().decode('utf-8') # 读取

# python 3
f = open('filename','rt',encoding='utf-8') # t 指定文本模式
f.write(s)

# tips
# 如果一次性读完了所有文件,需要使用
f.seek(0) # 回到开头

如何处理二进制文件

二进制文件,例如:视频,图片,录音等。

大小端字节

参考:https://blog.csdn.net/hnlyyk/article/details/52541923

大端:低地址存放高字节位

小端:低地址存放低字节位

  • open 函数指定模式 ‘b’ 打开文件
  • 二进制数据可以用 readinto,读入到提前分配好的 buffer 中
  • 解析二进制数据可以使用标准库中的 struct 模块的 unpack 方法
1
2
3
4
5
6
7
8
9
import struct
import array
f = open('demo.wav','rb')
struct.unpack('h','\x01\02') # 小端存储,h 表示 short
struct.unpack('>h','\x01\02') # 大端存储
f.seek(0,2) # 将文件指针移到末尾
f.tell()
n = (f.tell() - 44)/2
buff = array.array('h',(0 for _ in xrange(n))) # 创建数组

如何设置文件的缓冲

问题:

1
2
3
f = open('demo.txt','w',buffering=2048)  # 全缓冲,默认 4096B
f = open('demo.txt','w',buffering=1) # 行缓冲,遇到换行符就写入一次
f = open('demo.txt','w',buffering=0) # 无缓冲

如何将文件映射到内存

  • 使用标准库中的 mmap 模块的 mmap() 函数
1
2
3
4
5
6
7
8
9
10
11
12
import mmap

f = open('demo.bin', 'r+b')
f.fileno() # 文件描述符
m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE)
m[0] # 索引
m[10:20] # 切片
m[0] = '\x88' # 修改文件

# mmap(fileno, length[, flags[, prot[, access[, offset]]]])

mmap.mmap(f.fileno(), mmap.PAGESIZE * 8, access=mmap.ACCESS_WRITE,offset=mmap.PAGESIZE*4)

如何访问文件的状态

  • 系统调用:os, stat 模块
  • os.path 下的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import os, stat
import os.path

s = os.stat('filename') # 跟随符号链接,指向原始文件
s
Out[8]: posix.stat_result(st_mode=33188, st_ino=2373061, st_dev=16777217, st_nlink=1, st_uid=501, st_gid=20, st_size=2064, st_atime=1530613571, st_mtime=1530613571, st_ctime=1530613571)

os.lstat('filename') # 不跟随符号链接
os.fstat('filename') # 打开的是文件描述符
stat.S_ISDIR(s.st_mode)

# os.path
os.path.isdir()
os.path.islink()

如何使用临时文件

1
2
3
4
5
6
7
8
9
10
from tempfile import TemporaryFile, NamedTemporaryFile
f = TemporaryFile() # 在文件系统中找不到
f.write('abcdef'*1000000)
f.seek(0)
f.read(100)
# Out[15]: 'abcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd'
ntf = NamedTemporaryFile() # 可以得到临时文件的路径,可以多个进程访问
ntf.name
Out[17]: '/var/folders/6t/b8j3jt1x3nl5bp70ll6fmqfh0000gn/T/tmpa6DRYC'

数据编码与处理

如何读写 csv 数据

  • 使用标准库 csv 模块,可以使用其中 reader 和 writer 完成 csv 文件读写
1
2
3
4
5
6
import csv
rf = open('pingan.csv','rb')
reader = csv.reader(rf)
wf = open('pingan.csv','wb')
writer = csv.writer(wf)
writer.writewrow() # 按行写

如何读写 json 数据

  • 使用标准库中的 json 模块,其中 loads,dumps 函数可以完成 json 数据的读写
1
2
3
from record import Record  # 录音模块
record = Record(channels=1)
audioData = record.record(2)
1
2
3
4
5
6
7
8
9
import json
l = [1, 2, 'abc', {'name': 'bpb', 'age': 'ddd'}]
s = json.dumps(l,separators=[',', ':'])
# Out[9]: '[1,2,"abc",{"age":"ddd","name":"bpb"}]'
json.dumps(d, sort_keys=True)
json.loads(s)
# 文件
json.dump() # 写入到文件
json.load() # 从文件导入

如何解析简单的 xml 文档

  • 使用标准库中的 xml 模块
1
2
from xml.etree import ElementTree
# 与 xpath 表达式类似

如何构建 xml 文档

1
2
3
4
5
6
7
8
9
10
11
from xml.etree.ElementTree import Element, ElementTree
e = Element('Data')
e.tag
Out[21]: 'Data'
e.set('name', 'abc')
from xml.etree.ElementTree import Element, ElementTree, tostring
tostring(e)
Out[24]: '<Data name="abc" />'
e.text = '123'
tostring(e)
Out[26]: '<Data name="abc">123</Data>'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import csv
from xml.etree.ElementTree import Element, ElementTree
from e2 import pretty # 美化格式的小函数

def csvToXml(fname):
with open(fname, 'rb') as f:
reader = csv.reader(f)
headers = reader.next()

root = Element('Data')
for row in reader:
eRow = Element('Row')
root.append(eRow)
for tag, text in zip(headers, row):
e = Element(tag)
e.text = text
eRow.append(e)

pretty(root)
return ElementTree(root)

et = csvToXml('pingan.csv')
et.write('pingan.xml')

如何读写 excel 文件

  • 使用 xlrd 和 xlwt(不好用)

类与对象

如何派生内置不可变类型并修改实例化行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# coding:utf-8


class IntTuple(tuple):
def __new__(cls, iterable):
g = (x for x in iterable if isinstance(x, int) and x > 0)
return super(IntTuple, cls).__new__(cls, g)

def __init__(self, iterable):
# before
print self
super(IntTuple, self).__init__(iterable)
# after


t = IntTuple([1, -1, 'abc', 6, ['x', 'y'], 3])
print t

如何为创建大量实例节省内存

  • 定义类的 __slots__ 属性,用来声明实例属性名字的列表

普通的实例创建完成之后,会有一个 __dict__ 方法,这个方法可以动态绑定实例,但是也造成了很大的内存开销,所以可以通过 __slot__ 属性,阻止动态绑定实例

如何让对象支持上下文管理器

  • 实现上下文管理协议,需要定义实例的 __enter__, __exit__ 方法,它们分别在 with 开始和结束时被调用
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
67
68
69
70
71
72
73
74
75
76
# 在类中实现
def __enter(self):
#...
#...
# 需要返回值 作为 with .. as .. 的对象
return self

def __exit__(self, exc_type, exc_val, exc_tb):
# 关闭实例
return True # 则不会抛出异常
# 例子
from telnetlib import Telnet
from sys import stdin, stdout
from collections import deque

class TelnetClient(object):
def __init__(self, addr, port=23):
self.addr = addr
self.port = port
self.tn = None

def start(self):
raise Exception('Test')
# user
t = self.tn.read_until('login: ')
stdout.write(t)
user = stdin.readline()
self.tn.write(user)

# password
t = self.tn.read_until('Password: ')
if t.startswith(user[:-1]): t = t[len(user) + 1:]
stdout.write(t)
self.tn.write(stdin.readline())

t = self.tn.read_until('$ ')
stdout.write(t)
while True:
uinput = stdin.readline()
if not uinput:
break
self.history.append(uinput)
self.tn.write(uinput)
t = self.tn.read_until('$ ')
stdout.write(t[len(uinput) + 1:])

def cleanup(self):
pass

def __enter__(self):
self.tn = Telnet(self.addr, self.port)
self.history = deque()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
print 'In __exit__'

self.tn.close()
self.tn = None
with open(self.addr + '_history.txt', 'w') as f:
f.writelines(self.history)
return True

with TelnetClient('127.0.0.1') as client:
client.start()

print 'END'

'''
client = TelnetClient('127.0.0.1')
print '\nstart...'
client.start()
print '\ncleanup'
client.cleanup()
'''

如何创建可管理的对象属性

问题:不想让用户通过属性来管理,形式上是使用属性访问,实际上调用方法

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

class Circle(object):
def __init__(self, radius):
self.radius = radius

def getRadius(self):
return self.radius

def setRadius(self, value):
if not isinstance(value, (int, long, float)):
raise ValueError('wrong type.')
self.radius = float(value)

def getArea(self):
return self.radius ** 2 * pi
R = property(getRadius, setRadius) # get 方法,set 方法

c = Circle(3.2)
c.R
c.R = 23
print c.R

如何让类支持比较操作

  • 比较运算符重载,需要实现以下方法:__lt__, __le__, __gt__, __ge__, __eq__, __ne__
  • 使用标准库下的 functools 下的类装饰器 total_ordering 可以简化此过程
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
# 第一种方法,挨个实现
class Rectangle(object):
def __init__(self, w, h):
self.w = w
self.h = h

def area(self):
return self.w * self.h

def __lt__(self, obj):
print 'in__lt__'
return self.area() < obj.area()

# 第二种方法
from functools import total_ordering
from abc import abstractmethod


@total_ordering # 实现运算符重载
class Shape(object):

@abstractmethod
def area(self): # 抽象接口
pass

# 而实现其他的大于等于,小于等于等的逻辑都是通过小于和等于来实现的,例如,>= 就是 not __lt__
def __lt__(self, obj):
print 'in__lt__'
if not isinstance(obj, Shape):
raise TypeError('obj is not shape')
return self.area() < obj.area()

def __eq__(self, obj):
print 'in__eq__'
if not isinstance(obj, Shape):
raise TypeError('obj is not shape')
return self.area() == obj.area()


class Rectangle(Shape):
def __init__(self, w, h):
self.w = w
self.h = h

def area(self):
return self.w * self.h


class Circle(Shape):
def __init__(self, r):
self.r = r

def area(self):
return self.r ** 2 * 3.14


r1 = Rectangle(5, 3)
r2 = Circle(3)

print r1 < r2 #
print r2 < r1

如何使用描述符对实例属性做类型检查

要求:

  • 可以指定类型
  • 类型不正确时抛出异常
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
class Attr(object):
def __init__(self, name, type_):
self.name = name
self.type_ = type_

def __get__(self, instance, cls):
return instance.__dict__[self.name]

def __set__(self, instance, value):
if not isinstance(value, self.type_):
raise TypeError('expected an %s' % self.type_)
instance.__dict__[self.name] = value

def __delete__(self, instance):
raise AttributeError("can't delete this attr")

class Person(object):
name = Attr('name', str)
age = Attr('age', int)
height = Attr('height', float)
weight = Attr('weight', float)

s = Person()
s.name = 'Bob'
s.age = 17
s.height = 1.82
s.weight = 52.5

如何在环状数据结构中管理内存

  • 使用标准库 weakref,它可以创建一种能访问对象但不增加引用计数的对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import weakref

class Data(object):
def __init__(self, value, owner):
self.owner = weakref.ref(owner) # 弱引用
self.value = value

def __str__(self):
return "%s's data, value is %s" % (self.owner(), self.value) # 引用函数

def __del__(self):
print 'in Data.__del__'

class Node(object):
def __init__(self, value):
self.data = Data(value, self)

def __del__(self):
print 'in Node.__del__'

node = Node(100)
del node
raw_input('wait...')

如何通过实例方法名字的字符串调用方法

  • 使用内置函数 getattr,通过名字在实例上获取方法对象,然后调用
  • 使用标准库 operator 下的 methodcaller 函数调用
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
# 第一种方法
from lib1 import Cirle
from lib2 import Triangle
from lib3 import Rectangle


def getArea(shape):
for name in ('area', 'getArea', 'get_area'):
f = getattr(shape, name, None)
if f:
return f()


shape1 = Circle(2)
shape2 = Triangle(3, 4, 5)
shape3 = Rectangle(6, 4)

shapes = [shape1, shape2, shape3]
print map(getArea, shapes)

# 第二种方法
from operator import methodcaller

s = 'abc123abc456'
print s.find('abc', 4)
print methodcaller('find', 'abc', 4)(s)

多线程与多进程

如何使用多线程

  • IO 密集型
  • CPU 密集型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from threading import Thread


class MyThread(Thread):
def __init__(self, sid):
Thread.__init__(self)
self.sid = sid

def run(self):
handle(self.sid)


threads = []
for i in xrange(1, 11):
t = MyThread(i)
threads.append(t)
t.start()
for t in threads:
t.join()

如何线程间通信

1
from Queue import Queue

如何在线程间进行事件通知

  • 等待事件用 wait
  • 通知事件用 set
1
from threading import Event, Thread

如何使用线程本地数据

如何使用线程池

如何使用多进程

装饰器

如何使用函数装饰器

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
# coding:utf-8

"""
题目 1 计算斐波那契数列 1,1,2,3,5,8,13
"""


def cache(func):
cache = {}

def wrap(*args):
# print args
if args not in cache:
cache[args] = func(*args)
return cache[args]

return wrap


# 常规写法
@cache # 加装饰器
def fibonacci(n):
if n <= 1:
return 1
return fibonacci(n - 1) + fibonacci(n - 2)


# 加缓存
def fibonacci_cache(n, cache=None):
if cache is None:
cache = {}
if n in cache:
return cache[n]
if n <= 1:
return 1
cache[n] = fibonacci_cache(n - 1, cache) + fibonacci_cache(n - 2, cache)
return cache[n]


# 题目 2 一个共有 10 个台阶的楼梯,从下面走到上面,一次只能迈 1-3 个台阶,并且不能后退,走完这个楼梯共有多少种方法
@cache
def climb(n, steps):
count = 0
if n == 0:
count = 1
elif n > 0:
for step in steps:
count += climb(n - step, steps)
return count


# 加缓存

# print fibonacci(50)
# print fibonacci_cache(50)

print climb(20, (1, 2,3))

如何为被装饰的函数保存元数据

参考:https://foofish.net/python-decorator.html

  • functools.wraps

使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring__name__、参数列表,先看例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 装饰器
def logged(func):
def with_logging(*args, **kwargs):
print func.__name__ # 输出 'with_logging'
print func.__doc__ # 输出 None
return func(*args, **kwargs)
return with_logging

# 函数
@logged
def f(x):
"""does some math"""
return x + x * x

logged(f)
1
2
3
4
5
6
7
8
9
10
11
12
13
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ # 输出 'f'
print func.__doc__ # 输出 'does some math'
return func(*args, **kwargs)
return with_logging

@logged
def f(x):
"""does some math"""
return x + x * x

如何定义带参数的装饰器

带参数的装饰器

装饰器还有更大的灵活性,例如带参数的装饰器,在上面的装饰器调用中,该装饰器接收唯一的参数就是执行业务的函数 foo 。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。比如,我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要的日志级别是不一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def use_logging(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
logging.warn("%s is running" % func.__name__)
elif level == "info":
logging.info("%s is running" % func.__name__)
return func(*args)
return wrapper

return decorator

@use_logging(level="warn")
def foo(name='foo'):
print("i am %s" % name)

foo()

上面的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。当我 们使用@use_logging(level="warn")调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。

@use_logging(level="warn")等价于@decorator

如何实现属性可修改的装饰器

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
from functools import wraps
import time, logging
from random import randint


def warn(timeout):
timeout = [timeout]
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
used = time.time() - start
if used > timeout[0]:
msg = '%s, %s, %s' % (func.__name__, used, timeout[0])
logging.warn(msg)
return res

def setTimeout(k):
#nonlocal timeout # python 3 语法
timeout[0] = k

wrapper.setTimeout = setTimeout # 将setTimeout()函数作为wrapper的属性

return wrapper

return decorator


@warn(1.5)
def test():
print 'In test'
while randint(0, 1):
time.sleep(0.5)


for _ in range(30):
test()

test.setTimeout(1)
for _ in range(30):
test()

puppet 安装

master

1
yum install puppet puppet-server 

puppet 配置