days10-python-modules&& namespace学习

days10-python- modules && namespace 学习

前言:

在我们之前的练习中我们都是在一个.py文件中操作的,但这样是存在问题的, 我们怎么去使用别人定义好的一些函数与变量呢?我们定义的函数名与其他人重复了怎么办呢,我们怎么解决这种命名冲突呢?

示例:

def sum(a, b):
    print("sum....")


sum(1, 2)

python 内置了sum函数,那我们自己再定义一个sum函数会怎样呢?

其实,要解决上面的问题,我们就要引入模块,所谓的模块,我们可以简单的理解成就是扩展名为.py的文件。通过模块我们可以将一个庞大的、难以处理的编程任务分解为独立的、更小的、更易管理的子任务或模块的过程。然后可以像拼积木一样将各个模块拼凑在一起,来创建复杂的程序。

模块的创建:

创建模块一般有两种方式

1.用python语言写一个.py的文件,这也是我们创建自定义模块的主要方式。

  1. 用C语言编写模块并在运行时动态加载进python解释器中,就像re(正则表达式)模块一样。

本文我们主要学习第一种模块创建的方式,关于第二种我们暂不讨论。

举例:

我们创建一个名字为myMod.py的文件并插入以下代码

myMod.py

mod_name = '我的第一个模块呀'
test_list = [1, 2, 3, 6]


def foo(arg):
    print(f'arg = {arg}')

模块的使用:

在这个myMod.py模块中我们定义了一个 str类型的mod_name 、一个列表(test_list)、一个函数(foo),那么我们如何使用我们这个已经定义好的模块呢?

我们可以使用import 语句来实现模块导入

import语法:

import <module_name>[, <module_name> ...]

这里的module_name就是.py文件的名字哦!

使用示例:

首先我们新建一个days10.py的文件,并在该文件中使用已经定义好的myMod.py模块

import myMod

print(myMod.mod_name)
print(myMod.test_list)
myMod.foo('hello mod!!')

结果: image-20221005141352863

from import语法:

from <module_name> import <name(s)>

我们在上面的使用模块内定义的函数与变量时候都是通过模块名.前缀的方式访问,这样访问多少有点麻烦,我们还可以通过from import以下方式来直接在调用者中直接使用模块中的内容了呀。

使用举例:

from myMod import test_list, mod_name, foo

print(test_list)
print(mod_name)
foo("hello model")

甚至我们可以通过from <module_name> import *来导入模块中的全部内容,但我们最好是不要使用这种方式,因为当我们需要导入许多模块的名称<name(s)>,我们很有可能无意中覆盖其他模块已有的名称 <name(s)>

import 语法扩展

import 语句也可以写在函数定义中

列如:

def hi_model():
    from myMod import test_list as alist, mod_name as name, foo as foo_fuc
    print(alist)
    print(name)
    foo_fuc("hello model")


hi_model()

但注意:python3不支持函数定义语句中 import *语法

def hi_model():
    from myMod import *
    print(mod_name)
    print(test_list)
    foo("hello model")


hi_model()

结果:

File "D:\python_projects\days10.py", line 2
    from myMod import *
                      ^
SyntaxError: import * only allowed at module level

as语法:

import <module_name> as <alt_name>

有时候,我们嫌弃我们的模块名太长了,使用不方便,我们也可以使用as语法给我导入的模块取一个简单的别名哦

使用举例:

import myMod as model

print(model.test_list)
print(model.mod_name)
model.foo("hello model")

当然from import也支持 别名 ,语法如下:

from <module_name> import <name> as <alt_name>

例如:

from myMod import test_list as alist, mod_name as name, foo as foo_fuc

print(alist)
print(name)
foo_fuc("hello model")

模块作为脚本执行

if __name__ == '__main__':

试想下,我们在编写我们自己的模块代码的时候,我们有时想让它单独作为一个脚本执行,但又不想在它被import时候执行,那么我们应该怎么实现呢?我们先不忙回答先看个下面的列子吧!

