Python的隐藏功能


1419

Python编程语言鲜为人知但有用的功能是什么?

  • 尝试将答案限于Python核心。
  • 每个答案一个功能。
  • 给出该功能的示例和简短描述,而不仅仅是指向文档的链接。
  • 使用标题作为第一行标记功能。

答案的快速链接:

Answers:


740

链接比较运算符:

>>> x = 5
>>> 1 < x < 10
True
>>> 10 < x < 20 
False
>>> x < 10 < x*10 < 100
True
>>> 10 > x <= 9
True
>>> 5 == x > 4
True

如果您以为它在做1 < x,它显示为True,然后比较True < 10,它也是True,那么不,那实际上不是什么事情(请参阅最后一个示例。)它实际上是翻译成1 < x and x < 10,和x < 10 and 10 < x * 10 and x*10 < 100,但键入和每个输入较少该术语仅评估一次。


121
那很有帮助。对于所有语言,它应该是标准的。可悲的是,事实并非如此。
stalepretzel

8
您应该添加一些返回false的示例。例如>>> 10 <x <20错误
ShoeLace

19
这也适用于其他比较运算符,这就是为什么人们有时会惊讶为什么代码([5]中的5为True)为False(但是像这样开头的布尔值进行显式测试是毫无道理的)。
英里

19
很好,但是要注意同等的优先级,例如“ in”和“ =”。“ B中的A = = D中的C”表示“(B中的A)和(B == C)和(C中的C)”,这可能是意外的。
Charles Merriam

15
Azafe:Lisp的比较自然是这样的。这不是特例,因为没有其他(合理的)解释方法(< 1 x 10)。您甚至可以将它们应用于单个参数,例如(= 10)cs.cmu.edu/Groups/AI/html/hyperspec/HyperSpec/Body/…–
肯(Ken)

512

获取python regex解析树以调试您的regex。

正则表达式是python的一个很棒的功能,但是调试它们可能会很麻烦,而且很容易使正则表达式出错。

幸运的是,python可以通过将未记录的实验性隐藏标记re.DEBUG(实际上是128)传递给,从而输出正则表达式分析树re.compile

>>> 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]

当然,您可以将其与所需的任何标志(例如带注释的正则表达式)结合使用:

>>> 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)

3
除了使用正则表达式解析HTML之外,这都是缓慢而痛苦的。甚至内置的“ html”解析器模块也不会使用正则表达式来完成工作。而且,如果html模块不令人满意,那么有很多XML / HTML解析器模块可以完成这项工作,而无需重新发明。
BatchyX 2010年

指向有关输出语法的文档的链接会很棒。
Personman 2010年

1
这应该是Python的正式部分,而不是实验性的部分。RegEx总是很棘手,并且能够跟踪正在发生的事情确实很有帮助。
Cahit

460

枚举

用enumerate包装一个可迭代对象,它将产生该项目及其索引。

例如:


>>> a = ['a', 'b', 'c', 'd', 'e']
>>> for index, item in enumerate(a): print index, item
...
0 a
1 b
2 c
3 d
4 e
>>>

参考文献:


56
令我惊讶的是,在有关python列表的教程中并未常规涵盖这一点。
Draemon

45
一直以来,我都是这样编码的:对于range(len(a))中的i:...然后使用a [i]获取当前项。
Fernando Martin

4
@Berry Tsakala:据我所知,它尚未被弃用。
JAB

23
天哪,这太棒了。对于xrange(len(a))中的i:一直是我最不喜欢的python习惯用法。
Personman 2010年

15
枚举可以从任意索引开始,不必为0。例如:“对于i,enumerate(list,start = 1)中的项目:print i,item”将从1开始枚举,而不是
0。– dmitry_romanov

419

创建生成器对象

如果你写

x=(n for n in foo if bar(n))

您可以找出生成器并将其分配给x。现在,这意味着您可以

for n in x:

这样做的好处是您不需要中间存储,如果需要的话

x = [n for n in foo if bar(n)]

在某些情况下,这可能会导致速度显着提高。

您可以在生成器的末尾附加许多if语句,基本上是复制嵌套的for循环:

>>> n = ((a,b) for a in range(0,2) for b in range(4,6))
>>> for i in n:
...   print i 

(0, 4)
(0, 5)
(1, 4)
(1, 5)

