跟着官网学Python(9):命名空间作用域和类

“要想考高分,基础概念最重要,命名空间与作用域,名称与对象,自定义对象类。”

01 面临问题

继续跟着官网学Python,第9章类。
(Class)是很多面向对象编程语言中的一个关键概念,实现信息封装的基础,类是一种用户定义的引用数据类型,每个类包含数据说明和一组操作数据或传递消息的函数,类的实例称为对象。
Python中的类和其他语言类似,那么一起看看官网。
发现放一起内容有点长,拆分为两部分吧,多思考多分享点自己的理解。

02 怎么办

名称和对象

以前有个概念说Python万物皆对象,任何东西,如数字1也有相应的属性和方法。
那么关于对象、名称的一些概念需要再理一理。
对象具有个性(原词individuality),多个名称(在多个作用域内)可以绑定到同一个对象,这在其他语言中称为别名
这里的名称应该可以理解为对象的名称,姓名,可以存着多个名字,简单理解就是经常接触的变量名,如a,b

20200621183808
这里的a is c应该标明ac都是对象1的名称,别名。那么为何bd都是10000但是不是同一对象呢?好像Python只将部分常用值这样操作,节省内存,不用重新分配对象1的存储空间
在处理不可变的基本类型(数字,字符串,元组)时可以安全地忽略它。
但是,别名对涉及可变对象,如列表,字典和大多数其他类型时,Python代码的语义可能会产生惊人的影响


这通常是有好处的,因为别名在某些方面表现得像指针。例如,传递一个对象很容易,因为实现只传递一个指针;如果函数修改了作为参数传递的对象,调用者将看到更改。
意思是我fun(a)里面修改了我传入的对象a,那么外面的a也会被修改
20200621184845
可以发现函数执行后,外面的可变对象a发生了改变,哪怕函数没有返回这个对象。

Python 作用域和命名空间

还需要了解Python的作用域规则,类定义对命名空间有一些巧妙的技巧,需要知道作用域和命名空间如何工作才能完全理解正在发生的事情。
顺便说一下,关于这个主题的知识对任何高级Python程序员(说的是我吗?)都很有用。

namespace(命名空间)是一个从名字到对象的映射。 这里的名字可以理解为变量。
大部分命名空间当前都由 Python 字典实现,但一般情况下基本不会去关注它们(除了要面对性能问题时),而且也有可能在将来更改。
一般有三种命名空间:

  • 内置命名空间(build-in namespace) 保存Python语言内置的名称,比如函数名 abschar 和异常名称 BaseExceptionException 等等。
  • 全局命名空间(global namespace) 模块(其实就是一个py文件)中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
  • 局部命名空间(local namespace) 函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量,包含类的定义。

20200621195046
假设我们要使用变量 runoob,则 Python 的查找顺序为:局部命名空间去 -> 全局命名空间 -> 内置命名空间

如果找不到变量 runoob,它将放弃查找并引发一个 NameError 异常。
新开一个Python解释器,通过dir()函数可以大概感受下命名空间中的名称

20200621195625

dir()探索命名空间

可以发现dir()显示的名称不多,可以进一步查看内置命名空间,发现很多属性的名称,如max等。

内置函数locals()globals()分别返回当前的局部命名空间和全局命名空间

20200621200405

命名空间查看

这里很明显可以发现命名空间是字典,是名称到对象的映射,比如开始dir()打印的名称In,直接输入返回对于的对象,一个列表

20200621200848

显示命名空间中具体名称

从某种意义上说,对象的属性集合也是一种命名空间的形式。 其实dir(obj)函数的作用就是返回对象obj的属性列表。


关于命名空间的重要一点是,不同命名空间中的名称之间绝对没有关系
如,两个不同的模块都可以定义一个maximize函数而不会产生混淆(使用前必须加模块名称,如果同时import *是否只会用后面的那个)。
还记得之前分享跟着官网学Python(7):学会用模块(轮子)中提到模块两种导入方式from module import xx 和 import module

  • 使用import module时,module本身被引入到当前模块的命名空间,需要使用module.name这种方式访问它内部的的函数和变量。
  • from module import xx将其它模块的函数或者变量xx引到当前的命名空间中,可以直接使用xx


顺便说明一下,把任何跟在一个点号之后的名称都称为属性,如在表达式z.real中,real是对象 z 的一个属性。
按严格的说法,对模块中名称的引用属于属性引用:在表达式 modname.funcname 中,modname 是一个模块对象而 funcname 是它的一个属性。
在此情况下在模块的属性和模块中定义的全局名称之间正好存在一个直观的映射:它们共享相同的命名空间

