>  기사  >  백엔드 개발  >  Python 네임스페이스 및 범위에 대한 관련 지식 요약

Python 네임스페이스 및 범위에 대한 관련 지식 요약

零下一度
零下一度원래의
2017-05-31 14:14:351159검색

아래 편집기에서 기사PythonAdvanced_About네임스페이스 및 범위(자세한 설명)를 가져올 것입니다. 에디터가 꽤 좋다고 생각해서 지금 공유해서 참고용으로 올려보겠습니다. 에디터를 따라가서 살펴보자

Written in front

다음은 모두 Python3을 기반으로 한다.

네임스페이스는 바인딩 이름과 밀접하게 연관되어 있으며 다른 글과 결합될 수 있음을 소개하는 글이다. Python 이름, 객체 및 해당 바인딩.

1. 네임스페이스

1.1 네임스페이스란 무엇입니까?

네임스페이스라고도 불리는 네임스페이스는 이름에서 개체로의 매핑입니다. Python에서는 대부분의 네임스페이스가 사전으로 구현되지만 이 문서에서는 네임스페이스 구현을 다루지 않습니다. 네임스페이스의 주요 역할은 이름 충돌을 피하는 것입니다.

def fun1():
  i = 1

def fun2():
  i = 2

동일 모듈에 있는 두 개의 함수 중에서 동일한 이름을 가진 두 이름 i는 서로 다른 명백한 공간에 속하기 때문에 전혀 관계가 없습니다.

1.2 네임스페이스 유형

일반적인 네임스페이스에는 다음이 포함됩니다.

abs() 및 내장 예외 이름과 같은 기능을 포함한 내장 이름 집합. 일반적으로 내장이라는 단어는 이 네임스페이스를 가리키는 데 사용됩니다. 내장 네임스페이스

모듈 전역 이름 모음, 클래스, 함수, 기타 가져온 모듈 등과 같이 모듈에 직접 정의된 이름입니다. 일반적으로 이는 전역 네임스페이스를 사용하여 표현됩니다.

함수 호출 시 이름 모음, 함수 내 매개변수, 함수 본문에 정의된 이름 등이 함수 호출 시 '활성화'되어 네임스페이스를 형성합니다. 일반적으로 로컬 네임스페이스 표현이 사용됩니다.

객체의 속성 모음도 네임스페이스를 구성합니다. 그러나 objname.attrname의 간접 메서드는 일반적으로 직접 액세스 대신 속성에 액세스하는 데 사용되므로 네임스페이스 논의에는 포함되지 않습니다.

클래스 정의의 네임스페이스. 일반적으로 인터프리터가 클래스 정의에 들어갈 때, 즉 class ClassName: 문을 실행할 때 새로운 네임스페이스가 생성됩니다. (클래스 정의에 대한 공식 설명 참조)

1.3 네임스페이스의 수명 주기

네임스페이스의 유형에 따라 수명 주기가 다릅니다.

내장 네임스페이스, Python 인터프리터에서 생성됨

글로벌 네임스페이스, 모듈의 전역 네임스페이스는 인터프리터가 모듈 정의를 읽을 때 생성되고,

로컬 네임스페이스, 여기서는 다음을 구별해야 합니다. 함수 및 클래스 정의. 함수의 로컬 네임스페이스는 함수가 호출될 때 생성되고 함수가 반환되거나 포착되지 않은 예외가 발생할 때 소멸됩니다. 클래스 정의의 네임스페이스는 인터프리터가 클래스 정의를 읽을 때 생성되고 클래스 정의가 끝난 후에 소멸됩니다. (클래스 정의의 네임스페이스는 클래스 정의가 끝나면 소멸되는데, 사실 클래스 객체는 이 네임스페이스의 내용을 패키징한 것입니다. 클래스 정의의 공식 설명을 참조하세요.)

2.

2.1 범위란 무엇입니까?

범위는 네임스페이스에 "직접 액세스"할 수 있는 Python의 텍스트 영역입니다. 여기서 직접 액세스는 네임스페이스에서 이름에 대한 절대 참조(정규화되지 않은 참조)를 찾으려는 시도를 의미합니다. 여기서는 직접 참조와 간접 참조를 설명해야 합니다.

직접 참조, 이름과 같은 이름 액세스를 직접 사용하는 이 방법은 네임스페이스에서 이름 이름을 검색하려고 시도합니다.

간접 참조; objname.attrname 형식의 메소드, 즉 속성 참조를 사용합니다. 이 메소드는 네임스페이스에서 attrname이라는 이름을 검색하지 않고 objname이라는 이름을 검색한 후 해당 속성에 액세스합니다.

2.2 네임스페이스와의 관계

