Python教程

effective python

本文主要是介绍effective python,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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解释器会按照下面的顺序来遍历各个作用域:

  1. 当前函数的作用域;
  2. 任何外围作用域,(包含当前函数的其他函数)
  3. 包含当前代码块的那个模块的作用域(也叫全局作用域,global scope)
  4. 内置作用域 (包含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提供的底层机制:

  1. 把numbers列表中的每一项输入数据都传给map
  2. 用pickle模块进行序列化,将数据编程二进制;
  3. 通过local socket将序列化之后的数据从主解释器的进程发动到子解释器的进程;
  4. 在子进程中,将序列化数据反序列化,还原成python对象
  5. 引入包含gcd函数的python模块
  6. 各条子进程各自运行gcd
  7. 结果进行序列化操作编程二进制数据
  8. 将二进制字节发送到主进程
  9. 主进程将数据进行反序列化解析成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对,会报错没有某个关键字

这篇关于effective python的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!