python变量的层级

global

当使用global 定义全局变量时,经常会提示:Global variable '变量名' is undefined at the module level

1
2
def test001(self)
global user_id

下边是查询后得到得结果 

1
2
3
4
5
6
7
8
9
10
a = '我是模块中的变量a'

def hi():
a = '我是函数里的变量a'
print('函数“hi”已经运行!')

class Go2:
a = '我是类2中的变量a'
def do2(self):
print('函数“do2”已经运行!')

在整个py文件中,非def和class之外得变量,一般在最上放得变量,才是模块变量

如果是在模块中为全局变量赋值

1
2
3
4
5
6
# 定义一个全局变量user_id,但是仍然需要在模块层先定义一个全局变量
gloabl user_id
def test001(self):
  globals()['user_id'] = '123'
def test002(self):
  print(user_id)

变量定义及变量生命周期

变量作用域范围

现象:python中,在if else重定义的变量,没有声明全局,在外部也可以使用,这里涉及到一个python变量生命周期的问题。

python能够改变变量作用域的代码段是def、class、lamda

if/elif/else、try/except/finally、for/while 并不能涉及变量作用域的更改,也就是说他们的代码块中的变量,在外部也是可以访问的

变量搜索路径是:本地变量->全局变量

函数内部访问函数外全局变量

下面代码编译报错:local variable 'COUNT' referenced before assignment

1
2
3
4
5
6
7
COUNT = 1
def func():
# global COUNT
COUNT = COUNT + 1
print(COUNT)
if __name__ == '__main__':
func()

将注释删除执行成功

1
2
3
4
5
6
7
COUNT = 1
def func():
global COUNT
COUNT = COUNT + 1
print(COUNT)
if __name__ == '__main__':
func()

装饰器

装饰器是 Python 中的高级功能,它可以用来修改或扩展函数或类的功能,有些类似java中的aop

装饰器的应用非常广泛,例如可以用来记录函数运行时间、缓存函数结果、验证函数参数等等

样例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 定义一个简单的装饰器
def my_decorator(func):
def wrapper():
print("Before the function is called.")
func()
print("After the function is called.")
return wrapper

# 使用装饰器
@my_decorator
def say_hello():
print("Hello!")

# 调用被装饰的函数
say_hello()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator

# 这个3层嵌套的decorator用法如下:

@log('execute')
def now():
print('2015-3-25')


# 执行结果如下:
>>> now()
execute now():
2015-3-25

样例代码解释

先定义一个名为 my_decorator 的装饰器函数,它接受一个函数作为参数,然后返回一个新的函数 wrapperwrapper 函数在调用被装饰的函数之前和之后打印一些文本。接着使用 @my_decorator 语法将 say_hello 函数传递给 my_decorator 装饰器,在调用 say_hello 函数时,实际上是调用了 wrapper 函数,从而在调用 say_hello 函数之前和之后打印了一些文本。

相对/绝对路径导包

1
2
3
4
5
6
7
8
9
10
11
ValueError: attempted relative import beyond top-level package
# 翻译:试图在顶级包之外进行相对导入

ImportError: attempted relative import with no known parent package
# 翻译:尝试相对导入,但没有已知的父包

ValueError: Attempted relative import in non-package
# 翻译:试图在非包中进行相对导入

SystemError: Parent module '' not loaded, cannot perform relative import
# 翻译:父模块'xxx'未加载,不能执行相对导入。

绝对导入

既然要介绍相对导入,那必然绕不开绝对导入。绝对导入的格式为 import A.B 或 from A import B

下面是绝对导入的一些 :

1
2
3
4
import fibo    # 隐式相对导入
from fibo import fibo1, fibo2 # 绝对路径导入
import fibo as fib # 重命名
from fibo import fib as fibonacci

相对导入

相对导入格式为 from .A import B 或 from ..X import Y, . 代表当前包, .. 代表上层包, ... 代表上上层包,依次类推。

相对导入的一些案例如下所示:

