鱼C论坛

 找回密码
 立即注册
查看: 1121|回复: 5

[知识点备忘] 第084讲:模块和包(中)

[复制链接]
发表于 2023-3-20 06:44:42 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

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

x
0. 本节视频




1. 温馨提示

如果在学习本节课的过程中遇到问题,可以在这个帖子下方提问哦~


想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-3-31 09:13:36 | 显示全部楼层
第一
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2023-5-28 17:33:05 | 显示全部楼层
所以这后面是不准备搞课堂笔记了吗
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-9-23 23:09:56 | 显示全部楼层
课代表呢~~
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-9-24 20:15:56 | 显示全部楼层
这节课整的课代表不好总结了
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

发表于 2023-11-5 16:57:56 | 显示全部楼层
帮大家写一下了
一、if __name__ == "__main__"

示例:这是提供摄氏度和华氏度相互转换的一个功能的一个Python源文件叫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}摄氏度")

执行代码可以直接看到测试的结果
#测试,0 摄氏度 = 32.00华氏度
#测试,0 华氏度 = -17.78摄氏度

如果试图在另一个源文件中导入模块就会出问题,比如我们现在创建一个新的源文件叫 TCtest.py
然后在 TCtest.py 这里导入 tc 这个模块,并且调用它相关的一个转换功能的一个函数

import tc

print(f"测试,32 摄氏度 = {tc.c2f(32):.2f}华氏度")
print(f"测试,99 华氏度 = {tc.f2c(99):.2f}摄氏度")

执行代码的结果
#测试,0 摄氏度 = 32.00华氏度
#测试,0 华氏度 = -17.78摄氏度
#测试,32 摄氏度 = 89.60华氏度
#测试,99 华氏度 = 37.22摄氏度
因为在导入的 tc 模块中它自身就有两行用于测试的打印语句,这里是当前文件和模块中的打印语句一并执行了
模块在导入过程中,是会先从头到尾执行一遍后,再导入模块中的所有语句

解决办法就是 if __name__ == "__main__": 因为当一个模块当作为脚本独立运行时,
它的 __name__ 属性就会被赋值为 "__main__",因此只要在模块执行代码之前判断 __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}摄氏度")
       
单独执行模块时:
#测试,0 摄氏度 = 32.00华氏度
#测试,0 华氏度 = -17.78摄氏度

此时 tc.py 作为模块导入时:
测试,32 摄氏度 = 89.60华氏度
测试,99 华氏度 = 37.22摄氏度
现在 TCtest.py 就打印这两条语句

那么如果 tc.py 作为模块的形式导入到其他程序中,此时 __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 调用时是 tc
#__name__的值是 t   #因为作为模块导入的时候,__name__ 的值是模块的名称,非 __main__
#测试,32 摄氏度 = 89.60华氏度
#测试,99 华氏度 = 37.22摄氏度

二、包
在实际开发中,一个大型的项目常常会涉及到非常多的源代码文件,如果将它们都放在一起的话,难免就容易产生混淆

Python发明了一个叫做package的东西,翻译过来叫包,它允许我们通过点(.)号将源文件组织成多个分级的形式
说白了就是它支持我们将模块分门别类,然后存储到不同的文件夹中

第一步:首先创建一个文件夹叫 TC,还是用上面2个例子来演示,然后把 tc.py 给放进去

第二步:如果是Python3.3以前的版本,需要在这个文件夹里边创建一个 __init__.py 的源文件,内容是空的
                注意:在Python3.3以前的版本必须要有 __init__.py 的源文件,在之后的引入了命名空间包的概念 __init__.py 不再强制要求了
       
第三步:在导入模块的时候使用点(.)号将包和模块分隔.

那来修改一下 TCtest.py 这个源代码,本身是导入模块的,现在多了一个文件夹
import TC.tc

print(f"测试,32 摄氏度 = {TC.tc.c2f(32):.2f}华氏度")
print(f"测试,99 华氏度 = {TC.tc.f2c(99):.2f}摄氏度")

在这个 __init__.py 这里边写入 print(f'__init__.py 被调用,值是 {__name__}')

运行 TCtest.py

#__init__.py 被调用,值是 TC
#__name__的值是 TC.tc
#测试,32 摄氏度 = 89.60华氏度
#测试,99 华氏度 = 37.22摄氏度
当这个包被导入的时候 __init__.py 就被调用,此时它的 name 的值呢是 TC.tc,作为负责初始化工作的文件,
甚至可以在里边定义属于包的全局变量---------------------------------------------

来测试一下,在这个 __init__.py 里面加两个变量
print(f'__init__.py 被调用,值是 {__name__}')
x = 520
y = 'dasdsa'

然后在模块(tc.py)中去进行访问这个 __init__.py 里面的全局变量,这个包的全局变量,在这里加一个函数调用
"""摄氏度 ->华氏度"""
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 这个包里边的,所以在模块中它是看不到,需要将其作为模块来使用才可以,否则会报错

把 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(TC.y)  # 传入模块的源文件也是可以访问到 模块包(TC)中的全局变量

执行的结果
#__init__.py 被调用,值是 TC
#__name__的值是 TC.tc
#测试,32 摄氏度 = 89.60华氏度
#测试,99 华氏度 = 37.22摄氏度
#520
#TC.y = dasdsa
这个全局变量比之前的全局变量又厉害了一点,因为它是跨文件的,