이제 네임스페이스에는 이름이 포함됩니다. 범위는 Python의 텍스트 영역, 즉 코드 영역입니다. 코드 영역은 이름(액세스 변수)을 참조해야 하므로 범위와 네임스페이스 사이에 연결이 있어야 합니다.

顾名思义,名字作用域就是名字可以影响到的代码文本区域,命名空间的作用域就是这个命名空间可以影响到的代码文本区域。那么也存在这样一个代码文本区域,多个命名空间可以影响到它。
作用域只是文本区域,其定义是静态的;而名字空间却是动态的,只有随着解释器的执行,命名空间才会产生。那么,在静态的作用域中访问动态命名空间中的名字,造成了作用域使用的动态性。

那么,可以这样认为:

静态的作用域,是一个或多个命名空间按照一定规则叠加影响代码区域;运行时动态的作用域,是按照特定层次组合起来的命名空间。

在一定程度上,可以认为动态的作用域就是命名空间。在后面的表述中,我会把动态的作用域与其对应命名空间等同起来。

2.3 名字搜索规则

在程序中引用了一个名字,Python是怎样搜索到这个名字呢?

在程序运行时,至少存在三个命名空间可以被直接访问的作用域:

Local
首先搜索,包含局部名字的最内层(innermost)作用域,如函数/方法/类的内部局部作用域;

Enclosing
根据嵌套层次从内到外搜索,包含非局部(nonlocal)非全局(nonglobal)名字的任意封闭函数的作用域。如两个嵌套的函数,内层函数的作用域是局部作用域,外层函数作用域就是内层函数的 Enclosing作用域;

Global
倒数第二次被搜索,包含当前模块全局名字的作用域;

Built-in
最后被搜索,包含内建名字的最外层作用域。

程序运行时,LGB三个作用域是一定存在的,E作用域不一定存在;若程序是这样的:

i = 1
print(i)

局部作用域在哪里呢?我们认为(Python Scopes And Namespaces):

Usually, the local scope references the local names of the (textually) current function. Outside functions, the local scope references the same namespace as the global scope: the module's namespace. Class definitions place yet another namespace in the local scope.

一般地,局部作用域引用函数中定义的名字。函数之外,局部作用域和全局作用域引用同一个命名空间:模块的明星空间。然而类型的局部作用域引用了类定义新的命名空间。

Python按照以上L-E-G-B的顺序依次在四个作用域搜索名字。没有搜索到时,Python抛出NameError异常。

2.4 何时引入作用域我们知道:

我们知道:

在Python中一个名字只有在定义之后,才能引用。

print(i)

直接引用未定义的名字i,按照搜索规则,在LGB三个作用域均没有搜索到名字i(LB相同命名空间)。抛出NameError异常:

Traceback (most recent call last):
 File "scope_test.py", line 15, in <module>
  print(i)
NameError: name &#39;i&#39; is not defined

那对于这段代码呢?

def try_to_define_name():
  &#39;&#39;&#39;函数中定义了名字i,并绑定了一个整数对象1&#39;&#39;&#39;
  i = 1

try_to_define_name()
print(i) #引用名字i之前,调用了函数

在引用名字i之前,明明调用了函数,定义了名字i,可是还是找不到这个名字:

Traceback (most recent call last):
 File "scope_test.py", line 20, in <module>
  print(i) #引用名字i之前,调用了函数
NameError: name &#39;i&#39; is not defined

虽然定义了名字i,但是定义在了函数的局部作用域对应的局部命名空间中,按照LEGB搜索规则,在全局作用域中自然访问不到局部作用域;再者,函数调用结束后,这个命名空间被销毁了。

引用名字总是与作用域相关的,因此:

在Python中一个名字只有在定义之后,才能在合适的作用域引用。

那么,在定义名字时,就要注意名字定义的作用域了,以免定义后需要访问时却找不到。所以,了解Python在何时会引入新的作用域很有必要。一般来说,B,G两个作用域的引入在不能够通过代码操作的,能够通过语句引入的作用域只有E,L了。Python中引入新作用域的语句很有限,总的来说只有两类一个:

函数定义引入local作用域或者Enclosing作用域;本质上,lambda和生成器表达式也是函数,会引入新作用域。

类定义引入local作用域;

列表推导式引入local作用域,传说在python2中列表推导式不引入新的作用域

几个会让有其他高级语言经验的猿困惑的地方:

if语句:

if True:
  i = 1
print(i) # output: 1,而不是NameError

if语句并不会引入新的作用域,所以名字绑定语句i = 1与print(i)是在同一个作用域中。

for语句

for i in range(6):
  pass
print(i) #output: 5,而不是NameError

