对象(object)

一切皆对象。

在Python中,包括数据和处理数据的函数,一切实体都是对象。在一个程序的运行过程中,不断地动态创建对象,然后通过对象之间的相互作用修改、销毁已存在的对象或生成新的对象。这些对象之间或通过归属相互联系,或通过接口相互作用。Python对象的层次结构

Python通过创建对象和对象间的相互作用来完成特性的任务。因此,我们需要在程序中访问对象并通过指令操控对象间的相互作用。那么我们就需要一种方式去引用创建在内存中的Python对象。方式就是为对象命名,通过名字引用对象。名字是最基本的表达式之一,通过对名字求值返回其引用的对象,实现对对象的访问。通过名字引用对象

名字(name)

在一个程序中,要保持并访问某个的对象,需要为对象命名,并通过名字访问对象。所谓命名,就是把对象和某个名字进行绑定(binding),绑定确定了名字与实体的对应。

如果在程序中创建一个对象,但是没有为其命名,那么会导致这个对象无法被使用,因为没有任何方式可以访问它。一个无法引用的对象没有存在的意义,所以Python解释器会自动销毁这个对象。可以理解为这个对象的生命周期是一种“方生方死”的过程。

下面通过几个例子说明对象和名字的绑定。

# 创建字符串对象

name = 'Bob Smith'

# 创建函数对象

def add(a, b):

return a + b

# 创建类对象

class Point(object):

def __init__(self, x, y):

self.x = x

self.y = y

def __repr__(self):

return "{}({}, {})".format(self.__class__.__name__, self.x, self.y)

# 创建函数对象

def dist_from_zero(self):

return (self.x ** 2 + self.y ** 2) ** 0.5

# 创建实例对象

a = Point(3, 4)

# 创建模块对象

import math

上面这些例子中,创建了不同类型的对象,并分别为每个对象绑定不同的名字。

其中,赋值语句(assignment statement)是名字绑定的最简单的情况。如,name = 'Bob Smith',其中“=”为赋值操作符(assignment operator),“=”右边的表达式返回一个对象,并与“=”左边的名字进行绑定。

本质上,定义函数、定义类、导入模块等操作和简单的赋值语句没有任何区别,它们做的事情都是创建对象,并和一个名字进行绑定:def 语句创建一个函数对象,并将其与名字 add 进行绑定。

class 语句创建一个类对象,并与名字Point绑定。

import 语句创建一个模块对象,并于模块文件的名字math绑定。

但名字不是引用对象的唯一方式,更一般的术语称为对象引用(object reference)。名字是对象引用的一种。

mobile = ["apple", "huawei", "xiaomi"]

在这个例子中,创建了一个列表对象,并用名字mobile引用它。但是列表包含的3个字符串并没有名字,但却可通过 mobile[0], mobile[1], mobile[2] 分别去引用它们,"apple"虽然没有名字,但mobile[0]是它的对象引用。可以认为对象引用是广义的名字。

a = 1

b = [a, 2]

一个对象可以有多个对象引用。上面的例子中,可以通过a去访问对象1,也可以通过b[0]去访问。

但是这却要求顶层的容器(collection)对象必须有名字。所以,归根到底还是名字的问题。

作为支持动态类型的面向对象编程语言,理解名字是更好地理解Python的基础。事实上,在许多Python文档和教程中,经常使用“变量(variable)”而不是“名字(name)”,个人认为在Python使用名字以及命名空间的概念,更符合面向对象的思想,也更符合Python特性。

个人的理解是,无论变量还是名字,都只是一个标识符(identifier),当我们强调“名”的时候,对名字进行求值,参与一些处理或计算,那么这个标识符类似代数中的变量,符号参与了运算,且一个名字可以与不同的对象绑定,那么使用变量是合乎逻辑的。但是在底层,仅有的逻辑就是创建对象并给对象命名,事实上,名字(或对象引用)可认为是对象的一种属性。

命名空间(namespace)

命名空间,或名字空间,记录了名字和对象之间的绑定关系,命名空间能够将名字映射到对象。赋值语句(包括函数和类的定义等)用来创建对象并为对象命名,Python解释器使用命名空间来维护名字与对象的绑定关系。

但是在程序运行的特定时刻,并非所有的名字都位于同一个命名空间中,事实上,名字分散在相互独立的不同命名空间中。一个名字到底属于哪个命名空间,是由名字定义的位置决定的;对名字求值,会根据特定的顺序去查找各个命名空间。

全局命名空间(global namespace)

