最开始对名称空间的了解是在学习函数的时候,那时候知道了作用域的查找顺序,以及全局名称空间和局部名称空间,产生疑惑的时候为学递归的时候,那时候还没有名称空间这个概念,只知道递归有个最大深度,那时候以后递归产生的名称空间是一层套一层的,以及类里面的名称空间,所以产生了深究名称空间的想法,这才诞生了这篇博客,本篇博客借鉴了的内容,本人对里面的例子都试验了并发现了一个错误,在类中定义的列表生成式产生的命名空间也可以访问到类属性.

# a.py
lis = ['musibii','thales']
print('lis1',id(name))

class A:
def init(self):
pass

def func(self):
    global lis
    lis.append('maffia')
    print('lis',id(lis))
    return lis

print('lis2',lis)

b.py

from a import A
print('b',A)

执行 b 文件的结果为:

lis1 4421146632
lis2 ['musibii','thales']
b 

可以发现,虽然 b 只是导入了 a 中的 class A,但导入这个过程执行了整个 a 文件,那么是否能够在 b 中访问 a 中的全局变量 lis 呢?

使用 from a import A 的形式只是把 A 产生的名称空间复制了一份到 b 产生的名称空间中.

在Python 中一切皆对象,那么对象到底代表什么呢?我的理解是在执行 py 文件时产生的一切变量都称为对象,如果把内存比作一座超大的房子的话,那么对象就是这座房子里的租客,那么这个租客随身携带的东西就是这个对象的一切了,对象都具有唯一的 id( 内存地址),类型( python3中统一了类和类型的概念),以及对象的值,对象一旦建立,id 便不会改变,id 就是对象在内存中的地址.

用常见的对象来类比一下这三个概念:

  1. 常量
NAME = 'musibii'
print(id(NAME))
print(type(NAME))
print(globals()) # 查看全局名称空间

运行结果

4374978776
<class 'str'>
{'name': 'main','doc': None,'package': None,'loader': <_frozen_importlib_external.SourceFileLoader object at 0x104c45fd0>,'spec': None,'annotations': {},'builtins': <module 'builtins' (built-in)>,'file': '/Users/jingxing/PycharmProjects/python全栈/作业/day27/duixiang.py','cached': None,'NAME': 'musibii'}

出来的结果分别是 id,类型以及字典里面最后一个键值对对应的就是 NAME 的值.

  1. 函数
def func():
    name = 'musibii'
    def inner():
        age = 18
        print('age',age)
    inner()
    print(locals()) # 想要查看函数里面的值,必须在函数内部查看,因为函数执行完成会释放内存资源

print(id(func))
print(type(func))
func()

运行结果

4529634840
<class 'function'>
age 18
{'inner': <function func..inner at 0x10e1cac80>,'name': 'musibii'}

id 和类型不必多说,函数对应的属性值里面不包括嵌套函数里面的属性(说明:名称空间是相互独立的)

class Cls:
    school = 'hashangda'
def __init__(self,name,age):
    self.name = name
    self.age = age

def tell_info(self):
    print('%s : %s' % (self.name,self.age))

print(id(Cls))
print(type(Cls))
print(Cla.dict)

运行结果

140390381504248
<class 'type'>
{'module': 'main','school': 'hashangda','init': <function Cls.init at 0x10648eae8>,'tell_info': <function Cls.tell_info at 0x10648ebf8>,'dict': <attribute 'dict' of 'Cls' objects>,'weakref': <attribute 'weakref' of 'Cls' objects>,'doc': None}

类的 id 为内存地址,类型为type类型,在 type 里面 type 就是类型的意思,所以说 python 里所有的类的类型都是类型,而类里面的属性就是类的值了.

python 里面所有的对象都具有的并且根确定身份有关的值为 id,类型和值了.名称不是对象的属性,名称只是指向对象,因为可以多个名称指向同一个对象.

在对象里把变量名叫为名称其实是不准确的,这些名称都有一个共同的名字: identifier(和 id 是两个意思),中文名为'标识符'.

标识符:在 Python中,各类对象的名称,比如函数名,方法名,类名,变量名.常量名都称为标识符.

在 Python 中赋值并不会直接复制数据,而只是将名称绑定到对象,对象本身不需要知道和关心自己的标识符叫什么,一个对象甚至可以指向不同的标识符.真正管理这些标识符的事物是'名称空间'.

名称空间(NameSpace):名字(标识符)到对象的映射.

