python-命名空间和作用域

前言

前段时间写脚本的时候,在调用函数的时候,一直在想在python中函数的参数是传值还是传引用?先看一下下面两个例子

a = 1

def setvalue(arg):
    arg = 100
    print(arg)
    
setvalue(a)
print(a)

这个例子中,会发现最后打印出的 a 仍然是 1 ,看起来像是传值调用。再看另一个例子:

a = [1]

def setvalue(arg):
    arg[0] = 100
    print(arg[0])

setvalue(a)
print(a[0])

这个例子的结果 a 中的数值已经修改成了 100,这样看又是传引用。熟悉C语言的可能在这里会说,这里传的是地址,所以修改有效。当然可以这么理解,但是在 python 中,对待变量与赋值需要换一个角度去理解。这也是我学python遇到的第一个难点,涉及到了python的namespacescope,想要学好python是必须要弄懂的。

一切皆对象

python 最大的特点也是最核心的思想,以前就提到过就是: 一切皆对象 。在说 namespacescope 之前,先说一下python中给变量赋值的原理。

a = 1   #1
a = 2   #2
b = a   #3
del a   #4

在 python 中,所有的int,string,list,dict,函数和类等等等等,我们都把它看成是一个对象

比如上面的 1 的意思,正确的理解是 a 引用了 1 这个对象或者将 a 绑定给了 1 ,,而不是将 1 这个值赋给 a 。深入点说,就是 1 这个对象的所有属性包括值都只存在自身,a 只是在 1 上打了个标签,并没有将实际的数据拷贝到自身!你只是可以通过命名 a 能够访问到 1 这个对象的信息。而如果执行了上面语句 4 ,含义就是在对象上删掉 a 这个标签,在通过 a 访问时就会报a命名未定义的错误。

语句 2 会删除掉a1上的标签,触发python的垃圾回收机制,然后语句 3 会将 b 也绑定到2这个对象上。

再回到最先的例子中,用刚谈到的对象概念去重新理解。例子1中,在调用函数 setvalue 时,只是将参数 arg 也绑定到 a 所引用的对象 1,但是在 setvalue 函数中,删除掉了 arg 在对象 1 上的标签,重新绑定到了对象 100 上,对 a 是没有影响的。
在例子2中,arg 也绑定到 [0] 这个列表对象上。函数中的操作仍然是对 a[0] 这个对象的操作,所以结果肯定是同时影响到已经绑定到 a[0] 上的所有变量的。

Consider

消化完上面的内容之后,再看一个例子

a = 1

def setvalue():
    a = 100
    
setvalue()
print(a)

简单的加一句声明之后

a = 1

def setvalue():
    global a
    a = 100
    
setvalue()
print(a)

简单的解释一下,因为在第一个例子中,函数中的 a 是局部变量,作用域只在函数内,所以不影响函数外的命名空间。第二个例子中,使用了 global 关键字,作用是将函数内对 a 的操作影响扩展到全局,所以函数外的结果收到了影响。

Namespace

Definition

命名空间的定义:变量到对象的映射集合。一般都是通过字典来实现的。主要可以分为三类:

  • 内置命名空间
  • 函数的本地命名空间
  • 模块的全局命名空间

对于模块的全局命名空间,因为有着模块名的前缀,所以互相是没有影响的。比如模块A和B都有c变量,那么通过A.c和B.c来使用是不冲突的。这三种命名空间也有着自己的生存周期,除了第二个函数的本地命名空间生存周期只在函数的调用开始到结束,其他两个的生存周期都是可以看做持续到解释器退出的。

作用

命名空间的作用:程序在直接访问变量时,会在当前的命名空间内查找。

比如:你在程序内执行 x = A 时,就会在当前的命名空间增加x到A的映射。如果使用del x 会删除掉命名空间中x的命名。

这里稍微拓展一下,import导入模块的本质,就是将其他模块的类、函数、变量等对象加入到程序的命名空间。再谈的深一点,python 中类的继承也是通过命名空间来实现的!这个下次笔记再说。。跑偏了

