Python进阶 - 对象,名字以及绑定
Python进阶 - 对象,名字以及绑定
写在前面
如非特别说明,下文均基于
Python3
1、一切皆对象
Python哲学:
Python中一切皆对象
1.1 数据模型-对象,值以及类型
对象是Python对数据的抽象。Python程序中所有的数据都是对象或对象之间的关系表示的。(在某种意义上,为顺应冯·诺依曼“存储式计算机”的模型,Python中的代码也是对象。)
Python中每一个对象都有一个身份标识,一个值以及一个类型。对象创建后,其身份标识绝对不会改变;可以把身份标识当做对象在内存中的地址。is操作符比较两个对象的身份标识;id()函数返回表示对象身份标识的整数。
CPython实现细节: 在CPython解释器的实现中,id(x)函数返回存储x的内存地址
对象的类型决定了对象支持的操作(例如,对象有长度么?),同时也决定了该类型对象可能的值。type()函数返回对象的类型(这个类型本身也是一个对象)。与其身份标识一样,对象的类型也是不可改变的[1]。
一些对象的值可以改变。可改变值的对象也称作可变的(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版本。
1.2 对象小结
官方文档已经对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'
import sys
class Person(object):
''' Person class'''
def __init__(self, name, age):
self.name = name
self.age = age
def tset():
print(sys.path)
p = Person('Richard', 20)
print(p.name, ':', p.age)
def main():
tset()
if __name__ == '__main__':
main()
这段Python代码中有很多对象,包括hello这个模块对象,创建的Person类对象,几个函数如test, main函数对象,数字,字符串,甚至代码本身也是对象。
2、名字即“变量”
几乎所有语言中都有“变量”的说法,严格说来,Python中的变量不应该叫变量,称为名字更加贴切。
以下翻译自 Code Like a Pythonista: Idiomatic Python # Python has "names"
2.1 其他语言有变量
其他语言中,为变量分配值就像将值放到“盒子”里。
int a = 1;

盒子a现在有了一个整数1。
为同一个变量分配值替换掉盒子的内容:
a =2;

现在盒子a中放了整数2
将一个变量分配给另一个变量,拷贝变量的值,并把它放到新的盒子里:
int b = a;


b是第二个盒子,装有整数2的拷贝。盒子a有一份单独的拷贝。
2.2 Python有名字
Python中,名字或者标识符就像将一个标签捆绑到对象上一样。
a = 1

这里,整数对象1有一个叫做a的标签。
如果重新给a分配值,只是简单的将标签移动到另一个对象:
a = 2


现在名字a贴到了整数对象2上面。原来的整数对象1不再拥有标签a,或许它还存在,但是不能通过标签a访问它了(当对象没有任何引用时,会被回收。)
如果将一个名字分配给另一名字,只是将另一个名字标签捆绑到存在的对象上:
b = a

名字b只是绑定到与a引用的相同对象上的第二个标签而已。
虽然在Python中普遍使用“变量”(因为“变量”是普遍术语),真正的意思是名字或者标识符。Python中的变量是值得标签,不是装值得盒子。
2.3 指针?引用?名字?
C/C++中有指针,Java中有引用,Python中的名字在一定程度上等同于指针和引用。
2.1节中其他语言的例子,也只是针对于它们的基本类型而言的,若是指针或者引用,表现也跟Python的名字一样。这也在一定程度上说明了Python将面向对象贯彻得更加彻底。
2.4 名字支持的操作
可以对一个变量做什么?声明变量,使用变量,修改变量的值。名字作为Python中的一个重要概念,可以对它做的操作有:
- 定义;名字需要先定义才能使用,与变量需要先声明一样。
- 绑定:名字的单独存在没有意义,必须将它绑定到一个对象上。
- 重绑定:名字可以重新引用另一个对象,这个操作就是重绑定。
- 引用:为什么要定义名字,目的是使用它。
3、绑定的艺术
名字以及对象,它们之间必然会发生些什么。
3.1 变量的声明
其他如C/C++和Java的高级语言,变量在使用前需要声明,或者说定义。以下在Java中声明变量:
public static void main(String[] args) {
int i = 0; // 先声明,后使用
System.out.println(i); // 使用变量i
}
这样,在可以访问到变量i所在作用域的地方,既可以使用i了。还有其他声明变量的方法么?好像没有了。
3.2 名字的定义
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,并把它指向上述对象:

上述过程就是通过赋值语句的名字对象绑定了。名字首次和对象绑定后,这个名字就定义在当前命名空间了,以后,在能访问到这个命名空间的作用域中可以引用该名字了。
3.3 引用不可变对象
定义完名字之后,就可以使用名字了,名字的使用称为“引用名字”。当名字指向可变对象和不可变对象时,使用名字会有不同的表现。
a = 9 #1
a = 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
3.4 引用可变对象
3.4.1 示例1:改变可变对象的值
可变对象可以改变其值,并且不会造成地址的改变:
>>> 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引用的对象地址的改变。
3.4.2 示例2:可变对象循环引用
再来看一个比较“奇怪”的例子:
values = [1, 2, 3]
values[1] = values
print(values)
一眼望去,期待的结果应该是
[1, [1, 2, 3], 3]
但实际上结果是:
[1, [...], 3]
我们知道list中的元素可以是各种类型的,list类型是可以的:

3.4.3 示例3:重绑定可变对象
观察以下代码段:
>>> list1 = [1]
>>> id(list1)
42695136
>>> list1 = [1, 2]
>>> id(list1)
42717432
两次输出的名字list1引用对象的地址不一样,这是因为第二次语句list 1 = [1, 2] 对名字做了重绑定:

3.5 共享对象
当两个或两个以上的名字引用同一个对象时,我们称这些名字共享对象。共享的对象可变性不同时,表现会出现差异。
3.5.1 共享不可变对象
函数attempt_change_immutable将参数i的值修改为2
def attempt_change_immutable(i):
i = 2
i = 1
print(i)
attempt_change_immutable(i)
print(i)
Output:
1
1
如果你对输出不感到意外,说明不是新手了 _。
- 首先,函数的参数
i与全局名字i不是在同一命名空间中,所以它们之间不相互影响。 - 调用函数时,将两个名字
i都指向了同一个整数对象。 - 函数中修改
i的值为2, 因为整数对象不可变,所以新建值为2的整数对象,并把函数中的名字i绑定到对象上。 - 全局名字
i的绑定关系并没有被改变。


值得注意的是,这部分内容与命名空间和作用域有关系,另外有文章介绍它们,可以参考。
3.5.2 共享可变对象
函数attempt_change_mutable为列表增加字符串。
def attempt_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可见。
3.6 绑定何时发生
总的来说,触发名字对象绑定的行为有以下一些:
赋值操作;
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都会发生名字的绑定
4、其他说明
待续。。。
参考
- The Python Language References#Data model# Objects, values, types
- Python的名字绑定
- Python一切皆对象
- Code Like a Pythonista: Idiomatic Python
- python基础(5):深入理解 python 中的赋值、引用、拷贝、作用域
脚注
[1] 在特定的控制条件下,改变对象的类型是可能的。但不是一种明智的做法,如果处理不当的话,会发生一些奇怪的行为。
Python进阶 - 对象,名字以及绑定的更多相关文章
- Python进阶:对象复制与比较,分深浅,见真假
"==" 与 is python 为 10 开辟内存空间, a与b同时指向这块内存,即a与b的值相等,a与b的id也相等.因此 a==b 与 a is b 都返回True: a = ...
- Python进阶 - 命名空间与作用域
Python进阶 - 命名空间与作用域 写在前面 如非特别说明,下文均基于Python3 命名空间与作用于跟名字的绑定相关性很大,可以结合另一篇介绍Python名字.对象及其绑定的文章. 1. 命名空 ...
- 你相信吗??Python把数字也当做对象!@@@对象,名称绑定,引用计数
本文学习自:http://blog.csdn.net/yockie/article/details/8474408 1.对象 Python中, 万物皆对象,包括12345等int常量.不信吗??用di ...
- Python进阶:自定义对象实现切片功能
2018-12-31 更新声明:切片系列文章本是分三篇写成,现已合并成一篇.合并后,修正了一些严重的错误(如自定义序列切片的部分),还对行文结构与章节衔接做了大量改动.原系列的单篇就不删除了,毕竟也是 ...
- Python - 面对对象(进阶)
目录 Python - 面对对象(进阶) 类的成员 一. 字段 二. 方法 三. 属性 类的修饰符 类的特殊成员 Python - 面对对象(进阶) 类的成员 一. 字段 字段包括:普通字段和静态字段 ...
- Python进阶(三)----函数名,作用域,名称空间,f-string,可迭代对象,迭代器
Python进阶(三)----函数名,作用域,名称空间,f-string,可迭代对象,迭代器 一丶关键字:global,nonlocal global 声明全局变量: 1. 可以在局部作用域声明一 ...
- Python进阶-继承中的MRO与super
Python进阶-继承中的MRO与super 写在前面 如非特别说明,下文均基于Python3 摘要 本文讲述Python继承关系中如何通过super()调用"父类"方法,supe ...
- Python进阶之面向对象编程
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. 面向过程的程序设计把计算机 ...
- python进阶篇
python进阶篇 import 导入模块 sys.path:获取指定模块搜索路径的字符串集合,可以将写好的模块放在得到的某个路径下,就可以在程序中import时正确找到. import sys ...
随机推荐
- win10 平台 elasticsearch 与 elasticsearch-head 的安装
由于elasticsearch是基于java开发的,所以 第一步需要安装JDK. 具体JDK的安装步骤 http://jingyan.baidu.com/article/6dad5075d1dc40 ...
- Centos7多网卡绑定操作,通过nmcli命令操作。
运行 ip link 命令查看系统中可用的接口1.创建bond网卡nmcli con add type team con-name team0 ifname team0 config '{" ...
- Webpack模块加载器
一.介绍 Webpack是德国开发者 Tobias Koppers 开发的模块加载器,它能把所有的资源文件(JS.JSX.CSS.CoffeeScript.Less.Sass.Image等)都作为模块 ...
- 在eclipse中使用Maven建web工程项目
在eclipse中使用Maven建web工程项目: 第一种方式: 右键新建maven工程,勾选创建一个简单工程 填入信息,注意打包方式要改为war 点击完成,创建完的工程目录如下: 项目中没有WEB- ...
- Centos7完全分布式搭建Hadoop2.7.3
(一)软件准备 1,hadoop-2.7.3.tar.gz(包) 2,三台机器装有cetos7的机子 (二)安装步骤 1,给每台机子配相同的用户 进入root : su root ---------& ...
- memory库函数的实现
下面主要对常用的几个memory库函数的实现(memcpy.memmove.memset.memcmp): memcpy函数与memmove函数: 相同点: 两者实现的功能均为从src拷贝count个 ...
- CPP--借助神器VS理解内存存储
之前也有想了解这些,第一个不是学底层的不知道从何理解,第二个上网搜概念,大牛们三言两语就结束了,举得例子也比较复杂,对于非C方向的可能有点吃力,所以一直没理解. 今天偶然发现原来还要内存窗口之说,就慢 ...
- angular 实现自定义样式下拉菜单
自己闲着没事写了一个自定义的下拉菜单希望和大家交流一下!望能和大神们成为朋友. 下面上代码: <!doctype html> <html lang="en" ng ...
- 《Effective C#》读书笔记-1.C# 语言习惯-1.使用属性而不是可访问的数据成员
思维导图: 大纲: 1.使用属性而不是可访问的数据成员 属性 指定不同的访问权限 隐式属性降低了声明属性的工作量 允许将数据成员作为公共接口的一部分暴露 ...
- Hadoop - 操作练习之单机配置 - Hadoop2.8.0/Ubuntu16.04
系统版本 anliven@Ubuntu1604:~$ uname -a Linux Ubuntu1604 4.8.0-36-generic #36~16.04.1-Ubuntu SMP Sun Feb ...