摘选改善Python程序的91个建议

1、理解Pythonic概念

Pythonic

Tim Peters 的 《The Zen of Python》相信学过 Python 的都耳熟能详,在交互式环境中输入import this可以查看,其实有意思的是这段 Python 之禅的源码:

d = {}
for c in (65, 97):
    for i in range(26):
        d[chr(i+c)] = chr((i+13) % 26 + c)
 
print "".join([d.get(c, c) for c in s])

书中还举了一个快排的例子:

def quicksort(array):
    less = []
    greater = []
    if len(array) <= 1:
        return array
    pivot =array.pop()
    for x in array:
        if x <= pivot:
            less.append(x)
        else:
            greater.append(x)
    return quicksort(less) + [pivot] + quicksort(greater)

8、利用assert语句来发现问题

>>> y = 2
>>> assert x == y, "not equals"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: not equals
>>> x = 1
>>> y = 2
# 以上代码相当于
>>> if __debug__ and not x == y:
...     raise AssertionError("not equals")
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
AssertionError: not equals

运行时加入-O参数可以禁用断言。

 9、数据交换不推荐使用中间变量
x, y = 1, 2

x, y = y, x

原理:右值创建元组,左值接收元组的对应元素。

10、充分利用Lazy evaluation 的特性

def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

11、理解枚举替代实现的缺陷

  利用Python的动态特性可以实现枚举:

# 方式一
class Seasons:
    Spring, Summer, Autumn, Winter = range(4)
# 方式二
def enum(*posarg, **keysarg):
    return type("Enum", (object,), dict(zip(posarg, range(len(posarg))), **keysarg))
Seasons = enum("Spring", "Summer", "Autumn", Winter=1)
Seasons.Spring
# 方式三
>>> from collections import namedtuple
>>> Seasons = namedtuple('Seasons', 'Spring Summer Autumn Winter')._make(range(4))
>>> Seasons.Spring
0
# 但通过以上方式实现枚举都有不合理的地方
>>> Seasons._replace(Spring=2)                                             │
Seasons(Spring=2, Summer=1, Autumn=2, Winter=3)  
# Python3.4 中加入了枚举,仅在父类没有任何枚举成员的时候才允许继承
View Code

12、不推荐使用type来进行类型检查、而使用isinstance()

 

14、警惕eval()的安全漏洞

# 合理正确地使用
>>> eval("1+1==2")
True
>>> eval('"a"+"b"')
'ab'
# 坏心眼的geek
>>> eval('__import__("os").system("dir")')
Desktop  Documents  Downloads  examples.desktop  Music  Pictures  Public  __pycache__  Templates  Videos
0
>>> eval('__import__("os").system("del * /Q")')     # 嘿嘿嘿
View Code

  如果确实需要使用eval,建议使用安全性更好的ast.literal_eval。

 

19、有节制地使用from ... import 语句
Python 提供三种方式来引入外部模块:import语句、from...import语句以及__import__函数,其中__import__函数显式地将模块的名称作为字符串传递并赋值给命名空间的变量。

使用import需要注意以下几点:

优先使用import a的形式

有节制地使用from a import A

尽量避免使用from a import *

为什么呢?我们来看看 Python 的 import 机制,Python 在初始化运行环境的时候会预先加载一批内建模块到内存中,同时将相关信息存放在sys.modules中,我们可以通过sys.modules.items()查看预加载的模块信息,当加载一个模块时,解释器实际上完成了如下动作:

在sys.modules中搜索该模块是否存在,如果存在就导入到当前局部命名空间,如果不存在就为其创建一个字典对象,插入到sys.modules中

加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译

执行动态加载,在当前命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中
View Code
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__']
>>> import test
testing module import
>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'test']
>>> import sys
>>> 'test' in sys.modules.keys()
True
>>> id(test)
140367239464744
>>> id(sys.modules['test'])
140367239464744
>>> dir(test)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b']
>>> sys.modules['test'].__dict__.keys()
dict_keys(['__file__', '__builtins__', '__doc__', '__loader__', '__package__', '__spec__', '__name__', 'b', 'a', '__cached__'])
View Code

