使用id()理解Python中的6个关键概念

开发 后端
在本文中,我想对使用id()函数理解六个关键Python概念进行系统的回顾。

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

[[327474]]

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

1. 一切都是Python中的对象

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

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

  1. >>> import sys 
  2. >>> class Foo: 
  3. ...     pass 
  4. ...  
  5. >>> def foo(): 
  6. ...     pass 
  7. ...  
  8. >>> a_tuple = ('Error', 404) 
  9. >>> a_dict = {'error_code': 404} 
  10. >>> a_list = [1, 2, 3] 
  11. >>> a_set = set([2, 3, 5]) 
  12. >>> objects = [2, 2.2, 'hello', a_tuple, a_dict, a_list, a_set, Foo, foo, sys] 
  13. >>>  
  14. >>> for item in objects: 
  15. ...     print(f'{type(item)} with id: {id(item)}') 
  16. ...  
  17. <class 'int'> with id: 4479354032 
  18. <class 'float'> with id: 4481286448 
  19. <class 'str'> with id: 4483233904 
  20. <class 'tuple'> with id: 4483061152 
  21. <class 'dict'> with id: 4483236000 
  22. <class 'list'> with id: 4483236720 
  23. <class 'set'> with id: 4483128688 
  24. <class 'type'> with id: 140235151304256 
  25. <class 'function'> with id: 4483031840 
  26. <class 'module'> with id: 4480703856 

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

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

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

2. 变量分配和别名

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

  1. var_name = the_object 

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

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

  1. >>> hello = 'Hello World!' 
  2. >>> print(f'{hello} from: {id(hello)}') 
  3. Hello World! from: 4341735856 
  4. >>> world = hello 
  5. >>> print(f'{world} from: {id(world)}') 
  6. Hello World! from: 4341735856 
  7. >>> 
  8. >>> bored = {'a': 0, 'b': 1} 
  9. >>> print(f'{bored} from: {id(bored)}') 
  10. {'a': 0, 'b': 1} from: 4341577200 
  11. >>> more_bored = bored 
  12. >>> print(f'{more_bored} from: {id(more_bored)}') 
  13. {'a': 0, 'b': 1} from: 4341577200 
  14. >>> more_bored['c'] = 2 
  15. >>> bored 
  16. {'a': 0, 'b': 1, 'c': 2} 
  17. >>> more_bored 
  18. {'a': 0, 'b': 1, 'c': 2} 

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

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

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

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

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

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

  1. >>> list0 = [1, 2, 3, 4] 
  2. >>> list1 = [1, 2, 3, 4] 
  3. >>> print(f'list0 == list1: {list0 == list1}') 
  4. list0 == list1: True 
  5. >>> print(f'list0 is list1: {list0 is list1}') 
  6. list0 is list1: False 
  7. >>> print(f'list0 id: {id(list0)}') 
  8. list0 id: 4341753408 
  9. >>> print(f'list1 id: {id(list1)}') 
  10. list1 id: 4341884240 
  11. >>> 
  12. >>> str0 = 'Hello' 
  13. >>> str1 = str0 
  14. >>> print(f'str0 == str1: {str0 == str1}') 
  15. str0 == str1: True 
  16. >>> print(f'str0 is str1: {str0 is str1}') 
  17. str0 is str1: True 
  18. >>> print(f'str0 id: {id(str0)}') 
  19. str0 id: 4341981808 
  20. >>> print(f'str1 id: {id(str1)}') 
  21. str1 id: 4341981808 

