命名空间与作用域

Python之禅

  Python之禅中记录了我们使用Python时应该遵循的一些原则。在交互式环境下使用import this即可看到我们用Python编写代码时应当遵循的核心思想点。Python之禅并非Python的创始人所写,但是Tim Peters所总结的这十几条理念依然被Python官方所认可。

>>> import this
The Zen of Python, by Tim Peters
​
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
>>>
Python之禅 by Tim Peters
 
优美胜于丑陋(Python 以编写优美的代码为目标)
明了胜于晦涩(优美的代码应当是明了的,命名规范,风格相似)
简洁胜于复杂(优美的代码应当是简洁的,不要有复杂的内部实现)
复杂胜于凌乱(如果复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁)
扁平胜于嵌套(优美的代码应当是扁平的,不能有太多的嵌套)
间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题)
可读性很重要(优美的代码是可读的)
即便假借特例的实用性之名,也不可违背这些规则(这些规则至高无上)
 
不要包容所有错误,除非你确定需要这样做(精准地捕获异常,不写 except:pass 风格的代码)
 
当存在多种可能,不要尝试去猜测
而是尽量找一种,最好是唯一一种明显的解决方案(如果不确定,就用穷举法)
虽然这并不容易,因为你不是 Python 之父(这里的 Dutch 是指 Guido )
 
做也许好过不做,但不假思索就动手还不如不做(动手之前要细思量)
 
如果你无法向人描述你的方案,那肯定不是一个好方案;反之亦然(方案测评标准)
 
命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召)

  而今天我们主要探索的内容就是这最后一句,命名空间。以下内容将围绕Python之禅最后一句来进行逐一解惑。

命名空间Namespaces

命名空间是什么

  命名空间说白了就是存储名字的地方,它本身是只是一种虚拟的定义,实际上并不存在,前面已经说过变量名是存储于栈区内存之中。这里做一下补充,不光只是变量名,包括函数名以及后期所学的类名等等都是存在于栈区内存之中。但是在栈区内存中又详细的划分了三大块命名空间用于存放这些变量名,有了命名空间后,就可以在栈区中存放相同的变量名而互不影响,我们将要学习的时如何区分不同命名空间中变量名的查找顺序

  三大命名空间分别是:内置命名空间,全局命名空间,局部命名空间。我们先用一段代码再加图解,详细的看一下定义3个相同的变量名他们分别存在于内存中的哪一个命名空间中。

# ==== Python的不同命名空间 ====

print(type)  # 存放于内置命名空间的函数名type指向Python自带的type类

type = "global type"  # 自定义的变量名type指向的是变量值 "global type" 的内存地址。存放于全局命名空间中
print(type)


def func():
    type = "local type"  # 自定义的变量名type指向的是变量值 "local type" 的内存地址。存放于局部命名空间中
    print(type)


func()

# ==== 执行结果 ====

"""
<class 'type'>
global type
local type
"""

img

内置命名空间

  存放名字的范畴:存放Python解释器自带的一些变量名,函数名,类名等等。如printidtypeint等等这些变量名或者函数名都是存在于内置命名空间中。

  生命周期:他们在Python解释器启动时产生,关闭时销毁。

  数量:最多只有1个内置命名空间。

>>> print  # BIF,内置函数
<built-in function print>
>>> id
<built-in function id>
>>> type  # class,内置的类
<class 'type'>
>>> int
<class 'int'>

全局命名空间

  存放名字的范畴:只要不是在函数内,类内以及不存在于内置命名空间中的变量名,函数名,类名等等这些名字全部存放于全局命名空间中

  生命周期:Python脚本文件执行则产生,执行完毕时销毁

  数量:最多只有1个全局命名空间。

global_name = "全局命名空间中存储的变量"


def global_func():  # <-- global_func 这个函数名也是存储于全局命名空间中
    def local_func():  # <--由于是存放在函数体内的函数名,故 local_func 这个函数名并不是存储于全局命名空间中。
        pass


class Global_class(object):  # <-- Global_class 这个类名也是存储于全局命名空间中
    def local_calss_func(self):  # local_calss_func 名字 未存储于全局命名空间
        pass

    class Local_class(object):  # Local_class 名字 未存储于全局命名空间
        pass

局部命名空间

  存放名字的范畴:在调用类或函数时产生的变量名,函数名,类名等等都是存储于局部命名空间之中

  生命周期:对于函数的局部命名空间来说,函数调用时存活,调用完毕则销毁。

  数量:可以有多个局部命名空间(取决于内存的大小)

global_name = "全局命名空间中存储的变量"


def global_func():
    local_name = "局部命名空间中存储的变量"

    def local_func():  # 存储于局部命名空间
        pass


class Global_class(object):
    def local_calss_func(self):  # 存储于局部命名空间
        pass

    class Local_class(object):  # 存储于局部命名空间
        pass

img

命名空间的加载与销毁顺序

  加载顺序: 内置命名空间 --> 全局命名空间 --> 局部命名空间

  销毁顺序: 局部命名空间 --> 全局命名空间 --> 内置命名空间

