哲学:
- Python
Python 中一切皆对象
对象是
对数据的抽象。
- Python
程序中所有的数据都是对象或对象之间的关系表示的。(在某种意义上,为顺应冯 · 诺依曼" 存储式计算机 " 的模型,
- Python
中的代码也是对象。)
- Python
中每一个对象都有一个身份标识,一个值以及一个类型。对象创建后,其身份标识绝对不会改变;可以把身份标识当做对象在内存中的地址。
- Python
操作符比较两个对象的身份标识;
- is
函数返回表示对象身份标识的整数。
- id()
CPython 实现细节: 在
解释器的实现中,
- CPython
函数返回存储
- id(x)
的内存地址
- x
对象的类型决定了对象支持的操作 (例如,对象有长度么?),同时也决定了该类型对象可能的值。
函数返回对象的类型 (这个类型本身也是一个对象)。与其身份标识一样,对象的类型也是不可改变的 [1]。
- type()
一些对象的值可以改变。可改变值的对象也称作可变的 (mutable);一旦创建,值恒定的对象也叫做 不可变的 (immutable)。(当不可变容器对象中包含对可变对象的引用时,可变对象值改变时,这个不可变容器对象值也被改变了;然而,不可变容器对象仍被认为是不可变的,因为对象包含的值集合确实是不可改变的。因此,不可变性不是严格等同于拥有不可变的值,它很微妙。) (译注:首先不可变容器对象的值是一个集合,集合中包含了对其他对象的引用;那么这些引用可以看做地址,即使地址指向的内容改变了,集合中的地址本身是没有改变的。所以不可变容器对象还是不可变对象。) 对象的可变性取决于其类型;例如,数字,字符串和元组是不可变的,但字典和列表是可变的。
对象从不显式销毁;当对象不可达时会被垃圾回收 (译注:对象没有引用了)。一种解释器实现允许垃圾回收延时或者直接忽略——这取决于垃圾回收是如何实现的,只要没有可达对象被回收。
CPython 实现细节:
解释器实现使用引用计数模式延时探测循环链接垃圾,这种方式可回收大多数不可达对象,但并不能保证循环引用的垃圾被回收。查看
- CPython
模块的文档了解控制循环垃圾回收的更多信息。其他解释器实现与
- gc
不同,
- CPython
实现将来也许会改变。因此不能依赖垃圾回收器来回收不可达对象 (因此应该总是显式关闭文件对象。)。
- CPython
需要注意,使用工具的调试跟踪功能可能会导致应该被回收的对象一直存活,使用
- try
- ...
语句捕获异常也可以保持对象的存活。
- except
一些对象引用了如文件或者窗口的外部资源。不言而喻持有资源的对象被垃圾回收后,资源也会被释放,但因为没有机制保证垃圾回收一定会发生,这些资源持有对象也提供了显式释放外部资源的方式,通常使用
方法。强烈推荐在程序中显式释放资源。
- close()
- try
- ...
语句和
- finally
语句为释放资源提供了便利。
- with
一些对象包含对其他对象的引用,这些对象被称作 容器。元组,列表和字典都是容器。引用的容器值的一部分。大多数情况下,谈论容器的值时,我们暗指容器包含的对象值集合,而不是对象的身份标识集合;然而,谈论容器的可变性时,我们暗指容器包含的对象的身份标识。因此,如果不可变对象 (如元组) 包含对可变对象的引用,可变对象改变时,其值也改变了。
类型影响对象的绝大多数行为。在某些情况下甚至对象的身份标识的重要性也受到影响:对于不可变类型,计算新值的操作实际上可能会返回已存在的,值和类型一样的对象的引用,然而对于可变对象来说这是不可能的。例如,语句
执行之后,
- a = 1; b = 1
和
- a
可能会也可能不会引用具有相同值得同一个对象,这取决于解释器实现。但是语句
- b
执行之后,可以保证
- c = []; d = []
和
- c
会指向不同的,唯一的新创建的空列表。(注意
- d
分配相同的对象给
- c = d = []
和
- c
)
- d
Note: 以上翻译自 《The Python Language References#Data model# Objects, values, types》 3.6.1 版本。
官方文档已经对
对象做了详细的描述,这里总结一下。
- Python
对象的三个特性:
解释器实现为对象的内存地址。 操作:
- CPython
,内建函数
- id()
函数返回标识对象的一个整数;
- id()
比较两个对象的身份标识。 示例:
- is
- >>> id(1)
- 1470514832
- >>> 1 is 1
- True
,内建函数返回对象的类型 示例:
- type()
- >>> type('a')
- <class 'str'>
操作符用于比较两个对象的值是否相等,其他比较运算符比较对象间大小情况。 示例:
- ==
- >>> 'python'
- 'python'
- >>> 1 == 2
- False
可变与不可变:一般认为,值不可变的对象是不可变对象,值可变的对象是可变对象,但是要注意不可变集合对象包含可变对象引用成员的情况。
中的对象:
- Python
- # -*- coding: utf-8 -*-
- # filename: hello.py
- 'a test module'__author__= 'Richard Cheng'
- importsysclassPerson(object):''' Person class'''
- def __init__(self, name, age):self.name=nameself.age=agedeftset():print(sys.path)
- p=Person('Richard',20)print(p.name,':', p.age)defmain():
- tset()if __name__ == '__main__':
- main()
这段
代码中有很多对象,包括
- Python
这个模块对象,创建的
- hello
类对象,几个函数如
- Person
,
- test
函数对象,数字,字符串,甚至代码本身也是对象。
- main
几乎所有语言中都有 "变量" 的说法,严格说来,
中的变量不应该叫变量,称为名字更加贴切。
- Python
以下翻译自 Code Like a Pythonista: Idiomatic Python # Python has "names"
其他语言中,为变量分配值就像将值放到 "盒子" 里。
- int a = 1;
盒子
现在有了一个整数
- a
。
- 1
为同一个变量分配值替换掉盒子的内容:
- a =2;
现在盒子
中放了整数
- a
- 2
将一个变量分配给另一个变量,拷贝变量的值,并把它放到新的盒子里:
- int b = a;
是第二个盒子,装有整数 2 的拷贝。盒子
- b
有一份单独的拷贝。
- a
中,名字或者标识符就像将一个标签捆绑到对象上一样。
- Python
- a = 1
这里,整数对象
有一个叫做
- 1
的标签。
- a
如果重新给
分配值,只是简单的将标签移动到另一个对象:
- a
- a = 2
现在名字
贴到了整数对象
- a
上面。原来的整数对象 1 不再拥有标签
- 2
,或许它还存在,但是不能通过标签
- a
访问它了 (当对象没有任何引用时,会被回收。)
- a
如果将一个名字分配给另一名字,只是将另一个名字标签捆绑到存在的对象上:
- b = a
名字
只是绑定到与
- b
引用的相同对象上的第二个标签而已。
- a
虽然在
中普遍使用 "变量"(因为" 变量 "是普遍术语),真正的意思是名字或者标识符。
- Python
中的变量是值得标签,不是装值得盒子。
- Python
中有指针,
- C/C++
中有引用,
- Java
中的名字在一定程度上等同于指针和引用。
- Python
2.1 节中其他语言的例子,也只是针对于它们的基本类型而言的,若是指针或者引用,表现也跟
的名字一样。这也在一定程度上说明了
- Python
将面向对象贯彻得更加彻底。
- Python
可以对一个变量做什么?声明变量,使用变量,修改变量的值。名字作为
中的一个重要概念,可以对它做的操作有:
- Python
名字以及对象,它们之间必然会发生些什么。
其他如
和
- C/C++
的高级语言,变量在使用前需要声明,或者说定义。以下在
- Java
中声明变量:
- Java
- public static void main(String[] args) {inti =0;// 先声明,后使用System.out.println(i);// 使用变量i}
这样,在可以访问到变量
所在作用域的地方,既可以使用
- i
了。还有其他声明变量的方法么?好像没有了。
- i
中有多种定义名字的途径,如函数定义,函数名就是引用函数对象的名字;类定义,类名就是指向类对象的名字,模块定义,模块名就是引用模块对象的名字;当然,最直观的还是赋值语句。
- Python
赋值语句
官方对赋值语句做了这样的说明 (地址):
Assignment statements are used to (re)bind names to values and to modify attributes or items of mutable objects.
即:
赋值语句被用来将名字绑定或者重绑定给值,也用来修改可变对象的属性或项
那么,我们关心的,就是赋值语句将名字和值 (对象) 绑定起来了。
看一个简单的赋值语句:
- a= 9
在处理这条语句时:
- Python
:
- 9
,并把它指向上述对象:
- a
上述过程就是通过赋值语句的名字对象绑定了。名字首次和对象绑定后,这个名字就定义在当前命名空间了,以后,在能访问到这个命名空间的作用域中可以引用该名字了。
定义完名字之后,就可以使用名字了,名字的使用称为 "引用名字"。当名字指向可变对象和不可变对象时,使用名字会有不同的表现。
- a= 9 #1a=a+ 1 #2
语句 1 执行完后,名字
指向表示整数
- a
的对象:
- 9
由于整数是不可变对象,所以在语句 2 处引用名字
,试图将表示整数
- a
的对象
- 9
,但该对象的值是无法改变的。因此就将该对象表示的整数值
- + 1
加
- 9
,以整数
- 1
新建一个整数对象:
- 10
接下来,将名字
- a
到新建对象上,并移除名字对原对象的引用:
- 重绑定
使用
函数,可以看到名字
- id()
指向的对象地址确实发生了改变:
- a
- >>>a= 9
- >>> id(a)1470514960
- >>>a=a+ 1
- >>> id(a)1470514976
可变对象可以改变其值,并且不会造成地址的改变:
- >>>list1=[1]>>> id(list1)42695136
- >>>list1.append(2)>>> id(list1)42695136
- >>>list1
- [1,2]>>>
执行语句
,创建一个
- list1 = [1]
对象,并且其值集中添加
- list
,将名字
- 1
指向该对象:
- list1
执行语句
,由于
- list1.append(2)
是可变对象,可以直接在其值集中添加
- list
:
- 2
值得改变并没有造成
引用的对象地址的改变。
- list1
再来看一个比较 "奇怪" 的例子:
- values=[1,2,3]
- values[1]=valuesprint(values)
一眼望去,期待的结果应该是
- [1, [1,2,3],3]
但实际上结果是:
- [1, [...],3]
我们知道
中的元素可以是各种类型的,
- list
类型是可以的:
- list
观察以下代码段:
- >>>list1=[1]>>> id(list1)42695136
- >>>list1=[1,2]>>> id(list1)42717432
两次输出的名字
引用对象的地址不一样,这是因为第二次语句
- list1
对名字做了重绑定:
- list 1 = [1, 2]
当两个或两个以上的名字引用同一个对象时,我们称这些名字共享对象。共享的对象可变性不同时,表现会出现差异。
函数
将参数
- attempt_change_immutable
的值修改为
- i
- 2
- defattempt_change_immutable(i):
- i= 2i= 1
- print(i)
- attempt_change_immutable(i)print(i)
Output:
- 1
- 1
如果你对输出不感到意外,说明不是新手了 ^_^。
与全局名字
- i
不是在同一命名空间中,所以它们之间不相互影响。
- i
都指向了同一个整数对象。
- i
的值为
- i
, 因为整数对象不可变,所以新建值为
- 2
的整数对象,并把函数中的名字
- 2
绑定到对象上。
- i
的绑定关系并没有被改变。
- i
值得注意的是,这部分内容与命名空间和作用域有关系,另外有文章介绍它们,可以参考。
函数
为列表增加字符串。
- attempt_change_mutable
- defattempt_change_mutable(list_param):
- list_param.append('test')
- list1=[1]print(list1)
- attempt_change_mutable(list1)print(list1)
output:
- [1]
- [1,'test']
可以看到函数成功改变了列表
的值。传递参数时,名字
- list1
引用了与名字
- list_param
相同的对象,这个对象是可变的,在函数中成功修改了对象的值。
- list1
首先,名字
与名字
- list_param
指向对象:
- list1
然后,通过名字
修改了对象的值:
- list_param
最后,这个修改对名字
可见。
- list1
总的来说,触发名字对象绑定的行为有以下一些:
- a = 1
将名字
- def test():
- pass
绑定到函数对象
- test
将名字
- class Test(object):
- pass
绑定到类对象
- Test
将名字
- def test(i):
- pass
- test(1)
绑定到整数对象
- i
- 1
语句:
- import
将名字
- import sys
绑定到指定模块对象。
- sys
循环
- for
每次循环都会绑定 / 重绑定名字
- for i in range(10):
- pass
- i
操作符
- as
- with open('dir', 'r') as f:
- pass
- try:
- pass
- except NameError as ne:
- pass
语句,异常捕获语句中的
- with open
都会发生名字的绑定
- as
待续。。。
[1] 在特定的控制条件下,改变对象的类型是可能的。但不是一种明智的做法,如果处理不当的话,会发生一些奇怪的行为。
来源: http://www.cnblogs.com/crazyrunning/p/6909733.html