说实话,有点不太理解这句话,大概明确了命名空间就是字典,或者说映射的集合,每个key-value,表示名称和对象的对应关系。


属性可以是只读或者可写的,对可写对属性的赋值是可行的。
模块属性是可写的,modname.the_answer = 42 。可写的属性同样可以用 del 语句删除, del modname.the_answer 将会从名为 modname 的对象中移除 the_answer 属性。

不同时刻创建的命名空间拥有不同的生存期

  • 包含内置名称的命名空间是在 Python 解释器启动时创建的,永远不会被删除
  • 模块的全局命名空间在模块定义被读入时创建;通常,模块命名空间也会持续到解释器退出。
    被解释器的顶层调用执行的语句,从一个脚本文件读取或交互式地读取,被认为是 __main__ 模块调用的一部分,因此它们拥有自己的全局命名空间。(内置名称实际上也存在于一个模块中;这个模块称作builtins 。)
  • 一个函数的本地命名空间在这个函数被调用时创建,并在函数返回或抛出一个不在函数内部处理的错误时被删除。(事实上,比起描述到底发生了什么,忘掉它更好。)
    当然,每次递归调用都会有它自己的本地命名空间。


作用域(scope)是一个命名空间可直接访问的Python程序的文本区域
这里的 “可直接访问” 意味着对名称的非限定引用会尝试在命名空间中查找名称。

作用域被静态确定,但被动态使用
在程序运行的任何时间,至少有三个命名空间可被直接访问的嵌套作用域:

  • 最先搜索的最内部作用域包含局部名称
    从最近的封闭作用域开始搜索的任何封闭函数的作用域包含非局部名称,也包括非全局名称
  • 倒数第二个作用域包含当前模块的全局名称
  • 最外面的作用域(最后搜索)是包含内置名称的命名空间

比如在一个函数你写abs(),那么优先找本地,最后才是内置名称的命名空间,这也是保留关键字的原因吧,一不小心重写了内置函数

如果一个名称被声明为全局变量,则所有引用和赋值将直接指向包含该模块的全局名称的中间作用域。
要重新绑定在最内层作用域以外找到的变量,可以使用 nonlocal 语句声明为非本地变量。
如果没有被声明为非本地变量,这些变量将是只读的(尝试写入这样的变量只会在最内层作用域中创建一个 新的局部变量,而同名的外部变量保持不变)。

通常,当前局部作为域将(按字面文本)引用当前函数的局部名称。 在函数以外,局部作用域将引用与全局作用域相一致的命名空间:模块的命名空间。
类定义将在局部命名空间内再放置另一个命名空间。

重要的是应该意识到作用域是按字面文本来确定的:
在一个模块内定义的函数的全局作用域就是该模块的命名空间,无论该函数从什么地方或以什么别名被调用。
另一方面,实际的名称搜索是在运行时动态完成的 --- 但是,Python 正在朝着“编译时静态名称解析”的方向发展,因此不要过于依赖动态名称解析! (事实上,局部变量已经是被静态确定了。)

Python 的一个特殊规定是这样的 -- 如果不存在生效的 global 或 nonlocal 语句 -- 则对名称的赋值总是会进入最内层作用域。 赋值不会复制数据 --- 它们只是将名称绑定到对象。 删除也是如此:语句 del x 会从局部作用域所引用的命名空间中移除对 x 的绑定。 事实上,所有引入新名称的操作都是使用局部作用域:特别地,import 语句和函数定义会在局部作用域中绑定模块或函数名称。

global 语句可被用来表明特定变量生存于全局作用域并且应当在其中被重新绑定;nonlocal 语句表明特定变量生存于外层作用域中并且应当在其中被重新绑定。
关于作用域,可以简单如下理解:
上述命名空间定义了变量名称与对象的映射,但是这些变量在哪个区域有效?这个区域就是作用域。
同样的有局部作用域、全局作用域和内置作用域之分,其实某些时候把命名空间和作用域的概念等价好像也没啥大问题。


作用域和命名空间示例
官网给了一个如何引用不同作用域和名称空间的示例,包括global 和 nonlocal 会如何影响变量绑定,尝试解读一下。

def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

代码输出是:

20200619231712

作用域和命名空间示例代码输出