for语句同样不会引入新的作用域,所以名字i的绑定和重绑定与print(i)在同一个作用域。这一点Python就比较坑了,因此写代码时切忌for循环名字要与其他名字不重名才行。

import语句:

def import_sys():
  &#39;&#39;&#39;import sys module&#39;&#39;&#39;
  import sys

import_sys()
print(sys.path) # NameError: name &#39;sys&#39; is not defined

这个算非正常程序员的写法了,import语句在函数import_sys中将名字sys和对应模块绑定,那sys这个名字还是定义在局部作用域,跟上面的例子没有任务区别。要时刻切记Python的名字,对象,这个其他编程语言不一样,但是:

打破第一编程语言认知的第二门编程语言,才是值得去学的好语言。

3. 作用域应用

3.1 自由变量可读不可写

我不太想用“变量”这个词形容名字,奈何变量是家喻户晓了,Python中的自由变量:

If a variable is used in a code block but not defined there, it is a free variable.

如果引用发生的代码块不是其定义的地方,它就是一个自由变量。专业一点,就是:

引用名字的作用域中没有这个名字,那这个名字就是自由名字

Note: “自由名字”只是作者YY的,并没得到广泛认可。

我们已经了解了作用域有LEGB的层次,并按顺序搜索名字。按照搜索顺序,当低层作用域不存在待搜索名字时,引用高层作用域存在的名字,也就是自由名字:

[示例1]

def low_scope():
  print(s)

s = &#39;upper scope&#39;
low_scope()

很清楚,这段代码的输出是upper scope。

[示例2]

def low_scope():
  s = &#39;lower scope&#39;

s = &#39;upper scope&#39;
low_scope()
print(s)

很遗憾,最后的打印语句没有按照期待打印出lower scope而是打印了upper scope。

A special quirk of Python is that – if no global statement is in effect – assignments to names always go into the innermost scope.

Python的一个怪癖是,如果没有使用global语句,对名字的赋值语句通常会影响最内层作用域。
即赋值语句影响局部作用域,赋值语句带来的影响是绑定或重绑定,但是在当前局部作用域的命名空间中,并没有s这个名字,因此赋值语句在局部作用于定义了同名名字s,这与外层作用域中的s并不冲突,因为它们分属不同命名空间。
这样,全局作用域的s没有被重绑定,结果就很好解释了。

当涉及可变对象时,情况又有所不同了:

[示例3]

def low_scope():
  l[0] = 2

l = [1, 2]
low_scope()
print(l) # [2, 2]

很遗憾,最后的打印语句没有按照期待输出[1, 2]而是输出了[2, 2]。
上一个例子的经验并不能运用在此,因为list作为一个可变对象,l[0] = 2并不是对名字l的重绑定,而是对l的第一个元素的重绑定,所以没有新的名字被定义。因此在函数中成功更新了全局作用于中l所引用对象的值。

注意,下面的示例跟上面的是不一样的:

[示例4]

def low_scope():
  l = [2, 2]

l = [1, 2]
low_scope()
print(l) # [1, 2]

我们可以用本节中示例1的方法解释它。

综上,可以认为:

自由变量可读不可写。

3.2 global和nonlocal

总是存在打破规则的需求:

在低层作用域中需要重绑定高层作用域名字,即通过自由名字重绑定。

于是global语句和nonlocal语句因运而生。

global_stmt ::= "global" identifier ("," identifier)*
The global statement is a declaration which holds for the entire current code block. It means that the listed identifiers are to be interpreted as globals. It would be impossible to assign to a global variable without global, although free variables may refer to globals without being declared global.

global语句是适用于当前代码块的声明语句。列出的标识符被解释为全局名字。虽然自由名字可以不被声明为global就能引用全局名字,但是不使用global关键字绑定全局名字是不可能的。

nonlocal_stmt ::= "nonlocal" identifier ("," identifier)*
The nonlocal statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope excluding globals. This is important because the default behavior for binding is to search the local namespace first. The statement allows encapsulated code to rebind variables outside of the local scope besides the global (module) scope.

nonlocal语句使得列出的名字指向最近封闭函数中绑定的名字,而不是全局名字。默认的绑定行为会首先搜索局部作用域。nonlocal语句使得在内层函数中重绑定外层函数作用域中的名字成为可能,即使同名的名字存在于全局作用域。

经典的官方示例:

def scope_test():
  
  def do_local():
    spam = &#39;local spam&#39;

  def do_nonlocal():
    nonlocal spam # 当外层作用域不存在spam名字时,nonlocal不能像global那样自作主张定义一个
    spam = &#39;nonlocal spam&#39; # 自由名字spam经nonlocal声明后,可以做重绑定操作了,可写的。

  def do_global():
    global spam # 即使全局作用域中没有名字spam的定义,这个语句也能在全局作用域定义名字spam
    spam = &#39;global spam&#39; # 自有变量spam经global声明后,可以做重绑定操作了,可写的。

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


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

