QQ登录

只需一步,快速开始

开启左侧

Python学习第七十八天—模块和包(中)

[复制链接]
15271953841 发表于 2024-5-4 22:06:03 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

x
本帖最后由 15271953841 于 2024-5-4 22:18 编辑

模块和包(中)

一、If __name__ ==  “__main__”
在阅读开源代码的时候,经常会看到if __name__ == “__main__”这个语句,这个__name__是什么?字符串”__main__”又是什么?
看一个源文件,在编辑模式下,tc.py,内容如下:
"""摄氏度 -> 华氏度"""
def c2f(c):
    f = c*1.8 + 32
    return f

"""华氏度 -> 摄氏度"""
def f2c(f):
    c = (f-32)/1.8
    return c

"""测试"""
print(f"测试,0摄氏度 = {c2f(0):.2f} 华氏度")
print(f"测试,0华氏度 = {f2c(0):.2f} 摄氏度")
若是临时编辑,在编辑模式下,保存为tc.py,运行后,我们可以看到测试的结果:正常运行:
===================== RESTART: D:/谷兴武/创客/python学习/源代码/tc.py ====================
测试,0摄氏度 = 32.00 华氏度
测试,0华氏度 = -17.78 摄氏度
但是我们试图在另一个源文件中导入改模块,就会出现问题:
比如,再创建一个新的源文件叫TCtest.py,内容是:
import tc

print(f"32摄氏度等于 {tc.c2f(32):.2f} 华氏度")
print(f"99华氏度等于 {tc.f2c(99):.2f} 摄氏度")
保存为TCtest.py,执行后,发现问题暴露了。
=================== RESTART: D:/谷兴武/创客/python学习/源代码/TCtest.py ==================
测试,0摄氏度 = 32.00 华氏度
测试,0华氏度 = -17.78 摄氏度
32摄氏度等于 89.60 华氏度
99华氏度等于 37.22 摄氏度
在TCtest.py只要两行打印语句,但是执行起来却打印了4行。
原因是:模块在导入的过程中,是会从头到尾执行一遍导入模块中的所有语句的。
当一个模块作为脚本独立执行的时候,它的__name__属性就会被赋值”__main__”,因此,只要在模块执行代码之前,判断__name__是否等于”__main__”就可以解决问题了,在tc.py中加入一句if __name__ == “__main__”条件判断:
"""摄氏度 -> 华氏度"""
def c2f(c):
    f = c*1.8 + 32
    return f

"""华氏度 -> 摄氏度"""
def f2c(f):
    c = (f-32)/1.8
    return c

"""测试"""
if __name__ == "__main__":
    print(f"测试,0摄氏度 = {c2f(0):.2f} 华氏度")
    print(f"测试,0华氏度 = {f2c(0):.2f} 摄氏度")
那么单独执行tc这个模块脚本的时候,测试语句是没有问题的,会被执行:
===================== RESTART: D:/谷兴武/创客/python学习/源代码/tc.py ====================
测试,0摄氏度 = 32.00 华氏度
测试,0华氏度 = -17.78 摄氏度
当我们把它作为模块导入的时候,只执行TCtest模块里面两行打印语句,而用于测试的两行语句并没有被执行:
=================== RESTART: D:/谷兴武/创客/python学习/源代码/TCtest.py ==================
32摄氏度等于 89.60 华氏度
99华氏度等于 37.22 摄氏度
那么tc.py作为模块的形式导入到其它程序中,它此时的__name__是什么呢?
在tc.py的”””测试”””前面添上一句:
print(f”__name__的值是{__name__}”)
把__name__的值打印出来,我们就可以窥探到它背后的真相了。
"""摄氏度 -> 华氏度"""
def c2f(c):
    f = c*1.8 + 32
    return f

"""华氏度 -> 摄氏度"""
def f2c(f):
    c = (f-32)/1.8
    return c

print(f"__name__的值是{__name__}")
"""测试"""
if __name__ == "__main__":
    print(f"测试,0摄氏度 = {c2f(0):.2f} 华氏度")
    print(f"测试,0华氏度 = {f2c(0):.2f} 摄氏度")
