< 返回技术文档列表

使用id()理解Python中的6个关键概念分别是怎样的

发布时间:2021-11-07 03:15:46

这期内容当中小编将会给大家带来有关使用id()理解Python中的6个关键概念分别是怎样的,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

启动任何Python解释器时,都有70多个内置函数可用。 每个Python学习者都不应不熟悉一些普通的学习者。  例如,我们可以使用len()来获取对象的长度,例如列表或字典中的项目数。 再举一个例子,我们可以使用print()打印出感兴趣的对象,以进行学习和调试。

此外,几乎所有Python程序员都应该在教程中看到内置的id()函数的使用,以用于指导特定的Python概念。 但是,据我所知,这些信息是分散的。  在本文中,我想对使用id()函数理解六个关键Python概念进行系统的回顾。

1. 一切都是Python中的对象

作为一种流行的面向对象的编程语言,Python在其实现中随处使用对象。 例如,诸如整数,浮点数,字符串,列表和字典之类的内置数据类型都是对象。  而且,函数,类甚至模块也被用作对象。

根据定义,id()函数接受一个对象并返回该对象的标识,即以整数表示的内存地址。 因此,我们可以使用此函数来证明Python中的所有对象都是真实的。

>>> import sys >>> class Foo: ...     pass ...  >>> def foo(): ...     pass ...  >>> a_tuple = ('Error', 404) >>> a_dict = {'error_code': 404} >>> a_list = [1, 2, 3] >>> a_set = set([2, 3, 5]) >>> objects = [2, 2.2, 'hello', a_tuple, a_dict, a_list, a_set, Foo, foo, sys] >>>  >>> for item in objects: ...     print(f'{type(item)} with id: {id(item)}') ...  <class 'int'> with id: 4479354032 <class 'float'> with id: 4481286448 <class 'str'> with id: 4483233904 <class 'tuple'> with id: 4483061152 <class 'dict'> with id: 4483236000 <class 'list'> with id: 4483236720 <class 'set'> with id: 4483128688 <class 'type'> with id: 140235151304256 <class 'function'> with id: 4483031840 <class 'module'> with id: 4480703856

在上面的代码片段中,您可以看到对象列表中的每个项目都可以在id()函数中使用,该函数显示每个对象的内存地址。

我认为很有趣的以下操作是,作为函数本身,id()函数也应具有其内存地址。

>>> print(f'{type(id)} with id: {id(id)}') <class 'builtin_function_or_method'> with id: 4480774224

2. 变量分配和别名

在Python中创建变量时,通常使用以下语法:

var_name = the_object

此过程基本上将在内存中创建的对象绑定到特定的变量名称。 如果为变量分配另一个变量,例如var_name1 = var_name,会发生什么?

考虑以下示例。 在下面的代码片段中,我们首先创建了一个名为hello的变量,并为其分配了字符串值。  接下来,我们通过分配之前的变量hello创建了另一个名为world的变量。  当我们打印出他们的内存地址时,我们发现hello和world都具有相同的内存地址,这表明它们是内存中的同一对象。

>>> hello = 'Hello World!' >>> print(f'{hello} from: {id(hello)}') Hello World! from: 4341735856 >>> world = hello >>> print(f'{world} from: {id(world)}') Hello World! from: 4341735856 >>> >>> bored = {'a': 0, 'b': 1} >>> print(f'{bored} from: {id(bored)}') {'a': 0, 'b': 1} from: 4341577200 >>> more_bored = bored >>> print(f'{more_bored} from: {id(more_bored)}') {'a': 0, 'b': 1} from: 4341577200 >>> more_bored['c'] = 2 >>> bored {'a': 0, 'b': 1, 'c': 2} >>> more_bored {'a': 0, 'b': 1, 'c': 2}

在这种情况下,变量世界通常称为变量hello的别名,通过分配现有变量来创建新变量的过程可以称为别名。  在其他编程语言中,别名非常类似于与内存中基础对象有关的指针或引用。