当运行一个Python文件时,会创建一个全局命名空间,其中包含了该模块文件中创建的所有名字。也就是说,在模块文件顶层定义的名字属于全局命名空间。

如下例所示:

# file: test1.py

a = 3

b = 4

c = a + b

c = "haha"

程序的执行分为如下步骤:执行模块文件test1.py时,解释器首先创建一个空的全局命名空间,执行第一条语句会创建一个整型对象3,并与名字a绑定,名字a被加入到全局命名空间中。接着又创建一个整型对象4,并于名字b绑定,名字b也被加入到全局命名空间中。

随后python执行赋值语句c = a + b 时,会先对表达式a + b求值,这是一个复合表达式,python会先对名字a和b求值,这时python会到全局命名空间中查找名字a和b并返回与它们对应的对象,即3和4。然后3 + 4创建了一个新的整型对象7,并与名字c进行绑定,全局命名空间再次更新,此时其中包含a, b, c三个名字。

接着,创建了一个字符串对象"haha",并与名字c进行绑定。这时全局命名空间中仍然只有三个名字,但是名字 c 已经和对象7的绑定解除,转而和对象“haha”绑定在一起。对象7再也无法访问,将被解释器销毁。这种情况属于命名冲突,如果这不是有意为之,那么就要注意命名冲突的问题。

通过上面这个例子,可以看到两个事实:随着程序的进行,名字的定义会动态更新命名空间。

对名字求值,就是到命名空间中查找并返回与之绑定的对象。

内置命名空间(Built-in namespace)

在Python中有一个天然存在的命名空间,称为内置命名空间,这个命名空间在Python解释器启动的时候自动创建,其中包含了内置对象的名字,包括内置类和内置函数,如int, list和print, len等。内置命名空间无法修改。

局部命名空间(local namespace)

除了全局命名空间和内置命名空间外,当在程序调用函数的时候,会创建属于这个函数的局部命名空间。局部命名空间中包含该函数的函数体中定义的名字,当函数返回,属于该函数的局部命名空间被销毁。

下面的例子将说明内置、全局和局部命名空间的工作方式。

# file: test2.py

a = 1

def foo():

a = 2

print(a)

foo()

print(a)

------------------

result:

2

1

程序的执行分为如下步骤:当执行test2.py时,首先python解释器在启动时创建内置命名空间,接着为模块文件test2.py创建一个空的全局命名空间。

然后开始执行test2.py中的语句,整型对象1被创建,a被加入全局命名空间中;其次创建函数对象和名字foo,foo被加入全局命名空间。此时只存在全局和内置两个命名空间,因为Python只有调用函数的时候,才会执行函数体中的代码。

当执行foo()时,Python解释器会对名字foo进行求值,根据作用域规则,解释器会先在全局命名空间中查找foo,如果找不到才会去内置命名空间中查找。结果会在全局命名空间中找到foo并返回其引用的函数对象,随后调用这个函数对象。

这时Python解释器会创建一个空的局部命名空间,然后跳转到函数内部执行函数体中的语句。a = 2 创建对象2并命名为a, 名字a加入到该函数的局部命名空间中;执行print(a)时,首先对a求值,这时函数的局部命名空间和全局命名空间中都有名字a,python会优先在局部命名空间中进行查找,其次是全局命名空间,最后是内置命名空间,因此对a求值返回2,然后再对名字print求值,还是按照局部命名空间,全局命名空间,内置命名空间的顺序,最终会在内置命名空间找到该名字,返回print对应的内置函数对象,以整型对象2为参数,将器输出到标准输出流中。

函数foo执行完所有的内部语句返回,函数调用的流程结束,属于该函数的局部命名空间被销毁,并跳转回主程序。

最后执行print(a)时,只存在全局和内置两个命名空间,结果分别在全局和内置命名空间中查找到a和print的值。最终打印输出1。

在一个Python程序运行的某个时刻,可以同时存在多个命名空间,各个命名空间之间相互独立。这就允许不同的命名空间中拥有相同的名字,当时使用这个名字时,Python解释器以特定的顺序到各个命名空间中查找这个并返回对应的对象。

核心问题只有两个:在程序中创建的名字属于哪个命名空间?

对一个名字求值时,去哪个命名空间中去找?

一个名字到底属于哪个命名空间,是由名字定义的位置决定的。内置的名字位于内置命名空间中。

模块文件顶层定义的名字位于该模块的全局命名空间中。

函数中定义的名字位于该函数的局部命名空间中。

对一个名求值时,会按照一定顺序搜索存在的命名空间,在test1.py和test2.py的例子中,已经看到了这一点。现在更详细的说明这个问题。