我们回到我们一开始定义的myMod.py文件,并将它修改成如下内容打印模块中的__name__变量:

myMod.py

mod_name = '我的第1个模块呀'
test_list = [1, 2, 3, 6]
def foo():
    print(__name__)

我们试着在我们的另外一个.py文件中import myMod

import myMod
myMod.foo()

#输出:myMod

那我们直接在myMod.py直接执行打印__name__会怎样呢?

mod_name = '我的第1个模块呀'
test_list = [1, 2, 3, 6]
def foo():
    print(__name__)
print(__name__)

输出:

__main__

结论:

模块被import时候,模块中__name__变量是模块名,而模块直接被当成脚本运行时候模块中__name____main__

解释:

首先我们要明白,python并没有像其他语言(java、c)等有个main函数当作程序执行的入口,实际上python中是没有类似c与java的main方法的。python程序总是从上而下逐行运行的,在.py文件中,除了函数的定义语句外都会被认为是“main”方法中的内容从上而下执行。python是通过if __name__ == '__main__':来实现所谓其他语言的入口函数的功能,其本质也就是个if条件语句,判断成功就执行里面代码,失败就跳过执行。

其次:当.py文件作为模块导入时,Python解释器会将特殊的=变量__name__设置为模块的名称。然而,如果一个文件作为一个独立的脚本运行,__name__会被设置为字符串'__main__'。使用这种方式我们就可以用于区分模块是被当成一个单独的脚本文件执行,还是被import了。

mod_name = '我的第1个模块呀'
test_list = [1, 2, 3, 6]
def foo():
    print(__name__)

if __name__ == '__main__':
    print(__name__)
    foo()

模块的位置:

在上面那个例子中我们就使用了import myMod就将我们定义的myMod.py的模块给引入进来了,但它是如何找到这个myMod.py文件的呢?我们可没告诉解释器我们定义的myMod.py的路径呀

在python中我们可以通过模块的__file__获取模块所在位置。一旦模块被导入,我们就可以通过模块的__file__属性确定模块的所在位置。

示例:

import myMod
print(myMod.__file__) #D:\python_projects\myMod.py

那是任意位置它都能找到吗?其实当我们执行import myMod时候,python解释器会默认到下面的几个路径去找,

  • 当前运行脚本所在的目录,如果解释器正在以交互方式运行,则为当前目录。

  • PYTHONPATH 环境变量所指向的路径。

  • 标准库的目录。

  • 任何能够找到的.pth文件的内容

  • 第三方扩展的site-package目录

那我自己都不知道上面这些路径在哪儿,我如何查看呢?

我们可以通过从sys模块中的sys.path列表中获取,换句话就是我们的包要放在sys.path输出路径中才能成功导入哦,否则会报ModuleNotFoundError

示例:

import sys

print(sys.path)
#下面就是我的sys.path输出路径呀
['D:\\python_projects', 'D:\\python_projects', 'C:\\Users\\63534\\Desktop', 'E:\\python\\python310.zip', 'E:\\python\\DLLs', 'E:\\python\\lib', 'E:\\python', 'D:\\python_projects\\venv', 'D:\\python_projects\\venv\\lib\\site-packages']

我的myMod.py 是在D:\python_projects下面当然解释器是找的到呀。注意我执行的环境是在pycharm的虚拟环境中哦,所以我的path输出有工程路径哦,cmd环境下的解释器是没有工程路径的呀。

image-20221006112219374

使用举例:

在下面我主要演示模块中配置路径到sys.path的几种方式

模块在工程下的子目录下的配置

这种方式我们可以通过pycharm的Mark Directory as sources root 方式,将目录自动加到sys.path路径中。