保存后,运行TCtest.py后,发现:
=================== RESTART: D:/谷兴武/创客/python学习/源代码/TCtest.py ==================
__name__的值是tc                     (看此时__name__的值是tc,因为作为模块导入的时候,__name__的值就是模块的名称,而非__main__
32摄氏度等于 89.60 华氏度
99华氏度等于 37.22 摄氏度

二、包
在实际开发中,一个大型的项目,常常需要涉及到很多源代码文件,如果将它们都放在一起的话,难免会产生混淆,这时候如果通过文件夹的形式,将这些源文件进行整理和分类,那感情就会很巴适了。只要我们有需求,Python就能够满足,Python发明了一个packages的东西,翻译过来就叫“包”,运行我们通过“.”号,将源文件组织成多个分级的形式,说白了,就是它我们将模块分门别类,然后存储到不同的文件夹里,怎么做呢?
首先,我们来创建一个文件夹,叫大写的TC,把源文件tc.py放进去,
然后,第二步,如果Python3.3以前的版本,我们需要在TC文件夹中创建一个__init__.py这么一个源文件,内容可以是空的,注意在Python3.3以前的版本必须要有这么一个__init__.py这么一个源文件,在之后的版本由于引入命名空间包的概念,__init__.py就不再强制要求了,但3.3之前,它是强制要求的,要不,Python就识别不出这个文件夹是“包”了。
第三步,在导入模块的时候,使用“.”号将“包”和“模块”分割,那么我们来修改一下这个TCtest.py这个源代码即可,它是导入这个模块的:
import TC.tc

print(f"32摄氏度等于 {TC.tc.c2f(32):.2f} 华氏度")
print(f"99华氏度等于 {TC.tc.f2c(99):.2f} 摄氏度")
保存后,执行一下,互动模式下显示正常,没问题:
=================== RESTART: D:/谷兴武/创客/python学习/源代码/TCtest.py ==================
__name__的值是TC.tc
32摄氏度等于 89.60 华氏度
99华氏度等于 37.22 摄氏度
那么,我们刚才提到了__init__.py,__init__这个名字是否有点耳熟,在前面学习的魔法方法中,__init__就是一个构造函数,在packages的概念中,它也存在类似的功能,我们可以通过编写该文件,对包进行初始化操作,打开__init__.py,输入一行代码:
print(f"__init__.py被调用,此时__name__的值是 {__name__}")
保存后,关闭(不关闭也行等待再次编辑),再在TCtest.py中点运行,看一下运行的结果:
__init__.py被调用,此时__name__的值是 TC
__name__的值是TC.tc
32摄氏度等于 89.60 华氏度
99华氏度等于 37.22 摄氏度
当我们的这个包被导入的时候,__init__.py被调用,此时它的__name__的值是TC,那么作为负责初始化工作的文件,那么我们甚至在里面可以定义属于“包”的全局变量,这里我们在__init__.py中加入两变量:
print(f"__init__.py被调用,此时__name__的值是 {__name__}")

x = 520
s = "FishC"

保存后,再打开tc.py,在tc.py中去访问__init__.py中的全局变量,这个包的一个全局变量,在tc.py中加入一个函数printX().
"""摄氏度 -> 华氏度"""
def c2f(c):
    f = c*1.8 + 32
    return f

"""华氏度 -> 摄氏度"""
def f2c(f):
    c = (f-32)/1.8
    return c

def printX():
    import TC
    print(TC.x)

print(f"__name__的值是{__name__}")
"""测试"""
if __name__ == "__main__":
    print(f"测试,0摄氏度 = {c2f(0):.2f} 华氏度")
print(f"测试,0华氏度 = {f2c(0):.2f} 摄氏度")
这时我们要注意了:我们是不能够直接执行这个脚本来访问这个包的全局变量,因为tc.py是在TC这个包里面,所以在模块中,自己是看不到这个包的,我们如果霸王硬上弓在tc.py的最后加上printX()
"""摄氏度 -> 华氏度"""
def c2f(c):
    f = c*1.8 + 32
    return f

"""华氏度 -> 摄氏度"""
def f2c(f):
    c = (f-32)/1.8
    return c

def printX():
    import TC
    print(TC.x)


print(f"__name__的值是{__name__}")
"""测试"""
if __name__ == "__main__":
    print(f"测试,0摄氏度 = {c2f(0):.2f} 华氏度")
    print(f"测试,0华氏度 = {f2c(0):.2f} 摄氏度")
printX()
保存后,点击运行,看一下结果,它是看不到的,报错:
=================== RESTART: D:\谷兴武\创客\python学习\源代码\TC\tc.py ===================
__name__的值是__main__
测试,0摄氏度 = 32.00 华氏度
测试,0华氏度 = -17.78 摄氏度
Traceback (most recent call last):
  File "D:\谷兴武\创客\python学习\源代码\TC\tc.py", line 20, in <module>
    printX()
  File "D:\谷兴武\创客\python学习\源代码\TC\tc.py", line 12, in printX
    import TC
ModuleNotFoundError: No module named 'TC'
我们需要怎么做呢?
我们需要将其作为模块来使用才可以,那么我们应该在TCtest.py中去调用,加入一行TC.tc.printX(),保存
import TC.tc

print(f"32摄氏度等于 {TC.tc.c2f(32):.2f} 华氏度")
print(f"99华氏度等于 {TC.tc.f2c(99):.2f} 摄氏度")

TC.tc.printX()
保存后,运行,问题不大,520这个全局变量被访问到,
=================== RESTART: D:/谷兴武/创客/python学习/源代码/TCtest.py ==================
__init__.py被调用,此时__name__的值是 TC
__name__的值是TC.tc
32摄氏度等于 89.60 华氏度
99华氏度等于 37.22 摄氏度
520
然后,我们在TCtest.py源文件中,我们也可以直接访问到“包”的全局变量的,在TCtest.py源文件的最后再加一句:print(f"TC.s = {TC.s}")
import TC.tc

print(f"32摄氏度等于 {TC.tc.c2f(32):.2f} 华氏度")
print(f"99华氏度等于 {TC.tc.f2c(99):.2f} 摄氏度")

TC.tc.printX()
print(f"TC.s = {TC.s}")
保存后,运行一下:
=================== RESTART: D:/谷兴武/创客/python学习/源代码/TCtest.py ==================
__init__.py被调用,此时__name__的值是 TC
__name__的值是TC.tc
32摄氏度等于 89.60 华氏度
99华氏度等于 37.22 摄氏度
520
TC.s = FishC
我们这时在TCtest.py源文件中就访问TC包的全局变量,这个全局变量比我们之前学的全局变量要厉害些,因为它是跨文件的,比如说:我们可以在TCtest.py外部的源文件中去修改tc.py模块中访问的TC.x值,在TCtest.py的最后,间隔一行,加入:
import TC.tc

print(f"32摄氏度等于 {TC.tc.c2f(32):.2f} 华氏度")
print(f"99华氏度等于 {TC.tc.f2c(99):.2f} 摄氏度")

TC.tc.printX()
print(f"TC.s = {TC.s}")

TC.x = 250
TC.tc.printX()              (再调用一次TC.tc.printX(),对比一下)
保存后,运行
=================== RESTART: D:\谷兴武\创客\python学习\源代码\TCtest.py ==================
__init__.py被调用,此时__name__的值是 TC
__name__的值是TC.tc
32摄氏度等于 89.60 华氏度
99华氏度等于 37.22 摄氏度
520
TC.s = FishC
250                 (上面还是520,下面我们改动了,这是一个跨文件级别的全局变量的修改)

万一有多个包层层叠加,可以采用impot…as…(别名)
另一种可以用__init__.py这个包构造文件,我们可以让包在导入的时候,自动导入模块,例如在__init__.py,加入最后一行:
print(f"__init__.py被调用,此时__name__的值是 {__name__}")

x = 520
s = "FishC"

import TC.tc
保存后,在TCtest.py中的第一句import TC.tc改成直接导入这个包就行了。
import TC

print(f"32摄氏度等于 {TC.tc.c2f(32):.2f} 华氏度")
print(f"99华氏度等于 {TC.tc.f2c(99):.2f} 摄氏度")

TC.tc.printX()
print(f"TC.s = {TC.s}")

TC.x = 250
TC.tc.printX()
导入这个包TC,它就会调入__init__.py的这个构造文件,构造文件它会导入里面的模块,相当于我们这里省了一步操作,保存后,运行,执行结果是一样的。
=================== RESTART: D:\谷兴武\创客\python学习\源代码\TCtest.py ==================
__init__.py被调用,此时__name__的值是 TC
__name__的值是TC.tc
32摄氏度等于 89.60 华氏度
99华氏度等于 37.22 摄氏度
520
TC.s = FishC
250

三、遏制from…import*的附加伤害
用这个需谨慎,
毕竟模式中包含了大量的其它程序不需要用到变量、函数和类,直接全部导入,就会做成命名空间的污染,因此Python也有提供了一种方案来遏制这种导入语法的附加伤害,那就是__all__属性,那在模式中使用__all__属性就可以指定from…import*所能导入的内容了。
那么我们创建一个hello.py的源文件(上节课曾创建过),内容是:
__all__ = ["say_hello","x"]        (通过__all__属性,我们仅允许变量x和函数say_hello()可以使用from…import*的语法进行导入)
x = 250
s = "FishC"
def say_hello():
    print("Hello Little sister.")

def say_hi():
    print("Hi Little sister.")
让我们测试一下,打开call_hello.py(上节课曾创建过)
from hello import *
print(x)
say_hello()
print(s)
say_hi()
int("250")
执行call_hello.py,运行一下,发现:
=================== RESTART: D:\谷兴武\创客\python学习\call_hello.py ==================
250                  (x变量被成功访问到)
Hello Little sister.         (say_hello()函数被成功调用了)
Traceback (most recent call last):
  File "D:\谷兴武\创客\python学习\call_hello.py", line 4, in <module>
    print(s)
NameError: name 's' is not defined         (但它尝试去访问这个s的时候,在__all__里面没有指定s变量的时候,它就是not defined)
但是如果call_hello.py中不用from hello import *这种语法(方案),而是用:
import hello as h
print(h.x)
h.say_hello()
print(h.s)
h.say_hi()
保存后,运行
=================== RESTART: D:\谷兴武\创客\python学习\call_hello.py ==================
250
Hello Little sister.
FishC
Hi Little sister.
这就没有问题了,这就是__all__属性起到的一个遏制作用了。

四、__all__属性还可以定义在包的构造文件中
因为默认情况下,我们使用from…import*的语法导入一个包是无法直接访问其包含的模块的
我们现在有一个包叫FC(就是一个文件夹),里面有三个模块,fc1.py、fc2.py、fc3.py,它们的内容都是一样的:
def say_hello():
print(f"Hello Little sister.--{__name__}")
包FC里面还有__init__.py这个空的构造文件
包FC的外面还有一个FCtest.py的源文件,内容是:
from FC import *
print(dir())
运行后,它得了什么,它什么也没有得到:
=================== RESTART: D:/谷兴武/创客/python学习/源代码/FCtest.py ==================
['__annotations__', '__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
包里面的fc1、fc2、fc3,它全部没有看到,
这个时候,我们可以在包FC里的__init__.py                                                                                                                                                          构造文件里面,使用定义__all__属性
__all__ = ["fc1","fc2"]
保存后,再回到FCtest.py里面,运行后,测试一下:
=================== RESTART: D:/谷兴武/创客/python学习/源代码/FCtest.py ==================
['__annotations__', '__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fc1', 'fc2']
可以看出多了'fc1', 'fc2'这两个模块可以被看到了。
那既然能看到了,就可以去访问它了。
修改FCtest.py为:
from FC import *
print(dir())
fc1.say_hello()
fc2.say_hello()

保存后,点击运行,执行一下,测试
=================== RESTART: D:/谷兴武/创客/python学习/源代码/FCtest.py ==================
['__annotations__', '__builtins__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fc1', 'fc2']
Hello Little sister.--FC.fc1
Hello Little sister.--FC.fc2           (成功的访问到了)

总结:对于模块来说,如果没有定义__all__属性,那么from…import*的语法将导入模块的所有东西。
对于包来说,如果没有定义__all__,那么from…import*的语法则不导入包里面的任何模块。



客服热线
400-1234-888 周一至周日:09:00 - 21:00
公司地址:襄阳市樊城区长虹路现代城5号楼188

创客帮MAKER.BAND青少年创客创意社区是一个融教育、科技、体育资讯为一体的综合服务平台,专注于教育创新、专注于科技体育、专注于教育资讯。

Powered by Discuz! X3.4 © 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表