Tag Archives: Python

Python里一个卡住我的Trick

我定义了一个小class(比如叫做myclass),然后想弄一个list(比如长度为10),每个元素里放一个myclass的实例(的引用),其实就是个数组啦。然后我就想pythonic一下,写成了这个样子

mylist = [myclass()] * 10

您一定已经发现问题了,然而我一直杯具地debug了半个多小时,才终于发现这样的话list里放的是10个指向同一个实例的引用,所以看上去的结果就是修改一个元素会导致所有的元素都被改动。

Python Notes: Reference and Dynamic Type

内容较水,让大牛们见笑了。Python语言采用所谓的“动态类型”,顺序执行如下三条语句是完全合法的:

>>> loli = 5
>>> loli = 3.142
>>> loli = "hello"

这种类型系统与静态类型的语言如C和Java等有很大不同。我认为比较容易理解的表述方式是:数据的类型与对象有关,与名字无关。变量名只是一个名字而已,它没有类型,可以理解成一个C语言里的(void*),类型信息是存储在变量指向的对象中。这个对象是内存中的一块区域,里面包含具体的值、这块对象的类型、引用计数等等信息。对于上面三条语句,可以理解为有一个叫做loli的变量,一开始指向一个整数对象,然后指向一个浮点数对象,最后指向一个字符串对象。

如前所述,Python变量表示的是对对象的“引用”,对象分为可变和不可变两种(这一点和Java类似),数值、字符串、tuple是不可变的,其余的是可变的。比如下面两条语句

>>> loli = 3
>>> roba = loli

首先loli指向一个整型对象(3),然后roba也指向同一个对象。Python有个id()函数,可以查看对象的内存地址,可以发现id(loli)和id(roba)是相同的。如果继续执行

>>> roba = roba + 1

则id(roba)可以发现指向的不同的位置。此时roba指向4,而loli仍指向3。因为整型对象是不可变的,所以roba必须指向一个新申请的叫做4的对象,而不能改变原来指向的那个3。与此相反,如果

>>> loli = [3]
>>> roba = loli
>>> roba[0] = roba[0] + 1
>>> loli
[4]
>>> roba
[4]

这里对象是可变的list,所以修改roba[0]的时候,loli指向的对象值也变了,因为它们始终指向同一个对象。如果我们想让他们指向不同的对象,则需调用copy.copy()进行复制。(关于copy和deepcopy的区别就以后再说吧)

在传递函数参数的时候也要注意这个问题,如果传的是不可变类型,在函数内部的修改不会影响到外面,如果是可变类型则有影响。

Python notes

初学,把需要注意的地方随便记一下
1. 行首缩进不是随便用的,它起到C里面花括号或Pascal里begin end的作用。行末一个冒号提示新一个”block”的开始。
2. while, for等等后面都可以加个else,不过不加也是同样的效果
3. 函数参数的传递可以不按照顺序,只需在调用时明确指出形参的名称,像这样定义def foo(a,b,c),调用时可写foo(a=1, c=3, b=2)
4. List用方括号,Tuple用圆括号,Dict用花括号
5. 下标为负数表示从0开始反着数,如-1表示最后一个元素
6. range(a,b)、下标中的[a:b]都表示左闭右开区间[a,b)
7. 与Java相似,赋值运算作用在对象上表示的是指向对象的引用而不是把对象再拷贝一份。
8. 所谓的“自然字符串”,在引号前加r,可以省去大量转义符的麻烦,如”D:\roba” == r”D:roba”
9. Python类中的__init__方法对应于C++里的constructor,__del__对应于destructor,self参数对应于C++里的this,另外成员函数的第一个参数必须为self(可以不是这个名称)
10. Python类中的属性默认为public和static,如果想定义属于对象的属性(即非static),只能使用self.xxx的方法。如果想定义private的属性,在变量名前加双下划线
11. 子类创建时不会自动调用父类的__init__