如何用Python實現(xiàn)字符串插值

原文來自: How to Implement String Interpolation in Python - DZone Web Dev,本文是在自行理解之后的翻譯,粗淺之處,望請諒解。

字符串插值是將字符串中的占位符替換為局域變量的過程。許多編程語言都可以做到,比如 Scala:

// Scale 2.10+
var name = "John";
println(s"My name is $name")
>>> My name is John

Perl:

my $name = "John";
print "My name is $name";
>>> My name is John

CoffeeScript:

name = "John"
console.log "My name is #{name}"
>>> My name is John

… 還有很多。

乍看之下,似乎不大可能使用 Python 實現(xiàn)字符串插值,但實際上,我們只需要兩行代碼就可以實現(xiàn)。

首先,讓我們從基礎(chǔ)開始說起。通常我們構(gòu)建一個復(fù)雜的 Python 字符串時都會使用 format 函數(shù):

print "Hi, I am {} and I am {} years old".format(name, age)
>>> Hi, I am John and I am 26 years old

可以看出,format 的實現(xiàn)比字符串連接看起來整潔許多:

print "Hi, I am " + name + " and I am " + str(age) + " years old"
Hi, I am John and I am 26 years old

但如果通過這種方式使用 format 函數(shù),輸出的內(nèi)容就取決于參數(shù)的位置順序:

print "Hi, I am {} and I am {} years old".format(age, name)
Hi, I am 26 and I am John years old

為了避免這種情況,我們可以構(gòu)造鍵值對形式的參數(shù)序列傳給 format 函數(shù),如下:

print "Hi, I am {name} and I am {age} years old".format(name="John", age=26)
Hi, I am John and I am 26 years old
print "Hi, I am {name} and I am {age} years old".format(age=26, name="John")
Hi, I am John and I am 26 years old

這里,為實現(xiàn)字符串插值,我們不得不傳入將所有變量傳入 format 函數(shù),但是這依然沒有達到我們想要的效果,因為 nameage 并不是局域變量。那么,format 函數(shù)可以在某種程度上訪問到局域變量嗎?

答案是可以的,使用 locals 函數(shù)我們能夠獲得存儲著所有局域變量對象的字典:

name = "John"
age = 26
locals()
>>> {
 ...
 'age': 26,
 'name': 'John',
 ...
}

現(xiàn)在,我們可以將這個字典傳給 format 函數(shù)了。不幸的是,我們不能像這樣調(diào)用 s.format(locals()) :

print "Hi, I am {name} and I am {age} years old".format(locals())
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-5-0fb983071eb8> in <module>()
----> 1 print "Hi, I am {name} and I am {age} years old".format(locals())
KeyError: 'name'

這是因為 locals 函數(shù)返回的是一個字典,而 format 函數(shù)期望的是鍵值對參數(shù)序列。
幸運的是,我們可以使用 ** 操作符將字典轉(zhuǎn)換為鍵值對參數(shù)序列。如下,假設(shè)我們有一個期望鍵值對序列作為參數(shù)的函數(shù):

def foo(arg1=None, arg2=None):
    print "arg1 = " + str(arg1)
    print "arg2 = " + str(arg2)

那么,我們就可以將存儲于字典中的參數(shù)進行解包傳入了:

d = {
    'arg1': 1,
    'arg2': 42
}
foo(**d)
>>> arg1 = 1
arg2 = 42

現(xiàn)在,使用這項技巧,我們就可以完成字符串插值的初版了,它大概長成這樣:

print "Hi, I am {name} and I am {age} years old".format(**locals())
Hi, I am John and I am 26 years old

以上代碼確實可以達到我們的需求,但看起來既笨重又不雅觀。因為在進行字符串插值的時候,我們每次都不得不寫上長長的一串 format(\*\*locals()) 。如果能夠?qū)懸粋€函數(shù)來完成這個過程會好很多,像這樣:

# Can we implement inter() function in Python?
print inter("Hi, I am {name} and I am {age} years old")
>>> Hi, I am John and I am 26 years old

你可能覺得這不科學(xué),因為如果我們將完成字符串插值的代碼移動到另一個函數(shù)中,那么它不就無法訪問原本作用域中的局域變量了嗎:

name = "John"
print inter("My name is {name}")
...
def inter(s):
  # How can we access "name" variable from here?
  return s.format(...)

然而,這是有可能的。Python 提供了 sys.\_getframe 方法,借用它的便利,我們可以方便地監(jiān)測到用于保存當(dāng)前局域變量的 frame 對象:

import sys
def foo():
     foo_var = 'foo'
     bar()
 def bar():
     # sys._getframe(0) would return frame for function "bar"
     # so we need to to access 1-st frame
     # to get local variables from "foo" function
     previous_frame = sys._getframe(1)
     previous_frame_locals = previous_frame.f_locals
     print previous_frame_locals['foo_var']
foo()
>>> foo

稍作解釋:
f_localsframe 的一個屬性,它用于保存對應(yīng)作用域的局域?qū)ο笞值?,因此可以通過 f_locals[‘foo_var’] 獲取到函數(shù) foo 的局域變量 foo_var
關(guān)于framef_locals,可以參考python inspect模塊解析 - 2.9. 棧幀(frame) 或者 Python程序的執(zhí)行原理 - PyFrameObject 部分。

現(xiàn)在的工作就只剩將獲得的 frame 數(shù)據(jù)與函數(shù) format 結(jié)合起來了。下面就給出實現(xiàn) Python 字符串插值的兩行代碼,請盡情使用吧:

def inter(s):
    return s.format(**sys._getframe(1).f_locals)

## example
name = "John"
age = 26
print inter("Hi, I am {name} and I am {age} years old")
>>> Hi, I am John and I am 26 years old
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容