Py学习  »  Python

为什么Python中的函数会修改全局的列表和字典

Python程序员 • 5 年前 • 367 次点击  

Python中的函数(内置函数和我们自己编写的自定义函数)是处理数据的关键工具。但是他们对我们的数据做了什么可能有点令人困惑,如果我们不知道发生了什么,它可能会在我们的分析中造成严重的错误。

在本教程中,我们将详细研究Python在函数中处理不同数据类型时是如何对它们进行操作的,并学习如何确保只有在希望更改数据时才更改数据。

函数中的内存隔离

为了理解Python如何处理函数内部的全局变量,我们来做一个小实验。我们将创建两个全局变量number_1number_2,并为它们赋值整数 510。然后,我们将使用这些全局变量作为函数的参数来执行一些简单的数学运算。我们还将使用变量名作为函数的参数名。然后,我们将查看函数中所有变量的使用是否影响了这些变量的全局值。

正如我们在上面看到的,函数正常运行,全局变量number_1number_2的值没有变化,尽管我们在函数中使用它们作为形参和实参名。这是因为Python将函数中的变量存储在与全局变量不同的内存位置。它们是被隔离的。因此,变量number_1可以在全局中有一个值(5),而在函数内部有一个不同的值(50),在这个函数中它是独立的。

(顺便说一句,如果你对parameters(形参) 和 arguments(实参)之间的区别感到困惑,Python文档中关于这个主题的内容非常有用。)

那么列表和字典呢?

列表

我们已经看到,我们在函数内部对上面的number_1这样的变量所做的操作并不影响它的全局值。但是number_1是一个整数,这是一个非常基本的数据类型。如果我们用不同的数据类型(比如列表)尝试相同的实验,会发生什么?下面,我们将创建一个名为duplicate_last() 的函数,它将复制我们作为参数传递的任何列表中的最终条目。

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

字典

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

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

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

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

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

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

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

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

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

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

可变和不可变的数据类型

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

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

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

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

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

  • a 初始指向 5.

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

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

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

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

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

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

保持可变数据类型不变

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

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

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

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

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

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

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

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

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

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

正如我们所看到的,修改我们的函数来创建字典的副本,然后只在副本中将计数更改为百分比,这样我们就可以在不更改content_ratings的情况下执行我们想要的操作。

结论

在本教程中,我们研究了可变数据类型(可以更改)和不可变数据类型(不能更改)之间的区别。我们学习了如何使用.copy()方法复制列表和字典等可变数据类型,这样我们就可以在不更改其全局值的情况下在函数中使用它们。

英文原文:https://www.dataquest.io/blog/tutorial-functions-modify-lists-dictionaries-python
译者:野生大熊猫
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/31090
 
367 次点击