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

时间:2014-01-10 06:41:58

标签: 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 ++的思维方式(而不是“Pythonic”方式),但我很好奇:

我想知道在p中的qcharToUnicodeSURROGATE_OFFSET中编译的RE对象joinSurrogates的评估是否会在每次调用相应函数时进行或者只在定义点一次?我的意思是在C / C ++中,可以将值声明为static const,并且编译将(IIUC)使构造仅发生一次,但在Python中我们没有任何此类声明。

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

  

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

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

解释器也可以通过启发式确定特定符号指向的值是常量(并且仅在特定函数中可见),因此无需在下一个函数中重建?此外,这是由语言还是依赖于实现来定义的? (我希望我不要求太多!)

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

3 个答案:

答案 0 :(得分:3)

简单的答案是,在编写代码时,代码将在每次函数调用时重复执行。对于您描述的情况,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还将函数对象绑定到名称 - 创建的基础对象是相同的。

答案 1 :(得分:1)

是的,他们是。假设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。

答案 2 :(得分:1)

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

每次都会调用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