Scope

作用域 就是一个 Python 程序可以直接访问命名空间的正文区域。也可以理解是多种层级命名空间的叠加作用。在一个python程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。可以具体分为以下四个作用域:

  • Local(innermost)
    包含局部变量,比如一个函数/方法内部。
  • Enclosing
    包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,内层函数可能搜索外层函数的namespace,但该namespace对内层函数而言既非局部也非全局。
  • Global(next-to-last)
    当前脚本的最外层,比如当前模块的全局变量。
  • Built-in(outtermost)
    Python builtin 模块。包含了内建的变量/关键字等。

一个命名的作用域在它初次定义的时候确定,如果在当前作用域找不到命名时,会到外层作用域中寻找相应的命名,最后回到全局作用域和内置作用域中寻找。也就是按照LEGB的顺序来寻找一个命名对应的对象。

概念很抽象,具体来看代码

  1. 作用域如何生效的例子:
def test():
    b = 200
    def test2():
        b = 100
        print(b)
    test2()
    print(b)

b = 300
print(b)

函数 test2() 中访问b时,在本地作用域率先找到 b=100 这条语句,所以由100这个对象起作用。在 test() 中,它的本地作用域中b的引用为200,不会去 test2() 中去搜索。如果删除掉 test2()b=100的语句,那么test2函数调用时,在本地作用域找不到b的引用,会向上级enclosing作用域寻找,成功找到b的引用200,所以200在test2函数中生效!

  1. 作用域在定义时生效:
def test():
    print a
    a = 100

a = 200
test()

这个例子会报错,因为在 test() 函数中,因为本地作用域有命名a的引用操作,所以 print a 会优先使用本地作用域的命名。但是定义在使用之后,所以会报错。

事实上,所有引入新命名的操作都作用于局部作用域。

官方文档这句话的意思,这里的局部作用域要以相对角度去理解。

global、nonlocal

通过 globalnonlocal 两个关键字,可以将内层的变量引入到全局作用域。终于讲到了之前那个例子,弄懂了命名空间和作用域的基础上,就能很简单的理解那个例子了。

看具体的例子:

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)

输出是

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

需要注意的是,python2中并不支持 nonlocal 关键字来重新关联作用域。

Globals() Locals()

这两个方法是关于返回作用域的函数,前者返回全局作用域。后者返回当前的本地作用域。用这两个函数可以帮助理解namespacescope,直接贴上我写的一段代码:

#!usr/bin/env python
# -*- encoding: utf-8 -*-

import os
from sys import argv

class test:
    pass

def test2(arg1,arg2):
    print(locals())
    return arg1 + arg2

def test3():
    b = 2
    def test4():
        b = 200
        return b
    test4()
    print(locals())

a = 1
b = 100
test2(a,b)
test3()
print(globals())

输出结果:

{'arg2': 100, 'arg1': 1}
{'test4': <function test3.<locals>.test4 at 0x02AB36A8>, 'b': 2}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00F1A310>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'python.py', '__cached__': None, 'os': <module 'os' from 'E:\\python\\lib\\os.py'>, 'argv': ['python.py'], 'test': <class '__main__.test'>, 'test2': <function test2 at 0x00EDD540>, 'test3': <function test3 at 0x02AB36F0>, 'a': 1, 'b': 100}
  • 第一个字典记录的是test2()函数的本地作用域,可以看到只有两个参数的命名。
  • 第二个字典是test3()函数的本地作用域,有一个函数对象和一个命名。
  • 第三个是这个py文件的全局作用域信息,除了py文件中定义的函数、类、变量等,还有一些特殊变量__name__ __doc__ 内置模块 __builtins__ 我们引入的 os 模块也是作为一个模块对象,但是不同的是通过 form sys import argv 来引入的 argv 已经和原模块脱离联系,我们已经在本py文件中重新拷贝了一份。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容