Sources Root:让IDEA知道这个文件夹及其子文件夹中包含源代码,是需要编译构建的一部分。设置后后出现Unmark as Sources Root,点击可撤回。如果导入其他包中的py文件出现错误,无法显示需要导入包的文件,可以尝试使用此方法让IDEA知道这个文件夹及其子文件夹中包含源代码。

例如:我新建了一个model目录,并将model 执行 Mark Directory as sources root,用于实现我的模块的管理。

image-20221005171215262

配置PYTHONPATH环境变量的方式

示例:

这里我演示以下通过系统配置环境变量实现myMod.py的导入,我将之前的myMod.py从pycharm所在工程目录中移到桌面中,并删除model目录,结果当然是找不到报错了。

image-20221005165739660

1.通过cmd 下通过 set PYTHONPATH 命令查看是否配置环境变量

image-20221005160050594

2.配置 PYTHONPATH 环境变量

image-20221005170004550

3.重新打开cmd 通过 set PYTHONPATH 命令查看是否配置环境变量

image-20221005170114135

4.重启pycharm,一定要重启呀,来重新加载环境变量。

这种方式可能pycharm会提示包找不到,有警告,忽略就可以了

image-20221005170548315

配置.pth文件方式

路径配置文件的扩展名是”.pth”,它的作用是在该文件中放置一些需要让python解释器查找的路径,就是列出需要加载进sys.path中的路径。

Python 在遍历已知的库文件目录过程中(在遍历sys.path的路径的过程中要能够找得到才行哦!!!),如果见到一个* .pth 文件,就会将文件中所记录的路径加入到 sys.path 设置中,于是 .pth 文件所指明的库也就可以被 Python 运行环境找到了。所以这个文件不是乱放的,不要能够被解释器找得到的地方才行。

*.pth文件里面放置的其实就是几个目录。其中的每一行包含一个单独的路径,该路径会添加到sys.path列表中,”.pth”中的路径既可以是绝对路径,也可以是相对路径,如果是相对路径,则是相对于包含”.pth”文件的路径而言的。

示例:

我在我的工程路径D:\python_projects\venv\model.pth文件中配置了两个路径

image-20221006114641637

sys.path输出路径如下:

image-20221006114819845


sys.path.append()方法实现

当然你也可以通过 sys.path.append()方式实现手动添加到sys.path路径中

示例:

import sys
sys.path.append(r'C:\Users\63534\Desktop\__pycache__')
import myMod 
print(sys.path)
print(myMod.mod_name)
print(myMod.test_list)
myMod.foo('hello mod!!')

这种方式一定要将导入模块语句放到sys.path.append()方法的后面呀,否则还是找不到模块哦,但我们实际实践中一般不推荐这样做哦。

模块的搜素顺序:

那如果我将仅仅名字相同但内容不同的模块文件,同时放在sys.path下的几个目录中,python会优先导入哪一个呢?

一、导入模块的搜索顺序:
(1)首先导入内建模块。首先判断这个module是不是built-in即内建模块,如果是内建模块则引入内建模块,如果不是则在一个称为sys.path的list中寻找;

(2)在sys.path返回的列表按照以下先后顺序寻找。sys.path在python脚本执行时动态生成,它返回的是一个列表,这个列表包含了以下几部分。包括以下5个部分:

  • 程序的根目录(即当前运行python文件的目录)
  • PYTHONPATH环境变量设置的目录
  • 标准库的目录
  • 任何能够找到的.pth文件的内容
  • 第三方扩展的site-package目录

其实记不住sys.path也没关系啊,我们打印sys.path列表,就会发现,这个列表中的从左到右的排列顺序刚好是上面那个5部分的顺序呀,所以,只需要记住列表下标小的肯定会先导入呀。

模块的学习进阶

问题:

​ 在前面模块的位置一章我们分别使用了模块的两个属性__file__,和__name__,那模块的中的还有多少像__file__,和__name__这样的变量呢?他们是什么?他们是存在哪儿的呢?可以直接修改吗?

