如何识别Python函数内意外读写全局变量?例如使用静态分析?

2024-09-26 04:49:17 发布

您现在位置:Python中文网/ 问答频道 /正文

python让我感到沮丧的一点是,如果我编写这样一个函数:

def UnintentionalValueChangeOfGlobal(a):
    SomeDict['SomeKey'] = 100 + a
    b = 0.5 * SomeDict['SomeKey']
    return b

然后像这样运行:

SomeDict = {}
SomeDict['SomeKey'] = 0
b = UnintentionalValueChangeOfGlobal(10)
print(SomeDict['SomeKey'])

Python将:1)在函数调用期间查找并使用SomeDict,即使我忘记将它作为函数的输入;2)永久更改SomeDict['SomeKey']的值,即使它没有包含在函数的return语句中。你知道吗

对我来说,这常常会导致变量无意中更改值-SomeDict['SomeKey']在这种情况下,在调用函数后变成110,而目的只是操纵函数输出b。你知道吗

在本例中,我更希望python:1)在函数内部发生错误,说明SomeDict未定义;2)在任何情况下,都不会在调用函数后永久更改除输出b之外的任何变量的值。你知道吗

我知道在python中不能同时禁用globals,但是是否有一个简单的方法(模块或IDE等)可以对python函数执行静态分析,并在函数使用和/或更改非函数输出的变量值时向我发出警告?即,在使用或操作非函数本地变量时发出警告?你知道吗


Tags: 函数目的警告returndef错误情况语句
3条回答

可以使用globals()检查变量:

def UnintentionalValueChangeOfGlobal(a):

    if 'SomeDict' in globals():
        raise Exception('Var in globals')

    SomeDict['SomeKey'] = 100 + a
    b = 0.5 * SomeDict['SomeKey']
    return b

SomeDict = {}
SomeDict['SomeKey'] = 0
b = UnintentionalValueChangeOfGlobal(10)
print(SomeDict['SomeKey'])

理想情况下,我希望我正在搜索的全局检查功能能够在IDE中实现,并持续用于评估函数中全局的使用情况。但由于这似乎不存在,我创建了一个特殊函数,它将python函数作为输入,然后查看该函数的字节码指令,以查看是否存在任何LOAD_GLOBALSTORE_GLOBAL指令。如果找到任何类型,它将尝试评估全局类型,并将其与用户提供的类型列表(int、float等)进行比较。然后打印出函数使用的所有全局变量的名称。你知道吗

这个解决方案还远远不够完美,而且很容易出现误报。例如,如果在导入numpy(import numpy as np)之前在函数中使用np.unique(x),它将错误地将np标识为全局变量而不是模块。它也不会研究嵌套函数等

但是对于一些简单的例子,比如本文中的例子,它似乎工作得很好。我只是用它来扫描我的代码库中的所有函数,它发现了另一个我不知道的全局用法-所以至少对我来说它是有用的!你知道吗

函数如下:

def CheckAgainstGlobals(function, vartypes):
    """
    Function for checking if another function reads/writes data from/to global
    variables. Only variables of the types contained within 'vartypes' and
    unknown types are included in the output.

     Inputs:
        function - a python function
        vartypes - a list of variable types (int, float, dict,...)
     Example:
        # Define a function
        def testfcn(a):
            a = 1 + b
            return a

        # Check if the function read/writes global variables.    
        CheckAgainstGlobals(testfcn,[int, float, dict, complex, str])

        # Should output:
        >> Global-check of function: testfcn
        >> Loaded global variable: b (of unknown type)
    """
    import dis
    globalsFound = []
    # Disassemble the function's bytecode in a human-readable form.
    bytecode = dis.Bytecode(function)
    # Step through each instruction in the function.
    for instr in bytecode:
        # Check if instruction is to either load or store a global.
        if instr[0] == 'LOAD_GLOBAL' or instr[0] == 'STORE_GLOBAL':
            # Check if its possible to determine the type of the global.
            try:
                type(eval(instr[3]))
                TypeAvailable = True
            except:
                TypeAvailable = False
            """
            Determine if the global variable is being loaded or stored and
            check if 'argval' of the global variable matches any of the 
            vartypes provided as input.
            """
            if instr[0] == 'LOAD_GLOBAL':
                if TypeAvailable:
                    for t in vartypes:
                        if isinstance(eval(instr[3]), t):
                            s = ('Loaded global variable: %s (of type %s)' %(instr[3], t))
                            if s not in globalsFound:
                                globalsFound.append(s)
                else:
                    s = ('Loaded global variable: %s (of unknown type)' %(instr[3]))
                    if s not in globalsFound:
                        globalsFound.append(s)
            if instr[0] == 'STORE_GLOBAL':
                if TypeAvailable:
                    for t in vartypes:
                        if isinstance(eval(instr[3]), t):
                            s = ('Stored global variable: %s (of type %s)' %(instr[3], t))
                            if s not in globalsFound:
                                globalsFound.append(s)
                else:
                    s = ('Stored global variable: %s (of unknown type)' %(instr[3]))
                    if s not in globalsFound:
                        globalsFound.append(s)
    # Print out summary of detected global variable usage.
    if len(globalsFound) == 0:
        print('\nGlobal-check of fcn: %s. No read/writes of global variables were detected.' %(function.__code__.co_name))
    else:
        print('\nGlobal-check of fcn: %s' %(function.__code__.co_name))
        for s in globalsFound:
            print(s)

