Learning Python Part I 之动态类型

来源:互联网 发布:qq飞车mac版下载 编辑:程序博客网 时间:2024/06/05 07:34

之前我们已经了解到,在Python中我们不需要提前声明在代码中所需要使用的变量。事实上,当我们设计程序时甚至不应该去关注特定类型,因为Python会自动分配类型。Python是简洁灵活的语言,而动态类型是灵活性的根本

变量、对象和引用(references)

变量创建:变量在当你在代码中第一次赋值时被创建,当以后再次赋值时会改变变量的值
变量类型:一个变量从来不会有特定类型或相关的限制,类型的概念依存于对象,而不是变量名。变量会在需要的时候指向特定的对象。
变量的使用:当变量在表达式中出现的时候,它会立即取代它当前所指向的对象。所有变量在使用前都必须赋值,否则的话会抛出错误。

python的动态类型模型和传统编程语言的声明类型模型完全不同。如果你能区分开变量名和对象的话,这个模型很好理解。例如赋值语句a = 3,Python会通过三步完成整个过程:

  1. 创建一个对象代表 3
  2. 如果变量a不存在的话创建一个变量a
  3. 建立变量a和新的对象3的联系

这个结果会在Python内部以下图的结构存在,变量名和对象储存在内存的不同部分并且相互联系。变量通常只会指向一个对象而不会指向其他变量,但是大部分的对象都会指向其他对象,例如:列表对象会与它所包含的对象相联系。

这里写图片描述

在Python中这种从变量到对象的联系称作引用(references)——引用是一种联系或者关系,在内存中通过指针(与C语言中的指针类似)实现。整个总结起来就是:

  1. 变量是系统表中的一个条目,并带有储存指向对象的链接的空间
  2. 对象是一系列相互联系的有足够空间存储它所代表的数据的储存
  3. 引用(references) 是变量到对象的指针

至少从概念上来说,每次你在代码中产生一个新的值,Python就会创建一个新的对象去代表这个值。为了最优化,Python在内部会自动的缓存和重(chong)用特定类型的不变对象,例如整数和字符串。不过从逻辑的角度,它工作起来好像每一个表达式的结果是完全不同的对象并且每个对象存储在完全不同的内存中。技术上来说,每个对象会保留更多的空间比起它们所代表内容所需要的空间。每个对象都有两个标准的的头区域:一个类型标识符标注对象的类型,一个引用计数器(reference counter)决定什么时间回收这个对象。

类型依存于对象,而不是变量

>>> a = 3    #a是整数>>> a = 'hello'   #a是字符串>>> a = 3.14    #a是浮点数

Python中的变量在需要的时候指向特定的对象。类型是与对象相联系的,而不是变量名。这也是Python的灵活性所在,如果能利用好这一点,你的代码可以适用于几种类型(例如函数在不改动代码的情况下可以接受数字参数和字符串参数)

对象的垃圾回收机制(Garbage-collected)

>>> a = 3>>> a = 'python'

在Python中,当一个变量被赋值了一个新的对象,那么他所引用(reference)的前一个对象如果没有被其他变量或对象引用的话就会被回收,这种自动回收无用对象所占空间的过程被称作垃圾回收机制(garbage collection)

>>> x = 32>>> x = 'python'   #32被回收>>> x = 3.145      #'python'被回收>>> x = [1, 2, 3]  #3.145被回收
    每次x被重新赋值了一个对象,前一个对象就会被回收,所占用的存储空间会被清空,变成空闲空间,等待重新利用。    在Python内部,Python通过**引用计数器**(reference counter)记录所有当前指向该对象的引用,当计数器的值变为零的时候,这个变量所占用的空间就会被自动回收。

垃圾回收机制最大的好处就是你可以自由的使用对象而不用担心垃圾
占用空间。在程序运行的过程中Python会自动释放弃用的空间。这帮你省掉了许多在其他语言如C、C++中的麻烦事。

循环引用cyclic reference

Python的垃圾回收机制主要是基于引用计数器。但是,Python也有一个组件通过循环引用检测和回收对象。这个组件可以关闭,但默认是启用的。