您也可以使用嵌套列表推导,是吗?
shapr

54
特别值得注意的是节省了内存开销。值是按需计算的,因此您永远不会在内存中获得列表理解的全部结果。如果您稍后仅遍历列表理解的一部分,则这是特别理想的。
2009年

19
这不是特别“隐藏”的imo,但值得注意的是,您不能倒带生成器对象,而可以在列表上重复任意次。
2010年

13
发电机的“不倒带”功能可能会引起一些混乱。具体来说,如果您打印生成器的内容以进行调试,然后在以后使用它来处理数据,则它将不起作用。产生的数据由print()消耗,然后不能用于正常处理。这不适用于列表推导,因为它们已完全存储在内存中。
johntellsall,2010年

4
类似的答案(dup?):stackoverflow.com/questions/101268/hidden-features-of-python / ...但是,请注意,我在此处链接的答案提到了有关发电机功率的非常好的陈述。您真的应该检查一下。
DenilsonSáMaia'8

353

iter()可以接受可调用参数

例如:

def seek_next_line(f):
    for c in iter(lambda: f.read(1),'\n'):
        pass

iter(callable, until_value)函数反复调用callable并产生其结果,直到until_value返回为止。


作为python的新手,您能否lambda在这里解释为什么需要关键字?
SiegeX

@SiegeX没有lambda,f.read(1)将在传递给iter函数之前进行评估(返回字符串)。相反,lambda创建一个匿名函数并将其传递给iter。
jmilloy 2011年

339

注意可变的默认参数

>>> def foo(x=[]):
...     x.append(1)
...     print x
... 
>>> foo()
[1]
>>> foo()
[1, 1]
>>> foo()
[1, 1, 1]

相反,您应该使用表示“未给定”的前哨值,并默认将其替换为您想要的可变变量:

>>> def foo(x=None):
...     if x is None:
...         x = []
...     x.append(1)
...     print x
>>> foo()
[1]
>>> foo()
[1]

39
那绝对是最讨厌的隐藏功能之一。我不时遇到它。
Torsten Marek

77
当我知道默认参数存在于作为函数属性的元组中时,我发现这更容易理解foo.func_defaults。作为元组,这是不可变的。
罗伯特·罗斯尼

2
@grayger:执行def语句时,其参数由解释器评估。这将创建(或重新绑定)到代码对象(函数集)的名称。但是,默认参数在定义时被实例化为对象。这适用于默认对象的任何时候,但仅当对象可变时才有意义(公开可见的语义)。尽管显然可以为任何调用覆盖默认参数名称,也可以重新定义整个功能,但无法在函数的闭包中重新绑定该默认参数名称。
Jim Dennis 2010年

3
@Robert当然,参数元组可能是不可变的,但是它指向的对象不一定是不可变的。
poolie 2010年

16
一个快速的技巧可以使您的初始化过程更短:x = x或[]。您可以使用它代替2行if语句。
戴夫·曼科夫

317

将值发送到生成器函数中。例如,具有以下功能:

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

您可以:

>>> 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

同意 让我们把它当作Python中的:)隐藏功能讨厌的例子
拉法尔Dowgird

89
用其他语言,我相信这种神奇的装置被称为“变量”。
finnw,2009年

5
协程应该是协程,并且发生器也应该自己,不要混合。超级链接以及此处的讨论和示例:dabeaz.com/coroutines
u0b34a0f6ae

31
@finnw:该示例实现了类似于变量的功能。但是,该功能可以通过多种其他方式使用...与变量不同。同样明显的是,可以使用对象(尤其是一个类,隐含Python的调用方法)来实现类似的语义。
Jim Dennis 2010年

4
对于从未见过(可能不会理解)例程的人来说,这是一个微不足道的例子。一个实现运行平均值而又不存在总和变量溢出风险的示例是一个很好的例子。
Prashant Kumar

313

如果您不喜欢使用空格来表示作用域,则可以通过发出以下命令来使用C样式的{}:

from __future__ import braces

122
太恶心了 :)
Jason Baker

37
>>>从__future__导入大括号文件“ <stdin>”,第1行SyntaxError:没有机会:P
Benjamin W. Smith Smith

40
这是亵渎!
Berk D. Demir,2009年

