python模块和包


如果把所有的内容都放在一个文件里,随着项目的发展,想要编辑这个文件会变得非常困难,所以引入了 模块 的概念,便于管理。
假设当前项目的文件夹层次结构如下:

root
├── main.py
├── child
│   ├── __init__.py
│   └── hello.py
├── empty
└── children
	├── test
	│   ├── __init__.py
    │   ├── hello.py
   	│	└── world.py
	└── __init__.py

模块

python中单个*.py文件就是一个模块,*.py的文件名就是模块名。import语句是用来导入模块或者从模块里导入特定的类或函数。
假设当前定位在root/children/test/world.py,要导入同级目录下的hello.py模块中的内容

导入模块

假如hello.py模块中,包含一个Talk的类,若要在world.py模块中实例化一个Talk类,代码如下:

import hello
talk = hello.Talk()

导入模块中的某个类

import hello后任何在hello这个模块里的类或者函数,都可以通过hello.类名这个写法来访问。或者使用如下方式来导入一个类:

from hello import Talk
talk = Talk()

同时导入模块中的多个类

也可以在一行语句里同时导入多个类,如果hello.py模块中同时还包含一个叫Speek的类,可以通过如下方式来导入两个类:

from hello import Talk,Speek
talk = Talk()
speek = Speek()

导入模块中的所有内容

还有一种写法可以一次性从模块里导入所有它所拥有的类和函数:

from hello import *

不推荐这么做!使用import *会把一些意想不到的对象导入到本地的命名空间。假如多个模块中存在同名的类,使用该写法,会分不清引用的类是来自于哪个模块,增加维护的难度。

使用别名区分同名的类

假如world.py模块中已存在一个叫Talk的类,我们不想让两个名字冲突,那么可以使用别名的方式来重新命名便于区分:

from hello import Talk as TK
from world import Talk as TA
hello_talk = TK()
world_talk = TA()

随着项目发展成越来越多模块的集合,我们需要增加另外一层的抽象。一个包 就是放到一个文件夹里的模块集合。包的名字就是文件夹的名字。通过把一个名为__init__.py的文件(通常是空的)放到文件夹里使该文件夹成为一个 hello.pyworld.py两个模块放到了test包下。当在包之间导入模块或类时,导入模块有两种方式:绝对导入和相对导入。

绝对导入

绝对导入 需要指明这个模块、函数的完整路径。如果需要访问hello.py模块里的Talk类,使用如下代码:

import children.test.hello
talk = children.test.hello.Talk()
# 或者
from children.test.hello import Talk
talk = Talk()
# 或者
from children.test import hello
talk = hello.Talk()
# 或者
from children import test
talk = test.hello.Talk()

import语句使用点号作为分隔符来分隔包或者模块。
对于任何模块,这些语句都可以运行。可以在main.py里,也可以在world.py模块里。

相对导入

当目录层级比较复杂的时候,详细指明完整的路径就有点过于多余了,因为知道父模块的名称,就可以使用 相对导入
假设当前定位在root/children/test/world.py,要导入同级目录下的hello.py模块中的内容,可以使用相对导入:

from .hello import Talk
# 或者
from hello import Talk

hello前面的点号说明当前包里的hello模块。当前的包指的是包含目前正在编辑的world.py文件的这个包,即test目录。
如果想要引用root/child/hello.py模块里的Talk类,可以使用如下代码:

from ...child.hello import Talk

一个点代表当前目录,两个点代表上级目录,再多个点代表再上一级目录,以此类推。

Pycharm使用问题

问题情况说明

在Pycharm中使用相对导入会产生各种问题:

root/children/test/hello.py内容如下:

class Talk:
    def __init__(self):
        print("talk")
  1. root/children/test/world.py内容如下:

    from hello import Talk
    talk = Talk()
    

    world.pyhelloTalk底部会有红线提示错误,且无法点击跳转到源码,但是可以正常运行输出结果。
    在这里插入图片描述

  2. root/children/test/world.py内容如下:

    from .hello import Talk
    talk = Talk()
    

    world.pyhelloTalk底部没有红线,且能跳转到源码,但是运行报错。

    ModuleNotFoundError: No module named ‘_main_.hello’; ‘_main_’ is not a package

  3. root/children/test/world.py内容如下:

    from ..test.hello import Talk
    talk = Talk()
    

    world.pyhelloTalk底部没有红线,且能跳转到源码,但是运行报错。

    attempted relative import beyond top-level package

问题解决方案
  1. 第1种解决方案:
    第一种能正常运行,但对于有强迫症的人来说,有红线看着难受可以用如下方法去掉红线,同时可以跳转到源码:
    选择相对导入的那个文件夹test,右键 → \rightarrow Mark Directory as → \rightarrow Sources Root 后,选中的文件夹会变成蓝色,解决问题1。

  2. 第2、3种解决方案
    第2种可以去掉“.”解决问题,都是表达导入当前包下的模块。2和3问题可以视为同一种问题,都用了相对导入的方式。在world.py中启动运行报错,暂时没有找到解决方法,但是在项目根目录下main.py

    from children.test import world
    

    通过这种方式,能正常输出hello.py中的内容,不会报错。

  3. 推荐的解决方案
    使用绝对导入可以解决以上问题,强烈推荐。

问题分析

问题2: 在world.py中执行时,__name__这个变量值为”_main_”,而相对引用符号“.”就是对应__name__这个变量。当这个模块是在别的模块中被导入使用,此时”.”就是该模块所在的包路径。在world.py中执行时,因world.py是main函数,此时”.”变成了”_main_”,因此报错。
world.py内容如下:

from hello import Talk
print(__name__)
talk = Talk()

world.py中运行程序输出

_main_

main.py中运行程序输出

children.test.world

问题3:个人猜测Pycharm是将它的项目根目录作为执行路径,当在world.py中执行时,world.py所在的目录为根目录,如果使用相对路径,根目录没有父目录,因此报错。

所以推荐使用绝对导入的方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值