Python语法基础之. Task12 模块(17/17)

0. 前述

之前的编程,如果 Python 解释器退出再进入,那么先前定义的所有的方法和变量就都消失了。

为此 Python 提供了一个办法,把这些定义存放在文件中,为一些脚本或者交互式的解释器实例使用,这个文件被称为模块(Module)。

模块是一个包含所有定义的函数和变量的文件,其后缀名是.py。模块可以被别的程序引入,以使用该模块中的函数等功能。这也是使用 python 标准库的方法。

1. 什么是模块

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

创建一个 m_test.py 文件

# m_test.py
def hi():
    print('Hi everyone, I love python!')

2. 命名空间

命名空间因为对象的不同,有所区别,可以分为如下几种:

  • 内置命名空间(Built-in Namespaces):只要 Python 运行起来,它们就会存在。内置函数的命名空间都属于内置命名空间,所以,我们可以在任何程序中直接运行它们,比如id(),不需要做什么操作,可以直接使用。
  • 全局命名空间(Module:Global Namespaces):每个模块创建它自己所拥有的全局命名空间,不同模块的全局命名空间彼此独立,不同模块中相同名称的命名空间,也会因为模块的不同而不相互干扰。
  • 本地命名空间(Function & Class:Local Namespaces):模块中有函数或者类,每个函数或者类所定义的命名空间就是本地命名空间。如果函数返回了结果或者抛出异常,则本地命名空间会结束。

上述三种命名空间的关系:

img

程序在查询上述三种命名空间的时候,就按照从里到外,从局部到全局的顺序,即:Local Namespaces --> Global Namesspaces --> Built-in Namesspaces。

import m_test

m_test.hi()  # Hi everyone, I love python!
hi()  # NameError: name 'hi' is not defined

3. 导入模块

导入模块的重要性毋庸置疑,无论是自己写的模块的复用,还是导入第三方库进而引入期望的模块,都需要导入模块这项操作。

下面是写一个模块并导入的Sample:

创建一个模块:C2F.py (摄氏度<==>华氏度 转换)

# C2F.py
def c2f(cel):
    fah = cel * 1.8 + 32
    return fah

def f2c(fah):
    cel = (fah - 32) / 1.8
    return cel
  • 第一种方式:import 模块名
import C2F

print('32摄氏度 = %.2f华氏度' % C2F.c2f(32))
print('99华氏度 = %.2f摄氏度' % C2F.f2c(99))
# 32摄氏度 = 89.60华氏度
# 99华氏度 = 37.22摄氏度
  • 第二种方式:from 模块名 import 函数名
from C2F import c2f, f2c

print('32摄氏度 = %.2f华氏度' % c2f(37))
print('99华氏度 = %.2f摄氏度' % f2c(100))
# 37摄氏度 = 98.60华氏度
# 100华氏度 = 37.78摄氏度

如果需要调用的函数很多,可以用*导入,但是一般不推荐使用。

from C2F import *

print('37摄氏度 = %.2f华氏度' % c2f(37))
print('100华氏度 = %.2f摄氏度' % f2c(100))
# 37摄氏度 = 98.60华氏度
# 100华氏度 = 37.78摄氏度
  • 第三种:import 模块名 as 新名字, 主要是原名称太长简写处理。e.g. numpy=>np
import C2F as T

print('37摄氏度 = %.2f华氏度' % T.c2f(37))
print('100华氏度 = %.2f摄氏度' % T.f2c(100))
# 37摄氏度 = 98.60华氏度
# 100华氏度 = 37.78摄氏度

4. dir()函数

内置的函数 dir() 可以找到模块内定义的所有名称。以一个字符串列表的形式返回:

import sys