为什么直接import 模块,使用模块的内容必须要带上模块名前缀呢,不带前缀就报错呢?而from import语句使用模块中的内容可以不用带上模块名的前缀呢,带上模块名反而报错了呢?

from myMod import test_list, mod_name, foo
print(test_list)
print(mod_name)
myMod.foo("hello model")

输出结果:

[1, 2, 3, 6]
我的第1个模块呀
Traceback (most recent call last):
  File "D:\python_projects\days10.py", line 4, in <module>
    myMod.foo("hello model")
NameError: name 'myMod' is not defined

命名空间

所有的这些问题都跟python的命名空间有关系。

定义:

命名空间指的是从名字到对象的一个映射关系Python中很多命名空间的实现用的就是字典。 ,它的键就是符号名,它的值就是那些变量的值。不同命名空间是相互独立的,没有任何关系的,所以同一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响。

namespace ={"name1":object1, "name2":object2}

注意 上面这个符号名就是你代码中的变量名、函数名、类名等呀

命名空间的分类:

python 中的命名空间大致可以分成四类:

Built-In NameSpace

内置命名空间,python自带的内建命名空间,任何模块均可以访问,存放着内置的函数和异常。

Global NameSpace

全局命名空间,每个模块加载执行时创建的,记录了模块中定义的变量,包括模块中定义的函数、类、其他导入的模块、模块级的变量与常量。

Enclosed Namespace

在函数内部定义函数时,它会创建一个封闭的名称空间,典型列子就是嵌套函数。

Local NameSpace

局部命名空间,每个函数、类所拥有的命名空间,记录了函数、类中定义的所有变量。

