本文学习自: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)) # 结果:
#
#
#

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

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

  1. Python 对象的引用计数和拷贝

    Python 对象的引用计数和拷贝 Python是一种面向对象的语言,包括变量.函数.类.模块等等一切皆对象. 在python中,每个对象有以下三个属性: 1.id,每个对象都有一个唯一的身份标识自己 ...

  2. python赋值和拷贝----一切皆对象,参数皆引用

    摘要: 1 python中的一切事物皆为对象,并且规定参数的传递都是对象的引用. 2  python参数传递都是"传对象引用"方式.实际上相当于c++中传值和传引用的结合. 3 如 ...

  3. Python的垃圾回收机制(引用计数+标记清除+分代回收)

    一.写在前面: 我们都知道Python一种面向对象的脚本语言,对象是Python中非常重要的一个概念.在Python中数字是对象,字符串是对象,任何事物都是对象,而它们的核心就是一个结构体--PyOb ...

  4. python的 del 函数是删对象还是删引用

    1.首先介绍下python的对象引用 1)Python中不存在传值调用,一切传递的都是对象引用,也可以认为是传址调用.即Python不允许程序员选择采用传值或传引用.Python参数传递采用的是“传对 ...

  5. 【python测试开发栈】python内存管理机制(一)—引用计数

    什么是内存 在开始进入正题之前,我们先来回忆下,计算机基础原理的知识,为什么需要内存.我们都知道计算机的CPU相当于人类的大脑,其运算速度非常的快,而我们平时写的数据,比如:文档.代码等都是存储在磁盘 ...

  6. python基础——数字&集合&布尔类型

    Python的核心数据类型 内置对象 对象类型 例子 数字 123,3.1415,3+4j,Decimal(小数),Fraction(分数) 字符串 'dodo',"guido's" ...

  7. python中数字类型与处理工具

    python中的数字类型工具 python中为更高级的工作提供很多高级数字编程支持和对象,其中数字类型的完整工具包括: 1.整数与浮点型, 2.复数, 3.固定精度十进制数, 4.有理分数, 5.集合 ...

  8. python 基础-----数字,字符串,列表,字典类型简单介绍

    一.第一个python小程序 1.下载安装python2.7和python3.6的版本及pycharm,我们可以再解释器中输入这样一行代码: 则相应的就打出了一句话.这里的print是打印的意思.你输 ...

  9. 【笔记】基于Python的数字图像处理

    [博客导航] [Python相关] 前言 基于Python的数字图像处理,离不开相关处理的第三方库函数.搜索网络资源,列出如下资源链接. Python图像处理库到底用哪家 python计算机视觉编程— ...

随机推荐

  1. Kconfig SourceCode GDB调试 *****

    1.GDB&makefile scripts->Makefile编译FLAGS  -g HOSTCC = gcc HOSTCXX = g++ HOSTCFLAGS := HOSTCXXF ...

  2. nginx开发(二)配置mp4文件在线播放

    1: 第一步先开打nginx的文件夹遍历功能 vi /usr/local/nginx/conf/nginx.conf #编辑配置文件,在http {下面添加以下内容: autoindex on; #开 ...

  3. 关于mysql的索引原理与慢查询优化

    大多情况下我们都知道加索引能提高查询效率,但是应该如何加索引呢?索引的顺序如何呢? 大家看一下下面的sql语句(在没有看下面的优化的方法之前)应该如何优化加索引以及优化sql语句: 1.select  ...

  4. Linux 系统命令 - pwd - 显示当前所在的位置

    命令详解 重要星级: ★★★★★ 功能说明: pwd命令是 "print working directory" 中每个单词的首字母缩写,其功能是显示当前工作目录的绝对路径.在实际工 ...

  5. Java properties配置文件

    Java中的配置文件常为properties文件,格式为文本文件,文件内容的格式是“键=值”格式.注释信息使用“#”来注释. Properties类的常用方法 String getProperty(S ...

  6. JQ 获取Table的td 值

    <script type="text/javascript"> function SetTable() { $("#myTab table").ea ...

  7. ROS-URDF仿真

    前言:URDF (标准化机器人描述格式),是一种用于描述机器人及其部分结构.关节.自由度等的XML格式文件. 一.首先做一个带有四个轮子的机器人底座. 1.1 新建urdf文件 在chapter4_t ...

  8. [W3School]JavaScript教程学习

    JavaScript 简介 JavaScript 是世界上最流行的编程语言.这门语言可用于 HTML 和 web,更可广泛用于服务器.PC.笔记本电脑.平板电脑和智能手机等设备. JavaScript ...

  9. CodeDOMProvider 类

    CodeDomProvider 可用来创建和检索代码生成器和代码编译器的实例.代码生成器可以生成特定语言的代码,如:C#.Visual Basic.JScript 等,而代码编译器可以将代码文件编译成 ...

  10. Linux系统下强制踢掉登录用户

    1,利用who命令,找出用户登录的终端代号 who root     pts/0        2017-03-14 22:30 (223.1.1.1) root     pts/1        2 ...