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 两个模块。