从上可以看出,对于用户自定义的模块,import 机制会创建一个新的 module 将其加入当前的局部命名空间中,同时在 sys.modules 也加入该模块的信息,但本质上是在引用同一个对象,通过test.py所在的目录会多一个字节码文件。

 

20、优先使用absolute import 来导入模块

23、使用else子句简化循环(处理异常)

Python 的 else 子句提供了隐含的对循环是否由 break 语句引发循环结束的判断

>>> def print_prime(n):
...     for i in range(2, n):
...         for j in range(2, i):
...             if i % j == 0:
...                 break
...         else:
...             print('{} is a prime number'.format(i))
... 
>>> print_prime(7)
2 is a prime number
3 is a prime number
5 is a prime number
View Code

可以看出,else 子句在循环正常结束和循环条件不成立时被执行,由 break 语句中断时不执行,同样,我们可以利用这颗语法糖作用在 while 和 try...except 中。

31、记住函数传参既不是传值也不是引用

  正确的说法是传对象(call by object)或传对象的引用(call-by-object-reference),函数参数在传递过程中将整个对象传入,对可变对象的修改在函数外部以及内部都可见,对不可变对象的”修改“往往是通过生成一个新对象然是赋值实现的。

39、使用 Counter 进行计数统计
  常见的计数统计可以使用dict、defaultdict、set和list,不过 Python 提供了一个更优雅的方式:
>>> from collections import Counter
>>> some_data = {'a', '2', 2, 3, 5, 'c', '7', 4, 5, 'd', 'b'}
>>> Counter(some_data)
Counter({'7',: 1, 2: 1, 3: 1, 4: 1, 5: 1, '2': 1, 'b': 1, 'a': 1, 'd': 1, 'c': 1})
View Code

  Counter 类属于字典类的子类,是一个容器对象,用来统计散列对象,支持+、-、&、|,其中&和|分别返回两个 Counter 对象各元素的最小值和最大值。

# 初始化
Counter('success')
Counter(s=3, c=2, e=1, u=1)
Counter({'s': 3, 'c': 2, 'u': 1, 'e': 1})
# 常用方法
list(Counter(some_data).elements())     # 获取 key 值
Counter(some_data).most_common(2)       # 前 N 个出现频率最高的元素以及对应的次数
(Counter(some_data))['y']               # 访问不存在的元素返回 0
c = Counter('success')
c.update('successfully')                # 更新统计值
c.subtract('successfully')              # 统计数相减,允许为0或为负
View Code

41、使用 argparse 处理命令行参数

import argparse
parse = argparse.ArgumentParser()
parse.add_argument('-o', '--output')
parse.add_argument('-v', dest='verbose', action='store_true')
args = parser.parse_args()
View Code

42、使用pandas处理大型CSV文件   教程

reader(csvfile[, dialect='excel'][, fmtparam])  # 读取一个 csv 文件,返回一个 reader 对象
csv.writer(csvfile, dialect='excel', **fmtparams) # 写入 csv 文件
csv.DictWriter(csvfile, fieldnames, restval='', extrasaction='raise', dialect='excel')
常用API

43、一般情况下使用 ElementTree 解析 XML     教程

count = 0
for event, elem in ET.iterparse('test.xml'):
    if event == 'end':
        if elem.tag == 'userid':
            count += 1
    elem.clear()
print(count)
View Code

45、使用 traceback 获取栈信息

  当发生异常,开发人员往往需要看到现场信息,trackback 模块可以满足这个需求

traceback.print_exc()   # 打印错误类型、值和具体的trace信息
traceback.print_exception(type, value, traceback[, limit[, file]])  # 前三个参数的值可以从sys.exc_info()
raceback.print_exc([limit[, file]])         # 同上,不需要传入那么多参数
traceback.format_exc([limit])               # 同 print_exc(),返回的是字符串
traceback.extract_stack([file, [, limit]])  # 从当前栈中提取 trace 信息
View Code

  traceback 模块获取异常相关的数据是通过sys.exc_info()得到的,该函数返回异常类型type、异常value、调用和堆栈信息traceback组成的元组。