在上面的代码中,我们还可以看到,当我们为字典创建别名并修改别名的数据时,该修改也将应用于原始变量,因为在后台,我们修改了内存中的同一字典对象。

3. 比较运算符:== vs. is

在各种情况下,我们需要比较两个对象作为决策点,以便在满足或不满足特定条件时应用不同的功能。 就相等比较而言,我们可以使用两个比较运算符:==和is。  一些新的Python学习者可能会错误地认为它们是相同的,但是有细微差别。

考虑以下示例。 我们创建了两个相同项目的列表。 当我们使用==运算符比较两个列表时,比较结果为True。  当我们使用is运算符比较两个列表时,比较结果为False。 他们为什么产生不同的结果?  这是因为==运算符会比较值,而is运算符会比较标识(即内存地址)。

正如您所期望的,这些变量引用了内存中的同一对象,它们不仅具有相同的值,而且具有相同的标识。  这导致==和is运算符的评估结果相同,如下面涉及str0和str1的示例所示:

>>> list0 = [1, 2, 3, 4] >>> list1 = [1, 2, 3, 4] >>> print(f'list0 == list1: {list0 == list1}') list0 == list1: True >>> print(f'list0 is list1: {list0 is list1}') list0 is list1: False >>> print(f'list0 id: {id(list0)}') list0 id: 4341753408 >>> print(f'list1 id: {id(list1)}') list1 id: 4341884240 >>> >>> str0 = 'Hello' >>> str1 = str0 >>> print(f'str0 == str1: {str0 == str1}') str0 == str1: True >>> print(f'str0 is str1: {str0 is str1}') str0 is str1: True >>> print(f'str0 id: {id(str0)}') str0 id: 4341981808 >>> print(f'str1 id: {id(str1)}') str1 id: 4341981808

4. 整数缓存

我们在编程中经常使用的一组数据是整数。  在Python中,解释器通常会缓存介于-5到256之间的小整数。这意味着在启动Python解释器时,这些整数将被创建并可供以后在内存中使用。  以下代码片段显示了此功能:

>>> number_range = range(-10, 265) >>> id_counters = {x: 0 for x in number_range} >>> id_records = {x: 0 for x in number_range} >>>  >>> for _ in range(1000): ...     for number in number_range: ...         idid_number = id(number) ...         if id_records[number] != id_number: ...             id_records[number] = id_number ...             id_counters[number] += 1 ...  >>> [x for x in id_counters.keys() if id_counters[x] > 1] [-10, -9, -8, -7, -6, 257, 258, 259, 260, 261, 262, 263, 264]

在上面的代码中,我创建了两个字典,其中id_counters跟踪每个整数的唯一标识的计数,而id_records跟踪整数的最新标识。  对于介于-10到265之间的整数,如果新整数的标识与现有整数不同,则相应的计数器将递增1。 我重复了这个过程1000次。

代码的最后一行使用列表推导技术向您显示具有多个同一性的整数。 显然,经过1000次后,从-5到256的整数对于每个整数仅具有一个标识,如上一段所述。  要了解有关Python列表理解的更多信息,您可以参考我以前关于此的文章:

5. 浅层和深层副本

有时,我们需要制作现有对象的副本,以便我们可以更改一个副本而不更改另一个副本。  内置的复制模块为此提供了两种方法:copy()和deepcopy(),它们分别进行浅拷贝和深拷贝。  如果您不知道它们是什么,让我们利用id()函数来了解这两个概念。

>>> import copy >>> original = [[0, 1], 2, 3] >>> print(f'{original} id: {id(original)}, embeded list id: {id(original[0])}') [[0, 1], 2, 3] id: 4342107584, embeded list id: 4342106784 >>> copycopy0 = copy.copy(original) >>> print(f'{copy0} id: {id(copy0)}, embeded list id: {id(copy0[0])}') [[0, 1], 2, 3] id: 4341939968, embeded list id: 4342106784 >>> copycopy1 = copy.deepcopy(original) >>> print(f'{copy1} id: {id(copy1)}, embeded list id: {id(copy1[0])}') [[0, 1], 2, 3] id: 4341948160, embeded list id: 4342107664

