好好学习,天天向上

译|Python的隐藏特性(下)

知乎上有人问了一个问题:Python有哪些新手不会了解的深入细节。 其中的一个答案引用了stackoverflow上的一个问题解答。 鉴于一直在努力摆脱Python小白,决定好好研究下这几个特性,顺手翻译一下下,扩展一下下~~

原文:Hidden features of Python

List stepping

切片操作符中的步长(step)参数。例如:

1
2
3
a = [1,2,3,4,5]
>>> a[::2] # iterate over the whole list in 2-increments
[1,3,5]
特殊例子x[::-1]对'x反转'来说相当有用。
1
2
>>> a[::-1]
[5,4,3,2,1]
### 碎碎念 当然,你可以使用reversed()函数来实现反转。 区别在于,reversed()返回一个迭代器,所以还需要一个额外的步骤来将结果转换成需要的对象类型。 这个特性在判断例如回文的时候灰常有用,一句话搞定
1
True if someseq == someseq[::-1] else False
今天看到了一个很有意思的东西。拿上面的例子来讲,猜猜a[10::]会输出什么呢? 如果你猜是IndexError,那就错了。答案是,[]。但是其实当你试图访问一个超过列表索引值的成员,例如a[10]时,确实会导致IndexError。但是,试图访问一个列表的以超出列表成员数作为开始索引的切片时,却会返回一个空列表.

missing items

从2.5开始,字典就有一个特别的方法__missing__,它在访问不存在的item时被引用:

1
2
3
4
5
6
7
8
9
10
>>> class MyDict(dict):
... def __missing__(self, key):
... self[key] = rv = []
... return rv
...
>>> m = MyDict()
>>> m["foo"].append(1)
>>> m["foo"].append(2)
>>> dict(m)
{'foo': [1, 2]}
还有collections中一个叫做defaultdict的dict子函数跟它灰常像,但这个子类为不存在的item调用了一个无参函数:
1
2
3
4
5
6
>>> from collections import defaultdict
>>> m = defaultdict(list)
>>> m["foo"].append(1)
>>> m["foo"].append(2)
>>> dict(m)
{'foo': [1, 2]}
推荐在传给一个不接收这样的子类的函数前,先将这样的dict转换为常规的dict。许多代码使用d[a_key],然后捕捉keyErrors来检查一个item是否存在,不存在则会在dict中新增一个item。

碎碎念

扩展阅读:defaultdict 和 dict.__missing__

Multi-line Regex

在Python中,你可以将一个正则表达式拆分成多行,命名你的匹配和插入注释。 详细语法举例(参考Dive into Python):

