探索 Python 的变量、类型和引用

我一开始为 Python 的强大和简洁所震撼,看了些 Python 的教程,学了不少东西。前面那些探索就以后再写吧……今天先讲讲今天知道的东西。

在探索到 Python 函数的参数传递的时候,我不禁赞叹 Python 灵活的参数设计,但慢慢的,开始迷惑与传递参数的修改和返回。

众所周知,在 C++ 中传递参数分为传值和传引用两种,但 Python 没有,那到底传进去的东西,修改一下,能不能传出来呢?这是一个很奇怪和让人费解的问题,不是么?在查阅了一些资料后,对 Python 关于变量、类型和引用的一些基本方式有了一些了解,进而基于这种理解并结合实验,了解了参数传递的奥妙。

Python 的变量是没有类型的,这与以往看到的大部分语言都不一样。但 Python 却是区分类型的,那类型在哪里呢?事实是,类型是跟着内存中的对象走的。Python 的所有变量其实都是指向内存中的对象的一个指针,所有的变量都是!此外,对象还分两类:一类是可修改的,一类是不可修改的。

现在,我插入在此先说说函数参数的问题,我们有下面一个实验:

def func1(a): a += 1
def func2(a): a[0] = 0
t1 = 0
func1(t1)
print t1
t2 = [1, 2, 3]
func2(t2)
print t2

结果是:

0
[0, 2, 3]

看看结果会不会很惊异?第一个看起来像传值,第二个看起来却像传引用?看到这里你是不是觉得 Python 是一种莫名其妙的语言?其实当时我也有这种想法……但 Python 果然没哟让我失望,它如同 UNIX 一样,一开始设计得就如此优美。继续往下看~

不可修改的对象是我们最常用和最熟悉,几乎在任何一个语言中都能看到的——整数、实数、字符串和元组。有人说,怎么不可变啊?我随便给他们赋值!是的,在 Python 里几乎一切都是可以改变的,甚至有人说“如果你愿意,None 的值也是可以变的”(当然我不知道怎么变……)。但是如果注意观察,会发现所谓的改变其实是——扔了旧的建个新的!验证这个的实验很简单:

a = 1
print id(a)
a += 1
print id(a)

类似的实验想怎么做怎么做,只要那两个是不可变对象,你就一定会发现 id 变了!为什么?因为对象不可变。那什么可变?变量的引用是可变的!

好,那么自然剩下的就是可变的对象了,上面的实验亦可以很容易的证实字典、列表、集合和类实例等对象是可变的。那么,这意味着什么呢?

下面,我们回到函数传值的问题。我们知道了可变对象和不可变对象的区别,不是吗?对于可变对象,对于对象的操作不会重建对象,而对于不可变对象,每一次操作就重建新的对象。那么函数参数到底是个什么东西呢?其实说白了也简单,就是把参数里传入的东西对相应对象的引用依次赋给对应的内部变量(有点晕吗?)。看看第一个实验,有没有明白些什么?其实都是将一个指向对象的引用传个一个名为“参数”的本地变量,所以 func1 中给 a 的是一个值为 0 的整数对象的引用,但我们知道,整数对象是不可变的,所以当 func1 对 a 进行修改的时候,实际上是修改本地变量 a 的引用到一个新的值为 1 的整数对象的引用。那么很显然,func2 修改的是一个可变的对象,也就是说即使 func2 修改了 a,本地变量 a 和全局变量 t2 指向的还是同一个对象,虽然他们不是同一个变量!这样一切情况都明了了,不是么?不明了的话再看看下面这个实验:

a = [[1, 2, 3], [4, 5, 6]]
b = a[0]
b[0] = 0
print a

输出一定是:

[[0, 2, 3], [4, 5, 6]]

其实原理和参数的传递是一致的。

我们下面来看看全局变量和本地变量的问题。如果一个函数里面使用了一个变量,那么 Python 会先看看有没有对应的本地变量,如果没有找到,但找到一个全局变量,那么 Python 会把那个全局变量的引用赋给一个新的本地变量。所以,现在在函数里的那个变量和全局变量其实不是同一个变量,他们只不过暂时有了相同的引用。这样其实可以看作 Python 为你做了隐式的参数传递。因此我们发现,他和参数一样,传值传引用表面上看过去漂移不定。那么如何修改一个指向不可变全局变量的值呢?靠返回值显然不那么优美。好在 Python 像 PHP 那样提供了一个叫 global 的语法,被 global 的变量使得本地变量成为相应全局变量的一个别名,也就是说这个语句使他们成为同一个变量,这一点很重要!

现在看到了 Python 优美的设计。那下面的问题是,如果我们一定要复制一个可变对象的副本怎么办?简单的等号赋值显然被证明无效了。Python 也提供了方法——copy 模块。copy 模块是每一个 Python 都有的,专门用于生成可变对象的副本。copy 模块中有两个函数:copy.copy 和 copy.deepcopy。其中 copy 叫做潜复制,它仅仅复制了第一你给它的东西,下面的不管了。而 deepcopy 叫做深复制,它将所有能复制的都复制了。这样说比较抽象,我们来看下面实验:

a = [[1, 2, 3], [4, 5, 6]]
b = a
c = copy.copy(a)
d = copy.deepcopy(a)
a.append(15)
a[1][2] = 10
print a
print b
print c
print d

输出结果:

[[1, 2, 3], [4, 5, 10], 15]
[[1, 2, 3], [4, 5, 10], 15]
[[1, 2, 3], [4, 5, 10]]
[[1, 2, 3], [4, 5, 6]]

我想,效果不言而喻了。

此外,我还看到一个叫做弱引用 (weakref) 的模块,暂时不知道是干嘛的……下次研究了再说……

参考:

Comments !

social