当在声明函数之后直接在示例中的函数上使用时,它将发现有关全局变量SomeDict的用法的警告,但不会知道其类型:

def UnintentionalValueChangeOfGlobal(a):
    SomeDict['SomeKey'] = 100 + a
    b = 0.5 * SomeDict['SomeKey']
    return b
# Will find the global, but not know its type.
CheckAgainstGlobals(UnintentionalValueChangeOfGlobal,[int, float, dict, complex, str])

>> Global-check of fcn: UnintentionalValueChangeOfGlobal
>> Loaded global variable: SomeDict (of unknown type)

在定义SomeDict之后使用时,它还检测到全局是dict:

SomeDict = {}
SomeDict['SomeKey'] = 0
b = UnintentionalValueChangeOfGlobal(10)
print(SomeDict['SomeKey'])
# Will find the global, and also see its type.
CheckAgainstGlobals(UnintentionalValueChangeOfGlobal,[int, float, dict, complex, str])

>> Global-check of fcn: UnintentionalValueChangeOfGlobal
>> Loaded global variable: SomeDict (of type <class 'dict'>)

注意:在当前状态下,函数无法检测到SomeDict['SomeKey']更改值。也就是说,它只检测加载指令,而不检测全局的前一个值是否被操纵。这是因为在本例中使用指令STORE_SUBSCR而不是STORE_GLOBAL。但是全局的使用仍然被检测到(因为它正在被加载),这对我来说已经足够了。你知道吗

Python没有提供任何明显且简单的方法来阻止在函数中访问(未声明的)全局名称的原因之一是,在Python中,所有东西(至少可以分配给名称的所有东西)都是对象,包括函数、类和模块,因此阻止函数访问未声明的全局名称将有助于相当冗长的代码。。。嵌套作用域(闭包等)也没有帮助。你知道吗

当然,尽管globals是邪恶的,但有时仍然有合理的理由使一个global对象发生变异。FWIW,即使是过梁(至少是pylint和pyflakes)似乎也没有任何选择来检测这个AFAICT-但你必须自己仔细检查,因为我可能忽略了它,或者它可能作为pylint扩展或存在于另一个过梁中。你知道吗

奥托,我很少有错误来自这样的问题在20多年来(我不记得一个单一的发生实际上)。常规应用基本的良好实践——尽可能避免副作用的简短函数、有意义的名称和良好的命名约定等,至少对关键部分进行单元测试等——似乎足以有效地防止此类问题。你知道吗

这里的一个要点是,我有一个关于不可调用全局变量被视为(伪)常量的规则,它通过将它们全部命名为\u UPPER来表示。这使得它非常明显,当你真的变异或重新绑定一个。。。你知道吗

作为一个更一般的规则:Python本质上是一种非常动态的语言(见鬼,你甚至可以在运行时更改对象的类……),并且有一个“我们都是同意的成年人”的哲学,因此它确实“缺乏”了大多数安全保护,你可以在更多的“B&D”语言(如Java)中找到,而不是依赖于约定,良好的实践和常识。你知道吗

现在,Python不仅是动态的,而且还公开了它的许多内部内容,因此您当然可以(如果这还不存在的话)编写一个pylint extension,它至少可以检测函数代码中的全局名称(提示:您可以使用yourfunc.co_code(py2)或yourfunc.__code__(py3)访问the compiled code of a function object,然后检查代码中使用的名称)。但是,除非你不得不面对一群草率、无纪律的开发人员(在这种情况下,你还有另一个问题——愚蠢没有技术解决方案),否则我非常谦虚的看法是你在浪费时间。你知道吗

相关问题 更多 >