[英]How is ternary operator implemented in Python
我知道條件表達式(或三元運算符)在Python中是懶惰的。 它們代表條件執行而不是條件選擇 。 換句話說,在以下中僅評估a
或b
中的一個:
c = a if condition else b
我有興趣知道它是如何在內部實現的。 Python是否轉換為如下所示的if
語句,如果是,則轉換發生在什么階段?
if condition:
c = a
else:
c = b
或者三元運算符實際上是一個獨立且獨立的表達式,完全單獨定義? 如果是這樣,我可以訪問條件表達式的CPython代碼嗎?
我看了這說明三元運營商做什么下文,但它們都沒有明確它們是如何實現的:
編輯:您可以假設CPython參考實現。
Python不需要轉換任何東西,如果它想要的話就不能。
通過使用語言語法將條件表達式解析為抽象語法樹 ,然后將其編譯為字節碼。 您可以使用ast.parse()
函數生成AST:
>>> import ast
>>> ast.parse('c = a if condition else b').body[0] # first statement in the tree
<_ast.Assign object at 0x10f05c550>
>>> ast.dump(ast.parse('c = a if condition else b').body[0])
"Assign(targets=[Name(id='c', ctx=Store())], value=IfExp(test=Name(id='condition', ctx=Load()), body=Name(id='a', ctx=Load()), orelse=Name(id='b', ctx=Load())))"
注意為賦值生成的AST中的ast.IfExp()
節點; 這是條件表達式的專用節點。 它有test
, body
和orelse
部分來表示構成條件,真假部分的3個表達式。 這在ast
模塊Abstract Grammar部分中有記錄 :
expr = [...] | [...] | IfExp(expr test, expr body, expr orelse)
這表明每個元素的類型是另一個expr
表達式節點。
然后將解析樹編譯為字節碼,該字節碼使用堆棧根據測試有條件地跳轉到右側部分; 我們可以將ast.parse()
生成的AST直接傳遞給compile()
函數 ,之后dis
模塊讓我們看一下compile()
生成的ast.parse()
碼的人性友好形式:
>>> import dis
>>> dis.dis(compile(ast.parse('c = a if condition else b'), '', 'exec'))
1 0 LOAD_NAME 0 (condition)
2 POP_JUMP_IF_FALSE 8
4 LOAD_NAME 1 (a)
6 JUMP_FORWARD 2 (to 10)
>> 8 LOAD_NAME 2 (b)
>> 10 STORE_NAME 3 (c)
12 LOAD_CONST 0 (None)
14 RETURN_VALUE
因此,如果條件為假,則解釋器循環跳轉到指令8,否則執行指令4和6,指令6跳轉到指令10(因此超過else
表達式)。 最終結果是指令4或指令8將新結果放在堆棧頂部,以便STORE_NAME
移動到變量。
if
語句導致不同的AST節點,並且生成的字節碼恰好非常相似,因為它也會使用跳轉。 但編譯器將它們視為不同的語法片段,並且必須這樣做。
表達式和語句是編程語言的兩個非常不同的基本構建塊 。 語句可以包含表達式,但表達式不能包含語句,只能包含其他表達式。 並且表達式可以生成一個值(對於要使用的周圍語法),但語句不能 。 因此,Python必須以非常不同的方式處理條件表達式,因為語法分析器知道何時期望語句以及何時允許表達式。 如果將條件表達式轉換為語句,則無法將此類表達式用作更大表達式的一部分!
因為if
語句不是表達式 ,所以它不返回值(因為只有表達式可以產生值),因此生成的字節碼不會在堆棧頂部產生一個值,供周圍的Python代碼使用(沒有c = if condition : ...
)。 if
語句包含一個條件表達式和一個套件 ,它必須總是由更多的語句組成(有一個'表達式語句'可以讓你只在一個語句中放置一個表達式,例如1 + 1
在一行上),這些陳述可以“做東西”,如作業或從函數返回,但if
返回某些內容,他們所做的一切都不會做。
這反映在if
語句的AST節點定義中:
stmt = [...] | [...] | If(expr test, stmt* body, stmt* orelse)
因此對於If
節點, test
是唯一的表達式節點, body
和orelse
都包含零個或多個語句。 orelse
部分將保存任何elif ...:
作為進一步的If()
節點或任何其他類型的語句測試以形成無條件的else:
. 使用零個或多個元素,您不能指望單個結果。
所以這不是CPython獨有的,這適用於所有Python實現。 Python 語法不是實現細節。
Python是否轉換為if語句,如下所示
幾乎。
import dis
def trenary():
x = 'a' if 1 == 1 else 'b'
def normal_if():
if 1 == 1:
c = 'a'
else:
c = 'b'
print('trenary')
dis.dis(trenary)
print()
print('normal if')
dis.dis(normal_if)
這輸出:
trenary
68 0 LOAD_CONST 1 (1)
2 LOAD_CONST 1 (1)
4 COMPARE_OP 2 (==)
6 POP_JUMP_IF_FALSE 12
8 LOAD_CONST 2 ('a')
10 JUMP_FORWARD 2 (to 14)
>> 12 LOAD_CONST 3 ('b')
>> 14 STORE_FAST 0 (x)
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
normal if
71 0 LOAD_CONST 1 (1)
2 LOAD_CONST 1 (1)
4 COMPARE_OP 2 (==)
6 POP_JUMP_IF_FALSE 14
72 8 LOAD_CONST 2 ('a')
10 STORE_FAST 0 (c)
12 JUMP_FORWARD 4 (to 18)
74 >> 14 LOAD_CONST 3 ('b')
16 STORE_FAST 0 (c)
>> 18 LOAD_CONST 0 (None)
20 RETURN_VALUE
這些看起來幾乎一樣,除了位置JUMP_FORWARD
和一個額外的STORE_FAST
由@ L3viathan指出。
我們也得到幾乎相同的執行時間(差別可以忽略不計):
from timeit import Timer
print(min(Timer(trenary).repeat(5000, 5000)))
print(min(Timer(normal_if).repeat(5000, 5000)))
# 0.0006442809999998023
# 0.0006442799999994975
至於何時發生這種轉換,我會假設在“編譯”到字節碼期間的某個時間。
如果你在問什么,那么為了最好地理解它,你需要理解功能和程序之間的區別。 一個可以轉換為另一個,但兩者都可以獨立查看,您不必將其中一個轉換為另一個來理解它們。
value_a if condition else value_b
是否有效,並返回值value_a
或value_b
。
if condition then:
do_a
else:
do_b
是程序性的,它確實是do_a
或do_b
。
注意:程序是關於做,做這個,然后做那個,或那個。 功能是關於價值,是這個還是那個。
如果您正在詢問如何,那么您將需要查看其中一個實現的源代碼。 請注意,只要行為正確,每個實現都不必以相同的方式執行。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.