素材牛VIP会员
关于python变量的作用域问题
 34***86  分类:Python  人气:909  回帖:3  发布于6年前 收藏

有这样一个函数:

def outside():
    x=[]
    print(id(x))
    def inside():
        print(id(x))
        x[:]=[1,2,3]
        print(id(x))
    inside()
    print(id(x))
    print(x)

调用后没出现问题,输出:
140560473157960
140560473157960
140560473157960
140560473157960
[1, 2, 3]

但是将里面的x换成一个字符串,如下:

def outside():
    x='outside'
    print(id(x))
    def inside():
        print(id(x))
        x='inside'
        print(id(x))
    inside()
    print(id(x))
    print(x)

再来进行调用的时候就变成了:

140560473762872
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in outside
  File "<stdin>", line 5, in inside
UnboundLocalError: local variable 'x' referenced before assignment
  • 按照规则,此时进入inside函数里,为什么x不指向原来的'outside'字符串了呢?

  • 未定义的变量不是应该产生NameError吗,为什么此处不是?

  • 我原来以为字符串和list都是类似于C中的指针,但现在看来不是这样,如果可以的话希望能对这一点进行一些介绍,谢谢。

 标签:python

讨论这个帖子(3)垃圾回帖将一律封号处理……

Lv6 码匠
24***23 软件测试工程师 6年前#1

从易到难一个个回答, 因为是个人愚见, 所以如果有大神看到说得不正确, 希望能够指出
类似C中的指针: 这个是正确的, 因为在py里面, 几乎所有的事物都是对象, 就连变量赋值, 也是先生成对象, 再让变量指向这个对象,而对象还分可变对象不可变对象, 在对可变对象操作时, 是会影响到其他指向这个对象的变量, 例如:

o = [1, 2, 3, 4]
b = o
print id(o)
print id(b)
b[1] = 123123
print b
print o
输出:
39946376
39946376
[1, 123123, 3, 4]
[1, 123123, 3, 4]  # o指向的列表也被改变

而对于不可变对象, 是直接就放弃旧的对象, 而指向新的对象, 例如:

s = '123123'
print id(s)
s = '32131'
print id(s)


# 输出:
41392768
41392808

所以你在操作python对象时, 需要谨记该对象是属于哪种类型, 你的操作又会不会因为这些特性而失败或者没达到自己想要的效果.

未定义的变量: python在查找变量时, 将遵循LEGB的顺序, 只有都查找完毕还是没找到的情况下, 才会触发NameError异常, 这个可以参考我的一篇博文: Python: 作用域(scope) 和 LEGB

UnboundLocalError: 这个问题是最常见, 也是最难解释的, 因为我们总是相当然地觉得, 它必定就会根据ELGB的顺序去查到变量;其实我们的理解并没错误, 只是我们忽略了一点:赋值语句,如果函数代码段没有赋值语句, 那么这个问题是不会出现, 但为什么出现赋值语句就会报错呢? 这和python的作用域有关, 在上面那篇文章提到, python作用域并不是动态的,而是静态的, 从脚本文件的缩进, 就能看出来的, 所以在代码:

 x='outside'
    print(id(x))
    def inside():
        print(id(x))
        x='inside'
        print(id(x))

inside中, 已经有了赋值语句, 所以对于x,他已经不会从enclosing 或者global甚至bulitin里面去查找, 它已经被认定在local域了, 只是这个值并没有和真正的对象'inside'建立起绑定关系, 因为代码没有运行到真正的赋值语句, 所以, 会触发这个UnboundLocalError. 而为什么那个列表会可以那样做, 因为他们两个是完全不同的操作, 同样都是print(id(x))list的操作字节码是LOAD_DEREF, 而字符串的操作字节码是LOAD_FAST, 而x[:]=[1,2,3]/x='inside'分别对应的字节码又是STORE_SLICE+3STORE_FAST, 前者是在原来的基础上修改, 而后者是重新指向新的对象, 而这两种方式的区别, 决定了,它们在构建函数时, 以怎样的形式存放x, 这个就涉及到python函数构建的原理了, 有兴趣可以看看源码中的object/ceval.c源码, 这是虚拟机运行的原理, 关于这个问题可以简单看我另一篇文章, 比较简单将UnboundLocalError: 说下那神奇的 UnboundLocalError: local variable x referenced before assignment

Lv3 码奴
默***H 学生 6年前#2

基本上Lin_R已经说的很清楚了。
outside函数与inside函数,他们的域是不同的。由于你在inside函数中对x进行了赋值,当在inside函数中使用变量x时,此时x就被认定是在inside的local域中。此时的x是不会使用其他域中的值的。所以在print(x)时,由于x没有初始化的值,因此出错。虽然在c中可以使用已定义而未赋值的变量,但是python不允许这种情况。

在python3中,有一个nonlocal语句可以解决这个问题。

def outside():
    x='outside'
    print(id(x))
    def inside():
        nonlocal x
        print(id(x))
        x='inside'
        print(id(x))
    inside()
    print(id(x))
    print(x)

注意,此时使用global语句是不行的,因为在global域内没有x这个变量。

Lv1 新人
岛***疤 页面重构设计 6年前#3

你在inside函数里面重新赋值了新的变量x,两个x的作用域是不相同的。
而导致UnboundLocalError,是因为你在inside的作用域里面,打印了一个没有初始化的变量。
具体看这个解释:
https://docs.python.org/2/faq...

 文明上网,理性发言!   😉 阿里云幸运券,戳我领取