循环引用是一种特殊情况。因为引用是通过指针实现的,所以一个对象有时可能会引用自身或者它所引用的其他对象存在这种情况(例如 L.append(L)),当向用户定义的类的属性赋值时也会出现循环引用。尽管十分罕见,但这类对象的引用计数器不会递减到零,所以需要区别对待。但用的时可以查看手册。

共享引用(shared references)

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

这里写图片描述
变量b指向了与变量a相同的对象,事实上在内部变量是一个指针,指向了第一个赋值语句所创建的对象的存储区域。引用示意图如上图所示。

 >>> a = 3 >>> b = a >>> a =  'spam'

这里写图片描述
当a改变之后,b依然指向对象3,因为数字对象是不可变对象,所以改变变量并不会改变对象,如下面这个例子,变量a会创建一个新的对象5,而不是去改变对象3.

>>> a = 3>>> b = a>>> a = a + 2

不像其它语言,在Python中,变量始终是指向其它对象的指针,而不是指向一块可改变的内存区域。对于不可变对象,更新对象并不会改变原始对象,而是会指向一个新的对象。对于不可变对象则完全不同。

共享引用与可变对象

Python中对可变对象(Mutable object)——包括列表、字典和集合 的操作会改变自身而不是去产生一个新的对象。例如,对列表中的一个元素赋值会更新列表的值而不是去产生一个新的列表。在程序设计过程中应该时刻注意,可变对象的多个引用的其中一个对数据的更新会影响原始数据。否则的话你的对象可能会毫无理由的变化,例如:

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

这种情况只会发生在可变对象身上,这也是可变对象的一个优点,但是也应该知道其中的而原理以及如何运用如果要避免这种情况,可以复制对象实现,以下是几种方法:

>>> L1 = [1, 2, 3]>>> L2 = L1[:]      #通过切片复制>>> L1[0] = 24>>> L1[24, 2, 3]>>> L2[1, 2, 3]

切片操作有局限性,它不适用与非序列的可变对象,例如字典、集合等。但也可以通过copy模块中的方法实现:

>>> import copy>>> X = copy.copy(Y)  #复制对象Y>>> X = copy.deepcopy(Y)  #复制所有嵌套部分

共享引用和相等

>>> x = 42>>> x = 'shrubbery'

因为Python缓存和重用一些常见的整数和字符串,数字对象42不会被立即回收。相反的,它会被保存在系统表(system table)中,以便当你的代码下次产生数字对象42时重新使用。大部分对象在不使用之后会被立即回收,对于那些例外的,他们的缓存机制可能和你的代码没有关系。

在Python中,有两种等号: ==is

>>> L = [1, 2, 3]  >>> M = L     #M和L指向一个相同的对象>>> N = [1, 2, 3]  #N和L值相同但不是同一个对象>>> L == MTrue>>> L is MTrue>>> L == NTrue>>> L is NFalse

==检验两个引用对象的值是否相同,而is检验的时对象的一致性,连个对象是否为同一个对象。所以结果如上面代码所示。然而,还有一件有趣的事情:

>>> x = 42>>> y = 42>>> x == yTrue>>> x is y   #证明前面两个赋值语句产生的是同一个对象True

因为Python会缓存和重用一些小的整数和字符串对象。
如果往更深层的挖掘,我们查出一个对象到底有多少引用,通过sys标准库中的getrefcount函数我们可以得到引用数,例如在 IDLE GUI中查询整数对象1的引用量:

>>> import sys>>> sys.getrefcount(1)735   #这些references和你的代码无关,是Python内部代码的引用

弱引用(‘weak’ reference)

弱引用通过weakref标准库实现

一个对象的弱引用并不能阻止该对象被回收,如果一个对象的最后一个引用是弱引用,那么这个对象就会被回收,弱引用也会被删除或者通知。这个在缓存以字典为基本结构的大对象时会很有用,这也是引用模型的特殊情况,在需要时可以查看手册。

总结

尽管动态类型概念看起来有点抽象,但在Python中赋值和引用(reference)随处可见,在赋值语句、函数传参、for循环变量、模块引入中等等,都是基于一个赋值模型,而且是Python中仅有的一个。
在实践上,动态类型意味着你可以写更少的代码。更重要的是,动态类型也是Python中多态(polymorphism)的根本。正是由于这些特性,Python才是简洁的高度灵活的语言!

原创粉丝点击