类型与对象
要知道,Python中所有数据都是对象来构建的。
每个对象都有一个身份,一个类型,和一个值。
例如,a = 42,使用值42创建一个整数对象,对象的身份可以看做指向它在内存中所处位置的指针,而a就是引用这个具体位置的名称。
在实例被创建后,它的身份和类型就不可改变。
如果对象的值是可以修改的,则称之为可变对象,反之,称之为不可变对象。
如果某个对象包含对其他对象的引用,则将其称之为容器或者集合。
对象的身份和类型
内置函数id()可以返回一个对象的身份,返回值为整数。这个整数对应该对象在内存中的位置。
is运算符用于比较两个对象的身份,type()函数返回一个对象的类型。
if type(b) is list:
print("b是一个列表")
还有一个内置函数isinstance(object, type),同样可以检测对象的类型
if isinstance(b, list):
print("b是一个列表")
值得一提的是,isinstance()函数能够实现继承,所以是检查对象类型的首选方式。
不要经常检查对象的类型,它会检查每一层继承关系,包括这个对象所属类的父类,这会严重影响程序的效率。
引用计数与垃圾收集
所有对象都有引用计数,无论是给一个对象分配一个新的名称,还是将其放入一个容器(list,dict,tuple),该对象的引用计数就会增加。
书上有几个例子比较有特点:
image.png
例子中,创建了一个值为37的对象,a引用了这个对象,然后b同样也引用了这个对象,那么这个对象的引用计数就会+1.
然后把b放到列表中的时候,值为37这个对象的引用计数又+1。
自始至终只有一个值为37的对象,所有其他操作都是创建了对该对象的新引用。
当使用del语句或者引用超出作用域的时候,引用计数会减少。
image.png
使用sys.getrefcount(对象)函数可以获取对象的当前引用计数。
image.png
image.png
这里发生了意外,在这个只有4行的代码中,3789789789这个对象的引用计数应该为2啊,为什么会是5呢?
当然不是函数算错了,而是对于不可变的数据(比如数字,字符串),解释器会主动在程序的不同部分共享对象,以便节约内存。原来是其他地方也引用了3789789789,我们仅仅引用了2次,所以引用计数加了2。
当一个对象的引用计数归零时,垃圾回收机制会回收该对象的内存。但是有些情况,很多已经不再使用的对象可能存在循环依赖关系,然后就导致了内存泄漏。
image.png
在执行了前4行代码时,ab的引用计数分别是2,然后又分别执行一次del操作,所以引用计数没有归零,对象也没有被销毁,从而导致了内存泄漏。
为了解决这个问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环引用,然后删除他们。
gc模块中的函数可以准确调整和控制该算法的行为(13章会讲解)。
引用与复制
a = b
这句话,a会引用b,也就是对b创建一个新的引用。
有两种情况,b是可变类型(list,dict,tuple),那么a和b就引用的是同一对象。比如:
image.png
image.png
另外一种情况,b是不可变类型(数字,字符串),这样的赋值其实是创建了一个b的副本。
深复制与浅复制
对于像字典,列表这样的可变对象,可以使用两种赋值操作:深复制和浅复制。
浅复制将创建一个新对象,但它是对原始对象包含的项的引用。
深复制也是对原始对象的引用,但是深复制是粒子性的,对list中的可变对象也会创建新的引用。
比如说:
import copy
a = [1, 2, 3, [4, 5, 6]]
b = copy.deepcopy(a)
c = copy.copy(a)
b对a进行了深复制,对于a中每一项和每一对象的子对象都进行了拷贝。
c对a进行了浅复制,b只对a的每一项元素进行了拷贝,就是1,2,3,[4,5,6]。
那么最大的区别在哪呢?当我们修改a[3]这个嵌套list时,c中会跟着改变,因为c中复制的是对[4,5,6]的引用,而[4,5,6]这是一个可变对象,它的改变不会改变引用。而b中不会有任何改动。
常见的浅复制有,copy.copy()函数和切片[:]。
深复制有:copy.deepcopy()
表示数据的内置类型
None:
如果一个函数没有return,那么就返回None。
None也经常作为可选参数的默认值,以便让调用者检测是否是为参数传递了实际的值。
None没有任何属性,在布尔值中返回False。
数字类型:
所有数字类型都是不可变的。
如果想精确控制float类型,可以使用numpy扩展库。
说一下复数,平时用到的地方很少。复数由两个浮点数表示。
实部和虚部分别用z.real,z.imag表示。还有,用z.conjugate()来计算共轭复数。
对于浮点数,如果想测试一个浮点数是否是整数,y.is_integar()。
序列类型
索引为非负整数的有序对象集合, string, dict, tuple
string,tuple不可变,dict可变。所有的序列都支持迭代。
all()函数,检查所有项是否都为True
any()函数,检查任意项是否为Ture
关于方法的类型
有三种常见的方法类型:实例方法,静态方法,类方法。
实例方法:就是操作特定类的实例方法,实例被作为第一个参数(self)传递给方法。
类方法:把类本身当做一个对象进行操作。第一个参数把类对象传递给方法
静态方法:就是打包在类中的函数,它不能使用实例或者类作为第一个参数。
绑定方法:由实例调用的函数的方法,比如
f = Foo()
meth = f.instance_method
非绑定方法:由类调用的函数的方法,但是调用时候要显示的提供对象,比如
umeth = Foo.instance_method
umeth(f, arg)
定义一个class,当创建一个实例时,会先调用类的init()函数,用来初始化新创建的实例。
如果一个实例定义了一个特殊方法call(),它就能够模拟函数的行为。
如果call()是为了某个实例定义的,那么x(args)等同于x.call(args)。
一个实例的创建过程:
x = class.new(A, args)
is isinstance(x, A):
x.init(args)
网友评论