你相信吗??Python把数字也当做对象!@@@对象,名称绑定,引用计数

本文学习自: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
View Code

有感觉了,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是一种纯粹的面向对象语言。
  * 赋值语句是名字和对象的绑定过程。
  * 函数的传参是对象到不同名字空间的绑定。

 

posted @ 2017-07-23 12:49  _revolution  阅读(1076)  评论(0编辑  收藏  举报