51、Python之模块和包:Python的包和文件夹有何区别

引言

大学有云:“苟日新,又日新,日日新”。

看到一些教材或者文章,介绍到包的时候,一定会提到一定要在文件夹中新建一个__init__.py的文件,哪怕空文件也可以……

我只想说,有些人的知识真的是需要更新迭代了。

虽然,据我所知,有些古董级的Python项目代码甚至仍然是基于Python2.x环境的,毕竟有些代码虽然危如屎山,只要还能跑起来,一般人谁敢乱动……

本文的主要内容有:

1、隐藏式命名空间包与传统包

2、责任的转嫁

3、__init__.py文件是否还有必要

命名空间包与传统包

先解释下这两种包的含义:

1、传统包(Regular Package):在Python的早期版本(3.3之前),需要在文件夹中创建一个名为__init__.py的文件,哪怕是空文件,也要创建,因为这是当时识别包的唯一方式。

2、命名空间包(Namespace Package):在Python3.3及更高版本中,Python解释器可以自动识别包,不再需要通过__init__.py文件了。

通过前面两种包的解释,就能清楚地知道,在Python3.3及之后的版本中,创建包的时候,__init__.py是非必须的了。

所以,我们在通常情况下,我们只要用不同的文件夹进行.py文件(模块)的组织,既可以自动创建相应的包(本质上也是模块)对象了。

在PyCharm等IDE中,通过创建文件夹(Directory)或者创建Python包(Python Package),其实都是可以作为Python包的。不同的是,前者就是一个文件夹,作为命名空间包来使用;后者更多的保留传统包的形式,会在创建文件夹的同时,在文件夹下创建一个名为__init__.py空文件。

b2759b5048417220bbcb63bfa2e07a7b.jpeg

5f4aa5ada68a4d7c6045e20ebb5b6791.jpeg

责任的转嫁

之前要创建一个包,即使没有特殊需求,也必须添加一个名为__init__.py的空文件,因为只有这样,Python解释器才能识别到它是包。

其实,从责任界定的角度来看,是很不合理的。

本应该由解释器来承担的责任,被转移给了开发者。

之所以要创建在我们看来完全没有必要的空文件__init__.py,只是因为Python解释器识别、解析包的能力不足,所以开发者必须多做一些不必要的工作。

在信息系统领域,类似的“责任转嫁”的场景,最典型的应该就是大名鼎鼎的“验证码(CAPTCHA)”。

验证码的全称是“Completely Automated Public Turing test to tell Computers and Humans Apart”,也就是这是一种用于区分用户是计算机还是人类的技术。

这种技术的应用场景,更多的在于系统为了提高自身的安全性,防止自动化攻击、恶意注册、暴力破解等。

这种事情从本来看,就是信息系统没有能力来区分一个用户是计算机还是人类,只好让用户自己告诉系统自己是计算机还是人类。

但是从责任界定的角度,信息系统不应该将信息系统自身的防护工作的负担转嫁给用户,而应该尽量通过技术手段自行承担部分工作,以减少对用户体验的影响。

虽然,后续发展中,验证码技术,甚至被应用与图片标注、文字识别等工作的众包分包形式中,也产生了相对积极的作用。但是,从其出发点来看,显然是不合理的责任转嫁。

而习惯于这种责任转嫁,将不合理视为理所当然,自然是限制进步与创新的阻碍,不小心又扯远了……

__init__.py文件是否还有必要

既然已经有了命名空间包,这种新的识别、解析包的方式,__init__.py是否就彻底沦为时代更迭的残余,理应被彻底淘汰?答案显然是否定的。

前面的描述中,一直有这样一个语境的限定,“在没有特殊需求的情况下”。

在有特殊需求的场景中,是指当__init__.py文件不再是包被识别、解析的标识,由空文件变为非空文件时,该文件所能发挥的作用。

由于在导入包的时候,如果有__init__.py文件,这个文件一定会被优先执行。所以,在有需要的情况下,我们还是可以利用包中的__init__.py文件来做不少事情。

首先简单介绍一下__init__.py文件的相关内容:

1、__init__.py文件,在包被导入时,会自动运行。

2、类似于模块中的__all__可以控制模块被通过from 模块名 import *时的行为,包中的__init__.py文件中,也可以通过__all__控制这种*式导入的行为。

接下来,在包、模块导入的简化及公共模块导入的场景,来看下__init__.py文件能够发挥的作用。

首先看这样一个项目结构:

547c2cbc00ad7378dd62d85ed160e34a.jpeg

其中,pkg1/common.py中定义了一个公共函数:

def com_func():
    print('这是一个公共的函数')

pkg1/m1.py和pkg1/m2.py,定义了两个模块,这两个模块都要调用common.py模块中的com_func()函数。

m1.py文件:

from .common import com_func


def m1_func():
    print("m1_func start")
    com_func()
    print("m1_func end")

m2.py文件:

from .common import com_func


def m2_func():
    print("m2_func start")
    com_func()
    print("m2_func end")

main.py文件,为项目的入口文件:

from pkg1.m1 import m1_func
from pkg1.m2 import m2_func

if __name__ == '__main__':
    m1_func()
    m2_func()

程序的执行结果:

1f967dd836ae750cadf2be28985e91bc.jpeg

其中,存在一些重复的、或者相对繁琐的导入工作,比如m1和m2都要导入common.py中的功能,如果公共模块的功能比较多,或者有多个公共模块,或者多个模块要调用多个公共模块,则会有很多行重复的模块导入的代码。

在入口文件中,如果模块较多,则要写多行,如果包下又有自保,模块导入的路径也会过于冗长。

接下来,利用__init__.py文件对代码进行优化:

首先,项目结构这是在包pkg1下新增了一个__init__.py文件:

b96e67fb029f4043008cbe2a0333527e.jpeg

其中,__init__.py文件内容为:

__all__ = ['m1_func', 'm2_func']

from .common import com_func
from .m1 import m1_func
from .m2 import m2_func

m1.py文件:

from . import com_func


def m1_func():
    print("m1_func start")
    com_func()
    print("m1_func end")

m2.py的调整同m1.py

入口文件main.py的调整:

from pkg1 import *

if __name__ == '__main__':
    m1_func()
    m2_func()

执行结果,跟调整前完全一样,只是导入代码进行了简化。

当然,由于__init__.py的运行机制,我们也除了把包中的模块的功能暴露到包级别,还可以作为包的入口文件,进行一些统一的初始化配置等功能,这里就不再展开了。

总结

本文简单介绍了Python中的两种包:命名空间包和传统包,展开论述了关于责任转嫁的问题,最后介绍了__init__.py文件在命名空间包存在的情况下,仍然能发挥的作用。

感谢您的拨冗阅读!

b8ca805a27b401c45fca38304d064e50.jpeg

  • 15
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南宫理的日知录

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值