名字查找顺序与实例

  注意一点:名字的查找顺序以定义阶段为标准。当前命名空间没找到名字时往定义自己的命名空间查找名字(可以理解为向上层空间查找,局部空间必须由类或函数进行开辟,但是类和函数自身的名字可能存在于全局命名空间中,也可能存在于另一个类或者函数创建的局部命名空间之中。故称为往定义自己的命名空间中查找)。

  注意:命名空间中并没有嵌套关系,但是为了方便理解故使用嵌套关系作图以便更容易理解名字查找顺序

def foo():
    print(x)  # 1


def func():
    x = 2
    foo()


x = 1  # 在执行调用函数阶段前。全局命名空间就已经定义好了变量名x
func()

# ==== 执行结果 ====

"""
1
"""

img

x = 1


def func():
    def foo():
        print(x)
    x = 2
    foo()


func()

# ==== 执行结果 ====

"""
2
"""

img

  下一题是重点:

x = 1



def func():
    print(x)
    x = 2


func()

# ==== 执行结果 ====

"""
Traceback (most recent call last):
  File "C:/Users/Administrator/PycharmProjects/learn/FunctionLearn.py", line 5, in <module>
    func()
  File "C:/Users/Administrator/PycharmProjects/learn/FunctionLearn.py", line 3, in func
    print(x)
UnboundLocalError: local variable 'x' referenced before assignment
"""

  按照上面的思路,以及Python解释器解释一行执行一行的特性。应该是去全局命名空间中查找变量名x啊,为什么会抛出异常呢?

  虽然函数定义时不会执行其中代码,但是会检查其中的语法。如果检查到有变量名就进行记录,当调用函数时开辟独立的局部命名空间,记录好的变量名将存放于局部命名空间之中,此时print(x)先检查自身局部命名空间中是否存在变量名x,发现已经存在则不继续向上查找而是根据变量名绑定的内存地址拿到变量值试图使用,但是Python语法规定变量的使用是先定义后使用,故抛出异常。

作用域

全局作用域

  特性:全局存活,全局有效

  解释:全局作用域下的变量名能被局部作用域中所调用

  全局作用域包含内置命名空间与全局命名空间。这也是为什么在取变量名的时候不要使用关键字作为变量名,因为全局命名空间中的名字与内置命名空间中的名字全部属于全局作用域。全局命名空间滥用内置命名空间的名字会导致Python功能的缺失。如下代码所示:

# ==== 全局作用域名字冲突 ====

print("hello,world")
print = "全局命名空间占用内置命名空间的名字,改变地址指向"

# 原本的print指向一个函数
# 现在print指向了一个字符串

print("hello,world")  # 无法使用

# TypeError: 'str' object is not callable

局部作用域

  特性:临时存活,局部有效

  解释:局部作用域下的变量名是执行函数或者类时产生的变量名,不能被全局作用域所引用。当类或者函数执行完毕后则进行销毁。

  局部作用域只包含局部命名空间中的名字。虽然当局部命名空间中占用内置命名空间中的名字后不会导致Python在全局作用域下的功能缺失,但是也不推荐这样做。

# ==== 局部作用域名字不冲突 ====
def func():
    print = "局部命名空间占用内置命名空间的名字"
    # 在当前的局部作用域中,print将会失效


func()
print("hello,world")
# 但是这并不影响全局作用域下的print使用

# ==== 执行结果 ====
"""
hello,world
"""

方法介绍

global()

  在局部作用域中如果想要修改全局作用域中的变量值(不可变类型,可变类型不需要这么做。如list直接进行append()即可改变值),则使用global()

# ==== global()方法介绍 ====

x = 1
print(x)



def func():
    global x  # 只将全局命名空间中的值引用入过来
    x = 2



func()
print(x)  # x的值已被改变

# ==== 执行结果 ====

"""
1
2
"""

nonlocal()

  nonlocal()修改的是上层局部命名空间中的变量值(不可变类型)。使用如下:

# ==== nonlocal()方法介绍 ====

x = 1


def func():
    x = 2
    print("func中原本x的值", x)

    def foo():
        nonlocal x  # 只将定义自己的局部命名空间中的变量值引入过来
        x = 3

    foo()
    print("func中被修改的x的值", x)


func()
print("当前全局变量中的x的值", x)

# ==== 执行结果 ====

"""
func中原本x的值 2
func中被修改的x的值 3
当前全局变量中的x的值 1
"""

扩展:LEGB

  函数嵌套下的变量值改变,很多教程中也并没有提及命名空间的概念。通过作用域依旧也能分析透彻,但是有的教程中更加详细的将命名空间分成了四层(实际上只有三层),分成四层的目的是让人更好理解。以下是分层结构:

# ==== LEGB介绍 ====

# builtin and global 全局作用域下两层,内置命名空间与全局命名空间

def f1():
    # enclosing # 封闭
    def f2():
        # local # 本地
        pass


"""
LEGB含义解释:
L-Local(function);函数内的名字空间
E-Enclosing function locals;外部嵌套函数的名字空间(例如closure)
G-Global(module);函数定义所在模块(文件)的名字空间
B-Builtin(Python);Python内置模块的名字空间
"""

 

posted @ 2020-05-14 16:31  云崖先生  阅读(365)  评论(0编辑  收藏  举报