代码结构比较简单,一个函数scope_test(),内部包含3个赋值函数。
首先 在scope_test内部赋值spam = "test spam"
然后 调用do_local()本地赋值函数,在子函数内对spam重新赋值,然后打印,发现spam没有改变,也就是说不带global和nonlocal关键字的本地赋值(默认选项),不会改变 scope_test 对 spam 的绑定,也就是说函数内本地赋值只是在函数局部命名空间中创建同名变量,不会传递到函数外部;
然后 调用do_nonlocal()后打印,可以发现nonlocal关键字赋值会改变 scope_test 对 spam 的绑定,传递到函数外部;
最后 调用do_global()后打印,可以发现global关键字赋值也不会改变 scope_test 对 spam 的绑定,不会传递到子函数外部;但是会改变模块层级的绑定,也就是整个全局命名空间中。

好像有那么点感觉

类的基本概念

类提供了一种组合数据和功能的方法。类本质上是一种对象类型,和str等内置类型一样。
创建一个新类意味着创建一个新的对象类型,用户自定义的,从而允许创建一个该类型的新实例
因此类不能被直接操作,只有实例化的对象才可操作。

每个类的实例可以拥有保存自己状态的属性,还有有改变自己状态的方法,比如学生类实例A的年龄age,和修改年龄的setAge()的方法

和其他编程语言相比,Python类的相关语法非常简单,是 C++ 和 Modula-3(不知道是什么东西) 中类机制的结合。
Python 的类提供了面向对象编程的所有标准特性:类继承机制允许多个基类,派生类可以覆盖它基类的任何方法,一个方法可以调用基类中相同名称的的方法。比如一个汽车类Car,里面有个行驶move()方法,其派生类AutoCar可以覆盖move()方法,实现自动驾驶逻辑。
一个类对象可以包含任意数量和类型的数据,和模块一样,类也拥有 Python 天然的动态特性:在运行时创建,可以在创建后修改。
总之Python的类吸收很多语言的特点和优势,同时内置类型,如list可以作为用户扩展的基类,这个非常厉害。

初探类

1) 类定义语法
最简单的类定义看起来像这样:

class ClassName:
    <statement-1>
    ……
    <statement-N>

类定义与函数定义 (def 语句) 一样必须被执行才会起作用。(你可以尝试将类定义放在 if 语句的一个分支或是函数的内部。)

在实践中,类定义内的语句通常都是函数定义,但也允许有其他语句,有时还很有用。
在类内部的函数定义通常具有一种特别形式的参数列表,这是方法调用的约定规范所指明的,self,更多看后面。
当进入类定义时,将创建一个新的命名空间,并将其用作局部作用域,所有对局部变量的赋值都是在这个新命名空间之内。
特别的,函数定义会绑定到这里的新函数名称。(新函数名称怎么理解?)
当(从结尾处)正常离开类定义时,将创建一个类对象


这基本上是一个包围在类定义所创建命名空间内容周围的包装器;
原始的局部作用域将重新生效(在进入类定义之前起作用的),类对象将在这里被绑定到类定义头所给出的类名称 (在这个示例中为 ClassName)
就是说,一旦离开class 缩进部分,就是外面的世界,原先的局部作用域,同时也把类名称加到外面的命名空间中,应该理解没错。


好了,今天先对类有个概念,更多只是下一篇再分享。

03 为什么

为什么要这么做?

首先任何东西的官方文档都是最全面最权威的教程。
以前只是受限于英语水平,
对官方网站敬而远之,
遇到问题都百度,
很多答案讲的都不到位,
没有说明为什么?
越到后面,收获越大。
类、面向对象很重要,当然今天重点在名称、对象、命名空间、作用域等前置知识,只是这些也学到很多。
包括三种命名空间和作用域,nonlocalglobal等语句,dir()locals()globals()等函数可以查看当前命名空间内容,进一步理解Python变量的一些工作机制。

收获满满

04 更好的选择

有没有更好的选择

还是那句话,多敲代码,结合案例,偶尔看看官方源代码,加深理解。
今天暂时对类涉及不多,主要命名空间问题,如果在过去实践过程中踩过坑可能比较有感受。
我是记得有一次不小心覆盖了内置某个函数,导致找了很久的bug

一句话

面向对象是语言的灵魂,python万物皆对象。命名空间中保存了变量与对象的映射关系,作用域决定了变量的生效范围,局部、全局和内置三种命名空间和作用域要区分清楚。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值