作者说不行nonlocal的邪:

def nest_outter():
  spam = &#39;outer&#39;

  def nest_inner():
    nonlocal spam1
    spam1 = &#39;inner&#39;

  nest_inner()
  print(spam)

nest_outter()

Output:

 File "scope_test.py", line 41
  nonlocal spam1
SyntaxError: no binding for nonlocal &#39;spam1&#39; found

4. 一些坑

作者曾经自信满满认为透彻了解了Python的作用域,但是一大堆坑踩得触不及防。

4.1 坑1 - UnboundLocalError

def test():
  print(i)
  i = 1

i = 2
test()

Output:

Traceback (most recent call last):
 File "scope_test.py", line 42, in <module>
  test()
 File "scope_test.py", line 38, in test
  print(i)
UnboundLocalError: local variable &#39;i&#39; referenced before assignment

其实忽略掉全局作用域中i = 2这条语句,都可以理解。

Usually, the local scope references the local names of the (textually) current function.

Python对局部作用域情有独钟,解释器执行到print(i),i在局部作用域没有。解释器尝试继续执行后面定义了名字i,解释器就认为代码在定义之前就是用了名字,所以抛出了这个异常。如果解释器解释完整个函数都没有找到名字i,那就会沿着搜索链LEGB往上找了,最后找不到抛出NameError异常。

4.2 坑2 - 类的局部作用域

class Test(object):

  i = 1

  def test_print(self):
    print(i)

t = Test()
i = 2
t.test_print()

我就问问大家,这个输出什么?
当然会出乎意料输出2了,特别是有其他语言经验的人会更加困惑。

上文强调过:
函数命名空间的生命周期是什么? 调用开始,返回或者异常结束,虽然示例中是调用的方法,但其本质是调用类的函数。
类命名空间的作用域是什么?类定义开始,类完成定义结束。

类定义开始时,创建新的属于类的命名空间,用作局部作用域。类定义完后,命名空间销毁,没有直接方法访问到类中的i了(除非通过间接访问的方式:Test.i)。

方法调用的本质是函数调用:

class Test(object):

  i = 1

  def test_print(self):
    print(i)

t = Test()
i = 2
# t.test_print()
Test.test_print(t) # 方法调用最后转换成函数调用的方式

函数调用开始,其作用域与全局作用域有了上下级关系(L和G),函数中i作为自由名字,最后输出2。
因此,不能被类中数据成员和函数成员的位置迷惑,始终切记,Python中两种访问引用的方式:

直接引用:试图直接写名字name引用名字,Python按照搜索LEGB作用域的方式搜索名字。

间接引用:使用objname.attrname的方式引用名字attrname,Python不搜索作用域,直接去对象里找属性。

4.3 坑3 - 列表推导式的局部作用域

一个正常列表推导式:

a = 1
b = [a + i for i in range(10)]
print(b) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

现在把列表推导式放到类中:

class Test(object):

  a = 1
  b = [a + i for i in range(10)]
  print(b)

  def test(self):
    pass

Output:

Traceback (most recent call last):
 File "scope_test.py", line 15, in <module>
  class Test(object):
 File "scope_test.py", line 18, in Test
  b = [a + i for i in range(10)]
 File "scope_test.py", line 18, in <listcomp>
  b = [a + i for i in range(10)]
NameError: name &#39;a&#39; is not defined

输出反馈名字a未定义。

上文强调过,解释器读取类定义开始class ClassName后,创建命名空间用作局部作用域。
语句a = 1,在这个局部作用域中定义了名字i
语句b = [a + i for i in rage(10)],列表推导式同样创建了一个局部作用域。这个作用域与类定义的局部作用域并没有上下级关系,所以,自然没有任何直接访问名字a的方法。

Python中只有四种作用域:LEGB,因为类定义的局部作用域与列表推导式的局部作用域于不是嵌套函数关系,所以并不能构成Enclosing作用域关系。因此它们是两个独立的局部作用域,不能相互访问。

既然是两个独立局部作用域,那么上述例子就等同于:

def test1():
  i = 1

def test2():
  print(i)

test1()
test2()

期待在test2中访问test1的名字i,显然是不可行的。

【相关推荐】

1. Python and、or以及and-or语法总结

2. 详解轻松搞定Python的作用域五个知识点

3. 详细介绍Python中and和or实际用法

4. python编程中常用的12种基础知识总结

위 내용은 Python 네임스페이스 및 범위에 대한 관련 지식 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.