191个回答

739

链式比较运算符:

>>> 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,但是以更少的输入方式,并且每个术语只被计算一次。


19
其他比较运算符同样适用,这就是为什么人们有时会感到惊讶,比如(5 in [5] is True)的代码结果是False(但从一开始就明确地针对布尔值进行测试是不符合Python习惯的)。 - Miles
1
我不知道有没有这个功能。不过Perl 6有这个功能 :) - ephemient
19
注意等级相同的情况,例如'in'和'='。'A in B == C in D' 的意思是'(A在B中)且(B等于C)且(C在D中)',这可能出乎意料。 - Charles Merriam
2
每个术语只被评估一次。这是关键。 - wilhelmtell
15
Azafe: Lisp的比较天然地按照这种方式工作。这不是一个特殊情况,因为没有其他(合理的)方式来解释(< 1 x 10)。你甚至可以把它们应用于单个参数,像(= 10)一样:http://www.cs.cmu.edu/Groups/AI/html/hyperspec/HyperSpec/Body/fun_eqcm_sleq__lteqcm_gteq.html - Ken
显示剩余8条评论

511

获取Python正则表达式解析树以调试你的正则表达式。

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

幸运的是,Python可以通过向re.compile传递未记录的、实验性的、隐藏的标志re.DEBUG(实际上是 128)来打印正则表达式解析树。

>>> 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
一个输出语法文档的链接会很好。 - Personman
1
这应该是Python的官方一部分,而不是实验性的...正则表达式总是很棘手的,能够追踪发生了什么是非常有帮助的。 - Cahit

459

45
这段时间我一直这样编写代码:for i in range(len(a)): ... 然后使用 a[i] 获取当前项。 - Fernando Martin
4
据我所知,它并未被废弃。 - JAB
1
@Tufa:你可能不想在同一语句中使用索引和项。在这个简单的例子中,你的代码是等效的,但在更复杂的情况下,它将无法执行for inx, itm in enumerate(a):所能做的所有操作。 - Tomas Aschan
15
枚举可以从任意索引开始,不一定是0。例如:'for i, item in enumerate(list, start=1): print i, item' 将从1开始枚举,而不是0。 - dmitry_romanov
3
不在 Py3K 中被弃用。 http://docs.python.org/py3k/library/functions.html?highlight=enumerate#enumerate - Léo Germond
显示剩余8条评论

418

创建生成器对象

如果您编写:

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
特别需要注意的是内存开销的节省。值是按需计算的,因此您永远不会在内存中拥有列表推导式的整个结果。如果您稍后仅迭代列表推导式的部分内容,这将特别有用。 - saffsd
19
在我的看法中,这并不是特别“隐藏的”,但值得注意的是生成器对象不能倒回播放,而列表则可以被反复迭代任意次数。 - susmits
13
生成器的“不可倒回”特性可能会引起一些困惑。具体来说,如果你为了调试而打印生成器的内容,然后稍后再使用它来处理数据,那么它是无法正常工作的。数据被生成、被print()消耗掉,然后在正常处理时不可用。这种情况不适用于列表推导式,因为它们完全存储在内存中。 - johntellsall
4
类似(重复?)的答案:https://dev59.com/oHVD5IYBdhLWcg3wE3No#165138。不过请注意,我在这里链接的答案提到了一份关于生成器强大能力的非常好的演示文稿。你真的应该去看一下。 - Denilson Sá Maia
显示剩余3条评论

352

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
如果没有 lambda,f.read(1) 将被计算(返回一个字符串),然后再传递给 iter 函数。相反,lambda 创建了一个匿名函数并将其传递给 iter。 - jmilloy

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,我发现这样更容易理解。由于它是一个元组,所以是不可变的。 - Robert Rossney
2
@grayger:当执行def语句时,解释器会评估其参数。这将创建(或重新绑定)一个名称到代码对象(函数的套件)。然而,默认参数在定义时被实例化为对象。这对于任何类型的默认对象都是正确的,但仅在对象是可变的时才具有重要意义(暴露可见的语义)。在函数的闭包中没有重新绑定默认参数名称的方法,尽管它显然可以被覆盖为任何调用或整个函数可以被重新定义。 - Jim Dennis
3
@Robert 当然,参数元组可能是不可变的,但它指向的对象不一定是不可变的。 - poolie
16
一个快速的技巧可以使你的初始化代码变得更短:使用 x = x or [] 替代两行的 if 语句。请注意,这样做不会改变原意,只是让代码更易读懂。 - dave mankoff
显示剩余7条评论

