Python编程要点
读写文件
python3有两种字符的类型,byte和str,前者的实例包含原始的8位值,后者的实例包含Unicode字符。把unicode字符转化为二进制数据常见方法就是utf-8。unicode 字符转为二进制,用encode方法,反过来,二进制到unicode字符,要用decode。
python2 允许随机像文件中写入一些二进制数据,但是python3是不孕讯这样做的,python3在open函数中设置了名为encoding的新参数,这个新参数的默认值是‘utf-8’。这要求编程者必须在写文件的时候传入包含unicode字符的str,而不是接受byte。但是采用wb 和 rb的方式读写二进制字符。
with open(file, 'wb') as f:
f.write(os.urandom(10))
with open(file, 'rb') as f:
pass
用辅助函数来取代复杂的表达式
复杂表达式虽然正确,但是不易读;
# 辅助函数写法
def get_first_int(values, key, default=0):
found = values.get(key, [''])
if found[0]:
found = int(found[0])
else:
found = default
return found
green = get_first_int(my_values, 'green')
#复杂表达式写法
green = my_values.get('red', [''])[0] or 0
切片
- 切片操作不会计较start 和 end 是否越界;
- 不要写多余的代码,start为0或者end为序列长度时候,省略掉;
- 单次切片,不要同时制定start, end, stride,否则会晦涩难度;
- 尽量要stride是正值,切不带start和end的切割操作;
- 如果一定要切割,步进,那么要拆解成两条语句,一条做范围切割,另一条做步进;如:
b = a[::2] # ['a', 'c', 'e', 'g']
c = b[1:-1] # ['c', 'e']
列表推导
- 字典和set也有和list一样的推导方式, 如:
dic = {rank : name for name, rank in chile_ranks.items()}
chile_len_set = {len(name) for name in chile_ranks.values()}
- 不要使用两个以上的表达式推导
用生成器表达式来改写数据量较大的列表推导
将列表推导所用的写法放在一对圆括号中,构成了生成器表达式;
# list
it = [len(x) for x in open('./tmp_fike')]
# generator
it = (len(x) for x in open('./tmp_file'))
print(next(it))
roots = ((x, x ** 0.5) for x in it)
print(next(roots))
- 生成器表达式返回的生成器,可以逐次产生输出值,避免内存用量问题;
- 把一个生成器表达式返回的生成器,放进另一个生成器表达式的for子表达式里,就可以二者结合起来;
python中的作用域
python解释器会按照下面的顺序来遍历各个作用域:
- 当前函数的作用域;
- 任何外围作用域,(包含当前函数的其他函数)
- 包含当前代码块的那个模块的作用域(也叫全局作用域,global scope)
- 内置作用域 (包含len及str等函数的那个作用域)
- 对于定义再某个作用域的闭包来说,他可以引用这些作用域中的变量;
- 使用默认方式对闭包内的变量赋值,不会影响到外围作用域的同名变量;
- 闭包内可以使用nolocal关键字来改变外围作用域的同名变量;
用none和文档字符串来描述具有动态默认值的参数
def decode(data, default={})
def log(message, when=datetime.now())
这两种都是有问题的默认值,因为参数默认值只会被初始化一次,第一次调用的时候,when使用默认值;第二次再调用的时候,when仍旧是第一次的默认值;所以如果要每次都使用新的时间,应该写成下面的形式,用文档字符串来描述:
def log(message, when=None):
""" log a message with a timestamp
Args:
message: Message to print.
when: datetime of when the message occurred.
Default to the present time.
"""
when = datetime.now() if when is None else when
print('%s: %s' %(when, message))
尽量用辅助类来管理数据,而不要用字典和数组
- 不要使用字典的字典,也不要使用过长的元组;
- 当字典变得复杂,要用多个辅助类来简化;
super初始化父类
- 子类调用父类的__init__的问题之一:他的调用顺序并不固定,并非按照写的顺序来执行的;
- 钻石继承的时候,要保证最顶端的父类的__init__只会被调用一次;
- 使用super实现继承,super能够按照标准方法解析顺序来初始化,另外,super保证钻石继承顶端的父类只会被调用一次;
尽量用concurrent.futures 中的 ProcessPoolExecutor 来实现并行计算
python 的全局解释器锁(GIL)使得没办法通过线程来实现真正的并行;
不仅如此,多线程进行计算密集的任务时,甚至会比单线程还要慢,因为线程之间的切换也有时间消耗。
ProcessPoolExecutor, ThreadPoolExecutor这两个的比较时间在下面:
其中ProcessPoolExecutor要更快些,因为这个类会用multiporcessing提供的底层机制:
- 把numbers列表中的每一项输入数据都传给map
- 用pickle模块进行序列化,将数据编程二进制;
- 通过local socket将序列化之后的数据从主解释器的进程发动到子解释器的进程;
- 在子进程中,将序列化数据反序列化,还原成python对象
- 引入包含gcd函数的python模块
- 各条子进程各自运行gcd
- 结果进行序列化操作编程二进制数据
- 将二进制字节发送到主进程
- 主进程将数据进行反序列化解析成python对象
10.每个子进程的结果进行整合,合并成一个返回给调用者。
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time
def gcd(pair):
a, b = pair
for i in range(min(a, b), 0, -1):
if a % i == 0 and b % i == 0:
return i
def test_futures_pool(paris):
start_time = time.time()
pool = ProcessPoolExecutor(max_workers=2)
results = list(pool.map(gcd, paris))
print(results)
print(f'cost time: {time.time() - start_time}')
def test_thread_pool(pairs):
start_time = time.time()
pool = ThreadPoolExecutor(max_workers=2)
results = list(pool.map(gcd, pairs))
print(results)
print(f'cost time: {time.time() - start_time}')
if __name__ == '__main__':
pairs = [(1963309, 2265973), (20305646, 45862136)]
test_thread_pool(pairs)
test_futures_pool(pairs)
'''
results:
[1, 2]
cost time: 1.1633341312408447
[1, 2]
cost time: 1.0789978504180908
'''
args 和 kwargs
*args用来表示函数接收可变长度的非关键字参数列表作为函数的输入。
def test_args(normal_arg, *args):
print("first normal arg:" + normal_arg)
for arg in args:
print("another arg through *args :" + arg)
test_args("normal", "python", "java", "C#")
**kwargs表示函数接收可变长度的关键字参数字典作为函数的输入
def test_kwargs(**kwargs):
if kwargs is not None:
for key in kwargs:
print("{} = {}".format(key, kwargs[key]))
test_kwargs(name="python", value="5")
例子:
def test_args(name, age, gender):
print(f'name: {name}, age: {age}, gender: {gender}')
if __name__ == '__main__':
info = ['Tom', 10, 'male']
test_args(*info)
info = {'name': 'Tom', 'age': 10, 'gender': 'male'}
test_args(**info)
# info不能有多余的key value对,会报错没有某个关键字