335
我认为我们这里可能有一个语法错误,那不应该是“来自__past__导入括号”吗?
比尔K

47
__cruft__进口大括号
Phillip B Oldham

305

切片运算符中的step参数。例如:

a = [1,2,3,4,5]
>>> a[::2]  # iterate over the whole list in 2-increments
[1,3,5]

特殊情况x[::-1]是“ x反转”的有用成语。

>>> a[::-1]
[5,4,3,2,1]

31
在我看来,reversed()函数更加清晰。>>> list(reversed(range(4)))[
3,2,1,0

3
那么如何更好地编写“ this ia string” [::-1]?扭转似乎没有帮助
Berry Tsakala 09年

24
reversed()的问题在于它返回一个迭代器,因此,如果您想保留反向序列的类型(元组,字符串,列表,unicode,用户类型...),则需要一个额外的步骤来将其转换回。
2009年

6
def reverse_string(string):返回字符串[::-1]
pi。

4
@pi我认为,如果您足够了解如何定义reverse_string,那么您可以将[::-1]保留在代码中,并对其含义和适当的感觉感到满意。
PhysicsMichael 2010年

289

装饰工

装饰器允许将一个函数或方法包装在另一个函数中,该函数或方法可以添加功能,修改参数或结果等。您可以在函数定义上方一行以“ at”符号(@)开头编写装饰器。

示例显示了一个print_args装饰器,该装饰器在调用之前打印装饰后的函数的参数:

>>> def print_args(function):
>>>     def wrapper(*args, **kwargs):
>>>         print 'Arguments:', args, kwargs
>>>         return function(*args, **kwargs)
>>>     return wrapper

>>> @print_args
>>> def write(text):
>>>     print text

>>> write('foo')
Arguments: ('foo',) {}
foo

54
定义装饰器时,建议使用@decorator装饰装饰器。它创建一个装饰器,当对它进行自省时,该装饰器将保留函数签名。此处的更多信息:phyast.pitt.edu/~micheles/python/documentation.html
sirwart

45
这是如何隐藏的功能?
Vetle

50
嗯,在大多数简单的Python教程中都没有,它在我开始使用Python很久以后才偶然发现。这就是我所说的隐藏功能,与此处的其他热门帖子几乎相同。
DzinX

16
vetler,这些问题要求“ Python编程语言鲜为人知但有用的功能”。您如何衡量“鲜为人知但有用的功能”?我的意思是这些响应中有什么是隐藏的功能?
约翰德,2009年

4
@vetler这里的大多数事情几乎都不是“隐藏”的。
汉弗莱·鲍嘉

288

for ... else语法(请参阅http://docs.python.org/ref/for.html

for i in foo:
    if i == 0:
        break
else:
    print("i was never 0")

除非调用break,否则“ else”块通常会在for循环的末尾执行。

上面的代码可以模拟如下:

found = False
for i in foo:
    if i == 0:
        found = True
        break
if not found: 
    print("i was never 0")

218
我认为for / else语法很尴尬。如果从未执行循环的主体,就好像应该执行else子句一样“感到”。
codeape

14
啊。没见过那个!但是我必须说这有点用词不当。谁会期望else块仅在break永远不会执行时执行?我同意codeape:好像为空foo输入了else。
达伦·托马斯

52
似乎该关键字应该是最后的关键字,而不是其他关键字
Jiaaro

21
除非最终已经以始终执行该套件的方式使用过。

7
绝对不应该是“ else”。也许是'then'之类的东西,然后是'else',以表示从未执行过循环。
Tor Valamo

258

从2.5开始,字典有一个特殊的方法__missing__用于缺失项的调用:

>>> 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子类,它的功能几乎相同,但是为不存在的项目调用不带参数的函数:

>>> from collections import defaultdict
>>> m = defaultdict(list)
>>> m["foo"].append(1)
>>> m["foo"].append(2)
>>> dict(m)
{'foo': [1, 2]}

我建议在将此类字典传递给不希望此类子类的函数之前,将其转换为常规字典。许多代码使用d[a_key]并捕获KeyErrors来检查是否存在某个项目,从而将新项目添加到dict中。


10
我更喜欢使用setdefault。m = {}; m.setdefault('foo',1)
格雷格

22
@grayger就是这个意思m={}; m.setdefault('foo', []).append(1)
克里斯蒂安·丘皮图

1
但是,在某些情况下,传递defaultdict非常方便。例如,该函数可能会遍历该值,并且它适用于未定义键而无需额外代码的情况,因为默认值为空列表。
玛丽安

3
在某些情况下,defaultdict比setdefault更好,因为除非缺少键,否则它不会创建默认对象。setdefault会创建它,无论它是否丢失。如果您创建默认对象的成本很高,那么这可能会降低性能-仅通过更改所有setdefault调用,我就可以使一个程序获得不错的加速。
Whatang 2011年

2
defaultdictsetdefault在其他情况下也比该方法更强大。例如,对于一个计数器dd = collections.defaultdict(int) ... dd[k] += 1-vs d.setdefault(k, 0) += 1
Mike Graham

247

就地价值交换

>>> a = 10
>>> b = 5
>>> a, b
(10, 5)

>>> a, b = b, a
>>> a, b
(5, 10)

分配的右侧是创建新元组的表达式。作业的左侧立即将(未引用的)元组解压缩到名称ab

分配后,新的元组将不被引用并标记为垃圾回收,并且值绑定到a并且b已经交换。

Python教程中有关数据结构的部分所述

注意,多重分配实际上只是元组打包和序列拆包的组合。


1
这会比传统方式使用更多的实际内存吗?我猜想是因为您正在创建一个元组,而不只是一个交换变量
Nathan 2010年

75
它不会使用更多的内存。它使用较少。.我只是用两种方式编写,然后反编译了字节码。.编译器进行了优化,如您所愿。dis结果表明它正在设置var,然后是ROT_TWOing。ROT_TWO的意思是“交换两个最顶部的堆栈变量”……实际上非常时髦。
皇家

5
您还会无意中指出Python的另一个不错的功能,那就是您可以通过用逗号分隔项目来隐式地创建项目的元组。
asmeurer 2010年

3
Dana the Sane:Python中的赋值是语句,而不是表达式,因此,如果=具有更高的优先级,则该表达式将无效(即,将其解释为a,(b = b),a)。
hbn

5
这是我在这里读到的最少隐藏的功能。不错,但是在每个Python教程中都有明确描述。
Thiago Chaves

235

可读的正则表达式

在Python中,您可以将正则表达式分成多行,命名匹配项并插入注释。

详细语法示例(从Dive into Python):

>>> 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)

命名匹配示例(来自正则表达式HOWTO

>>> p = re.compile(r'(?P<word>\b\w+\b)')
>>> m = p.search( '(((( Lots of punctuation )))' )
>>> m.group('word')
'Lots'

您还可以冗长地编写一个正则表达式,而不必使用re.VERBOSE多亏了字符串文字连接。

>>> 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})$"

