本文是Raymond Hettinger在2013年美国PyCon演讲的笔记(视频, 幻灯片)。

示例代码和引用的语录都来自Raymond的演讲。这是我按我的理解整理出来的,希望你们理解起来跟我一样顺畅!

遍历一个范围内的数字

for i in [0, 1, 2, 3, 4, 5]:

print i ** 2

for i in range(6):

print i ** 2

更好的方法

for i in xrange(6):

print i ** 2

xrange会返回一个迭代器,用来一次一个值地遍历一个范围。这种方式会比range更省内存。xrange在Python 3中已经改名为range。

遍历一个集合

colors = ['red', 'green', 'blue', 'yellow']

for i in range(len(colors)):

print colors[i]

更好的方法

for color in colors:

print color

反向遍历

colors = ['red', 'green', 'blue', 'yellow']

for i in range(len(colors)-1, -1, -1):

print colors[i]

更好的方法

for color in reversed(colors):

print color

遍历一个集合及其下标

colors = ['red', 'green', 'blue', 'yellow']

for i in range(len(colors)):

print i, '--->', colors[i]

更好的方法

for i, color in enumerate(colors):

print i, '--->', color

这种写法效率高,优雅,而且帮你省去亲自创建和自增下标。

当你发现你在操作集合的下标时,你很有可能在做错事。

遍历两个集合

names = ['raymond', 'rachel', 'matthew']

colors = ['red', 'green', 'blue', 'yellow']

n = min(len(names), len(colors))

for i in range(n):

print names[i], '--->', colors[i]

for name, color in zip(names, colors):

print name, '--->', color

更好的方法

for name, color in izip(names, colors):

print name, '--->', color

zip在内存中生成一个新的列表,需要更多的内存。izip比zip效率更高。

注意:在Python 3中,izip改名为zip,并替换了原来的zip成为内置函数。

有序地遍历

colors = ['red', 'green', 'blue', 'yellow']

# 正序

for color in sorted(colors):

print colors

# 倒序

for color in sorted(colors, reverse=True):

print colors

自定义排序顺序

colors = ['red', 'green', 'blue', 'yellow']

def compare_length(c1, c2):

if len(c1) < len(c2): return -1

if len(c1) > len(c2): return 1

return 0

print sorted(colors, cmp=compare_length)

更好的方法

print sorted(colors, key=len)

第一种方法效率低而且写起来很不爽。另外,Python 3已经不支持比较函数了。

调用一个函数直到遇到标记值

blocks = []

while True:

block = f.read(32)

if block == '':

break

blocks.append(block)

更好的方法

blocks = []

for block in iter(partial(f.read, 32), ''):

blocks.append(block)

iter接受两个参数。第一个是你反复调用的函数,第二个是标记值。

译注:这个例子里不太能看出来方法二的优势,甚至觉得partial让代码可读性更差了。方法二的优势在于iter的返回值是个迭代器,迭代器能用在各种地方,set,sorted,min,max,heapq,sum……

在循环内识别多个退出点

def find(seq, target):

found = False

for i, value in enumerate(seq):

if value == target:

found = True

break

if not found:

return -1

return i

更好的方法

def find(seq, target):

for i, value in enumerate(seq):

if value == target:

break

else:

return -1

return i

for执行完所有的循环后就会执行else。

译注:刚了解for-else语法时会困惑,什么情况下会执行到else里。有两种方法去理解else。传统的方法是把for看作if,当for后面的条件为False时执行else。其实条件为False时,就是for循环没被break出去,把所有循环都跑完的时候。所以另一种方法就是把else记成nobreak,当for没有被break,那么循环结束时会进入到else。

遍历字典的key

d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}

for k in d:

print k

for k in d.keys():

if k.startswith('r'):

del d[k]

什么时候应该使用第二种而不是第一种方法?当你需要修改字典的时候。

如果你在迭代一个东西的时候修改它,那就是在冒天下之大不韪,接下来发生什么都活该。

d.keys()把字典里所有的key都复制到一个列表里。然后你就可以修改字典了。

注意:如果在Python 3里迭代一个字典你得显示地写:list(d.keys()),因为d.keys()返回的是一个“字典视图”(一个提供字典key的动态视图的迭代器)。详情请看文档。

遍历一个字典的key和value

# 并不快,每次必须要重新哈希并做一次查找

for k in d:

print k, '--->', d[k]

# 产生一个很大的列表

for k, v in d.items():

print k, '--->', v

更好的方法

for k, v in d.iteritems():

print k, '--->', v

iteritems()更好是因为它返回了一个迭代器。

注意:Python 3已经没有iteritems()了,items()的行为和iteritems()很接近。详情请看文档。