首先,可以设想一下只有一个扁平的命名空间,而不是同时存在多个独立的命名空间的情况。当所有的名字都在一个命名空间中,看起来事情会变得更简单,因为定义的所有名字都添加到这个命名空间中,而且对名字进行求值时也只需要在这一个命名空间查找名字即可。但代价是为了维持对象,就必须为不同的对象取不同的名字,否则将发生命名冲突。这会让编程变得如履薄冰,难以阅读,简直是一种灾难。

就像一本数学著作会反复使用a, b, c, x, y, z等符号,而不是只允许它们使用一次来特指某个特定的量。重复使用这些符号根本不会产生问题,因为读者完全可以根据上下文理解它们代表什么。比如,在介绍勾股定理时,作者会声明a, b, c分别为直角三角形的两个直角边和一个斜边的长度,然后给出

的结论。在当前的语境下,我们会很清楚 c 指的就是三角形的斜边长度,而不是其他地方介绍的圆的周长。

这个思想很简单,Python也正是这么做的。查询命名空间的顺序,遵循了当前语境优先的准则。当执行模块中语句时,对一个名字求值,优先查找当前模块的全局命名空间,如果没有找到就去查找内置命名空间,内置命名空间充当了一个大的背景。

当执行函数中的语句时,对一个名字求值,优先查找当前函数的局部命名空间,如果没有找到就去查找当前模块的全局命名空间,最后是内置命名空间。命名空间的查找顺序

外层命名空间(enclosing namespace)

在一个函数内部定义的另一个函数,称为嵌套函数(nested function),为了阐述方便,我们可把嵌套函数称为内函数,定义嵌套函数的函数称为外函数。当在程序中调用外函数时,解释器会创建一个属于外函数的命名空间,当在外函数内部定义一个内函数,并随后在外函数的函数体内调用这个内函数时,解释器又为内函数创建一个命名空间。在内函数的函数体工作时,内函数的命名空间就是局部命名空间,外函数的命名空间称为了一种背景,被称为外层命名空间,在外层命名空间之外是全局命名空间和内置命名空间。

嵌套函数内部,还可以定义并调用另一个嵌套函数。如下图所示:多层函数嵌套

函数连续的嵌套3层,比如在模块顶层定义并调用函数f1,f1内创建并调用f2,f2内创建并调用f3,f3内创建并调用f4,会创建4个分别属于f1、f2、f3、f4的局部命名空间:L1、L2、L3、L4。当工作在模块中时,L1、L2、L3、L4都不存在,名字求值,会依次查询:G、B。

当工作在f1中时,L2、L3、L4都不存在,名字求值,会依次查询:L1、G、B。

当工作在f2中时,L3、L4都不存在,名字求值,会依次查询:L2、L1、G、B,外层命名空间为:L1。

当工作在f3中时,L4不存在,名字求值,会依次查询:L3、L2、L1、G、B,外层命名空间为:L2、L1。

当工作在f3中时,名字求值,会依次查询:L4、L3、L2、L1、G、B,外层命名空间为:L3、L2、L1。

下面举例说明:

# file: test3.py

a = 1

def outer():

a = 2

def inner():

print(a)

inner()

outer()

print(a)

--------------------

result:

2

1

执行test3.py文件,程序的执行分为如下步骤:

1、在执行outer()之前,命名空间状态:B:

G: a, outer

2、执行outer(),进入outer的函数体内,在执行a = 2 之前,命名空间状态:B:

G: a, outer

L(outer):

3、在outer内,执行inner()之前,命名空间状态:B:

G: a, outer

L(outer): a, inner

4、执行inner(),进入inner的函数体内,在执行print(a)之前,命名空间状态:B:

G: a, outer

L(outer): a, inner

L(inner):

5、执行inner内的print(a),按照L(inner)、L(outer)、G和B从内外到外的顺序查找a,在L(outer)中找到a并返回其引用的对象2。print(a)规约为print(2),按照同样的顺序查找print,在B中找到,并返回其引用的函数对象,以2为参数调用该函数对象,打印2。

6、inner()执行结束,返回到outer函数内,命名空间状态:B:

G: a, outer

L(outer): a, inner

7、outer()执行结束,并返回主程序内,命名空间状态:B:

G: a, outer

8、接着执行print(a),按照相同的从内到外的规则,不难知道,最终的结果是打印1,程序结束。

作用域(scope)

传统的定义:变量的作用域就是程序中能够访问这个变量的区域。

在Python中,作用域和命名空间的概念容易混淆和重叠,我们已经清楚了命名空间和名字求值的所有细节。可以完全抛弃作用域的概念。