7
我不知道我是否真的考虑过Python功能,大多数RE引擎都有详细选项。
杰里米·班克斯

18
是的,但是因为您无法在grep或大多数编辑器中做到这一点,所以很多人都不知道它在那里。其他语言具有等效功能这一事实并不意味着它不是python的有用且鲜为人知的功能
Mark Ba​​ker

7
在一个具有大量优化正则表达式的大型项目中(请阅读:针对机器而非人类进行了优化),我咬紧牙关,将所有这些都转换为冗长的语法。现在,将新开发人员介绍给项目变得容易得多。从现在开始,我们在每个项目上强制执行详细的RE。
Berk D. Demir,2009年

我只想说:数百=“((CM | CD | D?C {0,3})”#900(CM),400(CD)等。该语言已经有一种给事物名称的方法,添加注释的方法以及组合字符串的方法。为什么在这里使用特殊的库语法来处理已经很好的语言呢?它似乎直接与玻璃市的Epigram 9背道而驰。–
肯(Ken)

3
@Ken:regex可能并不总是直接位于源代码中,可以从设置或配置文件中读取。允许注释或仅附加空白(出于可读性)可能会很有帮助。

222

函数参数解压缩

您可以使用*和将列表或字典作为函数参数解压缩**

例如:

def draw_point(x, y):
    # do some magic

point_foo = (3, 4)
point_bar = {'y': 3, 'x': 2}

draw_point(*point_foo)
draw_point(**point_bar)

由于列表,元组和字典广泛用作容器,因此非常有用的快捷方式。


27
*也称为splat运算符
加百利(Gabriel)2010年

3
我喜欢这个功能,但是pylint并不令人遗憾。
斯蒂芬·保罗

5
皮林特的建议不是法律。从2.3开始不推荐使用apply(callable,arg_seq,arg_map)。
Yann Vernier

1
皮林特的建议可能不是法律,但这是很好的建议。过度沉迷于此类代码中的调试代码纯粹是地狱。如原始张贴者所述,这是一个有用的快捷方式
安德鲁

2
我看到它曾经在代码中使用过,不知道它做了什么。不幸的是,很难用Google搜索“ Python **”
Fraser Graham

205

当您在代码文件的顶部使用正确的编码声明时,ROT13是源代码的有效编码:

#!/usr/bin/env python
# -*- coding: rot13 -*-

cevag "Uryyb fgnpxbiresybj!".rapbqr("rot13")

10
大!请注意如何从字面上获取字节字符串,但是解码Unicode字符串:trycevag h"Uryyb fgnpxbiresybj!"
u0b34a0f6ae

12
不幸的是,它已从py3k中删除
mykhal

9
这对于绕过防病毒程序很有用。
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

96
这与编码无关,只是用威尔士语编写的Python。:-P
Olivier Verdier 2010年

33
Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn!
曼努埃尔·

183

以完全动态的方式创建新类型

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

完全一样

>>> class NewType(object):
>>>     x = "hello"
>>> n = NewType()
>>> n.x
"hello"

可能不是最有用的东西,但很高兴知道。

编辑:新类型的固定名称,应NewType与with class语句完全相同。

编辑:调整标题以更准确地描述功能。


8
这具有很大的实用价值,例如JIT ORM
Mark Cidade

8
我用它来基于动态输入生成HTML-Form类。非常好!
pi。

15
注意:所有类均在运行时创建。因此,您可以在条件或函数中使用“ class”语句(对于创建类族或充当闭包的类非常有用)。“类型”带来的改进是能够整齐地定义动态生成的一组属性(或基数)的能力。
spookylukey 2010年

1
您还可以使用空白字符串创建匿名类型,例如:type('',(object,),{'x':'blah'})
bluehavana 2011年

3
对于代码注入可能非常有用。
2011年

179

上下文管理器和“ with”语句

PEP 343中引入的上下文管理器是一个对象,它充当一组语句的运行时上下文。

由于该功能使用了新的关键字,因此逐步引入了该功能:通过__future__指令在Python 2.5中可用。Python 2.6及更高版本(包括Python 3)默认情况下可用。

我经常使用“ with”语句,因为我认为这是一个非常有用的结构,下面是一个快速演示:

from __future__ import with_statement

with open('foo.txt', 'w') as f:
    f.write('hello!')

在幕后发生的事情是,“ with”语句调用了文件对象上的special __enter____exit__method。__exit__如果with语句主体引发了任何异常,则异常详细信息也将传递到该异常,从而允许在那里进行异常处理。

在这种特殊情况下,这为您执行的操作是,它保证在执行超出with套件范围时关闭文件,无论是正常发生还是引发异常。从根本上讲,它是一种抽象通用异常处理代码的方法。

其他常见用例包括使用线程锁定和数据库事务。


3
我不会批准从将来导入任何内容的代码审查。这些功能更可爱而不是有用,通常它们最终会使Python新手感到困惑。
的付费书呆子

6
是的,嵌套作用域和生成器之类的“可爱”功能最好留给知道自己在做什么的人使用。任何想要与未来版本的Python兼容的人。对于嵌套作用域和生成器,Python的“未来版本”分别表示2.2和2.5。对于with语句,Python的“未来版本”表示2.6。
克里斯·B

10
这可能不言而喻,但是使用python v2.6 +,您不再需要从future导入。现在是头等关键字。
fitzgeraldsteele

25
在2.7中,您可以有多个withs:) with open('filea') as filea and open('fileb') as fileb: ...
奥斯丁·理查森