用key-value对构建字典

names = ['raymond', 'rachel', 'matthew']

colors = ['red', 'green', 'blue']

d = dict(izip(names, colors))

# {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}

Python 3: d = dict(zip(names, colors))

用字典计数

colors = ['red', 'green', 'red', 'blue', 'green', 'red']

# 简单,基本的计数方法。适合初学者起步时学习。

d = {}

for color in colors:

if color not in d:

d[color] = 0

d[color] += 1

# {'blue': 1, 'green': 2, 'red': 3}

更好的方法

d = {}

for color in colors:

d[color] = d.get(color, 0) + 1

# 稍微潮点的方法,但有些坑需要注意,适合熟练的老手。

d = defaultdict(int)

for color in colors:

d[color] += 1

用字典分组 — 第I部分和第II部分

names = ['raymond', 'rachel', 'matthew', 'roger',

'betty', 'melissa', 'judith', 'charlie']

# 在这个例子,我们按name的长度分组

d = {}

for name in names:

key = len(name)

if key not in d:

d[key] = []

d[key].append(name)

# {5: ['roger', 'betty'], 6: ['rachel', 'judith'], 7: ['raymond', 'matthew', 'melissa', 'charlie']}

d = {}

for name in names:

key = len(name)

d.setdefault(key, []).append(name)

更好的方法

d = defaultdict(list)

for name in names:

key = len(name)

d[key].append(name)

字典的popitem()是原子的吗?

d = {'matthew': 'blue', 'rachel': 'green', 'raymond': 'red'}

while d:

key, value = d.popitem()

print key, '-->', value

popitem是原子的,所以多线程的时候没必要用锁包着它。

连接字典

defaults = {'color': 'red', 'user': 'guest'}

parser = argparse.ArgumentParser()

parser.add_argument('-u', '--user')

parser.add_argument('-c', '--color')

namespace = parser.parse_args([])

command_line_args = {k: v for k, v in vars(namespace).items() if v}

# 下面是通常的作法,默认使用第一个字典,接着用环境变量覆盖它,最后用命令行参数覆盖它。

# 然而不幸的是,这种方法拷贝数据太疯狂。

d = defaults.copy()

d.update(os.environ)

d.update(command_line_args)

更好的方法

d = ChainMap(command_line_args, os.environ, defaults)

ChainMap在Python 3中加入。高效而优雅。

提高可读性

  • 位置参数和下标很漂亮

  • 但关键字和名称更好

  • 第一种方法对计算机来说很便利

  • 第二种方法和人类思考方式一致

用关键字参数提高函数调用的可读性

twitter_search('@obama', False, 20, True)

更好的方法

twitter_search('@obama', retweets=False, numtweets=20, popular=True)

第二种方法稍微(微秒级)慢一点,但为了代码的可读性和开发时间,值得。

用namedtuple提高多个返回值的可读性

# 老的testmod返回值

doctest.testmod()

# (0, 4)

# 测试结果是好是坏?你看不出来,因为返回值不清晰。

更好的方法

# 新的testmod返回值, 一个namedtuple

doctest.testmod()

# TestResults(failed=0, attempted=4)

namedtuple是tuple的子类,所以仍适用正常的元组操作,但它更友好。

创建一个nametuple

TestResults = namedTuple('TestResults', ['failed', 'attempted'])

unpack序列

p = 'Raymond', 'Hettinger', 0x30, 'python@example.com'

# 其它语言的常用方法/习惯

fname = p[0]

lname = p[1]

age = p[2]

email = p[3]

更好的方法

fname, lname, age, email = p

第二种方法用了unpack元组,更快,可读性更好。

更新多个变量的状态

def fibonacci(n):

x = 0

y = 1

for i in range(n):

print x

t = y

y = x + y

x = t

更好的方法

def fibonacci(n):

x, y = 0, 1

for i in range(n):

print x

x, y = y, x + y

第一种方法的问题

  • x和y是状态,状态应该在一次操作中更新,分几行的话状态会互相对不上,这经常是bug的源头。

  • 操作有顺序要求

  • 太底层太细节

第二种方法抽象层级更高,没有操作顺序出错的风险而且更效率更高。

同时状态更新

tmp_x = x + dx * t

tmp_y = y + dy * t

tmp_dx = influence(m, x, y, dx, dy, partial='x')

tmp_dy = influence(m, x, y, dx, dy, partial='y')

x = tmp_x

y = tmp_y

dx = tmp_dx

dy = tmp_dy

更好的方法

x, y, dx, dy = (x + dx * t,

y + dy * t,

influence(m, x, y, dx, dy, partial='x'),

influence(m, x, y, dx, dy, partial='y'))

