Python函数作用域

作者: 一根薯条 | 来源:发表于2018-03-21 08:08 被阅读0次

    先来看一个问题:

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

    这段程序的输出结果是

    3
    2
    

    如果你知道为什么输出这样的结果而且知道里面的机制,接下来的文章可以不看了,如果不了解,可以继续看下去。

    作用域介绍

    当我们在代码里使用变量时,Python创建对象,改变对象 或 查找对象都是在一个所谓命名空间下进行的(一个保存变量名的地方)。

    而函数除了打包代码之外,还定义了一个新的变量空间,一个函数所有的变量,都与函数的命名空间相关联:

    • def 内定义的变量名能够被 def内的代码使用,不能在函数外部引用这样的变量名
    • def之中的变量名与def之外的变量名并不冲突

    也就是说:

    • 如果一个变量在def内被赋值,它就被定义在这个函数之内
    • 如果在def之外赋值,它就是整个文件全局的

    那么回到一开始的问题:

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

    尽管这两个变量名都是x,但是他们作用域(命名空间)可以把他们区别开。作用域(命名空间)有助于防止程序之间变量名的冲突,而且,有助于函数成为更加独立的单元。

    在Python中,函数定义了一个函数本地内的作用域,而像x = 2这样赋值语句定义了一个全局作用域(模块级别的变量,使用范围仅限于单个文件)。 要注意的是,每次对函数的调用都会创建一个新的本地作用域。所有的变量名都可以分为本地变量 全局变量 或者Python内置变量

    在交互模式下(如 ipython ,jupyter notebook)赋值的变量,都位于一个叫__main__的内置模块中,以下是在ipython中 使用a = 2后,查看__main__模块的结果。

    In [2]: import __main__
    
    In [3]: a = 2
    
    In [4]: dir(__main__)
    Out[4]:
    ['In',
     'Out',
     '_',
     '__',
     '___',
     '__builtin__',
     '__builtins__',
     '__doc__',
     '__main__',
     '__name__',
     '__package__',
     '_dh',
     '_i',
     '_i1',
     '_i2',
     '_i3',
     '_i4',
     '_ih',
     '_ii',
     '_iii',
     '_oh',
     '_sh',
     'a',
     'exit',
     'get_ipython',
     'quit']
    

    __builtin__模块中,有Python预定义的变量名,我们同样可以通过dir(__builtin__)进行查看。

    In [5]: dir(__builtin__)
    Out[5]:
    ['ArithmeticError',
     'AssertionError',
     'AttributeError',
     'BaseException',
     'BufferError',
     'BytesWarning',
     'DeprecationWarning',
     'EOFError',
     'Ellipsis',
     'EnvironmentError',
     'Exception',
     'False',
     'FloatingPointError',
     'FutureWarning',
     'GeneratorExit',
     'IOError',
     'ImportError',
     'ImportWarning',
     'IndentationError',
     'IndexError',
     'KeyError',
     'KeyboardInterrupt',
     'LookupError',
     'MemoryError',
     'NameError',
     'None',
     'NotImplemented',
     'NotImplementedError',
     'OSError',
     'OverflowError',
     'PendingDeprecationWarning',
     'ReferenceError',
     'RuntimeError',
     'RuntimeWarning',
     'StandardError',
     'StopIteration',
     'SyntaxError',
     'SyntaxWarning',
     'SystemError',
     'SystemExit',
     'TabError',
     'True',
     'TypeError',
     'UnboundLocalError',
     'UnicodeDecodeError',
     'UnicodeEncodeError',
     'UnicodeError',
     'UnicodeTranslateError',
     'UnicodeWarning',
     'UserWarning',
     'ValueError',
     'Warning',
     'ZeroDivisionError',
     '__IPYTHON__',
     '__debug__',
     '__doc__',
     '__import__',
     '__name__',
     '__package__',
     'abs',
     'all',
     'any',
     'apply',
     'basestring',
     'bin',
     'bool',
     'buffer',
     'bytearray',
     'bytes',
     'callable',
     'chr',
     'classmethod',
     'cmp',
     'coerce',
     'compile',
     'complex',
     'copyright',
     'credits',
     'delattr',
     'dict',
     'dir',
     'display',
     'divmod',
     'dreload',
     'enumerate',
     'eval',
     'execfile',
     'file',
     'filter',
     'float',
     'format',
     'frozenset',
     'get_ipython',
     'getattr',
     'globals',
     'hasattr',
     'hash',
     'help',
     'hex',
     'id',
     'input',
     'int',
     'intern',
     'isinstance',
     'issubclass',
     'iter',
     'len',
     'license',
     'list',
     'locals',
     'long',
     'map',
     'max',
     'memoryview',
     'min',
     'next',
     'object',
     'oct',
     'open',
     'ord',
     'pow',
     'print',
     'property',
     'range',
     'raw_input',
     'reduce',
     'reload',
     'repr',
     'reversed',
     'round',
     'set',
     'setattr',
     'slice',
     'sorted',
     'staticmethod',
     'str',
     'sum',
     'super',
     'tuple',
     'type',
     'unichr',
     'unicode',
     'vars',
     'xrange',
     'zip']
    

    可以看到,预定义的包括python的关键字,异常,和一些内置模块等变量名。

    在Python2.0及之前的版本中,Python只支持3种作用域:局部作用域全局作用域内置作用域; 在python2.2中,引入了一种新的作用域:嵌套作用域,本质上就是Python实现了闭包,相应的,变量查找顺序由之前的LGB变成了LEGB(L:Local-本地作用域, E:Enclosing-上一层结构中的本地作用域, G:Global-全局作用域, B:Built-in - 内置作用域),变量名的引用分为4个作用域查找,首先是本地,之后是上个函数内,之后是全局,最后是去内置空间查找。

    聊了LEGB变量查找原则之后,再看看如果需要在函数内部引用全局变量需要怎么做:

    x = 3
    def use_global_param():
        global x
        x = 3 - 2
        return x 
    
    print use_global_param()
    

    这样输出的结果就是1。但是这样做不太好,引入全局变量后,就会破坏函数的独立性,让程序变得难以理解和使用。比如以下这个例子

    x = 5
    def func():
        global x
        x = 6
    
    def func2():
        global x
        x = 8
    

    那执行交替执行两个函数后,x的值到底是多少呢?

    在Python里,函数是可以嵌套声明的,比如我们可以这么声明函数:

    def out():
        x = 1
        def inner():
            x = 2
            print(x) 
        inner()
    out()
        
    

    这个程序的运行结果是2,如果想操作out函数里面的变量,我们可以引入Python3提供的nonlocal关键字:

    def out():
        x = 1
        def inner():
            nonlocal x
            print(x)
        inner()
    out()
    

    这样输出结果就是1.

    最后来看一些代码片段,如果读者心中的结果和编译器的结果一致,那就说明函数作用域这块理解了。

    x = 'spam'
    def func():
        print x
    func()
    
    x = 'spam'
    def func():
        x = 'hello'
    func()
    print(x)
    
    x = 'spam'
    def func():
        x = 'hello'
        print(x)
    func()
    print(x)
    
    x = 'spam'
    def func():
        global x
        x = 'hello'
    func()
    print(x)
    
    x = 'spam'
    def func():
        x = 'hello'
        def inner():
            print(x)
        inner()
    func()
    print(x)
    
    def func():
        x = 'hello'
        def inner():
            nonlocal x
            x  = 'spam'
        inner()
        print(x)
    func()
    

    欢迎把答案写在下面!

    相关文章

      网友评论

        本文标题:Python函数作用域

        本文链接:https://www.haomeiwen.com/subject/gihwqftx.html