5
@奥斯汀,我无法在2.7上使用该语法。但是,这确实起作用了: with open('filea') as filea, open('fileb') as fileb: ...
2011年

168

字典具有get()方法

字典有一个“ get()”方法。如果执行d ['key']而键不存在,则会出现异常。如果执行d.get('key'),则如果'key'不存在,则返回None。您可以添加第二个参数来取回该项目,而不是无,例如:d.get('key',0)。

这非常适合诸如加号之类的事情:

sum[value] = sum.get(value, 0) + 1


39
另外,请检出setdefault方法。
达伦·托马斯

27
另外,请检出collections.defaultdict类。
jfs

8
如果您使用的是Python 2.7或更高版本,或者3.1或更高版本,请在collections模块中检出Counter类。docs.python.org/library/collections.html#collections.Counter
Elias Zamaria 2010年

噢,伙计,这是我一直在做的事get(key, None)None默认情况下没有提供任何想法。
Jordan Reiter

152

描述符

它们是一大堆Python核心功能背后的魔力。

当您使用点分访问来查找成员(例如xy)时,Python首先在实例字典中查找该成员。如果找不到,它将在类字典中查找。如果它在类字典中找到它,并且该对象实现了描述符协议,而不是仅仅返回它,Python就会执行它。一个描述符是实现任何类__get____set____delete__方法。