命名空间的生命周期:
  • Built-In NameSpace:内置命名空间在Python解释器启动时创建,解释器退出时销毁。

  • Global NameSpace: 全局命名空间在模块被解释器读入时创建,解释器退出时销毁;

  • Enclosed Namespace、Local NameSpace: 在函数被调用时创建,函数返回结果或抛出异常时被销毁(每一个递归函数都拥有自己的命名空间 。

注意: 当函数被销毁时,Python可能不会立即回收分配给这些命名空间的内存,但是对它们所包含的对象的所有引用都不再有效。

作用域:

因为多个不同名称空间的存在意味着在Python程序运行时,一个特定名称的多个不同实例可以同时存在。只要每个实例在不同的名称空间中,它们都是单独维护的,彼此之间不会相互干扰。

但这样会存在问题呀,你在代码中引用名称x,而x存在于多个不同名称空间中。Python怎么知道你指的是哪一个?

作用域:是指是命名空间的可见范围。作用域是针对命名空间而言,指命名空间在程序里的可应用范围,或者说是Python程序(文本)的某一段或某几段,在这些地方,某个命名空间中的名字可以被直接引用。记住:只有函数、类、模块会产生新的作用域

命名空间的查找顺序

当我们要使用一个变量名的时候,python会下图的顺序(legb)依次在相应的命名空间中搜索变量名,如果都找不到将会抛出一个 NameError的异常。所以在局部命名空间中,可以看到局部命名空间、嵌套命名空间、全局命名空间、内建命名空间中所有定义的变量。

t

Local -> Enclosed -> Global -> Built-in

python-variable-scope-example.png

举例:

x = 10

def outer():
    x = 20
    print(f'x is {x}')
    def inner():
        x = 30
        print(f'x is {x}')
        print(len("abc"))

    inner()
outer()
print(f'x is {x}')

输出:

x is 20
x is 30
3
x is 10
命名空间的查看
dir() or dir(modname)

内置函数dir()返回命名空间中定义好的的名称列表。在不带参数的情况下,它会在当前命名空间中生成一个按字母顺序排序的名称列表,当给出当参数指定为模块名时,dir()会列出模块中定义的名称。

示例:

print(dir())
a = 1
import myMod

print(dir())
print(dir(myMod))

输出:

['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'myMod']
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'foo', 'mod_name', 'test_list']

我们也可以通过来打印查看内置命名空间中的名称。

print(dir(__builtins__))
globals()

Python提供了名为globals() 访问global Namespace。

image-20221005220918666

我们也可以通过字典的k直接访问

image-20221005221059419

还可以通过直接修改global()字典

image-20221005221353570

locals()

局部命名空间可以 locals()来访问

def func1(i, str):
    x = 12345
    local_list = [1, 2, 3, 4, 5]
    print(locals()) 


func1(1, "first")
#locals()
#输出:{'i': 1, 'str': 'first', 'x': 12345, 'local_list': [1, 2, 3, 4, 5]}

当然locals()函数本身没有什么命名空间的限制,你就是在全局命名空间中使用也是可以的 ,只不过失去了访问local space对象的作用而已

globals()locals()的差异

globals函数返回的是globals namespace字典的引用,所以对 globals 所返回的 dictionary 的任何的改动都会直接影响到globals namespace,而locals函数 实际上没有返回局部名字空间,它返回的是一个拷贝。所以对它进行改变对局部名字空间中的变量值并无任何影响。

结果:

回想一下 from module import import module 之间的不同。

使用 import module,模块自身被导入,但是它保持着自已的名命空间,这就是为什么需要使用模块名来访问它的函数或属性的原因。但是使用 from module import function,实际上是从另一个模块中将指定的函数和属性导入到我们自己的命名空间,这就是为什么可以直接访问它们却不需要引用它们所来源的模块。

glocalnonlocal关键字

当在一个函数内部为一个变量赋值时,并不是按照上面所说LEGB规则来首先找到变量,之后为该变量赋值。在Python中,在函数中为一个变量赋值时,有下面这样一条规则:

“当在函数中给一个变量名赋值是(而不是在一个表达式中对其进行引用),Python总是创建或改变 local namespace中的变量名,除非它已经在那个函数中被声明为全局变量.”

那么,若想要在函数中修改全局变量,而不是在函数中新建一个变量,此时便要用到关键字global了。

global

示例:

x = 20
def f():
    global x
    x = 40
    print(x)
f()
print(x)

输出:

40
40

其实上面的global 语句是等价于下面的

x = 20

def f():
    globals()['x'] = 40
    print(x)

f()
print(x)

nonlocal

关键字nonlocal的作用与关键字global类似,使用nonlocal关键字可以在一个嵌套的函数中修改嵌套作用域中的变量。

示例:

def f():
    x = 20
    def g():
        global x
        x=40
        print(x)
    g()
    print(x)
f()

输出:

40
20

显然 global 并未改变x变量的赋值,那是因为x不是定义在全局命名空间中所以不能直接用global修改呀!!

示例:将global换成nonlocal

def f():
    x = 20
    def g():
        nonlocal x
        x=40
        print(x)
    g()
    print(x)
f()

输出:

40
40

globalnonlocal的区别:

第一,两者的功能不同。global关键字修饰变量后标识该变量是全局变量,对该变量进行修改就是修改全局变量,而nonlocal关键字修饰变量后标识该变量是上一级函数中的局部变量,如果上一级函数中不存在该局部变量,nonlocal位置会发生错误(最上层的函数使用nonlocal修饰变量必定会报错)

第二,两者使用的范围不同。global关键字可以用在任何地方,包括最上层函数中和嵌套函数中,即使之前未定义该变量,global修饰后也可以直接使用,而nonlocal关键字只能用于嵌套函数中,并且外层函数中定义了相应的局部变量,否则会抛出NameError


参考资料:

Python中命名空间与作用域使用总结

Namespaces and Scope in Python

Python Modules and Packages – An Introduction

Python - Modules

Python Namespace and Variable Scope Resolution (LEGB)

python导入的模块搜索顺序详解


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值