摘选改善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参数可以禁用断言。
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 中加入了枚举,仅在父类没有任何枚举成员的时候才允许继承
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")') # 嘿嘿嘿
如果确实需要使用eval,建议使用安全性更好的ast.literal_eval。
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中 加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译 执行动态加载,在当前命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中
>>> 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__'])
从上可以看出,对于用户自定义的模块,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
可以看出,else 子句在循环正常结束和循环条件不成立时被执行,由 break 语句中断时不执行,同样,我们可以利用这颗语法糖作用在 while 和 try...except 中。
31、记住函数传参既不是传值也不是引用
正确的说法是传对象(call by object)或传对象的引用(call-by-object-reference),函数参数在传递过程中将整个对象传入,对可变对象的修改在函数外部以及内部都可见,对不可变对象的”修改“往往是通过生成一个新对象然是赋值实现的。
>>> from collections import Counter
>>> some_data = {'a', '', 2, 3, 5, 'c', '', 4, 5, 'd', 'b'}
>>> Counter(some_data)
Counter({'',: 1, 2: 1, 3: 1, 4: 1, 5: 1, '': 1, 'b': 1, 'a': 1, 'd': 1, 'c': 1})
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或为负
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()
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)
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 信息
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
显然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
以上代码的原理在于每个类都有一个__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
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')
注意学习 blinker 和 python-message 两个模块。
摘选改善Python程序的91个建议的更多相关文章
- 摘选改善Python程序的91个建议2
62.metaclass stackflow 中文翻译 63.Python对象协议 https://zhuanlan.zhihu.com/p/26760180 ...
- 《编写高质量代码:改善Python程序的91个建议》读后感
编写高质量代码:改善Python程序的91个建议 http://book.douban.com/subject/25910544/ 1.(建议16)is 用于判断两个对象的id是否相等,==才是判断 ...
- 给大家一些改善 Python 程序的 91 个建议
读了一本还不错的书「编写高质量代码改善 Python 程序的 91 个建议」,大多数的建议是真心不错,我虽然写python也有3年多了,但是有些地方确实没去注意过,特地整理了一下,给大家参考. 我已经 ...
- 《改善python程序的91个建议》读书笔记
推荐 <改善Pthon程序的91个建议>是从基本原则.惯用方法.语法.库.设计模式.内部机制.开发工具和性能优化8个方面深入探讨编写高质量python代码的技巧.禁忌和最佳实践. 读书就如 ...
- 编写高质量代码 改善Python程序的91个建议 (读后 小记)
此书是自己好久之前买的,当时总觉得Python语言中有各种trick, 总是要自己猝不及防的掉入到陷阱之中, 看了一些资料后发现了这本书,感觉很是不错,不过可惜自己平时总是杂事太多,总是找不到整块的时 ...
- 改善Python 程序的 91 个建议
建议1.理解Pythonic概念—-详见Python中的<Python之禅> 建议2.编写Pythonic代码 (1)避免不规范代码,比如只用大小写区分变量.使用容易混淆的变量名.害怕过长 ...
- 改善 Python 程序的 91 个建议
1.引论 建议1:理解Pythonic概念—-详见Python中的<Python之禅> 建议2:编写Pythonic代码 避免不规范代码,比如只用大小写区分变量.使用容易混淆的变量名.害怕 ...
- 读书笔记--《编写高质量代码:改善Python程序的91个建议》
第一章 引论 建议写Pythonic式的代码,我理解为充分利用pythonAPI,用最简洁方式写出代码 1.两个变量交换: a, b = b, a 2.翻转list: a = [1, 2, 3, ...
- 编写高质量代码--改善python程序的建议(六)
原文发表在我的博客主页,转载请注明出处! 建议二十八:区别对待可变对象和不可变对象 python中一切皆对象,每一个对象都有一个唯一的标识符(id()).类型(type())以及值,对象根据其值能否修 ...
随机推荐
- wireshark过滤器
一 概况 本文是对wireshark抓包过滤器, 跟显示过滤器的总结 由于一些未知的原因, 这俩过滤器的语法并不一样! 我大概知道为什么不一样了, 因为这俩出现的顺序不一样, 抓包过滤器先出现的, 可 ...
- ngnix和负载均衡
1 准备环境 =====>part1: iptables -F #systemctl disable firewalld #开机默认关闭 #systemctl stop firewalld #立 ...
- Windows10最新更新破坏了PowerShell功能
Java9%E6%8E%A5%E8%BF%91%E4%BA%A4%E4%BB%98%E6%97%A5%E6%9C%9F%E5%92%8C%E8%8C%83%E5%9B%B4%E5%AE%A1%E6%9 ...
- key diff 在input上的踩坑
1.在react-native 遇到一个坑,希望的效果如下,同一个按钮,不同的状态下显示不同的input. 2.然后,在做的时候遇到了这样一个问题.输入我的姓名,点击下一步 3.问题出来了,输入框已经 ...
- Monkey脚本API
Monkey脚本API简介 LaunchActivity(pkg_name, cl_name):启动应用的Activity.参数:包名和启动的Activity. Tap(x, y, tapDurati ...
- LeetCode1-5题
1.两数之和 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个 ...
- Storm实现实时大数据分析(storm介绍,与Hadoop比较,)
一.storm与Hadoop对比 Hadoop: 全量数据处理使用的大多是鼎鼎大名的hadoop或者hive,作为一个批处理系统,hadoop以其吞吐量大.自动容错等优点,在海量数据处理上得到了广泛的 ...
- 设计CUBE by kylin
1.新增或者选择一个已有项目(project)后,将hive数据导入到kylin中,web界面的Model→Data source,导入数据 2.创建数据模型,数据模型是Cube的基础,它主要用于描述 ...
- Vue stage2
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- django 之 ContentType的使用
1. ContentType 是干什么用的呢: 1. ContentType: 主要的作用就是Django orm的创建表的时候,可以方便多表查询使用,简化多表查询的过程 2.ContentType ...