《零基础入门学习Python》第050讲:模块:模块就是程序

目录

0. 请写下这一节课你学习到的内容:格式不限,回忆并复述是加强记忆的好方式!

测试题(笔试,不能上机哦~)

0. 说到底,Python 的模块是什么?

1. 我们现在有一个 hello.py 的文件,里边有一个 hi() 函数:

2. 你知道的总共有几种导入模块的方法?

3. 曾经我们讲过有办法阻止 from…import * 导入你的“私隐”属性,你还记得是怎么做的吗?

4. 倘若有 a.py 和 b.py 两个文件,内容如下:

5. 执行下边 a.py 或 b.py 任何一个文件,都会报错,请尝试解释一下此现象。

动动手(一定要自己动手试试哦~)

0. 问大家一个问题:Python 支持常量吗?


0. 请写下这一节课你学习到的内容:格式不限,回忆并复述是加强记忆的好方式!

这一讲将给大家介绍一个新的知识:模块。

(一)什么是模块?

模块是更高级的封装,说到封装,回顾一下我们之前学过哪些封装:

  • 容器  - >  数据的封装
  • 函数  - > 语句的封装
  • 类  - > 方法和属性的封装
  • 模块  - > 模块就是程序

没错,模块就是平常我们写的任何代码,保存每一个 .py 结尾的Python文件,就是保存了一个独立的模块。我们来句一个简单的例子:

我们在安装Python的目录下新建一个 hello.py 文件,我们在里面定义一个 hi() 函数,当我按下 Ctrl + S 将这个文件保存起来的时候,这就是一个独立的Python 模块了,大家要注意的是,为了使我们的 IDLE 能够顺利的找到这个模块,我们这个模块的位置应该跟我们调用的程序在同一个文件夹下。(注:关于从任何文件夹导入模块的方法,请参阅 -> Python如何从任何文件夹导入模块)

def hi():
        print('Hi, everyone!')

我们在IDLE里调用该模块:

>>> import hello

既然没有报错,就说明导入模块成功了,我们试着调用一下 hello 模块中的 hi() 函数:

>>> hi()
Traceback (most recent call last):
  File "", line 1, in 
    hi()
NameError: name 'hi' is not defined

出错了,这是为什么呢?从报错信息,我们可以知道错误的根源是Python找不到 hi() 这个函数。为什么会这样呢?我们明明在hello.py 文件中定义了 hi() 函数啊,其实这个问题跟我们这个 命名空间 这个概念有关。我们先来讲讲 命名空间 这个概念,你就知道问题所在了。那什么是命名空间呢?其实很好理解,我来讲个例子:

假如你们班有个叫小花的女孩,长得像个花瓶一样的漂亮,于是,你趁着2月14情人节这天,买通了学校广播站的同学,让他帮你播放爱的宣言,并约定于学校附近的XX宾馆OO房间等着她,但是呢,你意想不到的是,另一个班级恰好也有一个叫做小花的姑娘,长得略微惊悚,后边的剧情大家可以自行脑补了哈。

上边这个悲凉的故事反应的就是命名空间这个概念的重要性,如果说,我们在故事中引入了命名空间这个概念,那么广播站的同学应该是这么帮你播放爱的宣言的:

爱的宣言:世界上只有一个名字,使我这样牵肠挂肚,像是一根看不见的线,一头牢牢的系在我心尖上,一头攥在你手中,这个名字叫做 CSDN论坛的来自江南的妳......

这样的话,其他论坛的小花就不会带入了。我们这里 CSDN论坛,就是一个命名空间了。

所以,我们刚才的代码,应该把命名空间加上,才能够正常使用模块中的函数:

>>> hello.hi()
Hi, everyone!

相信这种用法大家都知道,我只是作为一个概念给大家强调一下。

(二)导入模块

我们知道 import + 模块名 是我们经常使用的方法,我们这里重新写一个例子来计算摄氏度和华氏度之间的转换,

#模块名:TemperatureConversion.py
def c2f(cel):
    fah = cel *1.8 + 32
    return fah
def f2c(fah):
    cel = (fah - 32) / 1.8
    return cel

然后再写一个文件来导入刚才的模块:

#文件名:calc.py
import TemperatureConversion

print("32摄氏度 = %.2f华氏度"%TemperatureConversion.c2f(32))
print("99华氏度 = %.2f摄氏度"%TemperatureConversion.f2c(99))

运行结果:

>>>
32摄氏度 = 89.60华氏度
99华氏度 = 37.22摄氏度

这就是我们的第一种方法:

  • 第一种:import 模块名
  • 第二种:from 模块名 import 函数名(from 模块名 import * 这种方法不建议使用,因为这样的话,命名空间的优势就荡然无存了,可能造成重名的困扰)
  • 第三种:import 模块名 as 新名字

这三种方法在我们之前讲解 EasyGui 的时候都已经详细讲解过了,忘了的同学可以返回去看一下。


测试题(笔试,不能上机哦~)

0. 说到底,Python 的模块是什么?

答:模块就是程序。没错,所谓模块就是平时我们写的任何代码,然后保存的每一个“.py”结尾的文件,都是一个独立的模块。

1. 我们现在有一个 hello.py 的文件,里边有一个 hi() 函数:

def hi():
    print("Hi everyone, I love FishC.com!")

请问我如何在另外一个源文件 test.py 里边使用 hello.py 的 hi() 函数呢?

答:只需要在 test.py 中导入 hello 模块(文件名 = 模块名)即可使用 hello.py 中的 hi() 函数。

2. 你知道的总共有几种导入模块的方法?

答:我们总共介绍了三种导入模块的方法。

第一种:import 模块名
第二种:from 模块名 import 函数名
第三种:import 模块名 as 新名字

3. 曾经我们讲过有办法阻止 from…import * 导入你的“私隐”属性,你还记得是怎么做的吗?

答:如果你不想模块中的某个属性被 from…import * 导入,那么你可以给你不想导入的属性名称的前边加上一个下划线(_)。不过需要注意的是,如果使用 import … 导入整个模块,或者显式地使用 import xx._oo 导入某个属性,那么这个隐藏的方法就不起作用了。

4. 倘若有 a.py 和 b.py 两个文件,内容如下:

# a.py
def sayHi():
    print("嗨,我是 A 模块~")

# b.py
def sayHi():
    print("嗨,我是 B 模块~")

那么我在 test.py 文件中执行以下操作,会打印什么结果?

# test.py
from a import sayHi
from b import sayHi

sayHi()

答:会打印“嗨,我是 B 模块~”,因为第二次导入的 b 模块把 a 模块的同名函数 sayHi() 给覆盖了,这就是所谓命名空间的冲突。所以,在项目中,特别是大型项目中我们应该避免使用 from…import...,除非你非常明确不会造成命名冲突。

5. 执行下边 a.py 或 b.py 任何一个文件,都会报错,请尝试解释一下此现象。

# a.py
from b import y
def x():
    print('x')

# b.py
from a import x
def y():
    print('y')

>>> 
Traceback (most recent call last):
  File "/Users/FishC/Desktop/a.py", line 1, in 
    from b import x
  File "/Users/FishC/Desktop/b.py", line 1, in 
    import a
  File "/Users/FishC/Desktop/a.py", line 1, in 
    from b import x
ImportError: cannot import name 'x'

答:这个是循环嵌套导入问题。无论运行 a.py 或 b.py 哪一个文件都会抛出 ImportError 异常。这是因为在执行其中某一个文件(a.py)的加载过程中,会创建模块对象并执行对应的字节码。但当执行第一个语句的时候需要导入另一个文件(from b import y),因此 CPU 会转而去加载另一个文件(b.py)。同理,执行另一个文件的第一个语句(from a import x)恰好也是需要导入之前的文件(a.py)。此时,之前的文件处于仅导入第一条语句的阶段,因此其对应的字典中并不存在 x,故抛出“ImportError: cannot import name 'x'”异常。

解决方案是直接使用 import 语句导入:

# a.py
import b

def x():
print('x')

# b.py
import a

def y():
    print('y')

a.x()

动动手(一定要自己动手试试哦~)

0. 问大家一个问题:Python 支持常量吗?

相信很多鱼油的答案都是否定的,但实际上 Python 内建的命名空间是支持一小部分常量的,比如我们熟悉的 True,False,None 等,只是 Python 没有提供定义常量的直接方式而已。那么这一题的要求是创建一个 const 模块,功能是让 Python 支持常量。

说到这里大家可能还是一头雾水,没关系,我们举个栗子。

test.py 是我们的测试代码,内容如下:

# const 模块就是这道题要求我们自己写的
# const 模块用于让 Python 支持常量操作
import const

const.NAME = "FishC"
print(const.NAME)

try:
    # 尝试修改常量
    const.NAME = "FishC.com"
except TypeError as Err:
    print(Err)

try:
    # 变量名需要大写
    const.name = "FishC"
except TypeError as Err:
    print(Err)

执行后的结果是:

>>> 
FishC
常量无法改变!
常量名必须由大写字母组成!

在 const 模块中我们到底做了什么,使得这个模块这么有“魔力”呢?大家跟着小甲鱼的提示,一步步来做你就懂了:

  • 提示一:我们需要一个 Const 类
  • 提示二:重写 Const 类的某一个魔法方法,指定当实例对象的属性被修改时的行为
  • 提示三:检查该属性是否已存在
  • 提示四:检查该属性的名字是否为大写
  • 提示五:细心的鱼油可能发现了,怎么我们这个 const 模块导入之后就把它当对象来使用(const.NAME = "FishC")了呢?难道模块也可以是一个对象?没错啦,在 Python 中无处不对象,到处都是你的对象。使用以下方法可以将你的模块与类 A 的对象挂钩。
'''
sys.modules 是一个字典,它包含了从 Python 开始运行起,被导入的所有模块。键就是模块名,值就是模块对象。
'''
import sys
sys.modules[__name__] = A()

代码清单:
 

# 该模块用于让 Python 支持常量操作
class Const:    
    def __setattr__(self, name, value):
        if name in self.__dict__:
            raise TypeError('常量无法改变!')
            
        if not name.isupper():
            raise TypeError('常量名必须由大写字母组成!')

        self.__dict__[name] = value

import sys
sys.modules[__name__] = Const()

 

你可能感兴趣的:(《零基础入门学习Python》第050讲:模块:模块就是程序)