Python-动态类型

动态类型

在Python中,类型是在运行时自动确定的,而不是通过代码声明,即Python没有必要事先声明变量。

1. 变量、对象和引用

变量创建:一个变量在代码第一次给它赋值时就创建了它,之后的赋值将会改变已创建的变量的值;Python在代码运行之前会先检测变量名,是最初的赋值操作在创建变量

变量类型:变量永远不会拥有与它相关联的类型信息或约束。类型的概念存在于对象而不是变量名中,变量原本是通用的,它只是在特定的时间引用了一个特定的对象而已。

变量使用:当变量出现在表达式中时,它会马上被当前引用的对象所替代,无论这个对象是什么类型。因此所有变量必须在使用前被明确地赋值,使用未赋值的变量会产生错误。

变量在赋值的时候才被创建,它可以引用任何类型的对象,并且必须在引用之前赋值。

以赋值语句a = 3为例,Python会执行以下三个步骤:

  • 创建一个对象来代表值3
  • 创建一个变量a,如果它还没有创建的话
  • 将变量与新的对象3相连接

image-20240305100610393

变量和对象保存在内存中的不同部分,并通过连接相关联。变量总是连接到对象,并且绝不会连接到其它变量上。在Python中,从变量到对象的连接称作引用,引用是一种关系,通过内存中的指针的形式来实现。一旦使用(引用)变量,Python将自动追踪这个变量到对象的连接。

  • 变量是一个系统表的入口,包含了指向对象的连接
  • 对象是被分配到的一块内存,有足够的空间去表示它们所代表的值
  • 引用是自动形成的从变量到对象的指针

对象包含了更复杂的结构,每个对象都有两个标准的头部信息——类型标志符(type designator)标识这个对象的类型;引用计数器(reference counter)决定何时回收这个对象。

2. 类型属于对象而不是变量

>>> a = 3
>>> type(a)
<class 'int'>
>>> a = 'spam'
>>> type(a)
<class 'str'>
>>> a = 1.23
>>> type(a)
<class 'float'>

上面的代码中,表面上看a的类型从一个整数变成了一个字符串最后又变成了一个浮点数。事实上并非如此,在Python中,类型属于对象而不是变量。上面的连续赋值只是把a修改为对不同对象的引用,因为变量没有类型,实际上并没有改变变量a的类型。实际上,Python的变量就是在一个特定的时间引用了一个特定的对象

从另一方面上看,对象知道自己的类型,每个对象都包含一个头部信息,其中标记了这个对象的类型。例如整数对象3,包含了值3以及一个标志符,标志符指向了一个整数类型。

3. 对象的垃圾回收

在Python中,每当一个变量被赋予一个新的对象时,如果原来的对象没有被其他的变量或对象所引用的话,那么之前那个对象占用的空间就会回收。这种自动回收对象空间的技术叫做垃圾回收

>>> x = 42
>>> x = 'shrubbery'    # 回收42
>>> x = 3.1415         # 回收'shrubbery'
>>> x = [1, 2, 3]      # 回收3.1415
# 当每一次x被赋值给一个新的对象,Python都会回收之前对象的空间(假设之前的对象没有被其他变量或对象所引用),回收之后对象的空间将会自动放入内存空间池,等待后来的对象使用

在内部Python的垃圾回收是这样实现的:Python在每个对象中保留了一个计数器,计数器记录当前指向该对象的引用的数目,一旦这个计数器被设置为零,那么这个对象的内存空间就会自动回收

4. 共享引用

>>> a = 3
>>> b = a

语句b = a将会创建变量b,变量a正在被使用,并且变量b没有被赋值,因此变量b被替换成其引用的对象3。实际的效果就是变量a和变量b都引用了相同的对象(也就是说指向了相同的内存空间)。

image-20240305110229152

这种多个变量引用了同一个对象的情况在Python中称为共享引用虽然变量a和变量b是共享引用的,但它们并没有彼此关联,真实情况是两个变量通过它们的引用指向了同一个对象。

>>> a = 3
>>> b = a       # a = 3, b = 3
>>> a = 'spam'  # a = 'spam', b = 3
# 对变量a进行新的赋值并不会改变变量b的值,变量b仍然引用对象3,只是变量a引用了新的对象'spam'

image-20240305111622737

将变量a赋值为一个值并不会连带着改变变量b,除非改变对象3的值,但是对象3是一个整数,整数是不可变的,因此无法在原位置修改它。

在Python中变量总是一个指向对象的指针,而不是可改变的内存区域的标签,该变量赋一个新值并不是替换原始的对象,而是让这个变量去引用完全不同的一个对象。

对于引用的对象是不可变类型时,共享引用的实际效果就是对一个变量赋值仅仅会影响那个被赋值的变量。

5. 共享引用和在原位置修改

在Python中有一些对象和操作(包括列表、字典和集合在内的Python可变类型)确实可以在原位置改变对象。

对于可变类型的对象,可变类型支持在原位置修改,此时使用共享引用要格外小心,因为对一个变量的修改会影响其他的变量。

>>> L1 = [2, 3, 4]
>>> L2 = L1
>>> L1[0] = 24
>>> L1
[24, 3, 4]
>>> L2
[24, 3, 4]

上面的代码中并没有改变L1,而是改变了L1所引用的一个元素,这类修改会在原位置覆盖列表对象的某部分值,因为这个列表对象是与其他对象共享的,那么像这样在原位置的修改就不不仅仅会影响L1,也会影响其他引用该列表的变量(如L2)。

如果不想这种对可变对象的共享引用导致所有引用的变量发生非预期改变,那么可以在创建对象时复制可变对象而不是创建引用

>>> L1 = [2, 3, 4]
>>> L2 = L1[:]  # 使用切片拷贝列表
>>> L1[0] = 24
>>> L1
[24, 3, 4]
>>> L2
[2, 3, 4]

对于字典和集合,复制一个字典或集合对象可以使用X.copy()方法:

import copy
X = copy.copy(Y)
X = copy.deepcopy(Y)

6. 共享引用与相等

对特定类型而言,垃圾回收机制并不总是当对象不被其他变量引用后就马上回收,Python缓存并复用了小的整数和小的字符串,这些缓存的对象可能仍被保存在一个系统表中,等待代码的下一次引用,但大多数其他类型的对象都会在不再被引用时马上回收

Python有两种检查是否相等的方法:

  • "=="运算符:"=="运算符用于测试两个被引用的对象是否有相同的值
  • "is"运算符:"is"运算符用于检查对象的同一性,只有两个变量精确地指向同一个对象,is运算符才会返回True
>>> L = [1, 2, 3]
>>> M = L
>>> L == M
True
>>> L is M
True

如果两个变量引用的值相同但是对象不同,is运算符将返回False:

>>> L = [1, 2, 3]
>>> M = [1, 2, 3]  # M和L都引用了值为[1, 2, 3]的对象,但那是创建的两个不同的列表对象
>>> L == M
True
>>> L is M
False

对于小整数和小字符串,由于Python会缓存它们,因此is运算符的结果将会有所不同:

>>> L = "abc"
>>> M = "abc"
>>> L == M
True
>>> L is M
True
>>> L = 42
>>> M = 42
>>> L == M
True
>>> L is M
True
# 本来L和M应该不是is同一个对象的,但是由于小整数和小字符串被缓存并复用了,所以是is同一个对象

可以使用Python标准库的sys模块中的getrefcount函数查看对象的引用次数:

import sys
sys.getrefcount(X)

Python弱引用

posted @   N1rv2na  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示