简单来说,名称空间可以理解为记录对象和对象名字对应关系的空间,在对象那里查看的对象的值就是名称空间,这是一个字典,一个命名空间就是名字到对象的映射,标识符是键,对象则是值.

与名称空间相对的一个概念就是'作用域',作用域本质是一块文本区域,Python 通过该文本区域可以直接访问相应的名称空间.

Python 中不加 . 的访问为直接访问,反之为属性访问.

因此可以简单的将作用域理解为直接访问名称空间的一种实现,具体而言:

  1. 作用域内相应的名称空间可以被直接访问;
  2. 只有作用域内的名称空间才可以被直接访问(因此并不是所有的名称空间都可以被直接访问).

这四类名称空间可以简记为 LEGB:

  1. 局部名称空间( local): 指的是一个函数或者一个类所定义的名称空间:包括函数的参数,局部变量,类的属性等;
  2. 闭包名称空间(enclosing function):闭包函数的名称空间( python3引入);
  3. 全局名称空间( global):读入一个模块(也即一个. py 文件)后产生的名称空间;
  4. 内建名称空间(builtin):Python 解释器启动时自动载入__builtin__ 模块后形成的名称空间;像 str/list/dict... 等内置对象的名称就处于这里.

举例:

# test.py
v1 = 'global var'

def func(v1):
v2 = 'local var'
def inner():
v3 = v2 + v
return v3
return inner

内建的反正每次都一样,在这里的三个名称空间里面的名称为:

  1. 'v1' 为全局变量 v1 的名字,其所处的名称空间为全局名称空间;需要注意的是全局名称空间包括 'func' 但不包括 func 的参数和内部变量;
  2. func 包括 'v','v2',和 'inner'名称的局部名称空间;
  3. 执行 func 后,func 的作用域释放,并返回绑定了 v 和 v2变量的闭包函数 inner,此时闭包函数的名称空间即为闭包名称空间,因此局部名称空间和闭包名称空间是相对而言的,对于父函数 func 而言,两者具有产生时间上的差异.

通过上面的例子,发现 LEGB 四类名称空间本身具有明显的内外层级概念,而这种层级概念正是构建作用域的前提:作用域依据这种层级概念将不同类型的名称空间组织起来并划归到不同层级的作用域,然后定义好不同层级作用域之间的访问规则,从而实现名称空间的直接访问.

LEGB 的访问规则:**同样的标识符在各层名称空间可以被重复使用而不会发生冲突,但 Python 寻找一个标识符的过程总是从当前层开始逐层网上找,直到首次找到这个标识符为止.

# main.py
v1 = 1
v2 = 3
def func():
    v1 = 2
    print(v1)
    print(v2)

func()
print(v1)

运行结果

2
3
1

全局变量和函数 func 都定义了变量 v1,在函数内部会优先选择自己局部名称空间内的变量 v1,对于 func 中未定义的变量 v2,Python 会向上查找全局名称空间,读取全局变量后打印输出.

对于上层名称空间里的变量,python 允许直接读取,但是不可以在内层作用域直接改写上层变量,在这方面很明显的区别就是在闭包函数里.

# e.py
gv = ['a','global','var']

def func(v):
gv = ['gv'] + gv
lv = []
def inner():
lv = lv + [v]
gv.insert(1,lv[0])
return
return inner

实际调用 func 函数后,上面两处对 gv 和 lv 进行赋值的操作都会发生UnboundLocalError:因为 python 在执行函数前,会首先生成各层名称空间和作用域,因此 python 会在执行赋值前将 func 内的 gv 和 lv 写入局部名称空间和闭包名称空间,当 python 执行赋值语句的时候,会发现在局部作用域,闭包作用域内发现局部名称空间和闭包名称空间内已经具有 gv 和 lv 标识符,但是这两个非全局标识符在赋值语句执行之前并没有被赋值,也即没有对象与标识符关联,因此无法参与赋值运算,从而触发在引用之前未赋值的错误;但这段程序的本意是为了让全局变量 gv 和局部变量 lv 参与运算,为了避免类似的情况发生,Python 引入了global和nonlocal语句来说明局部名称空间和闭包名称空间使用的标识符分别来自全局名称空间和局部名称空间,声明之后就可以在 func 和 inner 名称空间里直接改写上层名称空间内的gv和lv的值了.

# f.py
gv = ['a','var']

def func(v):
global gv
gv = ['gv'] + gv
lv = []
print(id(lv))
def inner():
nonlocal lv
lv = lv + [v]
print(id(lv))
gv.insert(1,lv[0])
return
return inner

dawei

【声明】:唐山站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。