我们首先创建了一个名为original的列表变量,它由一个嵌套列表和两个整数组成。  然后,我们分别使用copy()和deepcopy()方法制作了两个副本(copy0和copy1)。  如我们所料,原始的copy0和copy1具有相同的值(即[[0,1],2,3])。  但是,它们具有不同的身份,因为与别名不同,copy()和deepcopy()方法均会在内存中创建新对象,从而使新副本具有不同的身份。

浅层副本和深层副本之间最本质的区别是,深层复制将为原始复合对象递归创建副本,而浅层复制将在适用的情况下保留对现有对象的引用。  在上面显示的示例中,变量original实际上是一个复合对象(即一个列表嵌套在另一个列表中)。

在这种情况下,使用copy()方法,变量copy0的第一个元素与原始的第一个元素具有相同的标识(即,相同的对象)。  相比之下,deepcopy()方法在内存中复制嵌套列表,以使copy1中的第一个元素具有与原始元素不同的标识。

但是在深度复制中"递归"是什么意思?  这意味着如果存在多层嵌套(例如,嵌套在列表中的列表,又嵌套在另一个列表中),则deepcopy()方法将为每一层创建新对象。  请参见以下示例以了解此功能:

>>> mul_nested = [[[0, 1], 2], 3] >>> print(f'{mul_nested} id: {id(mul_nested)}, inner id: {id(mul_nested[0])}, innermost id: {id(mul_nested[0][0])}') [[[0, 1], 2], 3] id: 4342107824, inner id: 4342106944, innermost id: 4342107424 >>> mul_nested_dc = copy.deepcopy(mul_nested) >>> print(f'{mul_nested_dc} id: {id(mul_nested_dc)}, inner id: {id(mul_nested_dc[0])}, innermost id: {id(mul_nested_dc[0][0])}') [[[0, 1], 2], 3] id: 4342107264, inner id: 4342107984, innermost id: 4342107904

6. 数据可变性

Python编程中的一个高级主题与数据可变性有关。 一般来说,不可变数据是指其值在创建后便无法更改的对象,例如整数,字符串和元组。  相比之下,可变数据是指其值在创建后可以更改的那些对象,例如列表,字典和集合。

需要注意的一件事是,通过"更改值",我们的意思是是否可以更改内存中的基础对象。 在我的上一篇文章中可以找到关于数据可变性的详尽讨论:

不可变与可变

为了本文讨论id()函数的目的,让我们考虑以下示例。  对于不可变数据类型(代码片段中的整数变量千),当我们尝试更改其值时,会在内存中创建一个新的整数,这由千变量的新标识所反映。  换句话说,原始的基础整数对象无法更改。 尝试更改整数只会在内存中创建一个新对象。

>>> thousand = 1000 >>> print(f'{thousand} id: {id(thousand)}') 1000 id: 4342004944 >>> thousand += 1 >>> print(f'{thousand} id: {id(thousand)}') 1001 id: 4342004912 >>> numbers = [4, 3, 2] >>> print(f'{numbers} id: {id(numbers)}') [4, 3, 2] id: 4342124624 >>> numbers += [1] >>> print(f'{numbers} id: {id(numbers)}') [4, 3, 2, 1] id: 4342124624

如果这让您感到困惑,让我们看看可变数据类型发生了什么&mdash;在我们的例子中是列表变量编号。  如上面的代码所示,当我们尝试更改数字的值时,变量号得到了更新,并且更新后的列表仍具有相同的标识,从而确认了列表类型的对象的可变性。

上述就是小编为大家分享的使用id()理解Python中的6个关键概念分别是怎样的了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注血鸟云行业资讯频道。


/template/Home/Zkeys/PC/Static