本地变量分配前被引用报错"local variable 'XXX' referenced before assignment"

代码如下:

    def _Create(self):
        ...
        INSTANCETYPE_STRING=self.machine_type
        cpu_base_set = {"SMALL": 1, "MEDIUM": 2, "LARGE": 4}
        for typeKey in cpu_base_set.keys():
            if typeKey in INSTANCETYPE_STRING:
                cpu_base = cpu_base_set[typeKey]
                type_base = typeKey

        instance_type_all = INSTANCETYPE_STRING.split(".")[1]
        instance_type_cpu, instance_type_mem = instance_type_all.split(type_base)       # 异常报错在这一行
        if "X" in instance_type_cpu:
            cpu_multiple = instance_type_cpu.split("X")[0]
            instance_cpu = cpu_base * cpu_multiple
        else:
            instance_cpu = cpu_base

        instance_mem = instance_type_mem

代码简单说明:

  • INSTANCETYPE_STRING字符串是虚拟机实例的规则,字符串类似S2.2XLARGE16

  • cpu_base_set使用字典结构,表示规格关键字和VCPU的计算基数映射关系,例如LARGE关键字表示计算技术是4个VCPU

  • typeKey遍历cpu_base_set字典的每个key,取出key对比是否包含在INSTANCETYPE_STRING中,如果包含,则将对应VCPU数量赋值给cpu_base;同时记录下此时的typeKeytype_base这样就知道这个实例的类型,例如LARGE

  • instance_type_all.split(type_base)是为了将字符串instance_type_all(2XLARGE16)中LARGE前后的字符2X(2倍cpu_base)和数字16提取出来(内存大小)

Python程序执行过程抛出异常,其中显示本地变量在分配前被引用错误:

UnboundLocalError: local variable 'type_base' referenced before assignment

What are the rules for local and global variables in Python?介绍了类似的在一个函数中仅仅加了一个设置状态就会导致原先可以工作的代码出现UnboundLocalError异常,和这里的报错非常相似:

原先的可工作代码

>>> x = 10
>>> def bar():
...     print(x)
>>> bar()
10

修改后导致异常代码

>>> x = 10
>>> def foo():
...     print(x)
...     x += 1


>>> foo()
Traceback (most recent call last):
  ...
UnboundLocalError: local variable 'x' referenced before assignment

这是因为当你在一个作用域范围(scope)内设置变量,这个变量就会成为这个scope的local变量,并且影响(shadow)其他scope的任何相似命名的变量。由于在foo函数中最后的声明设置一个新的值给x,这样编译器就识别它作为一个本地变量。而在这之前print(x)试图答应这个还没有初始化的本地变量就会导致错误。

可以这样理解:如果在一个作用域(函数)内如果设置变量,这个变量就会被编译器识别local变量。此时就需要确保这个local变量在设置前没有被引用,否则就会出现UnboundLoaclError

以上案例我们可以通过声明x变量为global,这样就可以访问scope之外的变量x = 10

>>> x = 10
>>> def foobar():
...     global x
...     print(x)
...     x += 1
...     print(x)
...
>>> foobar()
10
11
>>> print(x)
11

可以看到在scope之变量也可以修改。

另外,在Python 3中,还有一个nonlocal关键字可以实现相似功能:

>>> def foo():
...    x = 10
...    def bar():
...        nonlocal x
...        print(x)
...        x += 1
...    bar()
...    print(x)
>>> foo()
10
11

Python中的本地和全局变量规则

在Python中,变量只在一个函数中引用(只使用不赋值)的是隐含全局。如果变量在函数体内部任何一个位置被赋值,就会被假设为本地变量,除非其明确声明为全局变量。

解决

参考了What are the rules for local and global variables in Python?,尝试修改:

    def _Create(self):
        """Create a VM instance."""
        global cpu_base
        global type_base
        cpu_base = ""
        type_base = ""
        ...
        INSTANCETYPE_STRING=self.machine_type
        cpu_base_set = {"SMALL": 1, "MEDIUM": 2, "LARGE": 4}
        for typeKey in cpu_base_set.keys():
            if typeKey in INSTANCETYPE_STRING:
                cpu_base = cpu_base_set[typeKey]
                type_base = typeKey

        instance_type_all = INSTANCETYPE_STRING.split(".")[1]
        instance_type_cpu, instance_type_mem = instance_type_all.split(type_base)
        if "X" in instance_type_cpu:
            cpu_multiple = instance_type_cpu.split("X")[0]
            instance_cpu = cpu_base * cpu_multiple
        else:
            instance_cpu = cpu_base

        instance_mem = instance_type_mem

这里在函数_Create(self)的前面首先申请变量cpu_basetype_base是全局变量,似乎可以绕过这个问题。但是实际上还是会出现其他异常

为何这里会出现异常的UnboundLoaclError呢?原因在

            if typeKey in INSTANCETYPE_STRING:
                cpu_base = cpu_base_set[typeKey]
                type_base = typeKey

可以看到,只有满足了条件之后,才会对数据进行赋值。也就是说,如果typeKey没有包含在INSTANCETYPE_STRING(取决于用户输入的参数),就会导致没有对local变量赋值,就直接行instance_type_all.split(type_base)。这就是程序的逻辑错误:

任何时候,一定要进行用户输入数据的校验。如果采用if判断,一定要考虑是/否两种情况。对于没有成功的判断,一定要有异常处理。

Python是一种弱类型语言,所以变量类型要小心处理。

实际解决方法,需要采用在用户输入入口进行字符串判断,以及在if判断中增加异常处理:

        for typeKey in cpu_base_set.keys():
            if typeKey in INSTANCETYPE_STRING:
                cpu_base = cpu_base_set[typeKey]
                type_base = typeKey
            else:
                ...

参考

Last updated