您能用Python解决这三个(看似)简单的任务吗?

从我作为软件开发人员开始的那一刻起,我就非常喜欢深入研究编程语言。我一直对这种或那种结构的工作方式,这种或那个团队的工作方式,语法糖的内幕等感兴趣。最近,我遇到了一篇有趣的文章,并举例说明了Python中的可变对象和不可变对象并不总是很明显地起作用。在我看来,关键是代码的行为如何根据所使用的数据类型而变化,同时保持所使用的相同语义和语言结构。这是一个很好的例子,说明了不仅在编写时而且在使用时都需要思考的问题。我邀请所有人阅读译文。







尝试这三个问题,然后在文章末尾检查答案。



提示:任务有一些共同点,因此请重新思考第一个任务的解决方案,当您继续执行第二个或第三个任务时,这对您来说会更容易。



第一项任务



有几个变量:



x = 1
y = 2
l = [x, y]
x += 5

a = [1]
b = [2]
s = [a, b]
a.append(5)


打印时显示什么ls



第二项任务



让我们定义一个简单的函数:



def f(x, s=set()):
    s.add(x)
    print(s)


如果您拨打电话会发生什么:



>>f(7)
>>f(6, {4, 5})
>>f(2)


第三项任务



我们定义了两个简单的函数:



def f():
    l = [1]
    def inner(x):
        l.append(x)
        return l
    return inner

def g():
    y = 1
    def inner(x):
        y += x
        return y
    return inner


执行这些命令后会得到什么?



>>f_inner = f()
>>print(f_inner(2))

>>g_inner = g()
>>print(g_inner(2))


您对答案的信心如何?让我们检查一下您的情况。



第一个问题的解决方案



>>print(l)
[1, 2]

>>print(s)
[[1, 5], [2]]


为什么第二个列表响应第一个元素的更改a.append(5),而第一个列表完全忽略相同的更改x+=5



解决第二个问题



让我们看看发生了什么:



>>f(7)
{7}

>>f(6, {4, 5})
{4, 5, 6}

>>f(2)
{2, 7}


等等,最后的结果不是{2}吗?



解决第三个问题



结果将是这样的:



>>f_inner = f()
>>print(f_inner(2))
[1, 2]

>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment


g_inner(2) 为什么不出卖3为什么内部函数会f()记住外部作用域,而内部函数g()却不记得?他们几乎一样!



说明



如果我告诉您所有这些奇怪的行为都与Python中可变对象和不可变对象之间的差异有关怎么办?



可以在本地修改列表,集合或字典等可修改对象。不可修改的对象,例如数字和字符串值,元组,无法修改;它们的“改变”将导致新对象的创建。



第一项任务的说明



x = 1
y = 2
l = [x, y]
x += 5

a = [1]
b = [2]
s = [a, b]
a.append(5)

>>print(l)
[1, 2]

>>print(s)
[[1, 5], [2]]


由于它是x不可变的,因此该操作x+=5不会更改原始对象,而是会创建一个新对象。但是列表中的第一项仍然引用原始对象,因此其值不会改变。



因为 一个可变的对象,然后该命令a.append(5)修改原始对象(而不是创建一个新的对象),并且列表s“看到”更改。



第二项任务的说明



def f(x, s=set()):
    s.add(x)
    print(s)

>>f(7)
{7}

>>f(6, {4, 5})
{4, 5, 6}

>>f(2)
{2, 7}


通过前两个结果,一切都很清楚:将第一个值7添加到最初为空的集合中,结果为{7};然后将该值6添加到集合中{4, 5},结果是{4, 5, 6}



然后奇怪的事情开始了。该值不2添加到空集,而是添加到{7}。为什么?可选参数的初始值s仅计算一次:在第一次调用时,s将被初始化为空集。并且由于它是可变的,f(7)因此在被调用后将在适当的位置进行更改。第二个调用f(6, {4, 5})不会影响默认参数:集合将替换它{4, 5},即它是{4, 5}一个不同的变量。第三次调用f(2)使用相同的变量s第一次调用时使用,但并未将其重新初始化为空集,而是从其先前的值中获取{7}



因此,不应将可变参数用作默认参数。在这种情况下,需要更改功能:



def f(x, s=None):
    if s is None:
        s = set()
    s.add(x)
    print(s)


第三项任务的说明



def f():
   l = [1]
   def inner(x):
       l.append(x)
       return l
   return inner

def g():
   y = 1
   def inner(x):
       y += x
       return y
   return inner

>>f_inner = f()
>>print(f_inner(2))
[1, 2]

>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment


在这里,我们处理闭包:内部函数记住定义它们时外部命名空间的外观。或至少他们应该记住,但是第二个功能使扑克牌看起来更像是没有听说过其外部名称空间。



为什么会这样呢?当我们执行l.append(x)时,定义函数时创建的可变对象会更改。但是该变量l仍然引用旧的内存地址。但是,尝试在第二个函数中更改不可变变量会y += x导致y开始引用不同的内存地址:原始y将被遗忘,这将导致UnboundLocalError。



结论



Python中可变对象与不可变对象之间的区别非常重要。避免本文中描述的怪异行为。特别:



  • .
  • - .



All Articles