总结起来就是一张图:Python作用域

内部可以访问外部名字,外部访问不小内部的名字。

python中不论类的名字是什么意思_Python中的名实关系——名字、命名空间、作用域...相关推荐

  1. 编写python程序、创建名为class的数据库_Python中的元类(metaclass)以及元类实现单例模式...

    一.理解类也是对象 在理解元类之前,你需要先掌握Python中的类.Python中类的概念借鉴于Smalltalk,这显得有些奇特.在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段.在P ...

  2. python类定义中、对象字符串的特殊方法是_python中自定义类对象json字符串化的方法_python json转字符串、...

    python中自定义类对象json字符串化的方法 1. 用 json 或者simplejson 就可以 2.定义转换函数: def convert_to_builtin_type(obj): prin ...

  3. 在python中、对于函数定义代码的理解_python中如何理解装饰器代码?

    长文预警,[最浅显易懂的装饰器讲解] 能不能专业地复制题目?配上代码,问题分段. 我来给提主配上问题的代码. 正式回答: 1:如何理解return一个函数,它与return一个值得用法区别在哪? 敲黑 ...

  4. python中与label类似的控件是_python中tkinter的使用(控件整理)(一)

    1.使用tkinter.Tk() 生成主窗口(window=tkinter.Tk()): window.title('标题名')修改框体的名字,也可在创建时使用className参数来命名: wind ...

  5. python函数def里面嵌套def_python 函数嵌套函数_Python中的嵌套函数

    python 函数嵌套函数 A nested function is simply a function within another function, and is sometimes calle ...

  6. python中所有的异常类都是谁的子类_Python中所有的异常类都是____的子类。

    [简答题]假设成年人的体重和身高存在此种关系: 身高(厘米)-100 =标准体重(千克) 如果一个人的体重与其标准体重的差值在正负5%之间,显示"体重正常",其他则显示" ...

  7. python中split的用法取第二个分片_python中split()函数的用法

    函数:split() Python中有split()和os.path.split()两个函数,具体作用如下: split():拆分字符串.通过指定分隔符对字符串进行切片,并返回分割后的字符串列表(li ...

  8. python调参工作都是干啥的_Python中Gradient Boosting Machine(GBM)调参方法详解

    1.前言 如果一直以来你只把GBM当作黑匣子,只知调用却不明就里,是时候来打开这个黑匣子一探究竟了! 这篇文章是受Owen Zhang (DataRobot的首席产品官,在Kaggle比赛中位列第三) ...

  9. python中的可变数据类型有列表和元组_Python中列表的 += 和 .extend() 的异同

    一道Python题 最近有朋友"考"了我一个Python的题:使用+=和.extend()两种方法扩展元组中的列表会发生什么.虽然我对Python中的可变数据类型.不可变数据类型的 ...

最新文章

  1. 机器学习模型调优总结!
  2. TableView的集合
  3. CODEVS-3303-翻转区间
  4. 在Windows中安装OpenCV
  5. 苹果黑色星期五大促销活动全球上线
  6. 输入一个三位整数,求出该数字各个位的数字之和
  7. g6-editor 使用
  8. 三刺激值计算公式_超实用的数控车床常用计算公式,绝对帅,赶紧收藏了!
  9. JAVA可以赋值分数吗_Java数据类型知多少?
  10. WordPress 实用SEO插件总结
  11. C语言程序设计(第三版)何钦铭著 习题6-3
  12. php 字符串长度函数
  13. linux下用c语言写吃金豆,吃金豆pacmanTC版
  14. MLP-Mixer简介和一些想法
  15. MAML代码及理论的深度学习 PyTorch二阶导数计算
  16. cocosCreator 全局变量(Ts版)
  17. 算法-查找(红黑树)
  18. 如何部署简单腾讯云服务器
  19. 汇编——十进制数据输入转二进制/十六进制输出(含数字判断,不限位数)
  20. 常州网站服务器_常州云主机

热门文章

  1. 【翻译】Focal Loss for Dense Object Detection(RetinaNet)
  2. 微信老客户维护的方法有哪些?
  3. windows10电脑无法连接到internet怎么解决
  4. 个人微信号发送zabbix告警信息
  5. ubuntu下搭建https web服务器
  6. 冬天是个冻手冻脚的流氓
  7. 来日本做码农如何呢(来听听东京大学计算机前辈的回答)
  8. ES集群搭建主机规划
  9. 如何上好一节中职计算机专业课,如何上好中职计算机专业课论文
  10. IXDC 2018 | 互联网金融已入深水区 如何构建新体验