通过令人惊讶的片段探索和理解 Python。
翻译:中文 中文 |越南语 Tiếng Việt |西班牙语 | 西班牙语 |韩语 한국어 |俄语 Русский |德语 德语 |添加翻译
其他模式:互动网站|互动笔记本
Python 是一种设计精美的基于解释器的高级编程语言,为我们提供了许多让程序员感到舒适的功能。但有时,Python 代码片段的结果乍一看可能并不明显。
这是一个有趣的项目,试图解释 Python 中一些反直觉的片段和鲜为人知的功能的幕后到底发生了什么。
虽然您在下面看到的一些示例可能不是真正意义上的 WTF,但它们将揭示您可能不知道的 Python 的一些有趣部分。我发现这是学习编程语言内部原理的好方法,我相信您也会发现它很有趣!
如果您是一位经验丰富的 Python 程序员,那么您可以将在第一次尝试中就完成大部分任务视为一项挑战。你可能已经经历过其中一些,而我也许能够唤起你甜蜜的旧回忆! ?
PS:如果您是老读者,您可以在这里了解新的修改(标有星号的示例是最新主要修订中添加的示例)。
那么,我们开始吧...
is
不使用方法is not ...
不是is (not ...)
del
操作goto
,但为什么呢?+=
更快dict
查找速度*dict
*所有示例的结构如下:
▶ 一些奇特的标题
# Set up the code. # Preparation for the magic...输出(Python 版本):
> >> triggering_statement Some unexpected output(可选):一行描述意外输出。
解释:
- 简要解释正在发生的事情以及为什么会发生。
# Set up code # More examples for further clarification (if necessary)输出(Python 版本):
> >> trigger # some example that makes it easy to unveil the magic # some justified output
注意:所有示例均在 Python 3.5.2 交互式解释器上进行测试,除非在输出之前明确指定,否则它们应适用于所有 Python 版本。
在我看来,充分利用这些示例的一个好方法是按顺序阅读它们,并且对于每个示例:
由于某种原因,Python 3.8 的“海象”运算符 ( :=
) 变得相当流行。让我们检查一下,
1.
# Python version 3.8+
> >> a = "wtf_walrus"
> >> a
'wtf_walrus'
> >> a := "wtf_walrus"
File "<stdin>" , line 1
a := "wtf_walrus"
^
SyntaxError : invalid syntax
>> > ( a := "wtf_walrus" ) # This works though
'wtf_walrus'
> >> a
'wtf_walrus'
2.
# Python version 3.8+
> >> a = 6 , 9
> >> a
( 6 , 9 )
> >> ( a := 6 , 9 )
( 6 , 9 )
> >> a
6
> >> a , b = 6 , 9 # Typical unpacking
> >> a , b
( 6 , 9 )
>> > ( a , b = 16 , 19 ) # Oops
File "<stdin>" , line 1
( a , b = 16 , 19 )
^
SyntaxError : invalid syntax
>> > ( a , b := 16 , 19 ) # This prints out a weird 3-tuple
( 6 , 16 , 19 )
>> > a # a is still unchanged?
6
>> > b
16
海象操作员快速复习
海象运算符 ( :=
) 是在 Python 3.8 中引入的,它在您想要为表达式中的变量赋值的情况下非常有用。
def some_func ():
# Assume some expensive computation here
# time.sleep(1000)
return 5
# So instead of,
if some_func ():
print ( some_func ()) # Which is bad practice since computation is happening twice
# or
a = some_func ()
if a :
print ( a )
# Now you can concisely write
if a := some_func ():
print ( a )
输出(> 3.8):
5
5
5
这节省了一行代码,并隐式阻止了调用some_func
两次。
未加括号的“赋值表达式”(使用 walrus 运算符)在顶层受到限制,因此第一个片段的a := "wtf_walrus"
语句中出现SyntaxError
。加上括号后它按预期工作并分配了a
.
与往常一样,不允许将包含=
运算符的表达式加括号。因此(a, b = 6, 9)
中存在语法错误。
Walrus 运算符的语法格式为NAME:= expr
,其中NAME
是有效标识符, expr
是有效表达式。因此,不支持可迭代的打包和解包,这意味着,
(a := 6, 9)
等价于((a := 6), 9)
并最终等价于(a, 9)
(其中a
的值为 6')
> >> ( a := 6 , 9 ) == (( a := 6 ), 9 )
True
> >> x = ( a := 696 , 9 )
> >> x
( 696 , 9 )
> >> x [ 0 ] is a # Both reference same memory location
True
类似地, (a, b := 16, 19)
等价于(a, (b := 16), 19)
它只不过是一个 3 元组。
1.
> >> a = "some_string"
> >> id ( a )
140420665652016
> >> id ( "some" + "_" + "string" ) # Notice that both the ids are same.
140420665652016
2.
> >> a = "wtf"
> >> b = "wtf"
> >> a is b
True
> >> a = "wtf!"
> >> b = "wtf!"
> >> a is b
False
3.
> >> a , b = "wtf!" , "wtf!"
> >> a is b # All versions except 3.7.x
True
> >> a = "wtf!" ; b = "wtf!"
> >> a is b # This will print True or False depending on where you're invoking it (python shell / ipython / as a script)
False
# This time in file some_file.py
a = "wtf!"
b = "wtf!"
print ( a is b )
# prints True when the module is invoked!
4.
输出(<Python3.7)
> >> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
> >> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
有道理,对吧?
'wtf'
将被实习,但''.join(['w', 't', 'f'])
不会被实习)'wtf!'
没有被实习是因为!
。可以在此处找到此规则的 CPython 实现a
和b
设置为"wtf!"
时在同一行中,Python 解释器创建一个新对象,然后同时引用第二个变量。如果你在不同的行上执行此操作,它不会“知道”已经有"wtf!"
作为一个对象(因为"wtf!"
并没有按照上述事实隐式地被保留)。这是编译时优化。此优化不适用于 3.7.x 版本的 CPython(查看此问题以获取更多讨论)。a, b = "wtf!", "wtf!"
是单个语句,而a = "wtf!"; b = "wtf!"
是一行中的两个语句。这解释了为什么a = "wtf!"; b = "wtf!"
,并解释为什么它们在some_file.py
中调用时相同'a'*20
在编译期间被替换为'aaaaaaaaaaaaaaaaaaaa'
以在运行时节省一些时钟周期。常量折叠仅发生在长度小于 21 的字符串中。(为什么?想象一下由于表达式'a'*10**10
生成的.pyc
文件的大小)。这是相同的实现源。 > >> ( False == False ) in [ False ] # makes sense
False
> >> False == ( False in [ False ]) # makes sense
False
> >> False == False in [ False ] # now what?
True
> >> True is False == False
False
> >> False is False is False
True
> >> 1 > 0 < 1
True
> >> ( 1 > 0 ) < 1
False
> >> 1 > ( 0 < 1 )
False
根据 https://docs.python.org/3/reference/expressions.html#comparisons
形式上,如果 a, b, c, ..., y, z 是表达式,op1, op2, ..., opN 是比较运算符,则 a op1 b op2 c ... y opN z 等价于 a op1 b b op2 c 和 ... y opN z,不同之处在于每个表达式最多计算一次。
虽然在上面的示例中,这种行为对您来说可能看起来很愚蠢,但对于a == b == c
和0 <= x <= 100
之类的东西来说,这是非常棒的。
False is False is False
相当于(False is False) and (False is False)
True is False == False
相当于(True is False) and (False == False)
并且由于语句的第一部分 ( True is False
) 计算结果为False
,因此整个表达式计算结果为False
。1 > 0 < 1
相当于(1 > 0) and (0 < 1)
其计算结果为True
。(1 > 0) < 1
等价于True < 1
并且 > >> int ( True )
1
> >> True + 1 #not relevant for this example, but just for fun
2
1 < 1
计算结果为False
is
不使用方法以下是互联网上出现的一个非常著名的例子。
1.
> >> a = 256
> >> b = 256
> >> a is b
True
> >> a = 257
> >> b = 257
> >> a is b
False
2.
> >> a = []
> >> b = []
> >> a is b
False
> >> a = tuple ()
> >> b = tuple ()
> >> a is b
True
3、输出
> >> a , b = 257 , 257
> >> a is b
True
输出(特别是 Python 3.7.x)
> >> a , b = 257 , 257
> >> a is b
False
is
和==
之间的区别
is
运算符检查两个操作数是否引用同一个对象(即,它检查操作数的标识是否匹配)。==
运算符比较两个操作数的值并检查它们是否相同。is
代表引用相等, ==
代表值相等。一个例子来澄清事情, > >> class A : pass
> >> A () is A () # These are two empty objects at two different memory locations.
False
256
是现有对象,但257
不是
当你启动 python 时,将分配从-5
到256
数字。这些数字被经常使用,因此准备好它们是有意义的。
引用自https://docs.python.org/3/c-api/long.html
当前的实现为 -5 到 256 之间的所有整数保留一个整数对象数组,当您创建该范围内的 int 时,您只需返回对现有对象的引用。所以应该可以改变 1 的值。我怀疑在这种情况下 Python 的行为是未定义的。 :-)
> >> id ( 256 )
10922528
> >> a = 256
> >> b = 256
> >> id ( a )
10922528
> >> id ( b )
10922528
> >> id ( 257 )
140084850247312
> >> x = 257
> >> y = 257
> >> id ( x )
140084850247440
> >> id ( y )
140084850247344
这里,解释器在执行y = 257
时不够聪明,无法识别我们已经创建了值257,
因此它继续在内存中创建另一个对象。
类似的优化也适用于其他不可变对象,例如空元组。由于列表是可变的,这就是为什么[] is []
将返回False
而() is ()
将返回True
。这解释了我们的第二个片段。我们继续第三个,
当在同一行中使用相同的值初始化时, a
和b
都引用同一个对象。
输出
> >> a , b = 257 , 257
> >> id ( a )
140640774013296
> >> id ( b )
140640774013296
> >> a = 257
> >> b = 257
> >> id ( a )
140640774013392
> >> id ( b )
140640774013488
当同一行中的 a 和 b 设置为257
时,Python 解释器创建一个新对象,然后同时引用第二个变量。如果您在单独的行上执行此操作,它不会“知道”已经有257
作为对象。
它是一种编译器优化,特别适用于交互式环境。当您在实时解释器中输入两行时,它们会单独编译,因此会单独优化。如果您要在.py
文件中尝试此示例,您将不会看到相同的行为,因为该文件是一次性编译的。这种优化不仅限于整数,它也适用于其他不可变数据类型,例如字符串(检查“字符串是棘手的示例”)和浮点数,
> >> a , b = 257.0 , 257.0
> >> a is b
True
为什么这不适用于 Python 3.7?抽象原因是因为此类编译器优化是特定于实现的(即可能随版本、操作系统等而变化)。我仍在弄清楚到底是什么实施更改导致了该问题,您可以查看此问题以获取更新。
1.
some_dict = {}
some_dict [ 5.5 ] = "JavaScript"
some_dict [ 5.0 ] = "Ruby"
some_dict [ 5 ] = "Python"
输出:
> >> some_dict [ 5.5 ]
"JavaScript"
> >> some_dict [ 5.0 ] # "Python" destroyed the existence of "Ruby"?
"Python"
> >> some_dict [ 5 ]
"Python"
> >> complex_five = 5 + 0j
> >> type ( complex_five )
complex
> >> some_dict [ complex_five ]
"Python"
那么,为什么 Python 无处不在呢?
Python 字典中键的唯一性是通过等价性,而不是同一性。因此,即使5
、 5.0
和5 + 0j
是不同类型的不同对象,但由于它们相等,因此它们不能同时位于同一个dict
(或set
)中。一旦插入其中任何一个,尝试查找任何不同但等效的键将成功并使用原始映射值(而不是因KeyError
失败):
> >> 5 == 5.0 == 5 + 0j
True
> >> 5 is not 5.0 is not 5 + 0j
True
> >> some_dict = {}
> >> some_dict [ 5.0 ] = "Ruby"
> >> 5.0 in some_dict
True
> >> ( 5 in some_dict ) and ( 5 + 0j in some_dict )
True
这在设置项目时也适用。因此,当您执行some_dict[5] = "Python"
时,Python 会找到具有等效键5.0 -> "Ruby"
的现有项目,覆盖其值,并保留原始键。
> >> some_dict
{ 5.0 : 'Ruby' }
> >> some_dict [ 5 ] = "Python"
> >> some_dict
{ 5.0 : 'Python' }
那么我们如何将密钥更新为5
(而不是5.0
)?我们实际上无法就地进行此更新,但我们可以做的是首先删除键( del some_dict[5.0]
),然后设置它( some_dict[5]
)以获取整数5
作为键而不是浮动5.0
,尽管在极少数情况下应该需要它。
Python 如何在包含5.0
的字典中找到5
? Python 在恒定时间内完成此操作,而无需使用哈希函数扫描每个项目。当 Python 在字典中查找键foo
时,它首先计算hash(foo)
(以恒定时间运行)。由于在 Python 中,要求比较相等的对象也具有相同的哈希值(此处的文档),因此5
、 5.0
和5 + 0j
具有相同的哈希值。
> >> 5 == 5.0 == 5 + 0j
True
> >> hash ( 5 ) == hash ( 5.0 ) == hash ( 5 + 0j )
True
注意:反之亦然:具有相等哈希值的对象本身可能不相等。 (这会导致所谓的哈希冲突,并降低哈希通常提供的恒定时间性能。)
class WTF :
pass
输出:
> >> WTF () == WTF () # two different instances can't be equal
False
> >> WTF () is WTF () # identities are also different
False
> >> hash ( WTF ()) == hash ( WTF ()) # hashes _should_ be different as well
True
> >> id ( WTF ()) == id ( WTF ())
True
当id
被调用时,Python 创建了一个WTF
类对象并将其传递给id
函数。 id
函数获取其id
(其内存位置),并丢弃该对象。物体被破坏。
当我们连续两次执行此操作时,Python 也会为第二个对象分配相同的内存位置。由于(在CPython中) id
使用内存位置作为对象id,因此两个对象的id是相同的。
因此,对象的 id 仅在对象的生命周期内是唯一的。在对象被销毁之后或创建之前,其他对象可以具有相同的 id。
但为什么is
运算符的计算结果为False
?让我们看看这个片段。
class WTF ( object ):
def __init__ ( self ): print ( "I" )
def __del__ ( self ): print ( "D" )
输出:
> >> WTF () is WTF ()
I
I
D
D
False
> >> id ( WTF ()) == id ( WTF ())
I
D
I
D
True
正如您可能观察到的,对象被销毁的顺序是造成这里所有差异的原因。
from collections import OrderedDict
dictionary = dict ()
dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
ordered_dict = OrderedDict ()
ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
another_ordered_dict = OrderedDict ()
another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
class DictWithHash ( dict ):
"""
A dict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
class OrderedDictWithHash ( OrderedDict ):
"""
An OrderedDict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
输出
> >> dictionary == ordered_dict # If a == b
True
> >> dictionary == another_ordered_dict # and b == c
True
> >> ordered_dict == another_ordered_dict # then why isn't c == a ??
False
# We all know that a set consists of only unique elements,
# let's try making a set of these dictionaries and see what happens...
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
TypeError : unhashable type : 'dict'
# Makes sense since dict don't have __hash__ implemented, let's use
# our wrapper classes.
> >> dictionary = DictWithHash ()
> >> dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
> >> ordered_dict = OrderedDictWithHash ()
> >> ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
> >> another_ordered_dict = OrderedDictWithHash ()
> >> another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
1
> >> len ({ ordered_dict , another_ordered_dict , dictionary }) # changing the order
2
这是怎么回事?
不及物相等在dictionary
、 ordered_dict
和another_ordered_dict
之间不成立的原因是因为OrderedDict
类中实现__eq__
方法的方式。来自文档
OrderedDict 对象之间的相等测试是顺序敏感的,并实现为
list(od1.items())==list(od2.items())
。OrderedDict
对象和其他 Mapping 对象之间的相等性测试与常规字典一样不区分顺序。
这种行为相等的原因是它允许在使用常规字典的任何地方直接替换OrderedDict
对象。
好的,那么为什么改变顺序会影响生成的set
对象的长度呢?答案只是缺乏不及物平等。由于集合是唯一元素的“无序”集合,因此插入元素的顺序并不重要。但在这种情况下,这确实很重要。让我们稍微分解一下,
> >> some_set = set ()
> >> some_set . add ( dictionary ) # these are the mapping objects from the snippets above
> >> ordered_dict in some_set
True
> >> some_set . add ( ordered_dict )
> >> len ( some_set )
1
> >> another_ordered_dict in some_set
True
> >> some_set . add ( another_ordered_dict )
> >> len ( some_set )
1
> >> another_set = set ()
> >> another_set . add ( ordered_dict )
> >> another_ordered_dict in another_set
False
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
> >> dictionary in another_set
True
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
因此,不一致是由于another_ordered_dict in another_set
为False
,因为ordered_dict
已经存在于another_set
中,并且如之前观察到的, ordered_dict == another_ordered_dict
为False
。
def some_func ():
try :
return 'from_try'
finally :
return 'from_finally'
def another_func ():
for _ in range ( 3 ):
try :
continue
finally :
print ( "Finally!" )
def one_more_func (): # A gotcha!
try :
for i in range ( 3 ):
try :
1 / i
except ZeroDivisionError :
# Let's throw it here and handle it outside for loop
raise ZeroDivisionError ( "A trivial divide by zero error" )
finally :
print ( "Iteration" , i )
break
except ZeroDivisionError as e :
print ( "Zero division error occurred" , e )
输出:
> >> some_func ()
'from_finally'
> >> another_func ()
Finally !
Finally !
Finally !
>> > 1 / 0
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
ZeroDivisionError : division by zero
> >> one_more_func ()
Iteration 0
try
套件中执行return
、 break
或continue
语句时, finally
子句也会在退出时执行。return
语句决定。由于finally
子句总是执行,因此finally
子句中执行的return
语句将始终是最后执行的语句。return
或break
语句,临时保存的异常将被丢弃。 some_string = "wtf"
some_dict = {}
for i , some_dict [ i ] in enumerate ( some_string ):
i = 10
输出:
> >> some_dict # An indexed dict appears.
{ 0 : 'w' , 1 : 't' , 2 : 'f' }
for
语句在 Python 语法中定义为:
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
其中exprlist
是赋值目标。这意味着对迭代中的每个项目执行相当于{exprlist} = {next_value}
的操作。一个有趣的例子可以说明这一点:
for i in range ( 4 ):
print ( i )
i = 10
输出:
0
1
2
3
您是否期望循环只运行一次?
解释:
i = 10
永远不会影响循环的迭代。在每次迭代开始之前,迭代器提供的下一个项目(在本例中range(4)
)被解包并分配给目标列表变量(在本例中为i
)。 enumerate(some_string)
函数在每次迭代中生成一个新值i
(一个递增的计数器)和some_string
中的一个字符。然后它将字典some_dict
的(刚刚分配的) i
键设置为该字符。循环的展开可以简化为:
> >> i , some_dict [ i ] = ( 0 , 'w' )
> >> i , some_dict [ i ] = ( 1 , 't' )
> >> i , some_dict [ i ] = ( 2 , 'f' )
> >> some_dict
1.
array = [ 1 , 8 , 15 ]
# A typical generator expression
gen = ( x for x in array if array . count ( x ) > 0 )
array = [ 2 , 8 , 22 ]
输出:
> >> print ( list ( gen )) # Where did the other values go?
[ 8 ]
2.
array_1 = [ 1 , 2 , 3 , 4 ]
gen_1 = ( x for x in array_1 )
array_1 = [ 1 , 2 , 3 , 4 , 5 ]
array_2 = [ 1 , 2 , 3 , 4 ]
gen_2 = ( x for x in array_2 )
array_2 [:] = [ 1 , 2 , 3 , 4 , 5 ]
输出:
> >> print ( list ( gen_1 ))
[ 1 , 2 , 3 , 4 ]
> >> print ( list ( gen_2 ))
[ 1 , 2 , 3 , 4 , 5 ]
3.
array_3 = [ 1 , 2 , 3 ]
array_4 = [ 10 , 20 , 30 ]
gen = ( i + j for i in array_3 for j in array_4 )
array_3 = [ 4 , 5 , 6 ]
array_4 = [ 400 , 500 , 600 ]
输出:
> >> print ( list ( gen ))
[ 401 , 501 , 601 , 402 , 502 , 602 , 403 , 503 , 603 ]
在生成器表达式中, in
子句在声明时计算,但条件子句在运行时计算。
因此在运行时之前, array
被重新分配给列表[2, 8, 22]
,并且由于在1
, 8
和15
中,只有8
的计数大于0
,因此生成器仅生成8
。
第二部分中g1
和g2
的输出差异是由于变量array_1
和array_2
重新赋值的方式造成的。
在第一种情况下, array_1
绑定到新对象[1,2,3,4,5]
,并且由于in
子句在声明时求值,因此它仍然引用旧对象[1,2,3,4]
(没有被破坏)。
在第二种情况下,对array_2
切片分配将相同的旧对象[1,2,3,4]
更新为[1,2,3,4,5]
。因此g2
和array_2
仍然引用同一个对象(现已更新为[1,2,3,4,5]
)。
好吧,按照到目前为止讨论的逻辑,第三个片段中list(gen)
的值不应该是[11, 21, 31, 12, 22, 32, 13, 23, 33]
吗? (因为array_3
和array_4
的行为就像array_1
一样)。 PEP-289 中解释了(仅) array_4
值更新的原因
只有最外面的 for 表达式会立即计算,其他表达式会延迟到生成器运行为止。
is not ...
不是is (not ...)
> >> 'something' is not None
True
> >> 'something' is ( not None )
False
is not
是单个二元运算符,其行为与使用is
和not
分隔不同。is not
计算为False
,否则计算为True
。(not None)
计算结果为True
因为值None
在布尔上下文中为False
,因此表达式变为'something' is True
。 # Let's initialize a row
row = [ "" ] * 3 #row i['', '', '']
# Let's make a board
board = [ row ] * 3
输出:
> >> board
[[ '' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
> >> board [ 0 ]
[ '' , '' , '' ]
> >> board [ 0 ][ 0 ]
''
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ 'X' , '' , '' ], [ 'X' , '' , '' ]]
我们没有分配三个"X"
,不是吗?
当我们初始化row
变量时,该可视化解释了内存中发生的情况
当通过乘以row
来初始化board
时,这就是内存内部发生的情况(每个元素board[0]
、 board[1]
和board[2]
都是对row
引用的同一列表的引用)
我们可以通过不使用row
变量来生成board
来避免这种情况。 (在本期中询问)。
> >> board = [[ '' ] * 3 for _ in range ( 3 )]
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
funcs = []
results = []
for x in range ( 7 ):
def some_func ():
return x
funcs . append ( some_func )
results . append ( some_func ()) # note the function call here
funcs_results = [ func () for func in funcs ]
输出(Python版本):
> >> results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> funcs_results
[ 6 , 6 , 6 , 6 , 6 , 6 , 6 ]
在将some_func
附加到funcs
之前,每次迭代中x
的值都不同,但所有函数在循环完成后求值时都返回 6。
> >> powers_of_x = [ lambda x : x ** i for i in range ( 10 )]
> >> [ f ( 2 ) for f in powers_of_x ]
[ 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 ]
x
,而不是使用创建函数时的x
值。因此所有函数都使用分配给变量的最新值进行计算。我们可以看到它使用周围上下文中的x
(即不是局部变量): > >> import inspect
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = { 'x' : 6 }, builtins = {}, unbound = set ())
由于x
是全局值,我们可以通过更新x
来更改funcs
将查找和返回的值:
> >> x = 42
> >> [ func () for func in funcs ]
[ 42 , 42 , 42 , 42 , 42 , 42 , 42 ]
x
的值。 funcs = []
for x in range ( 7 ):
def some_func ( x = x ):
return x
funcs . append ( some_func )
输出:
> >> funcs_results = [ func () for func in funcs ]
> >> funcs_results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
它不再在全局范围内使用x
:
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = {}, builtins = {}, unbound = set ())
1.
> >> isinstance ( 3 , int )
True
> >> isinstance ( type , object )
True
> >> isinstance ( object , type )
True
那么哪个是“最终”基类呢?顺便说一句,还有更多令人困惑的事情,
2.
> >> class A : pass
> >> isinstance ( A , A )
False
> >> isinstance ( type , type )
True
> >> isinstance ( object , object )
True
3.
> >> issubclass ( int , object )
True
> >> issubclass ( type , object )
True
> >> issubclass ( object , type )
False
type
是Python 中的元类。object
,其中包括类及其对象(实例)。type
是类object
的元类,每个类(包括type
)都直接或间接继承自object
。object
和type
之间没有真正的基类。上面代码片段中的混乱之所以出现,是因为我们正在根据 Python 类来考虑这些关系( issubclass
和isinstance
)。 object
和type
之间的关系无法在纯Python中重现。更准确地说,以下关系无法在纯 Python 中重现,object
和type
之间的这些关系(两者都是彼此的实例,也是它们自身的实例),是因为实现级别上的“作弊”。输出:
> >> from collections . abc import Hashable
> >> issubclass ( list , object )
True
> >> issubclass ( object , Hashable )
True
> >> issubclass ( list , Hashable )
False
子类关系应该是可传递的,对吧? (即,如果A
是B
的子类,并且B
是C
的子类,则A
应该是C
的子类)
__subclasscheck__
。issubclass(cls, Hashable)
时,它只是在cls
或其继承的任何内容中查找非 Falsey“ __hash__
”方法。object
是可散列的,但list
是不可散列的,因此它破坏了传递性关系。 class SomeClass :
def method ( self ):
pass
@ classmethod
def classm ( cls ):
pass
@ staticmethod
def staticm ():
pass
输出:
> >> print ( SomeClass . method is SomeClass . method )
True
> >> print ( SomeClass . classm is SomeClass . classm )
False
> >> print ( SomeClass . classm == SomeClass . classm )
True
> >> print ( SomeClass . staticm is SomeClass . staticm )
True
访问classm
两次,我们得到一个相等的对象,但不是同一个?让我们看看SomeClass
的实例会发生什么:
o1 = SomeClass ()
o2 = SomeClass ()
输出:
> >> print ( o1 . method == o2 . method )
False
> >> print ( o1 . method == o1 . method )
True
> >> print ( o1 . method is o1 . method )
False
> >> print ( o1 . classm is o1 . classm )
False
> >> print ( o1 . classm == o1 . classm == o2 . classm == SomeClass . classm )
True
> >> print ( o1 . staticm is o1 . staticm is o2 . staticm is SomeClass . staticm )
True
访问classm
或method
两次,会为SomeClass
的同一实例创建相等但不相同的对象。
self
作为第一个参数,尽管没有显式传递它)。 > >> o1 . method
< bound method SomeClass . method of < __main__ . SomeClass object at ... >>
o1.method is o1.method
永远不会为真。然而,将函数作为类属性(而不是实例)访问并不会创建方法;而是创建方法。所以SomeClass.method is SomeClass.method
是真的。 > >> SomeClass . method
< function SomeClass . method at ... >
classmethod
将函数转换为类方法。类方法是描述符,在访问时,它创建一个绑定对象的类(类型)而不是对象本身的方法对象。 > >> o1 . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
classmethod
在作为类属性访问时也会创建一个方法(在这种情况下,它们绑定类,而不是它的类型)。所以SomeClass.classm is SomeClass.classm
是假的。 > >> SomeClass . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
o1.method == o1.method
是真的,尽管内存中不是同一个对象。staticmethod
将函数转换为“无操作”描述符,它按原样返回函数。没有创建任何方法对象,因此与is
进行比较是正确的。 > >> o1 . staticm
< function SomeClass . staticm at ... >
> >> SomeClass . staticm
< function SomeClass . staticm at ... >
self
影响性能。 CPython 3.7 通过引入新的操作码来解决这个问题,这些操作码可以在不创建临时方法对象的情况下处理调用方法。这仅在实际调用所访问的函数时使用,因此这里的代码片段不受影响,并且仍然生成方法:) > >> all ([ True , True , True ])
True
> >> all ([ True , True , False ])
False
> >> all ([])
True
> >> all ([[]])
False
> >> all ([[[]]])
True
为什么会出现这种 True-False 变化?
all
函数的实现等价于
def all ( iterable ):
for element in iterable :
if not element :
return False
return True
all([])
返回True
因为可迭代对象为空。
all([[]])
返回False
因为传递的数组只有一个元素[]
,而在 python 中,空列表是假的。
all([[[]]])
和更高的递归变体始终为True
。这是因为传递的数组的单个元素 ( [[...]]
) 不再为空,并且包含值的列表是真实的。
输出(< 3.6):
> >> def f ( x , y ,):
... print ( x , y )
...
> >> def g ( x = 4 , y = 5 ,):
... print ( x , y )
...
> >> def h ( x , ** kwargs ,):
File "<stdin>" , line 1
def h ( x , ** kwargs ,):
^
SyntaxError : invalid syntax
>> > def h ( * args ,):
File "<stdin>" , line 1
def h ( * args ,):
^
SyntaxError : invalid syntax
输出:
> >> print ( " " " )
"
>> > print ( r""" )
"
>> > print ( r" " )
File "<stdin>" , line 1
print ( r" " )
^
SyntaxError : EOL while scanning string literal
>> > r''' == " \ '"
True
> >> "wt " f"
'wt"f'
r
所示),反斜杠按原样传递自身以及转义后续字符的行为。 > >> r'wt"f' == 'wt \ "f'
True
> >> print ( repr ( r'wt"f' ))
'wt \ "f'
> >> print ( " n " )
> >> print ( r"\n" )
' \ n'
print(r"")
),反斜杠转义了尾部引号,使解析器没有终止引号(因此出现SyntaxError
)。这就是为什么反斜杠在原始字符串末尾不起作用的原因。 x = True
y = False
输出:
> >> not x == y
True
> >> x == not y
File "<input>" , line 1
x == not y
^
SyntaxError : invalid syntax
==
运算符的优先级高于 Python 中的not
运算符。not x == y
等价于not (x == y)
等价于not (True == False)
最终评估为True
。x == not y
会引发SyntaxError
,因为它可以被认为等同于(x == not) y
和 not x == (not y)
这可能是您乍一看所期望的。not
标记成为not in
运算符的一部分(因为==
和not in
运算符具有相同的优先级),但是在无法找到not
标记后面的in
标记后,它会引发SyntaxError
。输出:
> >> print ( 'wtfpython' '' )
wtfpython
> >> print ( "wtfpython" "" )
wtfpython
> >> # The following statements raise `SyntaxError`
>> > # print('''wtfpython')
>> > # print("""wtfpython")
File "<input>" , line 3
print ("""wtfpython")
^
SyntaxError: EOF while scanning triple-quoted string literal
>>> print("wtf" "python")
wtfpython
>>> print("wtf" "") # or "wtf"""
wtf
'''
和"""
也是 Python 中的字符串分隔符,这会导致语法错误,因为 Python 解释器在扫描当前遇到的三引号字符串文字时期望终止三引号作为分隔符。1.
# A simple example to count the number of booleans and
# integers in an iterable of mixed data types.
mixed_list = [ False , 1.0 , "some_string" , 3 , True , [], False ]
integers_found_so_far = 0
booleans_found_so_far = 0
for item in mixed_list :
if isinstance ( item , int ):
integers_found_so_far += 1
elif isinstance ( item , bool ):
booleans_found_so_far += 1
输出:
> >> integers_found_so_far
4
> >> booleans_found_so_far
0
2.
> >> some_bool = True
> >> "wtf" * some_bool
'wtf'
> >> some_bool = False
> >> "wtf" * some_bool
''
3.
def tell_truth ():
True = False
if True == False :
print ( "I have lost faith in truth!" )
输出(< 3.x):
> >> tell_truth ()
I have lost faith in truth !
bool
是 Python 中int
的子类
> >> issubclass ( bool , int )
True
> >> issubclass ( int , bool )
False
因此, True
和False
是int
的实例
> >> isinstance ( True , int )
True
> >> isinstance ( False , int )
True
True
的整数值为1
, False
的整数值为0
。
> >> int ( True )
1
> >> int ( False )
0
请参阅此 StackOverflow 答案以了解其背后的基本原理。
最初,Python 没有bool
类型(人们使用 0 表示 false,使用非零值(如 1 表示 true))。 True
、 False
和bool
类型在 2.x 版本中添加,但是,为了向后兼容, True
和False
不能设为常量。它们只是内置变量,可以重新分配它们
Python 3 向后不兼容,问题最终得到解决,因此最后一个代码片段不适用于 Python 3.x!
1.
class A :
x = 1
class B ( A ):
pass
class C ( A ):
pass
输出:
> >> A . x , B . x , C . x
( 1 , 1 , 1 )
>> > B . x = 2
>> > A . x , B . x , C . x
( 1 , 2 , 1 )
>> > A . x = 3
>> > A . x , B . x , C . x # C.x changed, but B.x didn't
( 3 , 2 , 3 )
>> > a = A ()
>> > a . x , A . x
( 3 , 3 )
>> > a . x + = 1
>> > a . x , A . x
( 4 , 3 )
2.
class SomeClass :
some_var = 15
some_list = [ 5 ]
another_list = [ 5 ]
def __init__ ( self , x ):
self . some_var = x + 1
self . some_list = self . some_list + [ x ]
self . another_list += [ x ]
输出:
> >> some_obj = SomeClass ( 420 )
> >> some_obj . some_list
[ 5 , 420 ]
> >> some_obj . another_list
[ 5 , 420 ]
> >> another_obj = SomeClass ( 111 )
> >> another_obj . some_list
[ 5 , 111 ]
> >> another_obj . another_list
[ 5 , 420 , 111 ]
> >> another_obj . another_list is SomeClass . another_list
True
> >> another_obj . another_list is some_obj . another_list
True
+=
运算符就地修改可变对象,而不创建新对象。因此,更改一个实例的属性也会影响其他实例和类属性。 some_iterable = ( 'a' , 'b' )
def some_func ( val ):
return "something"
输出(<= 3.7.x):
> >> [ x for x in some_iterable ]
[ 'a' , 'b' ]
> >> [( yield x ) for x in some_iterable ]
< generator object < listcomp > at 0x7f70b0a4ad58 >
> >> list ([( yield x ) for x in some_iterable ])
[ 'a' , 'b' ]
> >> list (( yield x ) for x in some_iterable )
[ 'a' , None , 'b' , None ]
> >> list ( some_func (( yield x )) for x in some_iterable )
[ 'a' , 'something' , 'b' , 'something' ]
yield
的一个错误。yield
并会抛出SyntaxError
。1.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
yield from range ( x )
输出(> 3.3):
> >> list ( some_func ( 3 ))
[]
"wtf"
去哪儿了?是由于yield from
的一些特殊影响吗?让我们验证一下,
2.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
for i in range ( x ):
yield i
输出:
> >> list ( some_func ( 3 ))
[]
结果一样,这也行不通。
return
语句(请参阅 PEP380)。官方文档说,“...生成器中的
return expr
会导致在退出生成器时引发StopIteration(expr)
。”
在some_func(3)
的情况下,由于return
语句, StopIteration
在开始时被引发。 StopIteration
异常会在list(...)
包装器和for
循环内自动捕获。因此,上述两个片段会产生一个空列表。
要从生成器some_func
获取["wtf"]
我们需要捕获StopIteration
异常,
try :
next ( some_func ( 3 ))
except StopIteration as e :
some_string = e . value
> >> some_string
[ "wtf" ]
1.
a = float ( 'inf' )
b = float ( 'nan' )
c = float ( '-iNf' ) # These strings are case-insensitive
d = float ( 'nan' )
输出:
> >> a
inf
> >> b
nan
> >> c
- inf
> >> float ( 'some_other_string' )
ValueError : could not convert string to float : some_other_string
> >> a == - c # inf==inf
True
> >> None == None # None == None
True
> >> b == d # but nan!=nan
False
> >> 50 / a
0.0
> >> a / a
nan
> >> 23 + b
nan
2.
> >> x = float ( 'nan' )
> >> y = x / x
> >> y is y # identity holds
True
> >> y == y # equality fails of y
False
> >> [ y ] == [ y ] # but the equality succeeds for the list containing y
True
'inf'
和'nan'
是特殊字符串(不区分大小写),当显式转换为float
类型时,它们分别用于表示数学“无穷大”和“非数字”。
由于根据 IEEE 标准NaN != NaN
,遵守此规则打破了 Python 中集合元素的自反性假设,即如果x
是像list
这样的集合的一部分,则比较等实现基于x == x
的假设。由于这个假设,在比较两个元素时首先比较身份(因为它更快),并且仅当身份不匹配时才比较值。下面的代码片段将使事情变得更清楚,
> >> x = float ( 'nan' )
> >> x == x , [ x ] == [ x ]
( False , True )
> >> y = float ( 'nan' )
> >> y == y , [ y ] == [ y ]
( False , True )
> >> x == y , [ x ] == [ y ]
( False , False )
由于x
和y
的身份不同,所以考虑的值也不同;因此这次比较返回False
。
有趣的读物:反身性和文明的其他支柱
如果您知道引用在 Python 中的工作原理,这可能看起来微不足道。
some_tuple = ( "A" , "tuple" , "with" , "values" )
another_tuple = ([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 ])
输出:
> >> some_tuple [ 2 ] = "change this"
TypeError : 'tuple' object does not support item assignment
> >> another_tuple [ 2 ]. append ( 1000 ) #This throws no error
> >> another_tuple
([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 , 1000 ])
> >> another_tuple [ 2 ] += [ 99 , 999 ]
TypeError : 'tuple' object does not support item assignment
> >> another_tuple
([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 , 1000 , 99 , 999 ])
但我认为元组是不可变的......
引用自https://docs.python.org/3/reference/datamodel.html
不可变序列 不可变序列类型的对象一旦创建就不能更改。 (如果该对象包含对其他对象的引用,则这些其他对象可能是可变的并且可以被修改;但是,由不可变对象直接引用的对象集合不能更改。)
+=
运算符就地更改列表。项目分配不起作用,但当异常发生时,该项目已就地更改。
Python 官方常见问题解答中也有解释。
e = 7
try :
raise Exception ()
except Exception as e :
pass
输出(Python 2.x):
> >> print ( e )
# prints nothing
输出(Python 3.x):
> >> print ( e )
NameError : name 'e' is not defined
来源:https://docs.python.org/3/reference/compound_stmts.html# except
当使用as
目标分配异常时,它会在except
子句的末尾被清除。这就像
except E as N :
foo
被翻译成
except E as N :
try :
foo
finally :
del N
这意味着必须为异常分配一个不同的名称,以便能够在 except 子句之后引用它。异常会被清除,因为附加了回溯,它们与堆栈帧形成了一个引用循环,使该帧中的所有局部变量保持活动状态,直到发生下一次垃圾收集。
这些子句不在 Python 中。示例中的所有内容都存在于同一范围内,并且由于执行except
子句,变量e
被删除。对于具有独立内部作用域的函数来说,情况并非如此。下面的例子说明了这一点:
def f ( x ):
del ( x )
print ( x )
x = 5
y = [ 5 , 4 , 3 ]
输出:
> >> f ( x )
UnboundLocalError : local variable 'x' referenced before assignment
>> > f ( y )
UnboundLocalError : local variable 'x' referenced before assignment
>> > x
5
> >> y
[ 5 , 4 , 3 ]
在 Python 2.x 中,变量名e
被分配给Exception()
实例,因此当您尝试打印时,它不会打印任何内容。
输出(Python 2.x):
> >> e
Exception ()
> >> print e
# Nothing is printed!
class SomeClass ( str ):
pass
some_dict = { 's' : 42 }
输出:
> >> type ( list ( some_dict . keys ())[ 0 ])
str
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict # expected: Two different keys-value pairs
{ 's' : 40 }
> >> type ( list ( some_dict . keys ())[ 0 ])
str
对象s
和字符串"s"
哈希都达到相同的值,因为SomeClass
继承了str
类的__hash__
。
SomeClass("s") == "s"
评估为True
,因为SomeClass
还从str
类中继承了__eq__
方法。
由于两个对象都达到相同的值并且相等,因此它们在字典中以相同的键表示。
对于所需的行为,我们可以在SomeClass
中重新定义__eq__
方法
class SomeClass ( str ):
def __eq__ ( self , other ):
return (
type ( self ) is SomeClass
and type ( other ) is SomeClass
and super (). __eq__ ( other )
)
# When we define a custom __eq__, Python stops automatically inheriting the
# __hash__ method, so we need to define it as well
__hash__ = str . __hash__
some_dict = { 's' : 42 }
输出:
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict
{ 's' : 40 , 's' : 42 }
> >> keys = list ( some_dict . keys ())
> >> type ( keys [ 0 ]), type ( keys [ 1 ])
( __main__ . SomeClass , str )
a , b = a [ b ] = {}, 5
输出:
> >> a
{ 5 : ({...}, 5 )}
(target_list "=")+ (expression_list | yield_expression)
分配语句评估表达式列表(请记住,这可以是单个表达式或逗号分隔的列表,后者产生元组),并将单个结果对象分配给从左到右的每个目标列表。
+
in (target_list "=")+
意味着可以有一个或多个目标列表。在这种情况下,目标列表为a, b
和a[b]
(请注意,表达式列表正是一个,在我们的情况下是{}, 5
)。
评估表达式列表后,其值将从从左到右打开目标列表。因此,在我们的情况下,首先将{}, 5
元组解开为a, b
,现在我们有a = {}
和b = 5
。
a
现在分配给{}
,这是一个可变的对象。
第二个目标列表是a[b]
(您可能希望这会引发错误,因为a
和b
但是请记住,我们只是分配了a
至{}
和b
到5
)。
现在,我们将字典中的密钥5
设置为元组({}, 5)
创建圆形引用(输出中的{...}
是指a
已经引用的相同对象)。循环参考的另一个更简单的例子可能是
> >> some_list = some_list [ 0 ] = [ 0 ]
> >> some_list
[[...]]
> >> some_list [ 0 ]
[[...]]
> >> some_list is some_list [ 0 ]
True
> >> some_list [ 0 ][ 0 ][ 0 ][ 0 ][ 0 ][ 0 ] == some_list
True
在我们的示例中是类似的( a[b][0]
与a
相同的对象)
因此总而言之,您可以将示例分解为
a , b = {}, 5
a [ b ] = a , b
循环引用可以通过a[b][0]
事实证明是合理的a
> >> a [ b ][ 0 ] is a
True
> >> # Python 3.10.6
>> > int ( "2" * 5432 )
> >> # Python 3.10.8
>> > int ( "2" * 5432 )
输出:
> >> # Python 3.10.6
222222222222222222222222222222222222222222222222222222222222222. ..
>> > # Python 3.10.8
Traceback ( most recent call last ):
...
ValueError : Exceeds the limit ( 4300 ) for integer string conversion :
value has 5432 digits ; use sys . set_int_max_str_digits ()
to increase the limit .
python 3.10.6的呼叫int()
运行良好,并在Python 3.10.8中提高了一个值。请注意,Python仍然可以与大整数一起使用。仅在整数和字符串之间转换时会增加误差。
幸运的是,当您期望操作超过该操作时,您可以增加允许数量的限制。为此,您可以使用以下一个:
如果您希望代码超过此值,请检查文档以获取更改默认限制的更多详细信息。
x = { 0 : None }
for i in x :
del x [ i ]
x [ i + 1 ] = None
print ( i )
输出(Python 2.7- Python 3.5):
0
1
2
3
4
5
6
7
是的,它运行八次并停止。
RuntimeError: dictionary keys changed during iteration
。del
操作 class SomeClass :
def __del__ ( self ):
print ( "Deleted!" )
输出: 1。
> >> x = SomeClass ()
> >> y = x
> >> del x # this should print "Deleted!"
> >> del y
Deleted !
PHEW,最后被删除。您可能已经猜到了是什么节省了__del__
在我们第一次删除x
的尝试中被称为。让我们在示例中添加更多曲折。
2.
> >> x = SomeClass ()
> >> y = x
> >> del x
> >> y # check if y exists
< __main__ . SomeClass instance at 0x7f98a1a67fc8 >
> >> del y # Like previously, this should print "Deleted!"
> >> globals () # oh, it didn't. Let's check all our global variables and confirm
Deleted !
{ '__builtins__' : < module '__builtin__' ( built - in ) > , 'SomeClass' : < class __main__ . SomeClass at 0x7f98a1a5f668 > , '__package__' : None , '__name__' : '__main__' , '__doc__' : None }
好吧,现在被删除了吗?
del x
不会直接调用x.__del__()
。del x
时,python从当前范围中删除名称x
,并减少1个引用对象x
的参考计数。 __del__()
仅在对象的参考计数达到零时才调用。__del__()
,因为交互式解释器中的先前语句( >>> y
)创建了对同一对象的另一个引用(具体来说, _
魔术变量,该变量引用了最后一个non nonno None
的结果值在卧式上的表达),因此在遇到del y
时阻止参考计数达到零。globals
(或者实际执行任何将具有None
结果的任何内容)导致_
引用新结果,从而删除现有参考。现在,参考计数达到0,我们可以看到“已删除!”被打印(终于!)。1.
a = 1
def some_func ():
return a
def another_func ():
a += 1
return a
2.
def some_closure_func ():
a = 1
def some_inner_func ():
return a
return some_inner_func ()
def another_closure_func ():
a = 1
def another_inner_func ():
a += 1
return a
return another_inner_func ()
输出:
> >> some_func ()
1
> >> another_func ()
UnboundLocalError : local variable 'a' referenced before assignment
>> > some_closure_func ()
1
> >> another_closure_func ()
UnboundLocalError : local variable 'a' referenced before assignment
当您对范围中变量进行分配时,它将成为该范围的本地化。因此, a
成为another_func
的范围的本地,但是先前尚未在同一范围中初始化它,从而引发错误。
要在another_func
中修改外部范围变量a
,我们必须使用global
关键字。
def another_func ()
global a
a += 1
return a
输出:
> >> another_func ()
2
在another_closure_func
中, a
成为another_inner_func
范围的本地,但是先前尚未以同一范围初始化它,这就是为什么会引发错误的原因。
要修改another_inner_func
中的外部范围变量a
,请使用nonlocal
关键字。非本地语句用于指在最近的外部(不包括全局)范围中定义的变量。
def another_func ():
a = 1
def another_inner_func ():
nonlocal a
a += 1
return a
return another_inner_func ()
输出:
> >> another_func ()
2
关键字global
和nonlocal
告诉Python解释器不声明新变量,并在相应的外部范围中查找它们。
阅读此简短但很棒的指南,以了解有关名称空间和范围分辨率在Python中的工作方式的更多信息。
list_1 = [ 1 , 2 , 3 , 4 ]
list_2 = [ 1 , 2 , 3 , 4 ]
list_3 = [ 1 , 2 , 3 , 4 ]
list_4 = [ 1 , 2 , 3 , 4 ]
for idx , item in enumerate ( list_1 ):
del item
for idx , item in enumerate ( list_2 ):
list_2 . remove ( item )
for idx , item in enumerate ( list_3 [:]):
list_3 . remove ( item )
for idx , item in enumerate ( list_4 ):
list_4 . pop ( idx )
输出:
> >> list_1
[ 1 , 2 , 3 , 4 ]
> >> list_2
[ 2 , 4 ]
> >> list_3
[]
> >> list_4
[ 2 , 4 ]
您能猜出为什么输出是[2, 4]
?
更改要迭代的对象绝不是一个好主意。这样做的正确方法是迭代对象的副本, list_3[:]
就是这样做的。
> >> some_list = [ 1 , 2 , 3 , 4 ]
> >> id ( some_list )
139798789457608
> >> id ( some_list [:]) # Notice that python creates new object for sliced list.
139798779601192
del
, remove
和pop
之间的区别:
del var_name
只是从本地或全局名称空间中删除了var_name
的绑定(这就是为什么list_1
不受影响的原因)。remove
删除第一个匹配值,而不是特定索引,如果找不到该值,则会提高ValueError
。pop
在特定的索引处删除元素并将其返回,如果指定了无效的索引,则将IndexError
。为什么输出是[2, 4]
?
list_2
或list_4
删除1
索引时,列表的内容现在为[2, 3, 4]
。其余元素被向下移动,即索引为0,在索引1处3
由于下一个迭代将查看索引2
( 3
),因此2
完全跳过。列表序列中的每个替代元素都会发生类似的事情。 > >> numbers = list ( range ( 7 ))
> >> numbers
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> first_three , remaining = numbers [: 3 ], numbers [ 3 :]
> >> first_three , remaining
([ 0 , 1 , 2 ], [ 3 , 4 , 5 , 6 ])
>> > numbers_iter = iter ( numbers )
>> > list ( zip ( numbers_iter , first_three ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
# so far so good, let's zip the remaining
>> > list ( zip ( numbers_iter , remaining ))
[( 4 , 3 ), ( 5 , 4 ), ( 6 , 5 )]
元素3
从何处从numbers
列表中获取?
def zip ( * iterables ):
sentinel = object ()
iterators = [ iter ( it ) for it in iterables ]
while iterators :
result = []
for it in iterators :
elem = next ( it , sentinel )
if elem is sentinel : return
result . append ( elem )
yield tuple ( result )
result
列表中,通过调用下next
函数,并在任何峰值耗尽时停止。result
列表中的现有元素被丢弃。这就是numbers_iter
中3
的情况。zip
的正确方法是, > >> numbers = list ( range ( 7 ))
> >> numbers_iter = iter ( numbers )
> >> list ( zip ( first_three , numbers_iter ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
> >> list ( zip ( remaining , numbers_iter ))
[( 3 , 3 ), ( 4 , 4 ), ( 5 , 5 ), ( 6 , 6 )]
1.
for x in range ( 7 ):
if x == 6 :
print ( x , ': for x inside loop' )
print ( x , ': x in global' )
输出:
6 : for x inside loop
6 : x in global
但是x
从未被定义在循环范围之外...
2.
# This time let's initialize x first
x = - 1
for x in range ( 7 ):
if x == 6 :
print ( x , ': for x inside loop' )
print ( x , ': x in global' )
输出:
6 : for x inside loop
6 : x in global
3.
输出(Python 2.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
4
输出(Python 3.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
1
在Python,For-Loops使用它们存在的范围,并将其定义的环变量抛在后面。如果我们以前在全局名称空间中明确定义了循环变量,这也适用。在这种情况下,它将重新启动现有变量。
Python 2.x和Python 3.x解释器的列表理解示例的输出的差异可以通过以下记录在Python 3.0 ChangElog中的新事物的更改来解释:
“列表综合不再支持句法形式
[... for var in item1, item2, ...]
。使用[... for var in (item1, item2, ...)]
。另外,请注意该列表综合语义具有不同的语义:对于list()
构造函数中的发电机表达式,它们更接近句法糖,尤其是环路控制变量不再泄漏到周围的范围中。”
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
输出:
> >> some_func ()
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' ]
> >> some_func ([])
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' , 'some_string' ]
每次您调用该函数时,Python中功能的默认可变参数并不是真正初始化的。相反,最近分配给它们的值用作默认值。当我们将[]
明确传递给some_func
作为参数时,未使用default_arg
变量的默认值,因此该函数按预期返回。
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
输出:
> >> some_func . __defaults__ #This will show the default argument values for the function
([],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' ],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
> >> some_func ([])
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
避免由于可变参数而避免错误的常见做法是将None
分配为默认值,然后检查是否将任何值传递给与该参数相对应的函数。例子:
def some_func ( default_arg = None ):
if default_arg is None :
default_arg = []
default_arg . append ( "some_string" )
return default_arg
some_list = [ 1 , 2 , 3 ]
try :
# This should raise an ``IndexError``
print ( some_list [ 4 ])
except IndexError , ValueError :
print ( "Caught!" )
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except IndexError , ValueError :
print ( "Caught again!" )
输出(Python 2.x):
Caught !
ValueError : list . remove ( x ): x not in list
输出(Python 3.x):
File "<input>" , line 3
except IndexError , ValueError :
^
SyntaxError : invalid syntax
要将多个异常添加到除外,您需要将它们作为括号元素作为第一个参数传递。第二个参数是一个可选名称,在提供后将绑定已提出的异常实例。例子,
some_list = [ 1 , 2 , 3 ]
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except ( IndexError , ValueError ), e :
print ( "Caught again!" )
print ( e )
输出(Python 2.x):
Caught again!
list.remove(x): x not in list
输出(Python 3.x):
File "<input>" , line 4
except ( IndexError , ValueError ), e :
^
IndentationError : unindent does not match any outer indentation level
将异常与变量与逗号分开是弃用的,并且在Python 3中不起作用;正确的方法是使用as
例子,
some_list = [ 1 , 2 , 3 ]
try :
some_list . remove ( 4 )
except ( IndexError , ValueError ) as e :
print ( "Caught again!" )
print ( e )
输出:
Caught again!
list.remove(x): x not in list
1.
a = [ 1 , 2 , 3 , 4 ]
b = a
a = a + [ 5 , 6 , 7 , 8 ]
输出:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 ]
2.
a = [ 1 , 2 , 3 , 4 ]
b = a
a += [ 5 , 6 , 7 , 8 ]
输出:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
a += b
行为并不总是与a = a + b
相同的方式。类可能以不同的方式实现op=
运算符,并且列表执行此操作。
表达式a = a + [5,6,7,8]
生成了一个新列表,并设置了对该新列表的a
,而b
却没有变化。
表达式a += [5,6,7,8]
实际上映射到在列表上运行的“扩展”函数,以使a
和b
仍指向已修改的相同列表。
1.
x = 5
class SomeClass :
x = 17
y = ( x for i in range ( 10 ))
输出:
> >> list ( SomeClass . y )[ 0 ]
5
2.
x = 5
class SomeClass :
x = 17
y = [ x for i in range ( 10 )]
输出(Python 2.x):
> >> SomeClass . y [ 0 ]
17
输出(Python 3.x):
> >> SomeClass . y [ 0 ]
5
让我们实现一个幼稚的函数以获取列表的中间元素:
def get_middle ( some_list ):
mid_index = round ( len ( some_list ) / 2 )
return some_list [ mid_index - 1 ]
Python 3.x:
> >> get_middle ([ 1 ]) # looks good
1
> >> get_middle ([ 1 , 2 , 3 ]) # looks good
2
> >> get_middle ([ 1 , 2 , 3 , 4 , 5 ]) # huh?
2
> >> len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 # good
2.5
> >> round ( len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 ) # why?
2
似乎Python舍入2.5至2。
round()
使用银行家的圆形,其中.5个分数被舍入到最接近的数字: > >> round ( 0.5 )
0
> >> round ( 1.5 )
2
> >> round ( 2.5 )
2
> >> import numpy # numpy does the same
> >> numpy . round ( 0.5 )
0.0
> >> numpy . round ( 1.5 )
2.0
> >> numpy . round ( 2.5 )
2.0
get_middle([1])
仅返回1,因为索引为round(0.5) - 1 = 0 - 1 = -1
,返回列表中的最后一个元素。到目前为止
1.
x , y = ( 0 , 1 ) if True else None , None
输出:
> >> x , y # expected (0, 1)
(( 0 , 1 ), None )
2.
t = ( 'one' , 'two' )
for i in t :
print ( i )
t = ( 'one' )
for i in t :
print ( i )
t = ()
print ( t )
输出:
one
two
o
n
e
tuple ()
3.
ten_words_list = [
"some",
"very",
"big",
"list",
"that"
"consists",
"of",
"exactly",
"ten",
"words"
]
输出
> >> len ( ten_words_list )
9
4。不够强烈地断言
a = "python"
b = "javascript"
输出:
# An assert statement with an assertion failure message.
> >> assert ( a == b , "Both languages are different" )
# No AssertionError is raised
5.
some_list = [ 1 , 2 , 3 ]
some_dict = {
"key_1" : 1 ,
"key_2" : 2 ,
"key_3" : 3
}
some_list = some_list . append ( 4 )
some_dict = some_dict . update ({ "key_4" : 4 })
输出:
> >> print ( some_list )
None
> >> print ( some_dict )
None
6.
def some_recursive_func ( a ):
if a [ 0 ] == 0 :
return
a [ 0 ] -= 1
some_recursive_func ( a )
return a
def similar_recursive_func ( a ):
if a == 0 :
return a
a -= 1
similar_recursive_func ( a )
return a
输出:
> >> some_recursive_func ([ 5 , 0 ])
[ 0 , 0 ]
> >> similar_recursive_func ( 5 )
4
对于1, x, y = (0, 1) if True else (None, None)
。
对于2,对预期行为的正确说明是t = ('one',)
,或t = 'one',
(缺少逗号),否则解释器认为t
是str
,并按字符迭代。
()
是一个特殊的令牌,表示空tuple
。
在3中,正如您可能已经弄清的那样,列表中的第五元素( "that"
)之后有一个缺失的逗号。因此,通过隐式字符串字面串联,
> >> ten_words_list
[ 'some' , 'very' , 'big' , 'list' , 'thatconsists' , 'of' , 'exactly' , 'ten' , 'words' ]
我们没有断言单个表达式a == b
,而是在第四个片段中提出了任何AssertionError
。以下片段将清除一切,
> >> a = "python"
> >> b = "javascript"
> >> assert a == b
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError
>> > assert ( a == b , "Values are not equal" )
< stdin > : 1 : SyntaxWarning : assertion is always true , perhaps remove parentheses ?
>> > assert a == b , "Values are not equal"
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError : Values are not equal
至于第五段, dict.update
修改序列/ None
对象的项目(如list.append
, list.sort
等)等。如果可以在此处完成操作(从此处引用),则背后的理由是通过避免制作对象的副本来提高性能。
最后一个应该是相当明显的,可以在函数中更改可变的对象(喜欢list
),而不成熟的( a -= 1
)的重新分配不是值的改变。
从长远来看,了解这些挑剔可以节省您数小时的调试工作。
> >> 'a' . split ()
[ 'a' ]
# is same as
> >> 'a' . split ( ' ' )
[ 'a' ]
# but
> >> len ( '' . split ())
0
# isn't the same as
> >> len ( '' . split ( ' ' ))
1
' '
,但根据文档如果未指定SEP或未
None
SEP,则应用不同的分裂算法:连续的Whitespace运行被视为单个分离器,如果字符串具有领先或尾随的空格,则结果将在开始或结束时不包含空字符串。因此,将一个空的字符串或一个由无分离器返回的空格组成的字符串返回[]
。如果给出了SEP,则连续的定系数不会分组在一起,并被视为定界空字符串(例如,'1,,2'.split(',')
返回['1', '', '2']
)。用指定的分离器返回['']
将空字符串拆分。
> >> ' a ' . split ( ' ' )
[ '' , 'a' , '' ]
> >> ' a ' . split ()
[ 'a' ]
> >> '' . split ( ' ' )
[ '' ]
# File: module.py
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
输出
> >> from module import *
> >> some_weird_name_func_ ()
"works!"
> >> _another_weird_name_func ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name '_another_weird_name_func' is not defined
通常建议不要使用通配符进口。造成这种情况的第一个明显原因是,在通配符进口中,带有领先的下划线的名称不会被进口。这可能会导致运行时错误。
如果我们from ... import a, b, c
语法中使用,则不会发生上述NameError
。
> >> from module import some_weird_name_func_ , _another_weird_name_func
> >> _another_weird_name_func ()
works !
如果您真的想使用通配符导入,则必须在模块中定义列表__all__
,其中包含一个公共对象列表,当我们进行通配符导入时将可用。
__all__ = [ '_another_weird_name_func' ]
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
输出
> >> _another_weird_name_func ()
"works!"
> >> some_weird_name_func_ ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'some_weird_name_func_' is not defined
> >> x = 7 , 8 , 9
> >> sorted ( x ) == x
False
> >> sorted ( x ) == sorted ( x )
True
> >> y = reversed ( x )
> >> sorted ( y ) == sorted ( y )
False
sorted
方法始终返回列表,比较列表和元素总是在python中返回False
。
> >> [] == tuple ()
False
> >> x = 7 , 8 , 9
> >> type ( x ), type ( sorted ( x ))
( tuple , list )
与sorted
不同, reversed
方法返回迭代器。为什么?因为排序需要迭代器进行就位或使用额外的容器(列表)进行修改,而逆转可以简单地从最后一个索引到第一个索引来工作。
因此,在比较sorted(y) == sorted(y)
期间,第一个sorted()
将消耗迭代器y
,而下一个调用只会返回一个空列表。
> >> x = 7 , 8 , 9
> >> y = reversed ( x )
> >> sorted ( y ), sorted ( y )
([ 7 , 8 , 9 ], [])
from datetime import datetime
midnight = datetime ( 2018 , 1 , 1 , 0 , 0 )
midnight_time = midnight . time ()
noon = datetime ( 2018 , 1 , 1 , 12 , 0 )
noon_time = noon . time ()
if midnight_time :
print ( "Time at midnight is" , midnight_time )
if noon_time :
print ( "Time at noon is" , noon_time )
输出(<3.5):
( 'Time at noon is' , datetime . time ( 12 , 0 ))
午夜未打印。
在Python 3.5之前, datetime.time
的布尔值。如果时间对象表示UTC中的午夜,则将其视为False
。使用if obj:
语法检查obj
是null还是等效的“空”时,它容易出错。
本节包含一些关于Python的鲜为人知和有趣的事情,这些事情大多数像我这样的初学者都没有意识到(嗯,不再了)。
好吧,给你
import antigravity
输出: SSHH ...这是一个超级秘密。
antigravity
模块是Python开发人员发布的少数复活节彩蛋之一。import antigravity
打开了一个网络浏览器,指向有关Python的经典XKCD漫画。goto
,但是为什么呢? from goto import goto , label
for i in range ( 9 ):
for j in range ( 9 ):
for k in range ( 9 ):
print ( "I am trapped, please rescue!" )
if k == 2 :
goto . breakout # breaking out from a deeply nested loop
label . breakout
print ( "Freedom!" )
输出(Python 2.3):
I am trapped , please rescue !
I am trapped , please rescue !
Freedom !
goto
的工作版本被宣布为April愚人的笑话。goto
的原因。如果您是不喜欢在Python中使用Whitespace表示范围的人之一,则可以通过导入来使用C风格{}
from __future__ import braces
输出:
File "some_file.py" , line 1
from __future__ import braces
SyntaxError : not a chance
牙套?决不!如果您认为这很令人失望,请使用Java。好的,另一件令人惊讶的事情,您能找到__future__
模块代码中提出的SyntaxError
在哪里?
__future__
模块通常用于提供未来版本的Python的功能。但是,在这种特定情况下的“未来”具有讽刺意味。future.c
文件中存在的。future.c
运行适当的代码。C将其视为正常的导入语句。输出(Python 3.x)
> >> from __future__ import barry_as_FLUFL
> >> "Ruby" != "Python" # there's no doubt about it
File "some_file.py" , line 1
"Ruby" != "Python"
^
SyntaxError : invalid syntax
>> > "Ruby" <> "Python"
True
我们开始吧。
这与2009年4月1日发布的PEP-401有关(现在您知道这是什么意思)。
引用PEP-401
认识到!= python 3.0中的不平等运算符是一个可怕的,手指诱发的错误,Flufl将<>钻石操作员恢复为唯一的拼写。
巴里叔叔不得不分享更多的事情。您可以在这里阅读它们。
它在交互式环境中运行良好,但是当您通过Python文件运行时,它将提高SyntaxError
(请参阅此问题)。但是,您可以将语句包装在eval
或compile
中以使其正常工作,
from __future__ import barry_as_FLUFL
print ( eval ( '"Ruby" <> "Python"' ))
import this
等等,这是什么? this
是爱❤️
输出:
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
这是Python的禅宗!
> >> love = this
> >> this is love
True
> >> love is True
False
> >> love is False
False
> >> love is not True or False
True
> >> love is not True or False ; love is love # Love is complicated
True
this
模块是Python禅宗的复活节彩蛋(PEP 20)。love is not True or False; love is love
,具有讽刺意味的是,这是不言自明的(如果没有的话,请参见与is
和is not
操作员有关的示例)。循环的else
子句。一个典型的例子可能是:
def does_exists_num ( l , to_find ):
for num in l :
if num == to_find :
print ( "Exists!" )
break
else :
print ( "Does not exist" )
输出:
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> does_exists_num ( some_list , 4 )
Exists !
>> > does_exists_num ( some_list , - 1 )
Does not exist
异常处理中的else
条款。一个例子,
try :
pass
except :
print ( "Exception occurred!!!" )
else :
print ( "Try block executed successfully..." )
输出:
Try block executed successfully ...
break
时,才执行循环后的else
子句。您可以将其视为“ Nobreak”条款。else
子句也称为“完成子句”,因为在try
语句中达到else
子句意味着尝试块实际上成功完成。 def some_func ():
Ellipsis
输出
> >> some_func ()
# No output, No Error
> >> SomeRandomString
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'SomeRandomString' is not defined
> >> Ellipsis
Ellipsis
Ellipsis
是一个全球可用的内置对象,相当于...
> >> ...
Ellipsis
pass
语句一样) > >> import numpy as np
> >> three_dimensional_array = np . arange ( 8 ). reshape ( 2 , 2 , 2 )
array ([
[
[ 0 , 1 ],
[ 2 , 3 ]
],
[
[ 4 , 5 ],
[ 6 , 7 ]
]
])
three_dimensional_array
是一系列数组。假设我们要打印所有最内部阵列的第二个元素(索引1
),我们可以使用省略号绕过所有前面的维度 > >> three_dimensional_array [:,:, 1 ]
array ([[ 1 , 3 ],
[ 5 , 7 ]])
> >> three_dimensional_array [..., 1 ] # using Ellipsis.
array ([[ 1 , 3 ],
[ 5 , 7 ]])
n_dimensional_array[firs_dim_slice, ..., last_dim_slice]
)(Callable[..., int]
或Tuple[str, ...]
)))))拼写是意图的。请不要为此提交补丁。
输出(Python 3.x):
> >> infinity = float ( 'infinity' )
> >> hash ( infinity )
314159
> >> hash ( float ( '-inf' ))
- 314159
float('-inf')
的哈希是“-10⁵xπ”,而python 2中的“-10⁵xe”。1.
class Yo ( object ):
def __init__ ( self ):
self . __honey = True
self . bro = True
输出:
> >> Yo (). bro
True
> >> Yo (). __honey
AttributeError : 'Yo' object has no attribute '__honey'
> >> Yo (). _Yo__honey
True
2.
class Yo ( object ):
def __init__ ( self ):
# Let's try something symmetrical this time
self . __honey__ = True
self . bro = True
输出:
> >> Yo (). bro
True
> >> Yo (). _Yo__honey__
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'Yo' object has no attribute '_Yo__honey__'
为什么Yo()._Yo__honey
工作?
3.
_A__variable = "Some value"
class A ( object ):
def some_func ( self ):
return __variable # not initialized anywhere yet
输出:
> >> A (). __variable
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'A' object has no attribute '__variable'
> >> A (). some_func ()
'Some value'
__
(双重下划线又名“ Dunder”)开头,而不是通过在前面添加_NameOfTheClass
来结束多个尾随下划线。__honey
属性,我们必须将_Yo
附加到前面,这将防止与其他任何类中定义的同名属性发生冲突。return __variable
中的名称__variable
___variable被弄_A__variable
,这也恰好是我们在外部范围中声明的变量的名称。输出:
> >> value = 11
> >> valuе = 32
> >> value
11
呃?
注意:重现这一点的最简单方法是简单地复制上述片段的语句并将其粘贴到您的文件/外壳中。
一些非西方字符看起来与英语字母中的字母相同,但被解释器视为不同。
> >> ord ( 'е' ) # cyrillic 'e' (Ye)
1077
> >> ord ( 'e' ) # latin 'e', as used in English and typed using standard keyboard
101
> >> 'е' == 'e'
False
> >> value = 42 # latin e
> >> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here
> >> value
42
内置的ord()
函数返回字符的Unicode代码点,以及西里尔“ e”和Latin'E'的不同代码位置证明了上述示例的行为。
# `pip install numpy` first.
import numpy as np
def energy_send ( x ):
# Initializing a numpy array
np . array ([ float ( x )])
def energy_receive ():
# Return an empty numpy array
return np . empty ((), dtype = np . float ). tolist ()
输出:
> >> energy_send ( 123.456 )
> >> energy_receive ()
123.456
诺贝尔奖在哪里?
energy_send
函数中创建的NUMPY数组未返回,因此内存空间可以自由重新分配。numpy.empty()
返回下一个免费内存插槽,而无需重新定位它。这个记忆点恰好是刚被释放(通常但并非总是如此)相同的。 def square ( x ):
"""
A simple function to calculate the square of a number by addition.
"""
sum_so_far = 0
for counter in range ( x ):
sum_so_far = sum_so_far + x
return sum_so_far
输出(Python 2.x):
> >> square ( 10 )
10
那不是100吗?
注意:如果您无法重现此功能,请尝试通过Shell运行文件Mixed_tabs_and_spaces.py。
不要混合选项卡和空间!刚刚返回的字符是“选项卡”,代码在示例其他地方的“ 4个空格”中缩进。
这就是Python处理选项卡的方式:
首先,标签被(从左到右)替换1到八个空间,以至于替换的字符总数为八个<...>的倍数。
因此, square
功能的最后一行的“选项卡”被八个空间替换为循环。
Python 3足够友善,可以自动抛出错误。
输出(Python 3.x):
TabError : inconsistent use of tabs and spaces in indentation
+=
更快 # using "+", three strings:
> >> timeit . timeit ( "s1 = s1 + s2 + s3" , setup = "s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000" , number = 100 )
0.25748300552368164
# using "+=", three strings:
> >> timeit . timeit ( "s1 += s2 + s3" , setup = "s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000" , number = 100 )
0.012188911437988281
+=
对于串联超过两个字符串的速度比+
快,因为在计算完整的字符串时不会破坏第一个字符串(示例, s1
s1 += s2 + s3
)。 def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s += "xyz"
assert len ( s ) == 3 * iters
def add_bytes_with_plus ( iters ):
s = b""
for i in range ( iters ):
s += b"xyz"
assert len ( s ) == 3 * iters
def add_string_with_format ( iters ):
fs = "{}" * iters
s = fs . format ( * ([ "xyz" ] * iters ))
assert len ( s ) == 3 * iters
def add_string_with_join ( iters ):
l = []
for i in range ( iters ):
l . append ( "xyz" )
s = "" . join ( l )
assert len ( s ) == 3 * iters
def convert_list_to_string ( l , iters ):
s = "" . join ( l )
assert len ( s ) == 3 * iters
输出:
# Executed in ipython shell using %timeit for better readability of results.
# You can also use the timeit module in normal python shell/scriptm=, example usage below
# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
> >> NUM_ITERS = 1000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS )
124 µs ± 4.73 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS )
211 µs ± 10.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS )
61 µs ± 2.18 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS )
117 µs ± 3.21 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS )
10.1 µs ± 1.06 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
让我们将迭代次数增加10倍。
> >> NUM_ITERS = 10000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS ) # Linear increase in execution time
1.26 ms ± 76.8 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS ) # Quadratic increase
6.82 ms ± 134 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS ) # Linear increase
645 µs ± 24.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS ) # Linear increase
1.17 ms ± 7.25 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS ) # Linear increase
86.3 µs ± 2 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
您可以在这些链接上阅读有关TimeIt或%TimeIt的更多信息。它们用于测量代码件的执行时间。
不要使用+
来产生长字符串 - 在python中, str
是不变的,因此必须将左右字符串复制到每对串联的新字符串中。如果您的长度为10的四个字符串,则您将复制(10+10)+((10+10)+10)+((((10+10)+10)+10)= 90个字符,而不仅仅是40个字符人物。随着字符串的数字和大小的增加,情况会变得更糟(与add_bytes_with_plus
函数的执行时间合理)
因此,建议使用.format.
或%
语法(但是,对于非常短的字符串,它们比+
略慢)。
或者更好的是,如果您已经有内容的内容,则可以使用''.join(iterable_object)
,这要快得多。
与add_bytes_with_plus
不同,由于上一个示例中讨论的+=
优化, add_string_with_plus
并未显示执行时间的二次增加。如果该语句为s = s + "x" + "y" + "z"
而不是s += "xyz"
,则增加将是二次的。
def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s = s + "x" + "y" + "z"
assert len ( s ) == 3 * iters
> >> % timeit - n100 add_string_with_plus ( 1000 )
388 µs ± 22.4 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n100 add_string_with_plus ( 10000 ) # Quadratic increase in execution time
9 ms ± 298 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
与Python的禅宗形成鲜明对比的是,要格式化和创建巨型字符串的多种方法,据此
应该有一种——最好只有一种——明显的方法来做到这一点。
dict
查找 * some_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
another_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
输出:
> >> % timeit some_dict [ '5' ]
28.6 ns ± 0.115 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> some_dict [ 1 ] = 1
> >> % timeit some_dict [ '5' ]
37.2 ns ± 0.265 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> % timeit another_dict [ '5' ]
28.5 ns ± 0.142 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> another_dict [ 1 ] # Trying to access a key that doesn't exist
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
KeyError : 1
> >> % timeit another_dict [ '5' ]
38.5 ns ± 0.0913 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
为什么相同的查找变慢?
str
, int
,任何对象...),而专门的键则适用于由str
的键组成的常见词典。lookdict_unicode
)知道所有现有的键(包括查找键)都是字符串,并且使用更快,更简单的字符串比较来比较键,而不是调用__eq__
方法。str
键访问dict
实例时,它进行了修改,因此未来的查找使用通用函数。dict
实例,此过程不可逆转,并且键甚至不必存在。这就是为什么尝试失败查找具有相同效果的原因。dict
S * import sys
class SomeClass :
def __init__ ( self ):
self . some_attr1 = 1
self . some_attr2 = 2
self . some_attr3 = 3
self . some_attr4 = 4
def dict_size ( o ):
return sys . getsizeof ( o . __dict__ )
输出:( Python 3.8,其他3个版本可能会有所不同)
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104
> >> dict_size ( o2 )
104
> >> del o1 . some_attr1
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
> >> dict_size ( o1 )
232
让我们再试一次...在新的解释器中:
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104 # as expected
> >> o1 . some_attr5 = 5
> >> o1 . some_attr6 = 6
> >> dict_size ( o1 )
360
> >> dict_size ( o2 )
272
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
是什么使这些词典变成肿?为什么新创建的对象也肿了?
__init__
中添加许多属性,而不会导致添加许多属性“不共样”)。如果在调整大小时存在多个实例,则在同一类的所有未来实例中禁用密钥共享:cpython无法确定您的实例是否正在使用相同的属性集,并决定在尝试共享他们的实例时纾困键。__init__
中的所有属性! join()
是字符串操作而不是列表操作。 (首先使用时有点违反直觉)
说明:如果join()
是字符串上的一种方法,则可以在任何迭代(列表,元组,迭代器)上操作。如果是列表上的一种方法,则必须由每种类型分别实现。同样,在通用list
对象API上放置特定于字符串的方法并没有多大意义。
看起来很奇怪但在语义上正确的陈述:
[] = ()
是一种语义上正确的语句(将空tuple
包装到空list
中)'a'[0][0][0][0][0]
在语义上也是正确的,因为Python没有类似于C的字符数据类型单字符字符串。3 --0-- 5 == 8
和--5 == 5
都是语义上正确的语句,并评估为True
。鉴于a
是一个数字, ++a
和--a
都是有效的python语句,但与C,C ++或Java等语言中的类似语言相比,行为的行为并不相同。
> >> a = 5
> >> a
5
> >> + + a
5
> >> - - a
5
解释:
++
操作员。它实际上是两个+
运算符。++a
解析为+(+a)
,转化为a
。同样,语句的输出--a
可以是合理的。您必须意识到Python的海象操作员。但是,您是否听说过太空操作员?
> >> a = 42
> >> a -= - 1
> >> a
43
它被用作替代增量操作员,另一个
> >> a += + 1
> >> a
> >> 44
说明:这种恶作剧来自雷蒙德·赫廷格(Raymond Hettinger)的推文。空间入侵者运算符实际上只是一个畸形的a -= (-1)
。相当于a = a - (- 1)
。 a += (+ 1)
情况相似。
Python具有无证件的匡威含义操作员。
> >> False ** False == True
True
> >> False ** True == False
True
> >> True ** False == True
True
> >> True ** True == True
True
说明:如果将False
和True
替换为0和1并执行数学,则真相表等效于相反的含义操作员。 (来源)
由于我们正在谈论运算符,因此还有@
运算符用于矩阵乘法(不用担心,这次是真实的)。
> >> import numpy as np
> >> np . array ([ 2 , 2 , 2 ]) @ np . array ([ 7 , 8 , 8 ])
46
说明: @
操作员在Python 3.5中添加了 @ the @ operation。任何对象都可以超载__matmul__
魔术方法来定义该操作员的行为。
从Python 3.8开始,您可以使用典型的F-string语法,例如f'{some_var=}
进行快速调试。例子,
> >> some_string = "wtfpython"
> >> f' { some_string = } '
"some_string='wtfpython'"
Python在功能中使用2个字节用于局部变量存储。从理论上讲,这意味着在函数中只能定义65536个变量。但是,python内置了一个方便的解决方案,可用于存储超过2^16个变量名称。以下代码说明了定义了65536多个本地变量时堆栈中发生的情况(警告:此代码在2^18行文本左右打印,因此请准备好!):):
import dis
exec ( """
def f():
""" + """
""" . join ([ "X" + str ( x ) + "=" + str ( x ) for x in range ( 65539 )]))
f ()
print ( dis . dis ( f ))
多个Python线程不会同时运行您的Python代码(是的,您听到了!)。似乎要生成几个线程并让它们同时执行您的Python代码,这似乎是直观的,但是,由于Python中的全局解释器锁定,您所做的只是使您的线程在同一核心上转弯执行。 Python线程非常适合IO结合任务,但是为了实现Python中CPU结合任务的实际并行化,您可能需要使用Python多处理模块。
有时, print
方法可能不会立即打印值。例如,
# File some_file.py
import time
print ( "wtfpython" , end = "_" )
time . sleep ( 3 )
由于end
参数,这将在3秒后打印wtfpython
,因为遇到n
或程序完成执行后,输出缓冲区被冲洗。我们可以通过传递flush=True
参数来强迫缓冲区冲洗。
列表切成范围的切片,从界限指数列出没有错误
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> some_list [ 111 :]
[]
切片并不总是会创建一个新对象。例如,
> >> some_str = "wtfpython"
> >> some_list = [ 'w' , 't' , 'f' , 'p' , 'y' , 't' , 'h' , 'o' , 'n' ]
> >> some_list is some_list [:] # False expected because a new object is created.
False
> >> some_str is some_str [:] # True because strings are immutable, so making a new object is of not much use.
True
int('١٢٣٤٥٦٧٨٩')
在Python 3中返回123456789
在Python中,十进制字符包括数字字符,以及所有可用于形成十进制 - radix号码的字符,例如u+0660,阿拉伯语indic indic数字零。这是一个有趣的故事,与Python的这种行为有关。
您可以将数字文字与下划线(以获得更好的可读性)与Python 3分开。
> >> six_million = 6_000_000
> >> six_million
6000000
> >> hex_address = 0xF00D_CAFE
> >> hex_address
4027435774
'abc'.count('') == 4
。这是count
方法的大致实现,这将使事物更加清晰
def count ( s , sub ):
result = 0
for i in range ( len ( s ) + 1 - len ( sub )):
result += ( s [ i : i + len ( sub )] == sub )
return result
该行为是由于将空的子字符串( ''
)与原始字符串中的长度为0的匹配所致。
您可以为WTFPYTHON做出贡献的几种方式,
请参阅 CONTRIBUTING.md 了解更多详细信息。随意创建一个新问题来讨论事情。
PS:请不要与反向链接请求联系,除非它们与项目高度相关,否则不会添加链接。
该系列的想法和设计最初是受Denys Dovhan的出色项目WTFJ的启发。 Pythonistas的压倒性支持使它的形状现在处于形状。
© Satwik Kansal
If you like wtfpython, you can use these quick links to share it with your friends,
推特 |领英 | Facebook
I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details here to get them as soon as they are finished.
这就是大家! For upcoming content like this, you can add your email here.