4. 整数缓存

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

  1. >>> number_range = range(-10, 265) 
  2. >>> id_counters = {x: 0 for x in number_range} 
  3. >>> id_records = {x: 0 for x in number_range} 
  4. >>>  
  5. >>> for _ in range(1000): 
  6. ...     for number in number_range: 
  7. ...         idid_number = id(number) 
  8. ...         if id_records[number] != id_number: 
  9. ...             id_records[number] = id_number 
  10. ...             id_counters[number] += 1 
  11. ...  
  12. >>> [x for x in id_counters.keys() if id_counters[x] > 1] 
  13. [-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()函数来了解这两个概念。

  1. >>> import copy 
  2. >>> original = [[0, 1], 2, 3] 
  3. >>> print(f'{original} id: {id(original)}, embeded list id: {id(original[0])}') 
  4. [[0, 1], 2, 3] id: 4342107584, embeded list id: 4342106784 
  5. >>> copycopy0 = copy.copy(original) 
  6. >>> print(f'{copy0} id: {id(copy0)}, embeded list id: {id(copy0[0])}') 
  7. [[0, 1], 2, 3] id: 4341939968, embeded list id: 4342106784 
  8. >>> copycopy1 = copy.deepcopy(original) 
  9. >>> print(f'{copy1} id: {id(copy1)}, embeded list id: {id(copy1[0])}') 
  10. [[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()方法将为每一层创建新对象。 请参见以下示例以了解此功能:

  1. >>> mul_nested = [[[0, 1], 2], 3] 
  2. >>> print(f'{mul_nested} id: {id(mul_nested)}, inner id: {id(mul_nested[0])}, innermost id: {id(mul_nested[0][0])}') 
  3. [[[0, 1], 2], 3] id: 4342107824, inner id: 4342106944, innermost id: 4342107424 
  4. >>> mul_nested_dc = copy.deepcopy(mul_nested) 
  5. >>> 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])}') 
  6. [[[0, 1], 2], 3] id: 4342107264, inner id: 4342107984, innermost id: 4342107904 

6. 数据可变性

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

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

不可变与可变

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

  1. >>> thousand = 1000 
  2. >>> print(f'{thousand} id: {id(thousand)}') 
  3. 1000 id: 4342004944 
  4. >>> thousand += 1 
  5. >>> print(f'{thousand} id: {id(thousand)}') 
  6. 1001 id: 4342004912 
  7. >>> numbers = [4, 3, 2] 
  8. >>> print(f'{numbers} id: {id(numbers)}') 
  9. [4, 3, 2] id: 4342124624 
  10. >>> numbers += [1] 
  11. >>> print(f'{numbers} id: {id(numbers)}') 
  12. [4, 3, 2, 1] id: 4342124624 

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

总结

在本文中,我们利用内置的id()函数来了解Python中的六个关键概念。 以下是这些概念的快速回顾:

  • Python中的所有内容都是一个对象。
  • 我们通过赋值创建变量,别名指向内存中的相同对象。
  • 比较运算符==比较值,而比较运算符正在比较标识。
  • Python解释器在启动时会创建从-5到256的整数对象。
  • 浅副本和深副本均具有与其原始对象相同的值,但是浅副本仅复制原始对象的嵌套对象的引用。
  • 可变对象的值可以在内存中更改,而不可变对象不支持值更改。

 

责任编辑:赵宁宁 来源: 今日头条
相关推荐

2024-09-23 10:00:00

Python游戏开发

2019-04-12 10:33:44

2024-05-21 11:14:20

Python编程

2024-02-20 09:25:28

架构设计系统

2018-09-26 09:01:28

物联网项目物联网IOT

2024-06-13 09:05:12

2011-04-18 10:56:41

PythonDropBox

2015-12-10 09:24:54

Linux架构理解

2020-09-29 17:15:41

数据科学技术

2021-03-03 10:39:11

容器微服务IT

2024-12-02 11:34:15

Python面向对象编程

2010-05-05 18:18:55

IP负载均衡

2024-10-06 14:01:47

Python装饰器对象编程

2018-09-29 10:05:54

深度学习神经网络神经元

2019-02-21 05:38:13

Kubernetes容器云计算

2018-05-23 13:55:31

云原生SOA数据

2011-07-14 23:14:42

C++static

2015-07-14 17:06:17

手游运营

2015-09-21 13:05:32

创业初创企业失败

2022-08-02 12:03:26

Python可观测性软件开发
点赞
收藏

51CTO技术栈公众号