效率

  • 优化的基本原则

  • 除非必要,别无故移动数据

  • 稍微注意一下用线性的操作取代O(n**2)的操作

总的来说,不要无故移动数据

连接字符串

names = ['raymond', 'rachel', 'matthew', 'roger',

'betty', 'melissa', 'judith', 'charlie']

s = names[0]

for name in names[1:]:

s += ', ' + name

print s

更好的方法

print ', '.join(names)

更新序列

names = ['raymond', 'rachel', 'matthew', 'roger',

'betty', 'melissa', 'judith', 'charlie']

del names[0]

# 下面的代码标志着你用错了数据结构

names.pop(0)

names.insert(0, 'mark')

更好的方法

names = deque(['raymond', 'rachel', 'matthew', 'roger',

'betty', 'melissa', 'judith', 'charlie'])

# 用deque更有效率

del names[0]

names.popleft()

names.appendleft('mark')

装饰器和上下文管理

  • 用于把业务和管理的逻辑分开

  • 分解代码和提高代码重用性的干净优雅的好工具

  • 起个好名字很关键

  • 记住蜘蛛侠的格言:能力越大,责任越大

使用装饰器分离出管理逻辑

# 混着业务和管理逻辑,无法重用

def web_lookup(url, saved={}):

if url in saved:

return saved[url]

page = urllib.urlopen(url).read()

saved[url] = page

return page

更好的方法

@cache

def web_lookup(url):

return urllib.urlopen(url).read()

注意:Python 3.2开始加入了functools.lru_cache解决这个问题。

分离临时上下文

# 保存旧的,创建新的

old_context = getcontext().copy()

getcontext().prec = 50

print Decimal(355) / Decimal(113)

setcontext(old_context)

更好的方法

with localcontext(Context(prec=50)):

print Decimal(355) / Decimal(113)

译注:示例代码在使用标准库decimal,这个库已经实现好了localcontext。

如何打开关闭文件

f = open('data.txt')

try:

data = f.read()

finally:

f.close()

更好的方法

with open('data.txt') as f:

data = f.read()

如何使用锁

# 创建锁

lock = threading.Lock()

# 使用锁的老方法

lock.acquire()

try:

print 'Critical section 1'

print 'Critical section 2'

finally:

lock.release()

更好的方法

# 使用锁的新方法

with lock:

print 'Critical section 1'

print 'Critical section 2'

分离出临时的上下文

try:

os.remove('somefile.tmp')

except OSError:

pass

更好的方法

with ignored(OSError):

os.remove('somefile.tmp')

ignored是Python 3.4加入的, 文档。

注意:ignored 实际上在标准库叫suppress(译注:contextlib.supress).

试试创建你自己的ignored上下文管理器。

@contextmanager

def ignored(*exceptions):

try:

yield

except exceptions:

pass

把它放在你的工具目录,你也可以忽略异常

译注:contextmanager在标准库contextlib中,通过装饰生成器函数,省去用__enter__和__exit__写上下文管理器。详情请看文档。

分离临时上下文

# 临时把标准输出重定向到一个文件,然后再恢复正常

with open('help.txt', 'w') as f:

oldstdout = sys.stdout

sys.stdout = f

try:

help(pow)

finally:

sys.stdout = oldstdout

更好的写法

with open('help.txt', 'w') as f:

with redirect_stdout(f):

help(pow)

redirect_stdout在Python 3.4加入(译注:contextlib.redirect_stdout), bug反馈。

实现你自己的redirect_stdout上下文管理器。

@contextmanager

def redirect_stdout(fileobj):

oldstdout = sys.stdout

sys.stdout = fileobj

try:

yield fieldobj

finally:

sys.stdout = oldstdout

简洁的单句表达

两个冲突的原则:

  • 一行不要有太多逻辑

  • 不要把单一的想法拆分成多个部分

Raymond的原则:

  • 一行代码的逻辑等价于一句自然语言

列表解析和生成器

result = []

for i in range(10):

s = i ** 2

result.append(s)

print sum(result)

更好的方法

print sum(i**2 for i in xrange(10))

