在Python中,函数级赋值是在每次函数调用时计算的吗?

2024-09-30 03:23:18 发布

您现在位置: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++思维方式(而不是一个“Pythone”),但我还是好奇:

我想知道编译后的RE对象pqcharToUnicodeSURROGATE_OFFSETjoinSurrogates中的求值是在每次调用相应函数时进行,还是仅在定义时进行一次?我的意思是在C/C++中,可以声明值为{{CD5}},编译意愿(IIUC)使构造只发生一次,但在Python中,我们没有任何此类声明。你知道吗

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

Should you use these module-level functions, or should you get the pattern and call its methods yourself? If you’re accessing a regex within a loop, pre-compiling it will save a few function calls.

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

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

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


Tags: the对象函数re声明return定义def
3条回答

是的,他们是。假设重新编译()有副作用。每次对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正在被重新计算。你知道吗

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

那个重新编译每次都会被调用,就像其他语言一样。如果要模拟静态初始化,请考虑使用全局变量,这样只调用一次。更好的是,您可以使用静态方法和静态成员(类成员而不是实例成员)创建一个类。你知道吗

您可以使用Python中的dis模块检查所有这些。所以,我把你的代码复制粘贴到测试.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

简单的答案是,在编写时,代码将在每次函数调用时重复执行。对于您描述的情况,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 case的标准方法是将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还将函数对象绑定到一个名称-创建的底层对象是相同的。你知道吗

相关问题 更多 >

    热门问题