本文学习自:http://blog.csdn.net/yockie/article/details/8474408
1.对象
Python中, 万物皆对象,包括12345等int常量。不信吗??用dir()命令看一看就知道
当然了,既然他们都叫做对象。那么肯定有共同点了!所有的对象都有下面的三个特征
a = 1 b = 1 print(id(a)) print(id(b)) print(id(1)) # 结果: # 1634064176 # 1634064176 # 1634064176
有感觉了,int类似于string一样,是一个不可变的对象,内部可能有一个常量池的东西?
* 都有唯一的标识码 id()
* 都有确定的类型
* 有内容(或称为值)
一旦对象被创建,标识码就不能更改,对象类型也是不可更改的,内容可以改变(实际上不是内容改变而是把名字取给另一个不可变对象)
一个名称只能对应一个对象,一个对象可能有0或1或n个名称 这个0,1,n叫引用计数
(可变对象如dict、list 。恒定对象如int、string)
而一个对象有可能:
* 肯定有属性
* 有0个或者n个方法
* 有0个或者n个名字(引用计数为0,或者为n)
2.名字
我悄悄的认为,名字就是引用。不知道对不对
对象自己不知道有多少名字,叫什么,只有名字本身知道它所指向的是个什么对象。
Python将赋值语句认为是一个命名操作(或名称绑定)
一个对象的引用计数可以为0或者为n,要访问对象必须通过名字(引用),Python中赋值操作就是一个命名操作(或名字绑定)。
名字在一定的名字空间内有效。而且唯一,就是说一个名字只能对应一个对象,(在同一个名字空间内)而一个对象却可以有多个名字。
a = 1 在Python中的含义:
* 创建一个值为1的对象
* a是指向该对象的名字
3.绑定
绑定就是用引用指向对象,会增加该对象的引用计数。
a = a + 1 在Python中的含义:
* 创建一个新的对象,值为 a + 1
* a 这个名字指向新对象,新对象的引用计数 + 1 ,而a以前指向的对象引用计数 - 1
* a以前指向的对象值没有变
什么操作导致引用计数的变化?
* 赋值
* 在一个容器(list、 dict、seq)中包含该对象
——将增加对象的引用计数
* 离开当前的名字空间(该名字空间中的本地名字都会被销毁)
* 对象的一个名字被绑定到另外一个对象
* 对象被从包含它的容器中删除
* 用del()方法
——将减少对象的引用计数
区别
a = 1
b = a
a = 2
print(b) # 1 恒定对象
----------
a = [1, 2, 3]
b = a
a[0] = 2
print (b) # [2,2,3] 可变对象
-----------------------------
为什么修改字典d的值不用global关键字先声明呢?
s = 'foo'
d = {'a':1}
def f():
s = 'bar'
d['b'] = 2
f()
print s # foo
print d # {'a': 1, 'b': 2}
s = 'bar'这句话可以认为是
创建新对象'bar'绑定到f函数的名字空间认为是新名称s,或者是解绑外层s的引用,绑定到新对象'bar'
就产生了歧义~~~Python默认是执行第一种,
d['b'] = 2这句话是修改可变对象,不存在创建新对象的问题,没有歧义。
python里面一个方法内是一个名称空间,循环里面不是。旧名字->新对象,会被认为是使用新名称空间,修改可变对象如上例就是被认为是修改了老对象(新对象也没有啊)
-----------------------------------再看下面的代码-------------
list_a = []
def a():
list_a = [1] ## 语句1
a()
print list_a # []
print "======================"
list_b = []
def b():
list_b.append(1) ## 语句2
b()
print list_b # [1]
大家可以看到为什么 语句1 不能改变 list_a 的值,而 语句2 却可以?他们的差别在哪呢?
因为 = 创建了局部变量,而 .append() 或者 .extend() 重用了全局变量。
4.函数的传参问题
函数的参数传递也是一个名字与对象绑定的过程。(传参即增加了该对象的引用计数)而且是绑定到另外一个名字空间(即函数内部的名字空间)。
Python所有参数传递都是引用传递,也就是传址。函数内部修改可变对象的值会影响外部
因此在Python中,我们应该抛开传递参数这种概念,时刻牢记函数的调用参数是将对象用另外一个名字空间的名字绑定。在函数中不过是用了另外一个名字,但还是对同一个对象进行操作,。
-------------缺省参数的问题---------
Python的缺省参数暗藏玄机。看下面的代码:
>>> def foo(par=[]): ... par.append(0) ... print par ... >>> foo() # 第一次调用 [0] >>> foo() # 第二次调用 [0, 0]
par在执行结束就销毁,两次调用结果不是应该一样吗?为什么会出现这种结果????
问题就出在没有搞清楚缺省参数的生存周期。。。
这都是“对象,名字,绑定”这些思想惹的祸,“万物皆对象”,这里函数foo当然也是一个对象,可以称为函数对象(与一般对象没有什么不同),先看看它的属性
python2.x中
>>> dir(foo) ['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__module__',
'__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__',
'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
python 3.x中
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
defaults可能与缺省参数有关:看看它的值
foo() print(foo.__defaults__) # 第一次调用 ([0],) foo() print(foo.__defaults__) # 第二次调用([0, 0],)
验证一下:
def foo1(par=[] , st = "s", a = 1): par.append(0) print (par, st, a) foo1() print(foo1.__defaults__) # 第一次调用 ([0], 's', 1) foo1() print(foo1.__defaults__) # 第二次调用(([0, 0], 's', 1)
可以看出,这个函数对象的属性__defaults__中存放了这个函数的所有缺省参数。
在函数定义中有几个缺省参数,__defaults__中就会包括几个对象,暂且称之为缺省参数对象(如上列中的[]、“s”和1)。
这些缺省参数对象的生命周期与函数对象相同,从函数使用def定义开始,直到其消亡(如用del)。所以即便是在这些函数没有被调用的时候,但只要定义了,缺省参数对象就会一直存在。(☆☆☆☆☆)
前面讲过,函数调用的过程就是对象在另外一个名字空间的绑定过程。当在每次函数调用时,如果没有传递任何参数给这个缺省参数,那么这个缺省参数的名字就会绑定到在func_defaults中一个对应的缺省参数对象上。
函数foo1内的对象par就会绑定到__defaults__中的第[0]个名称,st绑定到第[1]个,a则是第[2]个。
所以我们看到在函数foo中出现的累加现象,就是由于par绑定到缺省参数对象上,而且它是一个可变对象(列表),par.append(0)就会每次改变这个缺省参数对象的内容。
将函数foo改进一下,可能会更容易帮助理解:
>>> def foo(par=[]): ... print id(par) # 查看该对象的标识码 ... par.append(0) ... print par ... >>> foo.func_defaults # 缺省参数对象的初始值 ([],) >>> id(foo.func_defaults[0]) # 查看第一个缺省参数对象的标识码 11279792 # 你的结果可能会不同 >>> foo() 11279792 # 证明par绑定的对象就是第一个缺省参数对象 [0] >>> foo() 11279792 # 依旧绑定到第一个缺省参数对象 [0, 0] # 该对象的值发生了变化 >>> b=[1] >>> id(b) 11279952 >>> foo(b) # 不使用缺省参数 11279952 # 名字par所绑定的对象与外部名字b所绑定的是同一个对象 [1, 0] >>> foo.func_defaults ([0, 0],) # 缺省参数对象还在那里,而且值并没有发生变化 >>> foo() 11279792 # 名字par又绑定到缺省参数对象上 ([0, 0, 0],)
为了预防此类“问题”的发生,python建议采用下列方法:
>>> def foo(par = None): ... if par is None: ... par = [] ... par.append(0) ... print par 或者: >>> def foo(par = []): ... if len(args) <= 0: ... par = [] ... par.append(0) ... print par 或者: >>> def foo(par = []): ... if not par: ... par = [] ... par.append(0) ... print par
永远不要使用可变的默认参数,可以使用None作为哨兵,以判断是否有参数传入,如果没有,就新创建一个新的列表对象,而不是绑定到缺省
参数对象上。
6.总结
* python是一种纯粹的面向对象语言。
* 赋值语句是名字和对象的绑定过程。
* 函数的传参是对象到不同名字空间的绑定。