同时 inspect 模块也提供了获取 traceback 对象的接口。

50、利用模块实现单例模式

51、用 mixin 模式让程序更加灵活

  模板方法模式就是在一个方法中定义一个算法的骨架,并将一些实现步骤延迟到子类中。模板方法可以使子类在不改变算法结构的情况下,重新定义算法中的某些步骤。看个例子:

class People(object):
    def make_tea(self):
        teapot = self.get_teapot()
        teapot.put_in_tea()
        teapot.put_in_water()
        return teapot
View Code

  显然get_teapot()方法并不需要预先定义,也就是说我们的基类不需要预先申明抽象方法,子类只需要继承 People 类并实现get_teapot(),这给调试代码带来了便利。但我们又想到如果一个子类 StreetPeople 描述的是正走在街上的人,那这个类将不会实现get_teapot(),一调用make_tea()就会产生找不到get_teapot()的 AttributeError,所以此时程序员应该立马想到,随着需求的增多,越来越多的 People 子类会选择不喝茶而喝咖啡,或者是抽雪茄之类的,按照以上的思路,我们的代码只会变得越发难以维护。

  所以我们希望能够动态生成不同的实例:

class UseSimpleTeapot(object):
    def get_teapot(self):
        return SimpleTeapot()

class UseKungfuTeapot(object):
    def get_teapot(self):
        return KungfuTeapot()

class OfficePeople(People, UseSimpleTeapot): pass

class HomePeople(People, UseSimpleTeapot): pass

class Boss(People, UseKungfuTeapot): pass

def simple_tea_people():
    people = People()
    people.__base__ += (UseSimpleTeapot,)
    return people

def coffee_people():
    people = People()
    people.__base__ += (UseCoffeepot,)

def tea_and_coffee_people():
    people = People()
    people.__base__ += (UseSimpleTeapot, UserCoffeepot,)
    return people

def boss():
    people = People()
    people.__base__ += (KungfuTeapot, UseCoffeepot, )
    return people
View Code

  以上代码的原理在于每个类都有一个__bases__属性,它是一个元组,用来存放所有的基类,作为动态语言,Python 中的基类可以在运行中可以动态改变。所以当我们向其中增加新的基类时,这个类就拥有了新的方法,这就是混入mixin。

  利用这个技术我们可以在不修改代码的情况下就可以完成需求:
import mixins   # 把员工需求定义在 Mixin 中放在 mixins 模块

def staff():
    people = People()
    bases = []
    for i in config.checked():
        bases.append(getattr(maxins, i))
    people.__base__ += tuple(bases)
    return people
View Code

52、用发布订阅模式实现松耦合

  发布订阅模式是一种编程模式,消息的发送者不会发送其消息给特定的接收者,而是将发布的消息分为不同的类别直接发布,并不关注订阅者是谁。而订阅者可以对一个或多个类别感兴趣,且只接收感兴趣的消息,并且不关注是哪个发布者发布的消息。要实现这个模式,就需要一个中间代理人 Broker,它维护着发布者和订阅者的关系,订阅者把感兴趣的主题告诉它,而发布者的信息也通过它路由到各个订阅者处。

from collections import defaultdict
route_table = defaultdict(list)
def sub(topic, callback):
    if callback in route_table[topic]:
        return
    route_table[topic].append(callback)

def pub(topic, *args, **kw):
    for func in route_table[topic]:
        func(*args, **kw)
Broker.py

  将以上代码放在 Broker.py 的模块,省去了各种参数检测、优先处理、取消订阅的需求,只向我们展示发布订阅模式的基础实现:

import Broker
def greeting(name):
    print('Hello, {}'.format(name))
Broker.sub('greet', greeting)
Broker.pub('greet', 'LaiYonghao')
View Code

  注意学习 blinker 和 python-message 两个模块。

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a
posted @ 2018-11-15 19:36  web123  阅读(369)  评论(0编辑  收藏  举报