1
2
3
from . import echo    # 表示从当前文件所在package导入echo这个module
from .. import formats # 表示从当前文件所在package的上层package导入formats这个子package或者moudle
from ..filters import equalizer # 表示从当前文件所在package的上层package导入的filters这个子package或者子module中导入equalizer

相对导入基于当前模块的名称。由于主模块的名称始终为"__main__",因此用作 Python 应用程序主模块的模块必须始终使用绝对导入。主模块所在文件夹不会被视作package,因此除了主模块外,与主模块处在同个文件夹的模块(也就是同级的模块)也必须使用绝对导入。

还有个更专业的说法: 相对导入使用模块的名称属性来决定模块在包层次结构中的位置,如果模块的名称不包含任何包信息(例如:模块名称为'main'),那么相对导入则被解析为最顶层的位置,不管这个时候这个模块实际上位于文件系统中的什么位置。

包package

文件夹被python解释器视作package需要满足两个条件:

1、文件夹中必须有__init__.py文件,该文件可以为空,但必须存在该文件。

2、不能作为顶层模块来执行该文件夹中的py文件(即不能作为主函数的入口)。顶层模块即是我们通常说的"main"方法所在的模块及其同级模块,其中"main"也常被称为主模块,即主模块所在文件夹不会被视作package。主模块的同级package被python解释器视为顶级包(也就是top-level package)。

如果想要导入顶层包更上层的包或者模块,需要将包路径添加到sys.path中

第一点很容易理解,下面详细介绍一下第二点。

脚本 & 模块?(script vs module)

python有两种加载文件的方法:一种是作为顶层的脚本,另一种是当做模块。如果你直接执行这个程序,那么这个文件就被当做是顶层脚本来执行了,在命令行里面输入 python myfile.py 就是这个情况。如果你输入python -m myfile.py或者在其他的文件当中使用import来导入这个文件的时候,它就被当做模块来导入。在同一时间里,只有一个主模块,主模块常被称为顶层脚本,顶层脚本可以这样解释:它是一个能够让你的程序从这里开始的python文件。

将模块作为脚本执行

test_script.py

1
2
3
4
5
6
7
8
# test_script.py
def fun1():
print("I'm fun1")
def fun2():
print("I'm fun2")
if __name__ == "__main__":
fun1()
fun2()

脚本中的__main__="name"下的代码仅在模块作为“主”文件执行时才运行:

1
2
3
$ python test_script.py
I'm fun1
I'm fun2

如果module是作为导入的模块,则不会执行该模块的__main__代码:

1
2
>>>import fibo
>>>

这通常用于为模块提供方便的用户界面,或用于测试目的(将模块作为脚本执行测试套件运行)。

模块的名称

当一个文件被加载进来,它就有一个名称(这个名称存储在__name__属性当中)。如果这个文件被当做一个主模块来执行,那么它的名字就是__main__。如果它被当做一个模块加载,那么它的名称就是文件名称,加上它所在的包名,以及所有的顶层的包名,这些名称中间是用点号隔开的。

比如下面的例子

1
2
3
4
5
6
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleA.py

比如你导入moduleX(from package.subpackag1 import moduleX),它的名称就package.subpackage1.mouleX。如果你导入moduleA的时候(from package import moduleA),它的名称就是package.moudleA。

(注:这里是使用包导入,即把package以及里面的所有文件看做一个包,导入的时候使用from xxx import yyy的形式来进行,我们调用第三方包的时候就是这种情况),

但是,当你直接从命令行里面运行moduleX的时候,他的名称则被替换为__main__。如果你直接从命令行运行moduleA,它的名称也是__main__。当一个模块被当做一个顶层脚本来执行的时候,它原来的名称则会被__main__取代。

结论

当一个模块的名称中没有包,也就是只有文件名的时候,说明这个模块是一个顶层模块。顶层模块中不能使用相对导入。相对导入使用模块的名称属性来决定模块在包层次结构中的位置,相对导入能向上相对多少级,完全取决于模块名称中有多少层。

当你运行交互式的解释器的时候,交互式进程的名称永远是__main__,因此你不能在交互式进程当中使用相对导入。相对导入只能在模块文件当中使用。