在Python中,是否在每次函数调用时评估函数级赋值



请考虑以下代码:

import re
def qcharToUnicode(s):
p = re.compile(r"QChar((0x[a-fA-F0-9]*))")
return p.sub(lambda m: '"' + chr(int(m.group(1),16)) + '"', s)
def fixSurrogatePresence(s) :
'''Returns the input UTF-16 string with surrogate pairs replaced by the character they represent'''
# ideas from:
# http://www.unicode.org/faq/utf_bom.html#utf16-4
# http://stackoverflow.com/a/6928284/1503120
def joinSurrogates(match) :
SURROGATE_OFFSET = 0x10000 - ( 0xD800 << 10 ) - 0xDC00
return chr ( ( ord(match.group(1)) << 10 ) + ord(match.group(2)) + SURROGATE_OFFSET )
return re.sub ( '([uD800-uDBFF])([uDC00-uDFFF])', joinSurrogates, s )

现在,我下面的问题可能反映了C/C++的思维方式(而不是"Python"思维方式),但我还是很好奇:

我想知道对qcharToUnicode中的已编译RE对象pjoinSurrogates中的SURROGATE_OFFSET的评估是在对各自函数的每次调用时进行,还是在定义时只进行一次?我的意思是,在C/C++中,可以将值声明为static const,编译(IIUC)将使构造只发生一次,但在Python中,我们没有任何这样的声明。

这个问题在编译的RE对象的情况下更为相关,因为构建这样一个对象的唯一原因似乎是避免重复编译,正如Python RE HOWTO所说:

应该使用这些模块级函数,还是应该获得模式并自己调用其方法?如果你在循环中访问regex,预编译它将节省一些函数调用。

。。。如果在每次函数调用时都要进行编译,那么这个目的就会失败。我不想将符号p(或SURROGATE_OFFSET)放在模块级别,因为我只想将其可见性限制为相关函数。

那么,解释器是否会做一些类似于启发式的事情来确定特定符号所指向的值是恒定的(并且仅在特定函数中可见),因此不需要在下一个函数中重构?此外,这是由语言或实现定义的吗?(我希望我没有要求太多!)

一个相关的问题是关于qcharToUnicode中函数对象lambda m的构造——它是否也像def声明的其他命名函数对象一样只定义了一次?

简单的答案是,正如编写的那样,代码将在每次函数调用时重复执行。对于您描述的情况,Python中没有隐式缓存机制。

你应该改掉谈论"声明"的习惯。函数定义实际上也"只是"一个正常的语句,所以我可以写一个循环来重复定义同一个函数:

for i in range(10):
def f(x):
return x*2
y = f(i)

在这里,我们将在每次循环运行时产生创建函数的成本。计时显示,此代码的运行时间约为前一代码的75%:

def f(x):
return x*2
for i in range(10):
y = f(i)

优化RE情况的标准方法是,如您所知,将p变量放在模块范围内,即:

p = re.compile(r"QChar((0x[a-fA-F0-9]*))")
def qcharToUnicode(s):
return p.sub(lambda m: '"' + chr(int(m.group(1),16)) + '"', s)

你可以使用诸如在变量前加"_"之类的约定来表示不应该使用它,但如果你没有记录它,通常人们不会使用它。使RE函数本地化的一个技巧是使用默认参数的结果:它们与函数定义同时执行,所以你可以这样做:

def qcharToUnicode(s, p=re.compile(r"QChar((0x[a-fA-F0-9]*))")):
return p.sub(lambda m: '"' + chr(int(m.group(1),16)) + '"', s)

这将允许您进行相同的优化,但在匹配功能方面也有更多的灵活性。

正确思考函数定义也可以让您停止将lambda视为与def不同。唯一的区别是def还将函数对象绑定到一个名称上——创建的底层对象是相同的。

Python是一种脚本/解释语言。。。是的,每次调用函数时都会进行赋值。解释器将只解析您的代码一次,生成Python字节码。下次调用此函数时,它将被编译成PythonVM字节码,因此该函数将被简单地执行。

re.compile每次都会被调用,就像在其他语言中一样。如果您想模拟静态初始化,请考虑使用全局变量,这样它只会被调用一次。更好的是,您可以创建一个具有静态方法和静态成员(类而非实例成员)的类。

您可以使用Python中的dis模块来检查所有这些。所以,我只是将您的代码复制并粘贴到teste.py模块中。

>>> import teste
>>> import dis
>>> dis.dis(teste.qcharToUnicode)
4           0 LOAD_GLOBAL              0 (re)
3 LOAD_ATTR                1 (compile)
6 LOAD_CONST               1 ('QChar\((0x[a-fA-F0-9]*)\)')
9 CALL_FUNCTION            1
12 STORE_FAST               1 (p)
5          15 LOAD_FAST                1 (p)
18 LOAD_ATTR                2 (sub)
21 LOAD_CONST               2 (<code object <lambda> at 0056C140, file "teste.py", line 5>)
24 MAKE_FUNCTION            0
27 LOAD_FAST                0 (s)
30 CALL_FUNCTION            2
33 RETURN_VALUE

是的。假设re.compile()有副作用。每当对p进行赋值时,即每次调用包含所述赋值的函数时,都会出现这种副作用。

这可以验证:

def foo():
print("ahahaha!")
return bar
def f():
return foo()
def funcWithSideEffect():
print("The airspeed velocity of an unladen swallow (european) is...")
return 25
def funcEnclosingAssignment():
p = funcWithSideEffect()
return p;
a = funcEnclosingAssignment()
b = funcEnclosingAssignment()
c = funcEnclosingAssignment()

每次调用封闭函数(类似于您的qcharToUnicode)时,都会打印该语句,显示p正在被重新求值。

最新更新