比如可以在 TCtext.py,就是在外部的源文件里边去修改 TC 模块中访问的 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.y = {TC.y}')

TC.x = 666
TC.tc.printx()

执行的结果
#__init__.py 被调用,值是 TC
#__name__的值是 TC.tc
#测试,32 摄氏度 = 89.60华氏度
#测试,99 华氏度 = 37.22摄氏度
#520
#TC.y = dasdsa
#666
这是一个跨文件级别的一个全局变量的一个修改

有些人会问,单个包还好,要万一有多个包层层叠加,那么在访问模块的时候不得费老劲儿了

这其实呢有两种方法可以解决问题
第一种:是使用上一节课教给大家的import ... as ...的语法给模块起一个别名,那么在访问的时候就直接使用别名就可以了

第二种:就是利用 __init__.py 这个包的构造文件,可以让包在导入的时候自动导入模块

打开 TC 包的 __init__.py  
print(f'__init__.py 被调用,值是 {__name__}')
x = 520
y = 'dasdsa'
import TC.tc # 这时候希望当这个包被导入的时候,这里导入TC.tc,它会自动导入这个包里边的一个模块

这样的话在 TCtest.py 这里边就不用导入 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.y = {TC.y}')

TC.x = 666
TC.tc.printx()

直接导入包就行了,因为导入这个包它就会调用这个 __init__.py 的这个构造文件,构造文件里边会导入里面的模块,所以相当于这里又省了一步操作
执行代码的结果:
#__init__.py 被调用,值是 TC
#__name__的值是 TC.tc
#测试,32 摄氏度 = 89.60华氏度
#测试,99 华氏度 = 37.22摄氏度
#520
#TC.y = dasdsa
#666

三、遏制 from ... import * 的附加伤害
from ...import * 这种导入语法很容易就会覆盖掉已有的变量,用这个需要谨慎
毕竟模块中包含了大量的其他程序,不需要用到的变量、函数和类直接全部导入,就会造成了命名空间的污染
因此,Python也有提供了一种方案来遏制这种导入语法的附加伤害,那就是 __all__ 属性
在模块中使用 __all__ 属性,就可以指定 from ... import * 所能导入的内容了

桌面创建个源文件叫 hallo.py
内容很简单,代码中定义了x和s两个变量,以及say_hallo()和say_hi()两个函数,
但通过 __all__ 属性,仅允许变量x和这个函数say hello(),可以使用 from ... import * 的语法进行导入
__all__ = ['say_hallo','x']

x = 250
s = "FishC"

def say_hallo():
    print('Hello FishC')

def say_hi():
    print('Hi FishC')
       
再创建一个源文件叫 call_hallo
在里边就使用 from hallo import * 的一种导入语法,然后尝试去访问x和ssay hello()和say_hi()
from hallo import *

print(x)
say_hallo()

print(s)
say_hi()

执行代码的结果:
#250
#Hello FishC
#Traceback (most recent call last):
  File "C:\Users\nidei\Desktop\a.py", line 6, in <module>
    print(s)
          ^
#NameError: name 's' is not defined
x变量被成功的访问到了,say hello()函数被成功的调用了,
但是它尝试调用这个 s 的时候,也就是在 hallo.py 的 __all__ 里面没有指定的 s 变量的时候,就报not defined了

如果这里不使用 from hallo import * 这种语法,直接 import hello as h
import hallo as h

print(h.x)
h.say_hallo()

print(h.s)
h.say_hi()

执行代码的结果:
#250
#Hello FishC
#FishC
#Hi FishC
现在恢复正常了
这就是 __all__ 属性起到的一个遏制的作用

四、__all__ 属性还可以作用于包的构造文件中
因为默认情况下,使用 from ...import * 的语法导入一个包,是无法直接访问其包含的模块的

现在有一个包叫 FC
包里面有3个模块,它们的内容是一样的,还有一个空的构造文件 __init__.py       
def say_hallo():
    print(f'f"Hello FishC.--{__name__}')

还有一个 FCtest.py 源文件,里面使用 from FC import *
from FC import *

print(dir())
执行代码的结果:
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
什么都没得到

FC 包里的 fc1、fc2、fc3 这些模块全部都没看到
这时候可以在构造文件(__init__.py)里边使用定义 __all__ 属性
__all__ = ['fc1','fc2'] # 比如包里有 fc1、fc2、fc3 三个模块,这里拿fc1、fc2 来测试一下

再次执行 FCtest.py 文件
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fc1', 'fc2']
这次多出了 fc1、fc2,看到了就是可以访问它了

再次修改 FCtest.py
from FC import *

print(dir())
fc1.say_hallo()
fc2.say_hallo()

执行代码的结果:
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'fc1', 'fc2']
f"Hello FishC.--FC.fc1
f"Hello FishC.--FC.fc2
这成功访问到了

五、总结
1、对于模块来说,如果没有定义 __all__ 属性,那么 from ... import * 的语法将导入模块中的所有东西
2、对于包来说,如果没有定义 __all__ 属性,那么 from ... import * 的语法则不导入包里面的任何模块
   如果包里面有些模块没有被 __all__ 定义到,那么 from ... import 模块名,
   不能和from ... import *一起,要重新写一条,如:from FC import *
                                                                                                  from FC import fc3
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 4 反对 0

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-11-21 18:32

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

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