为什么Python中的函数会修改全局的列表和字典_python 线程全局dict 会改变

[1, 2, 3, 3]
[1, 2, 3, 3]


正如我们所看到的,这里 initial\_list 的全局值被更新了,即使它的值只在函数内部更改!


**字典**


现在,我们来编写一个函数,该函数以一个字典作为参数来查看全局字典变量在函数中被操作时是否也会被修改。


为了看起来更直观一点,我们将使用Python基础课程中使用的 AppleStore.csv 数据集中的数据(数据可以从这里下载)。


在下面的代码片段中,我们从一个字典开始,它包含了数据集中各个年龄级别的应用程序的数量(因此有4433个应用程序的级别为“4+”,987个应用程序的级别为“9+”,等等)。假设我们想计算每个年龄等级的百分比,这样我们就可以得到在App Store中哪个年龄等级是最常见的。


为此,我们将编写一个名为 make\_percentages() 的函数,该函数以一个字典作为参数并将计数转换为百分比。我们需要从0开始计数,然后遍历字典中的每个值,将它们添加到计数中,这样就得到了评级的总数。然后我们将再次遍历字典,并对每个值做一些数学运算来计算百分比。



content_ratings = {‘4+’: 4433,‘9+’: 987,‘12+’: 1155,‘17+’:622}
def make_percentages(a_dictionary):
total = 0
for key in a_dictionary:
count = a_dictionary[key]
total += count
for key in a_dictionary:
a_dictionary[key] = (a_dictionary[key] / total)* 100

return a_dictionary

在查看输出之前,让我们快速回顾一下上面发生的事情。在将我们的app 年龄评级字典分配给变量content\_ratings之后,我们创建了一个名为make\_percentages()的新函数,它只接受一个参数: a\_dictionary。


为了计算每个年龄等级的应用程序所占比例,我们需要知道应用程序的总数,因此我们首先将一个名为total的新变量设置为0,然后在a\_dictionary中循环遍历每个键值,并将其添加到total中。


完成之后,我们需要做的就是再次遍历a\_dictionary,将每个条目除以总数,然后将结果乘以100。这将给我们返回一个包含百分比的词典。


但是,当我们使用全局content\_ratings变量作为这个新函数的参数时发生了什么呢?



c_ratings_percentages = make_percentages(content_ratings)
print(c_ratings_percentages)
print(content_ratings)


输出



{‘4+’: 61.595109073224954, ‘9+’: 13.714047519799916, ‘12+’: 16.04835348061692, ‘17+’: 8.642489926358204}
{‘4+’: 61.595109073224954, ‘9+’: 13.714047519799916, ‘12+’: 16.04835348061692, ‘17+’: 8.642489926358204}


正如我们在列表中看到的,我们的全局content\_ratings变量已经更改,尽管它只是在我们创建的make\_percentages()函数中进行了修改。


这里到底发生了什么?我们遇到了**可变**和**不可变**数据类型之间的差异。


#### 可变和不可变的数据类型


在Python中,数据类型可以是可变的(可更改的),也可以是不可变的(不可更改的)。虽然我们在介绍Python时使用的大多数数据类型都是不可变的(包括整数、浮点数、字符串、布尔值和元组),但是列表和字典是可变的。这意味着**全局列表或字典即使在函数内部使用时也可以更改**,就像我们在上面的示例中看到的那样。


要理解可变(可更改)和不可变(不可更改)之间的区别,了解Python如何处理这些变量是很有帮助的。


让我们从考虑一个简单的变量赋值开始:



a = 5


变量名a的作用类似于一个指向5的指针,它可以帮助我们随时检索5。


5是一个整数,整数是不可变的数据类型。如果数据类型是不可变的,这意味着一旦创建,就不能更新它。如果我们执行a += 1,我们实际上并没有更新5到6。在下面的动画中,我们可以看到这一点:


a 初始指向 5.


执行a += 1 后, 将指针从 5指向 6, 它并没有实际改变 5.


可变数据类型(如列表和字典)的行为有所不同。它们可以更新。举个例子,我们来创建一个非常简单的列表:



list_1 = [1,2]


如果我们在列表末尾添加一个3,我们不是简单地将list\_1指向另一个列表,而是直接更新现有列表:


即使我们创建多个列表变量,只要它们指向同一个列表,当列表被更改时,它们都会被更新,如下面的代码所示:



list_1 = [1,2]
list_2 = list_1
list_1.append(3)
print(list_1)
print(list_2)


输出:



[1, 2, 3]
[1, 2, 3]


下面是上面代码中实际发生的动态可视化:


这就解释了为什么我们之前在试验列表和字典时我们的全局变量被改变了。因为列表和字典是可变的,所以更改它们(即使是在函数中)也会更改列表或字典本身,这与不可变数据类型不同。


#### 保持可变数据类型不变


一般来说,我们不希望函数更改全局变量,即使它们包含列表或字典之类的可变数据类型。这是因为在更复杂的分析和程序中,我们可能会经常使用许多不同的函数。如果所有函数都更改它们正在调用的列表和字典,那么要跟踪什么在更改什么就会变得非常困难。


幸运的是,有一种简单的方法可以绕过这个问题:我们可以使用内建的Python方法.copy()复制列表或字典。


如果你还没有学习过方法,请不要担心。它们包含在我们的中级Python课程中,但是在本教程中,你只需要知道.copy()的工作原理类似于.append():



list.append() #adds something to a list
list.copy()# makes a copy of a list


让我们再看一下我们为列表写的函数,对它进行更新,这样函数内部执行的操作就不会更改initial\_list。我们只需要将传递给函数的参数从initial\_list更改为initial\_list.copy()



initial_list=[1,2,3]
def duplicate_last(a_list):
last_element= a_list[-1]
a_list.append(last_element)
return a_list

new_list = duplicate_last(a_list = initial_list.copy())
print(new_list)
print(initial_list)


输出:



[1, 2, 3, 3]
[1, 2, 3]


正如我们所看到的,这已经解决了我们的问题。原因如下:使用.copy()创建一个列表的独立副本,这样a\_list就不会指向initial\_list本身,而是指向一个以initial\_list副本开始的新列表。在此之后对a\_list所做的任何更改都将只对该独立列表生效,而不是initial\_list本身,因此initial\_list的全局值将保持不变。


不过,这个解决方案仍然不完美,因为每次向函数传递参数时都必须记得添加.copy(),否则可能会意外更改initial\_list的全局值。如果我们不想操心这个,我们可以在函数内部创建列表拷贝:



initial_list =[1,2,3]
def duplicate_last(a_list):
copy_list = a_list.copy()
last_element = copy_list[-1]
copy_list.append(last_element)
return copy_list

new_list = duplicate_last(a_list = initial_list)
print(new_list)
print(initial_list)


输出:



[1, 2, 3, 3]
[1, 2, 3]


使用这种方法,我们可以安全地将一个可变的全局变量(如initial\_list)传递给我们的函数,全局值不会被改变,因为函数本身会复制一个副本,然后对该副本执行操作。


.copy()方法也适用于字典。与列表一样,我们可以简单地将.copy()添加到传递给函数的参数中,创建一个用于函数的拷贝,而不会改变原始变量:



‘’’
学习中遇到问题没人解答?小编创建了一个Python学习交流QQ群:725638078
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
‘’’
content_ratings = {‘4+’:4433,‘9+’: 987,‘12+’:1155,‘17+’: 622}
def make_percentages(a_dictionary):
total = 0
for key in a_dictionary:
count = a_dictionary[key]
total +=count

for key in a_dictionary:
    a_dictionary[key] = (a_dictionary[key] / total) *100

return a_dictionary

c_ratings_percentages = make_percentages(content_ratings.copy())
print(c_ratings_percentages)
print(content_ratings)


输出:



{‘4+’: 61.595109073224954, ‘9+’: 13.714047519799916, ‘12+’: 16.04835348061692, ‘17+’: 8.642489926358204}
{‘4+’: 4433, ‘9+’: 987, ‘12+’: 1155, ‘17+’: 622}


但是,再次说明,使用这种方法意味着在每次将字典传递给make\_percentages()函数时,都要记得添加.copy()。如果我们要频繁地使用这个函数,最好在函数内部实现复制,这样我们就不需要记住了。


下面,我们将在函数内部使用.copy()。这样,就可以确保我们在将全局变量作为参数传递给函数时不会被更改,而且我们也不需要记得为传递的每个参数添加.copy()。



content_ratings = {‘4+’: 4433,‘9+’: 987,‘12+’:1155,‘17+’: 622}
def make_percentages(a_dictionary):
copy_dict = a_dictionary.copy()
total = 0
for key in a_dictionary:
count = a_dictionary[key]
total +=count

for key in copy_dict: 

在这里插入图片描述

感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的:

① 2000多本Python电子书(主流和经典的书籍应该都有了)

② Python标准库资料(最全中文版)

③ 项目源码(四五十个有趣且经典的练手项目及源码)

④ Python基础入门、爬虫、web开发、大数据分析方面的视频(适合小白学习)

⑤ Python学习路线图(告别不入流的学习)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值