【问题标题】:How can an optional parameter become required?可选参数如何成为必需的?
【发布时间】:2019-07-25 21:33:12
【问题描述】:

基于Disable global variable lookup in Python(以及我自己的答案),我在使用带有可选参数的函数时遇到了问题,例如在这个最小示例中:

import types

def noglobal(f):
    return types.FunctionType(f.__code__, {})

@noglobal
def myFunction(x=0):
    pass

myFunction()

基本上,它会像这样失败:

Traceback (most recent call last):
  File "SetTagValue.py", line 10, in <module>
    myFunction()
TypeError: myFunction() missing 1 required positional argument: 'x'

为什么x突然被当作必填参数了?

【问题讨论】:

    标签: python optional-parameters globals


    【解决方案1】:

    这是因为你没有正确复制函数。如果您查看 types.FunctionType 的签名,您会发现它接受 5 个参数:

    class function(object)
     |  function(code, globals, name=None, argdefs=None, closure=None)
     |
     |  Create a function object.
     |
     |  code
     |    a code object
     |  globals
     |    the globals dictionary
     |  name
     |    a string that overrides the name from the code object
     |  argdefs
     |    a tuple that specifies the default argument values
     |  closure
     |    a tuple that supplies the bindings for free variables
    

    您没有传递任何argdefs,因此该函数不再有可选参数。复制函数的正确方法是

    types.FunctionType(f.__code__,
                       {},
                       f.__name__,
                       f.__defaults__,
                       f.__closure__
                       )
    

    然而,这会导致另一个问题:切断对全局变量的访问也会切断对内置函数的访问。如果您尝试使用printopendictmyFunction 中的类似内容,您将得到NameError。所以真正正确的方式来写你的装饰器是这样的:

    import builtins
    import types
    
    def noglobal(f):
        return types.FunctionType(f.__code__,
                                  {'__builtins__': builtins},
                                  f.__name__,
                                  f.__defaults__,
                                  f.__closure__
                                  )
    

    【讨论】:

    • 谢谢,{'__builtins__': builtins} 是个好主意。我实际上使用globals().copy() 来允许到目前为止定义的所有全局变量(特别是内置函数、导入函数和其他函数)。还是有更聪明的方法来实现这一目标?
    • @bers 我没有看到这样做的意义。你不知道这个函数来自哪里,你不知道它的全局变量是什么。为什么要让它访问定义 noglobal 装饰器的模块的全局变量?这没什么意义。
    • 我知道函数的来源——我自己写的 :) 我只是想避免意外使用全局定义的变量(在if __name__ == "__main__" 内)。
    【解决方案2】:

    如果您想保留默认参数值,您还需要传递它们:

    import types
    
    def noglobal(f):
        return types.FunctionType(f.__code__, {}, f.__name__, f.__defaults__)
    
    @noglobal
    def myFunction(x=0):
        pass
    
    myFunction()
    

    您可以将最后一个closure 参数传递给types.FunctionType,如果您想保持带闭包的函数正常工作,您可能还想从f.__closure__ 继承该参数。

    【讨论】:

    • 谢谢!您能否添加在哪里可以找到types.FunctionType 的签名?我当然以前找过它,但没有找到。
    • @bers 不在官方文档中,因为它是一个 CPython 实现细节。不过你可以help(types.FunctionType)
    猜你喜欢
    • 1970-01-01
    • 2011-01-13
    • 2017-06-14
    • 1970-01-01
    • 2018-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-02
    相关资源
    最近更新 更多