这是使用描述符实现自己的(只读)属性版本的方法:

class Property(object):
    def __init__(self, fget):
        self.fget = fget

    def __get__(self, obj, type):
        if obj is None:
            return self
        return self.fget(obj)

您将像内置的property()一样使用它:

class MyClass(object):
    @Property
    def foo(self):
        return "Foo!"

描述符在Python中用于实现属性,绑定方法,静态方法,类方法和插槽等。理解它们可以很容易地弄清为什么以前看起来像Python的“怪癖”的很多东西都是它们的样子。

Raymond Hettinger 的教程很棒,比我做得更好。


这是装饰器的副本,不是吗?(stackoverflow.com/questions/101268/...
gecco

2
不,装饰器和描述符是完全不同的东西,尽管在示例代码中,我正在创建描述符装饰器。:)
尼克·约翰逊

1
另一种方法是使用lambda:foo = property(lambda self: self.__foo)
Pete Peterson

1
@PetePeterson是的,但是property它本身是通过描述符实现的,这就是我的帖子的重点。
尼克·约翰逊

142

条件分配

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

它确实听起来像:“如果y为1,则将3分配给x,否则将2分配给x”。请注意,不需要括号,但是出于可读性考虑,我喜欢它们。如果您有更复杂的东西,也可以将其链接起来:

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

尽管在某个时候,它有点太过分了。

请注意,您可以在任何表达式中使用if ... else。例如:

(func1 if y == 1 else func2)(arg1, arg2) 

如果y为1,则调用func1,否则调用func2。在这两种情况下,将使用参数arg1和arg2调用相应的函数。

类似地,以下内容也有效:

x = (class1 if y == 1 else class2)(arg1, arg2)

其中class1和class2是两个类。


29
任务不是特别的部分。您可以轻松地执行以下操作:如果(y == 1)否则返回
Brian

25
这种替代方法是我第一次看到混淆的Python。
Craig McQueen

3
Kylebrooks:在这种情况下,布尔运算符不会短路。如果bool(3)== False,它将只求值为2。
RoadieRich

15
这种向后风格的编码使我感到困惑。像这样x = ((y == 1) ? 3 : 2)对我来说更有意义
mpen

13
我觉得与@Mark相反,C风格的三元运算符总是让我感到困惑,是在错误条件下评估的是右侧还是右侧?我非常喜欢Python的三元语法。
杰弗里·哈里斯

141

Doctest:同时进行文档编制和单元测试。

从Python文档中提取的示例:

def factorial(n):
    """Return the factorial of n, an exact integer >= 0.

    If the result is small enough to fit in an int, return an int.
    Else return a long.

    >>> [factorial(n) for n in range(6)]
    [1, 1, 2, 6, 24, 120]
    >>> factorial(-1)
    Traceback (most recent call last):
        ...
    ValueError: n must be >= 0

    Factorials of floats are OK, but the float must be an exact integer:
    """

    import math
    if not n >= 0:
        raise ValueError("n must be >= 0")
    if math.floor(n) != n:
        raise ValueError("n must be exact integer")
    if n+1 == n:  # catch a value like 1e300
        raise OverflowError("n too large")
    result = 1
    factor = 2
    while factor <= n:
        result *= factor
        factor += 1
    return result