316

向生成器函数传递值。例如,有如下函数:

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

89
在其他语言中,我相信这个神奇的设备被称为“变量”。 - finnw
5
协程应该保持协程的特性,生成器也应该保持自身的特性,不要混淆两者。关于这个问题,此链接提供了超棒的参考资料、讲解以及示例:http://www.dabeaz.com/coroutines/。 - u0b34a0f6ae
31
这个例子实现了类似变量的功能。但是,这个特性可以用于很多其他方面...不像变量那样局限。同时,显然可以使用对象实现类似语义(特别是实现 Python 的 call 方法的类)。 - Jim Dennis
4
这个例子对于从未见过(或可能不理解)协程的人来说太琐碎了。 实现平均数而不会有和变量溢出风险的示例是一个很好的例子。 - Prashant Kumar
在这里了解更多关于yield的主题:https://dev59.com/yXVC5IYBdhLWcg3woSpW - gecco
显示剩余2条评论

312

如果你不喜欢使用空格来表示作用域,你可以使用C风格的{},方法如下:

from __future__ import braces

122
那是邪恶的。 :) - Jason Baker
37
from future import braces File "<stdin>", line 1 SyntaxError: not a chance
:P(这是Python中的一个玩笑,告诉你不能通过导入“braces”模块来使用花括号语法。)
- Benjamin W. Smith
40
那是亵渎! - Berk D. Demir
335
我认为我们可能有语法错误,那应该是 "from __past__ import braces",你同意吗? - Bill K
47
从**__cruft__**中导入花括号。 - Phillip B Oldham
显示剩余16条评论

305

切片操作中的步长参数。例如:

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]
- Christian Oudard
3
如何更好地写出 "this is a string"[::-1]?反转似乎并没有帮助。 - Berry Tsakala
24
reversed()函数的问题在于它返回一个迭代器,因此如果您想保留反转序列的类型(元组、字符串、列表、Unicode、自定义类型等等),则需要额外的步骤将其转换回来。 - Rafał Dowgird
6
反转字符串函数:def reverse_string(string): return string[::-1] - pi.
4
我认为如果一个人足够了解如何像您一样定义reverse_string,那么可以在代码中保留[ ::-1 ]并对其含义感到舒适和合适。 - physicsmichael
显示剩余8条评论

288

装饰器

装饰器允许将一个函数或方法包装在另一个函数中,该函数可以添加功能、修改参数或结果等。您可以在函数定义的上面写装饰器,以 "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将装饰器装饰一下。这会创建一个保留函数签名的装饰器,当对其进行内省时会很有帮助。更多信息可以在这里找到:http://www.phyast.pitt.edu/~micheles/python/documentation.html - sirwart
45
这怎么是个隐藏功能? - Vetle
50
大多数简单的 Python 教程中没有涉及到它,而我在开始使用 Python 很久之后才偶然发现了它。我认为这就是一个被隐藏起来的功能,类似于其他热门帖子中提到的内容。 - Dzinx
16
问:vetler,问题要求“了解Python编程语言中较少人知道但有用的功能。”如何衡量“较少人知道但有用的功能”?我的意思是这些回答中哪些特性是隐藏的? 答:如何衡量“较少人知道但有用的功能”并没有明确的标准。这个问题要求列举那些不太被人们广泛知晓,但实际上对编写 Python 程序很有帮助的功能。因此,这些功能可以说是“隐藏”的,因为他们并不是所有人都熟悉的常见功能。 - Johnd
4
这里大部分东西都不算“隐藏的”。 - Humphrey Bogart
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接