ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

effective python

2022-01-28 18:32:19  阅读:138  来源: 互联网

标签:__ name effective python 作用域 test time print


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对,会报错没有某个关键字

标签:__,name,effective,python,作用域,test,time,print
来源: https://blog.csdn.net/qq_31504207/article/details/122372885

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有