def _test():
    import doctest
    doctest.testmod()    

if __name__ == "__main__":
    _test()

6
文档测试是肯定很酷,但我真的不喜欢你所键入测试的东西应该抛出一个异常的克鲁夫特
TM。

60
Doctests被高估并污染文档。您多久测试一次没有任何setUp()的独立功能?
的付费书呆子

2
谁说您无法在doctest中进行设置?写一个生成上下文并返回的函数,locals()然后在您的doctest中做locals().update(setUp())= D
Jiaaro

12
如果独立功能需要setUp,则很有可能应将其与一些无关的内容分离或放入类中。然后可以在类方法doctests中重用类doctest命名空间,因此它有点像setUp,仅DRY并且可读。
Andy Mikhaylenko 2010年

4
“您多久测试一次独立功能”-很多。我发现在决定外观时,doctest通常会从设计过程中自然而然地出现。
Gregg Lind

138

命名格式

%-formatting需要一个字典(也适用于%i /%s等。验证)。

>>> 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()也是一个字典,因此您可以简单地将其作为字典传递,并从本地变量中获取%替换。我认为这是不满意的,但可以简化。

新样式格式

>>> print("The {foo} is {bar}".format(foo='answer', bar=42))

60
将被淘汰,并最终由字符串的format()方法代替。
康斯坦丁

3
命名格式对翻译人员非常有用,因为他们倾向于只看到格式字符串而没有上下文的变量名
pixelbeat

2
似乎在python 3.0.1中工作(需要在打印调用周围添加括号)。
Pasi Savolainen'2009年

9
一个散列,是吧?我知道你来自哪里。
shylent

11
%s格式不会被淘汰。str.format()当然是更pythonic的,但是对于简单的字符串替换来说实际上慢了十倍。我相信%s格式化仍然是最佳做法。
肯尼斯·雷兹

132

为了添加更多的python模块(尤其是第三方模块),大多数人似乎使用PYTHONPATH环境变量,或者在其站点包目录中添加符号链接或目录。另一种方法是使用* .pth文件。这是python官方文档的解释:

“ [修改python搜索路径的最方便的方法是将路径配置文件添加到Python路径上已经存在的目录中,通常是... / site-packages /目录。路径配置文件的扩展名为.pth。 ,并且每行必须包含一个附加到sys.path的路径。(由于新路径附加到sys.path,因此添加目录中的模块不会覆盖标准模块。这意味着您不能使用此机制用于安装标准模块的固定版本。)”


1
我从来没有在setuptools的site-packages目录中的.pth文件和这个想法之间建立连接。太棒了
戴夫·保罗

122

例外else子句:

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语句保护的代码未引发的异常。

参见http://docs.python.org/tut/node10.html


8
+1太棒了。如果在没有输入任何异常块的情况下执行try块,则将输入else块。然后,当然,将执行finally块
inspectorG4dget 2010年

我终于明白了为什么“ else”在那里!谢谢。
taynaron 2010年

继续使用会更有意义,但我想它已经被使用;)
PawełPrażak2010年

请注意,在旧版本的Python2上,您不能同时拥有else:和finally:子句以进行相同的try:阻止
Kevin Horn,

1
正如Kevin Horn提到的,@PawełPrażak,此语法是在Python的初始发行版之后引入的,向现有语言添加新的保留关键字始终是有问题的。这就是为什么通常会重用现有关键字的原因(参见最新的C ++标准中的“ auto”)。
君士坦丁

114

重新引发异常

# Python 2 syntax
try:
    some_operation()
except SomeError, e:
    if is_fatal(e):
        raise
    handle_nonfatal(e)

# Python 3 syntax
try:
    some_operation()
except SomeError as e:
    if is_fatal(e):
        raise
    handle_nonfatal(e)

错误处理程序中不带任何参数的'raise'语句告诉Python重新引发具有完整原始追溯的异常,允许您说“哦,对不起,对不起,我不是要抓住那个,对不起,对不起。 ”

如果您希望打印,存储或摆弄原始回溯,可以通过sys.exc_info()来获取,并像Python一样通过“回溯”模块完成打印。


抱歉,但这是几乎所有语言的众所周知的共同特征。
Lucas S.

6
注意斜体文本。有人会这样做raise e,但不会保留原始的追溯。
哈比人2009年

