Reason for unintuitive UnboundLocalError behaviour

2019-06-24 07:58发布

问题:

Following up on Reason for unintuitive UnboundLocalError behaviour (I will assume you've read it). Consider the following Python script:

def f():
    # a+=1          # 1
    aa=a
    aa+=1

    # b+='b'        # 2
    bb=b
    bb+='b'

    c[0]+='c'       # 3
    c.append('c')
    cc=c
    cc.append('c')

    d['d']=5        # Update 1
    d['dd']=6       # Update 1
    dd=d            # Update 1
    dd['ddd']=7     # Update 1

    e.add('e')      # Update 2
    ee=e            # Update 2
    ee.add('e')     # Update 2

a=1
b='b'
c=['c']
d={'d':4}           # Update 1
e=set(['e'])        # Update 2
f()
print a
print b
print c
print d             # Update 1
print e             # Update 2

The result of the script is:

1
b
['cc', 'c', 'c']
{'dd': 6, 'd': 5, 'ddd': 7}
set(['e'])

The commented out lines (marked 1,2) are lines that would through an UnboundLocalError and the SO question I referenced explains why. However, the line marked 3 works!

By default, lists are copied by reference in Python, therefore it's understandable that c changes when cc changes. But why should Python allow c to change in the first place, if it didn't allow changes to a and b directly from the method's scope?

I don't see how the fact that by default lists are copied by reference in Python should make this design decision inconsistent.

What am I missing folks?

UPDATES:

  • For completeness I also added the dictionary equivalent to the question above, i.e. I added the source code and marked the update with # Update
  • For further completeness I also added the set equivalent. The set's behavior is actually surprisingly for me. I expected it to act similar to list and dictionary...

回答1:

Unlike strings and integers, lists in Python are mutable objects. This means they are designed to be changed. The line

c[0] += 'c'

is identical to saying

c.__setitem__(0, c.__getitem__(0) + 'c')

which doesn't make any change to what the name c is bound to. Before and after this call, c is the same list – it's just the contents of this list that have changed.

Had you said

c += ['c']
c = [42]

in the function f(), the same UnboundLocalError would have occured, because the second line makes c a local name, and the first line translates to

c = c + ['c']

requiring the name c to be already bound to something, which (in this local scope) it isn't yet.



回答2:

The important thing to think about is this: what object does a (or b or c) refer to? The line a += 1 is changing which integer a refers to. Integers are immutable, so when a changes from 1 to 2, it's really the same as a = a + 1, which is giving a an entirely new integer to refer to.

On the other hand, c[0] += 'c' doesn't change which list c refers to, it merely changes which string its first element refers to. Lists are mutable, so the same list can be modified without changing its identity.