print(dir(sys))
'''
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', '__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '_base_executable', '_clear_type_cache', '_current_frames', '_debugmallocstats', '_enablelegacywindowsfsencoding', '_framework', '_getframe', '_git', '_home', '_xoptions', 'api_version', 'argv', 'base_exec_prefix', 'base_prefix', 'breakpointhook', 'builtin_module_names', 'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dllhandle', 'dont_write_bytecode', 'exc_info', 'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'get_asyncgen_hooks', 'get_coroutine_origin_tracking_depth', 'get_coroutine_wrapper', 'getallocatedblocks', 'getcheckinterval', 'getdefaultencoding', 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 'getsizeof', 'getswitchinterval', 'gettrace', 'getwindowsversion', 'hash_info', 'hexversion', 'implementation', 'int_info', 'intern', 'is_finalizing', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 'platform', 'prefix', 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'set_coroutine_wrapper', 'setcheckinterval', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 'version', 'version_info', 'warnoptions', 'winver']
'''

如果没有给定参数,那么 dir() 函数会罗列出当前定义的所有名称。

5. if __name__ == '__main__'

对于很多编程语言来说,程序都必须要有一个入口,而 Python 则不同,它属于脚本语言,不像编译型语言那样先将程序编译成二进制再运行,而是动态的逐行解释运行。也就是从脚本第一行开始运行,没有统一的入口。

首先设定一个 const.py 文件,用于定于PI的值,代码如下:

PI = 3.14

def main():
    print("PI:", PI)

main()
# PI: 3.14

接着,编写一个用于计算圆面积的 area.py 文件。area.py 文件需要用到 const.py 文件中的 PI 变量。这就需要从 const.py 中,把 PI 变量导入 area.py:

from const import PI

def calc_round_area(radius):
    return PI * (radius ** 2)

def main(r):
    print("round area: ", calc_round_area(r))

main(3)
# PI: 3.14
# round area:  28.26

这时我们发现: const.py 中的 main 函数也被运行了。但实际上我们不希望它被运行,因为 const.py 提供的 main 函数只是为了测试常量定义。

这时if __name__ == '__main__'派上了用场,我们把 const.py 改一下,添加if __name__ == "__main__"

PI = 3.14

def main():
    print("PI:", PI)

if __name__ == "__main__":
    main()
# PI: 3.14

单独运行 const.py,输出如下:

PI: 3.14

单独运行 area.py (半径设为2),输出如下:

round area:  12.56

此时:

__name__:是内置变量,可用于表示当前模块的名字。

import const

print(__name__)
# __main__

print(const.__name__)
# const

由此我们可知:如果一个 .py 文件(模块)被直接运行时,其__name__值为__main__,即模块名为__main__

所以,if __name__ == '__main__'的意思是:当 .py 文件被直接运行时,if __name__ == '__main__'之下的代码块将被运行;当 .py 文件以模块形式被导入时,if __name__ == '__main__'之下的代码块不被运行。

小结:**可以用__name__属性来使该程序块仅在该模块自身运行时执行。 **

6. 搜索路径

当解释器遇到 import 语句,如果模块在当前的搜索路径就会被导入。

import sys

print(sys.path)

# ['D:\\Coding\\PyCharm\\BasicPython', 'D:\\Coding\\PyCharm\\BasicPython', ……]

那么使用 import 语句的时候,Python 解释器是怎样找到对应的文件的呢?

这就涉及到 Python 的搜索路径,搜索路径是由一系列目录名组成的,Python 解释器就依次从这些目录中去寻找所引入的模块。

这看起来很像环境变量,事实上,也可以通过定义环境变量的方式来确定搜索路径。

搜索路径是在 Python 编译或安装的时候确定的,安装新的库应该也会修改。搜索路径被存储在 sys 模块中的 th 变量中。

7. 包(package)

包是一种管理 Python 模块命名空间的形式,采用"点模块名称"。

创建包分为三个步骤:

  • 创建一个文件夹,用于存放相关的模块,文件夹的名字即包的名字。
  • 在文件夹中创建一个 __init__.py 的模块文件,内容可以为空。
  • 将相关的模块放入文件夹中。