12
也许更神奇,exc_info = sys.exc_info(); raise exc_info[0], exc_info[1], exc_info[2]等效于此,但是您可以更改这些值(例如,更改异常类型或消息)
ianb 2009年

3
@Lucas S.好吧,我不知道,很高兴它写在这里。
e-satis

我可能在这里展示我的青春,但是我一直在python 2.7中使用python 3语法,没有任何问题
wim 2011年

106

主要信息:)

import this
# btw look at this module's source :)

解密

提姆·彼得斯(Tim Peters)撰写的《 Python之禅》

美丽胜于丑陋。
显式胜于隐式。
简单胜于复杂。
复杂胜于复杂。
扁平比嵌套更好。
稀疏胜于密集。
可读性很重要。
特殊情况还不足以打破规则。
尽管实用性胜过纯度。
错误绝不能默默传递。
除非明确地保持沉默。
面对模棱两可的想法,拒绝猜测的诱惑。应该有一种-最好只有一种-显而易见的方法。
尽管除非您是荷兰人,否则一开始这种方式可能并不明显。
现在总比没有好。
虽然从来没有比这更好正确的现在。
如果实现难以解释,那是个坏主意。
如果实现易于解释,则可能是个好主意。
命名空间是一个很棒的主意-让我们做更多这些吧!


1
知道为什么源是这样加密的吗?只是为了娱乐,还是有其他原因?
MiniQuark

42
源代码的编写方式与禅宗背道而驰!
hasen


2
我已经更新了/usr/lib/python2.6/this.py,用print s.translate("".join(chr(64<i<91 and 65+(i-52)%26 or 96<i<123 and 97+(i-84)%26 or i) for i in range(256)))它替换了旧代码,现在看起来好多了!!:-D
fortran


105

交互式口译员选项卡完成

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环境变量。


2
这是一个非常有用的功能。如此之多,我有一个简单的脚本即可启用它(以及其他一些自省功能):pixelbeat.org/scripts/inpy
pixelbeat

43
IPython为您提供了这一点以及大量其他其他
实用

在pdb提示符下,这比常规的python提示符更有用(因为IPython仍可达到该目的)。但是,这似乎在pdb提示符下不起作用,可能是因为pdb绑定了自己的tab选项卡(用处不大)。我尝试在pdb提示符下调用parse_and_bind(),但仍然无法正常工作。使用IPython获取pdb提示符的替代方法是更多的工作,因此我倾向于不使用它。
haridsv'4

2
@haridsv-- easy_install ipdb那么您可以使用import ipdb; ipdb.set_trace()
Doug Harris 2010年

1
在osx上(我想像其他使用libedit的系统),您必须做readline.parse_and_bind ("bind ^I rl_complete")
Foo Bah

91

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

[(i,j) for i in range(3) for j in range(i) ]    
((i,j) for i in range(4) for j in range(i) )

这些可以替换大量的嵌套循环代码。


“ for range(i)中的j”-这是错字吗?通常,您需要固定的i和j范围。如果您要访问2D数组,则会错过一半的元素。
彼得·吉布森

在此示例中,我不访问任何数组。该代码的唯一目的是表明内部范围内的表达式可以访问外部范围内的表达式。副产品是(x,y)对的列表,使得4> x> y> 0。
拉斐尔·道吉德(RafałDowgird),2009年

2
有点像微积分中的双重积分或双重求和。
Yoo

22
这里要记住的关键点(花了我很长时间才意识到)是,for语句的顺序应按照您希望它们从外部到内部以标准for循环的顺序编写。
sykora 2010年

2
补充一下sykora的评论:假设您是从一堆fors和ifs yield x内部开始的。要将其转换为生成器表达式,x请先移动,删除所有冒号(和yield),然后将整个内容括在括号中。要使列表更完整,请用方括号替换外部括号。
肯·阿诺德

91

set内置运算符重载:

>>> a = set([1,2,3,4])
>>> b = set([3,4,5,6])
>>> a | b # Union
{1, 2, 3, 4, 5, 6}
>>> a & b # Intersection
{3, 4}
>>> a < b # Subset
False
>>> a - b # Difference
{1, 2}
>>> a ^ b # Symmetric Difference
{1, 2, 5, 6}

标准库参考中的更多详细信息:设置类型


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.