python基础===如何优雅的写代码(转自网络)的更多相关文章

  1. 震惊!!!python可以用中文来写代码

    python可以用中文来写代码 说明: 偶尔间试了一下,python可以用中文来写代码,除了一些python内置函数,和运算符不能用中文外,其它的比如新定义的类名.函数名.变量名,甚至是函数间传的参数 ...

  2. [Python]从哪里开始学习写代码(未完待续)

    预警:这只是我在学习中的一点感受,可能并不完全准确,也不包括面向对象编程的思想(我还不太懂),也有水文的嫌疑,大佬请温和批评指正或者绕道. 计算机语言 语言,是用来交流的.计算机是不能直接听懂人的语言 ...

  3. python基础--小数据池,代码块的最详细、深入剖析

    本文转至太白金星 一,id,is,== 在Python中,id是什么?id是内存地址,那就有人问了,什么是内存地址呢? 你只要创建一个数据(对象)那么都会在内存中开辟一个空间,将这个数据临时加在到内存 ...

  4. 停止使用循环 教你用underscore优雅的写代码

    你一天(一周)内写了多少个循环了? var i; for(i = 0; i < someArray.length; i++) {   var someThing = someArray[i]; ...

  5. python基础知识-8-三元和一行代码(推导式)

    python其他知识目录 1.三元运算(三目运算) 三元运算符就是在赋值变量的时候,可以直接加判断,然后赋值格式:[on_true] if [expression] else [on_false]re ...

  6. Python基础之用PyQt5界面代码分离以及自定义一个槽函数

    最近开发一个项目,需要用到界面,遇到界面不能实时更新的问题,看到网上很多用槽函数,但是大多都是些button的,并不是我需要的,要么就是整数的,后来自己进行尝试,写了一个自定义的槽函数处理treewi ...

  7. Python基础之用tkinter写界面

    参考链接:https://blog.csdn.net/qq_37482202/article/details/84201259 Tkinter介绍 Tkinter(也叫Tk接口)是Tk图形用户界面工具 ...

  8. Python基础之实现界面和代码分离

    第一步:用QT Designer画一个TreeWidget,存为treeview4.ui,这个处理前面TreeWidget那一节讲过,这里不细讲 treeview4.py # -*- coding: ...

  9. Python基础之用PyQt5写一个tabview

    前面学习了menu的画图,现在学习tabview的画图,关于怎么打开designer.exe部分就不详细介绍了. 第一步:拖动一个Tab Widget控件到窗口去. 将控件拖上去之后就是这个样子,默认 ...

随机推荐

  1. 【题解】SCOI2006萌萌哒

    看到这题,首先想到\(n^{2}\)的暴力,就是用并查集暴力合并两个相等的点.但由于这样会导致反复地访问同一个操作,显然是不能够的.于是我们可以联想这题的特殊性质,就是互相连变的点都是一段一段的区间. ...

  2. [洛谷P2921][USACO08DEC]在农场万圣节Trick or Treat on the Farm

    题目大意:给你一张有向图,每个点最多一条出边,问从每个开始,走多少步会到一个已经过的点 题解:$tarjan$缩点,然后建反图$DP$ 卡点:无 C++ Code: #include <cstd ...

  3. 简单谈谈Docker镜像的使用方法_docker

    在上篇文章(在Docker中搭建Nginx服务器)中,我们已经介绍了如何快速地搭建一个实用的Nginx服务器.这次我们将围绕Docker镜像(Docker Image),介绍其使用方法.包括三部分: ...

  4. 洛谷4525 & 4526:【模板】自适应辛普森法——题解

    参考:https://phqghume.github.io/2018/05/19/%E8%87%AA%E9%80%82%E5%BA%94%E8%BE%9B%E6%99%AE%E6%A3%AE%E6%B ...

  5. BZOJ4245 [ONTAK2015]OR-XOR 【贪心】

    题目链接 BZOJ4245 题解 套路① 位运算当然要分位讨论,高位优先 考虑在\(or\)下,如果该位为\(0\),则每一位都为\(0\) 套路② 我们选m段异或和,转化为\(m\)个前缀和的点,且 ...

  6. 内存和cpu

    http://www.blogjava.net/fjzag/articles/317773.html ubuntu@ubuntu-vm:/work/sv-g5-application/projects ...

  7. angularJS新增 品优购新增品牌

    前台代码 brand.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"&g ...

  8. angularJS入门小Demo2 【包含不用数据库而用data.json格式响应前台的ajax请求方式测试】

    事件绑定: <html> <head> <title>angularJS入门小demo-5 事件指令</title> <script src=&q ...

  9. 解密百度图片URL

    今天想爬百度图片搜索,但因为爬出来的链接乱七八糟,有些打不开,对于我这个完美主义者而言,这实在是太残酷,但我还是把爬虫过程的核心部分——解密URL给记录下来了. 下图是捕获的json数据的其中一条数据 ...

  10. Linux下实现文档在线浏览

    使用php实现百度文库功能,网上搜索到的方案,实现doc转pdf,pdf转swf,然后显示出来. 这里简单的记录下,[doc转pdf,pdf转swf]两个功能的搭建流程. doc转pdf 使用到下列程 ...