不妨假设你想设计一套统一处理声音文件和数据的模块(或者称之为一个"包")。

现存很多种不同的音频文件格式(基本上都是通过后缀名区分的,例如:.wav,.aiff,.au),所以你需要有一组不断增加的模块,用来在不同的格式之间转换。

并且针对这些音频数据,还有很多不同的操作(比如混音,添加回声,增加均衡器功能,创建人造立体声效果),所以你还需要一组怎么也写不完的模块来处理这些操作。

这里给出了一种可能的包结构(在分层的文件系统中):

sound/                          顶层包
      __init__.py               初始化 sound 包
      formats/                  文件格式转换子包
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  声音效果子包
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  filters 子包
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

在导入一个包的时候,Python 会根据 sys.path 中的目录来寻找这个包中包含的子目录。

目录只有包含一个叫做 __init__.py 的文件才会被认作是一个包,最简单的情况,放一个空的 __init__.py 就可以了。

import sound.effects.echo

这将会导入子模块 sound.effects.echo。他必须使用全名去访问:

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

还有一种导入子模块的方法是:

from sound.effects import echo

这同样会导入子模块: echo,并且他不需要那些冗长的前缀,所以他可以这样使用:

echo.echofilter(input, output, delay=0.7, atten=4)

还有一种变化就是直接导入一个函数或者变量:

from sound.effects.echo import echofilter

同样的,这种方法会导入子模块: echo,并且可以直接使用他的 echofilter() 函数:

echofilter(input, output, delay=0.7, atten=4)

注意当使用 from package import item 这种形式的时候,对应的 item 既可以是包里面的子模块(子包),或者包里面定义的其他名称,比如函数,类或者变量。

设想一下,如果我们使用 from sound.effects import * 会发生什么?

Python 会进入文件系统,找到这个包里面所有的子模块,一个一个的把它们都导入进来。

导入语句遵循如下规则:如果包定义文件 __init__.py 存在一个叫做 __all__ 的列表变量,那么在使用 from package import * 的时候就把这个列表中的所有名字作为包内容导入。

这里有一个例子,在 sounds/effects/__init__.py中包含如下代码:

__all__ = ["echo", "surround", "reverse"]

这表示当你使用 from sound.effects import *这种用法时,你只会导入包里面这三个子模块。

如果 __all__ 真的没有定义,那么使用from sound.effects import *这种语法的时候,就不会导入包 sound.effects 里的任何子模块。他只是把包 sound.effects 和它里面定义的所有内容导入进来(可能运行__init__.py里定义的初始化代码)。

这会把 __init__.py 里面定义的所有名字导入进来。并且他不会破坏掉我们在这句话之前导入的所有明确指定的模块。

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

这个例子中,在执行 from...import 前,包 sound.effects 中的 echosurround 模块都被导入到当前的命名空间中了。

通常我们并不主张使用 * 这种方法来导入模块,因为这种方法经常会导致代码的可读性降低。

8.作用域

在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。

正常的函数和变量名是公开的(public),可以被直接引用,比如:abcx123PI等;

类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author____name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名;

类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc__abc等;

之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。

private函数或变量不应该被别人引用,那它们有什么用呢?请看例子:

def _private_1(name):
    return 'Hello, %s' % name

def _private_2(name):
    return 'Hi, %s' % name

def greeting(name):
    if len(name) > 3:
        return _private_1(name)
    else:
        return _private_2(name)

我们在模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()函数不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:

外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。

10. 安装第三方库

廖老师的一篇文章

其实主要就是pip安装或者在Anaconda中安装,只有像安装jieba这样的非常用第三库,而且需要安装在特定的虚拟环境中(如Anaconda下创建的某个虚拟环境),才需要用到命令行控制。

写在最后

17天的Python基础又过了一遍,总结了程式化、概念,还需更多实践。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值