1
2
3
4
5
6
7
8
9
10
11
12
>>> pattern = """
... ^ # beginning of string
... M{0,4} # thousands - 0 to 4 M's
... (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
... # or 500-800 (D, followed by 0 to 3 C's)
... (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
... # or 50-80 (L, followed by 0 to 3 X's)
... (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
... # or 5-8 (V, followed by 0 to 3 I's)
... $ # end of string
... """
>>> re.search(pattern, 'M', re.VERBOSE)
命名匹配举例(参考Regular Expression HOWTO
1
2
3
4
>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'
在字符串连接的帮助下,你还可以详尽编写一个正则表达式而不必使用re.VERBOSE.
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> pattern = (
... "^" # beginning of string
... "M{0,4}" # thousands - 0 to 4 M's
... "(CM|CD|D?C{0,3})" # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's),
... # or 500-800 (D, followed by 0 to 3 C's)
... "(XC|XL|L?X{0,3})" # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's),
... # or 50-80 (L, followed by 0 to 3 X's)
... "(IX|IV|V?I{0,3})" # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's),
... # or 5-8 (V, followed by 0 to 3 I's)
... "$" # end of string
... )
>>> print pattern
"^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$"
### 碎碎念 可以在正则中加注释是很棒的。至少让其他人很容易就看懂了这个正则是干什么的。

Named string formatting

%-格式化接收一个字典(也适用于%i%s等)

1
2
3
4
5
6
7
>>> print "The %(foo)s is %(bar)i." % {'foo': 'answer', 'bar':42}
The answer is 42.

>>> foo, bar = 'question', 123

>>> print "The %(foo)s is %(bar)i." % locals()
The question is 123.
而由于locals()也是一个字典,你可以简单的将其当做一个dict来传递,并从本地变量中获得%-字符串替换。虽然它令人难以接受,但是简化了操作。 新的格式化风格
1
>>> print("The {foo} is {bar}".format(foo='answer', bar=42))
### 碎碎念 看来format方法还是更被人推崇的,可能就是因为%-格式化方法能做的事它都可以做到,并且比%-格式化方法更漂亮的缘故吧。 但是,就速度来说,%-格式化方法会比str.format方法快。

Nested list/generator comprehensions

嵌套列表推导和生成器表达式:

1
2
[(i,j) for i in range(3) for j in range(i) ]    
((i,j) for i in range(4) for j in range(i) )
这些可以替代大块大块的嵌套循环代码

碎碎念

前面也提到过了,这两个是由空间使用上的区别。另外,它们生成的东东也不一样哦~

New types at runtime

1
2
3
4
>>> NewType = type("NewType", (object,), {"x": "hello"})
>>> n = NewType()
>>> n.x
"hello"

它与下面完全一样

1
2
3
4
5
>>> class NewType(object):
>>> x = "hello"
>>> n = NewType()
>>> n.x
"hello"
它可能不是最优用的特性,但知道有这么一个东东也是很棒的。

碎碎念

注意,所有的类都是在运行时创建的。因此,你可以在一个条件,或者在一个函数中使用'class'语句。而type的使用,则是给你提供了一个简洁的动态定义一个生成一堆属性的类的方式。 可以定义一个匿名类。例如:type('', (object,), {'x': 'blah'}) 可以在定义类的同时实例化。例如:x = type("X", (object,), {'val':'Hello'})() 再扩展一下下。type这个东东,还可以用来创建高大上的metaclasses(元类)。

.pth files

为了添加更多的python模块(特别是第三方模块),大部分人似乎都会使用PYTHONPATH环境变量,或者在他们的site-packages目录中增加符号链接或目录。另一种方法是使用*.pth文件。下面是python官方文档的解释: “修改python搜索路径最方便的方法是将一个路径配置文件添加到已经存在于python搜索路径中的目录下,通常是.../site-packages/目录。路径配置文件有.pth扩展名,并且每一行必须包含一个会添加到sys.pah的单一路径。(因为新的路径将会追加到sys.path中,新增的目录下的模块将不会覆盖掉标准模块。这意味着你不能使用这个机制来安装标准模块的修正版本。)”

碎碎念

引用如何方便地给Python环境注册新类库: 原理上, Python 运行环境查找库文件时本质是对 sys.path 列表的遍历,如果我们想给运行环境注册新的类库进来, 要么得用代码给 sys.path 列表增加新路径; 要么得调整 PYTHONPATH 环境变量; 要么就得把库文件复制到已经在 sys.path 设置中的路径中去(比如 site-packages 目录); 最简单的办法是用 .pth 文件来实现。Python 在遍历已知的库文件目录过程中,如果见到一个 .pth 文件,就会将文件中所记录的路径加入到 sys.path 设置中,于是 .pth 文件说指明的库也就可以被 Python 运行环境找到了。 python模块的绿色安装方法:只要将模块路径写入.pth文件中并把此文件放在正确的sys.path路径下即可。

ROT13 Encoding

对源代码来说,当你在代码文件顶部使用正确的编码声明时,ROT13是一种有效的编码。

1
2
3
4
5
#!/usr/bin/env python
# -*- coding: rot13 -*-

cevag "Uryyb fgnpxbiresybj!".rapbqr("rot13")
cevag h"Uryyb fgnpxbiresybj!"
### 碎碎念 上面例子的运行结果都是:Hello stackoverflow! 这个特性貌似在py3k中被移除了~~ 当然,有小伙伴提出来了邪恶的想法,可以用来对付反病毒工具……

Regex Debugging

正则表达式是python的一个很棒的特性,但是调试它们却是一件痛不欲生的事,然而,我们相当容易犯关于正则的错误Orz…… 幸运的是,通过传递一个未公开的实验性的隐藏标记re.DEBUG(实际上是,128)给re.compile函数,python可以打印正则解析树,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> re.compile("^\[font(?:=(?P<size>[-+][0-9]{1,2}))?\](.*?)[/font]",
re.DEBUG)
at at_beginning
literal 91
literal 102
literal 111
literal 110
literal 116
max_repeat 0 1
subpattern None
literal 61
subpattern 1
in
literal 45
literal 43
max_repeat 1 2
in
range (48, 57)
literal 93
subpattern 2
min_repeat 0 65535
any None
in
literal 47
literal 102
literal 111
literal 110
literal 116
一旦你了解了语法,则可以发现错误。在这里,我们可以看到,错误是忘记在[/font]中去掉[]。 当然,你可以把它和任何你想要的标记组合在一起,例如注释正则:
1
2
3
4
5
6
7
8
9
10
>>> re.compile("""
^ # start of a line
\[font # the font tag
(?:=(?P<size> # optional [font=+size]
[-+][0-9]{1,2} # size specification
))?
\] # end of tag
(.*?) # text between the tags
\[/font\] # end of the tag
""", re.DEBUG|re.VERBOSE|re.DOTALL)
### 碎碎念 虽然你也可以用128代替re.DEBUG,但是,为了可读性,还是推荐使用re.DEBUG。 另外,推荐一个可以解析正则表达式的网站:regular expressions 101

Sending to Generators

发送值到生成器函数。例如下面这个函数:

1
2
3
4
5
6
7
def mygen():
"""Yield 5 until something else is passed back via send()"""
a = 5
while True:
f = (yield a) #yield a and possibly get f in return
if f is not None:
a = f #store the new value
你可以:
1
2
3
4
5
6
7
8
9
>>> g = mygen()
>>> g.next()
5
>>> g.next()
5
>>> g.send(7) #we send this back to the generator
7
>>> g.next() #now it will yield 7 until we send something else
7

Tab Completion in Interactive Interpreter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
try:
import readline
except ImportError:
print "Unable to load readline module."
else:
import rlcompleter
readline.parse_and_bind("tab: complete")


>>> class myclass:
... def function(self):
... print "my function"
...
>>> class_instance = myclass()
>>> class_instance.<TAB>
class_instance.__class__ class_instance.__module__
class_instance.__doc__ class_instance.function
>>> class_instance.f<TAB>unction()

当然,你需要设置一个环境变量:PYTHONSTARTUP

碎碎念

噢,暂时还看不懂,~~~~(>_<)~~~~

Ternary Expression

1
x = 3 if (y == 1) else 2

上面这个语句会做这样的事:“将x赋值为3,如果y是1,否则将x赋值为2”。注意,括号不是必须的,但是有它们会使得代码更具可读性。如果你有更复杂的需求,也可以将它们串联起来:

1
x = 3 if (y == 1) else 2 if (y == -1) else 1
虽然在某种意义上,有点过分了。 注意,你可以在任何表达式中使用if ... else。例如:
1
(func1 if y == 1 else func2)(arg1, arg2) 
这里,当y为1时,func1将会被调用,否则,func2将会被调用。在这两种情况下,参数arg1和arg2都将会作为被调用的对应的函数的参数。 类似地,下面也是有效的:
1
x = (class1 if y == 1 else class2)(arg1, arg2)
这里,class1和class2是两个类。

碎碎念

常用的还有return 3 if (y == 1) else 2 上面第一个例子还有一种容易让人困惑的等价写法:y == 1 and 3 or 2

try/except/else

1
2
3
4
5
6
7
8
try:
put_4000000000_volts_through_it(parrot)
except Voom:
print "'E's pining!"
else:
print "This parrot is no more!"
finally:
end_sketch()

使用else从句比增加额外的代码在try子句中要好得多,因为它避免了无意中捕获到不是由try...except语句所保护的代码抛出的异常。

碎碎念

else语句块只有当try语句正常执行(也就是说,except语句未执行)的时候,才会执行。 python中的try/except/else/finally语句的语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
try:
Normal execution block
except A:
Exception A handle
except B:
Exception B handle
except:
Other exception handle
else:
if no exception,get here
finally:
this block will be excuted no matter how it goes above
扩展阅读: * python中的try/except/else/finally语句

with statement

PEP 343中引进的context manager是一个将一套语句作为运行时上下文的对象。由于这个特性使用了一些新的关键字,因此它是被逐步推行的:在Python 2.5中通过__future__可用。Python 2.6及以上(包括Python 3)则默认可用。 会经常使用with语句,则是因为它是一个非常有用的结构。下面是一个快速演示:

1
2
3
4
from __future__ import with_statement

with open('foo.txt', 'w') as f:
f.write('hello!')
在这个场景背后,则是with语句在file对象上调用特殊的__enter____exit__方法。如果在with语句体中发生了异常,异常细节也会传给__exit__,因此允许异常处理。 在这个特殊场景下,它为你所做的事是,保证了当执行到了with语句体之外时,file会被关闭,而忽略语句体正常结构或是否抛出了异常。它基本上是一种抽象出公用异常处理代码的方式。 其他常用场景包括线程锁和数据库事务锁。

碎碎念

支持多个with形式,例如:with open('filea') as filea, open('fileb') as fileb:...

扩展阅读: * 浅谈 Python 的 with 语句

请言小午吃个甜筒~~