Fork me on GitHub

python 浅析模块,包及其相关用法

    今天买了一本关于模块的书,说实话,模块真的太多了,小编许多也不知道,要是把模块全讲完,可能得出本书了,所以小编在自己有限的能力范围内在这里浅析一下自己的见解,同时讲讲几个常用的模块。

   

  这里是2018.3.6对此文章更新,为了让此文章更详细,此前文章标题为python 浅析模块的导入和调用,写于此示己。

一,模块

什么是模块?

  在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里面,代码会越来越长,越来越不容易维护。

  为了编写可以维护的代码,我们把很多函数分组,分别放到不同的文件,这样,每个文件包含的代码就相对比较少,很多编程语言都采用这种组织代码的方式。在python中,一个.py文件就称之为一个模块。

 

使用模块有什么好处?

  1,最大的好处就是大大的提高了代码的可维护性,其次,编写代码不必从零开始,当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括python内置的模块和来自第三方的模块。

  2,使用模块还可以避免函数名和变量名之间的冲突,每个模块有独立的命名空间,因此相同名字的函数和变量完全可以分别存在不同的模块中,所以,我们自己编写模块时候,不必考虑名字会和其他模块冲突。

模块分类

  模块分为三种:

  • 内置标准模块(又称为标准库),执行help('modules')查看所有python自带模块列表
  • 第三方开源模块,可以通过pip install modules  联网安装
  • 自定义安装

下面介绍一下如何导入模块:

   下面是常用的几种导入模块的方式:

模块调用的方法
            import module
            
            from module import xx
            
            from module.xx.xx   import xx  as rename
        
            from module.xx.xx  import  *

 

  注意:模块一旦被调用,即相当于执行了另一个py文件里面的代码

 

       在Python 中用关键字import来导入某个模块:

          import  modname

       比如要导入模块numpy,就可以在文件最开始的地方用import numpy 来引入。在调用numpy模块的函数时,必须这样引入:

         模块名.函数名

 

import numpy        

numpy.arange(5)

 

        与第一种方法的区别是:funcname被直接导入到本地名字空间取了,所以他可以直接使用,而不需要加上模块名的限定 * 表示,该模块的所有公共对象(public objects)都被导入到当前的名称空间,也就是任何只要不是以    "_"   开始的东西都会被导入。

         为什么必须加上模块名这样调用呢?因为可能存在这样一种情况,在多个模块下含有相同名称的函数,此时如果只是通过函数名来调用,解释器无法知道到底要调用那个函数,所以如果像上述这种情况引入模块的时候,调用函数必须加上模块名。

       有时候我们只需要引入模块中的某个函数,只需要引入该函数即可,此时通过下面语句实现:

        form modname   import    funcname

      (form  modname  import    fa,fb,fc

         form modname  import  *      但是这个得谨慎使用         )

      当然可以通过通过这种方法不仅可以引入函数,还可以引入常量,通过这种方式引入的时候,调用函数时只能给出函数名,不能给出模块,但是当两个模块中含有相同名称函数的时候,后面一次引入会覆盖前一次引入。所以有以下建议:

   1) 如果你经常访问模块的属性和方法,且不想一遍又一遍的敲入模块名称,使用  form  module import 

   2) 如果你想要有选择性地导入某些属性和方法,而不想要其他的,使用  form  module import 

   3)如果模块包含的属性和方法与你的某个模块同名,你必须使用  import module 来避免名字冲突

  4)尽量少使用   form  module import  *,因为判定一个特殊的函数或属性是从哪里来的有些困难,并且会造成调试和重构都更困难

      还有一种导入模块的方法就是内建函数 _import_ ()

      除了前两种使用 import 导入的方法以外,我们还可以使用内建函数来导入 module。两者的区别是: import 后面必须跟的是一个类型(type),而 _import_()的参数是一个字符串,这个字符串可能来自配置文件,也可能是某个表达式计算结果。例如:

           mymodule =  _import_ ( 'module_name ' )

 一般情况应该使用import , 但有几个例外 
      1)module文档告诉你要用from-import的 
      2)导入一个包组件。需要一个包里面的某个子模块,一般用from A.b import c比import A.b.c 更方便 且不会冒混淆的危险.

      你也许会想到,如果不同的人编写的模块名称相同怎么办?为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)

     包的引入解决了模块名冲突,就是只要顶层的包名字不与别人冲突,那么模块都不会与别人冲突。

    注意:每个包目录下都会有一个  _init_.py  的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。 _init_.py 可以是空文件,也可以是有Python代码,因为  _init_.py  本身就是一个模块,对于我下面的项目来说,模块名就是   mycompany

 

 模块查找路径

  初学者会发现,自己写的模块只能在当前路径下的程序里面才能导入,换一个目录再导入自己的模块就报错了,,这是为什么呢?

  答案就是:这与导入路径有关

import sys

print(sys.path)

  输出结果入下(这是windows下的目录):

['D:\\pycode\\模块学习', 'D:\\pycode', 'D:\\python3\\python36.zip',
 'D:\\python3\\DLLs', 'D:\\python3\\lib', 
'D:\\python3', 
'D:\\python3\\lib\\site-packages', 
'D:\\python3\\lib\\site-packages\\win32', 
'D:\\python3\\lib\\site-packages\\win32\\lib',
 'D:\\python3\\lib\\site-packages\\Pythonwin']

  所以python解释器就会按照列表顺序去依次到每隔目录下去匹配你要导入的模块名,只要在一个目录下匹配到了该模块名,就立马不继续往下找,如果找完了依旧没有找到,那么就会报错。

  (注意,可能是linux下,列表的第一个元素为空,即代表当前目录,所以你自己定义的模块在当前目录会被优先导入)

 

模块的调用

  首先举个例子:

def  main()
    pass

mian()

  如果直接在模块里面调用这个函数,那么,假设其他模块需要import当前模块,那么这个程序也会执行,但是其他模块想要的只是import而已,所以显然,这样是不可取的,我们需要将这种调用方式改成下面这种。

  

if __name__  == '__main__':
    main()

  这样的话,只有当前模块被执行的时候,这个函数才被调用,程序才执行。

 二,包(package)

  注意:如果代码出现  .pyc 文件,这个不需要担心,因为 pyc文件是py文件编译后生成的字节码文件,不需要自己创建,在你成功导入包后并运行成功后会自动生成,无需理会。

包的导入

  当你的模块文件越来越多的时候,就需要对模块文件进行划分,比如把负责跟数据库交互的都放在一个文件夹,把页面交互相关的放在一个文件夹。

└── my_proj
    ├── crm #代码目录
    │   ├── admin.py
    │   ├── apps.py
    │   ├── models.py
    │   ├── tests.py
    │   └── views.py
    ├── manage.py
    └── my_proj #配置文件目录
        ├── settings.py
        ├── urls.py
        └── wsgi.py

  比如上面这样的,一个文件夹管理多个模块文件,这个文件夹就称为包,那么不同的包之间如何相互导入呢?

  包就是文件夹,但该文件夹下必须存在 __init__.py 文件, 该文件的内容可以为空。__int__.py用于标识当前文件夹是一个包

from crm import views

views.sayhi()

其中crm/views.py内容如下:

def sayhi():
    print('hello world!')

 

跨模块导入包

目录结构如下:

├── __init__.py
├── crm
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   ├── views.py  
├── manage.py   
└── proj
    ├── __init__.py
    ├── settings.py
    ├── urls.py
    └── wsgi.py

  根据上面的结构,如何实现在crm/views.py里导入proj/settings.py模块?

from proj import views


结果:
Traceback (most recent call last):
  File "D:/crm/views.py", line 1, in <module>
    from proj import views
ModuleNotFoundError: No module named 'proj'

  为什么直接导入会报错呢?

是因为路径找不到,proj/settings.py 相当于是crm/views.py的父亲(crm)的兄弟(proj)的儿子(settings.py),settings.py算是views.py的表弟啦,在views.py里只能导入同级别兄弟模块代码,或者子级别包里的模块,根本不知道表弟表哥的存在。这可怎么办呢?

  答案是添加环境变量,把父亲级的路径添加到sys.path中,就可以了,这样导入 就相当于从父亲级开始找模块了。

os.path.dirname(path)  返回path的目录。其实就是os.path.split(path)的第一个元素

  crm/views.py中添加环境变量

import sys
import os

BASE_DIR = os.path.dirname(os.path.dirname(__file__))
#__file__的是打印当前被执行的模块.py文件相对路径,注意是相对路径
print(BASE_DIR)
sys.path.append(BASE_DIR)

from proj import settings

  注意:此时在proj/settings.py写上import urls会有问题么?

  答案是肯定会有问题的,因为现在的程序入口是views.py , 你在settings.py导入import urls,其实相当于在crm目录找urls.py,而不是proj目录,若想正常导入,要改成如下:

DATABASES= {
    'host':'localhost'
}

from proj import urls  #proj这一层目录已经添加到sys.path里,可以直接找到
print('in proj/settings.py')

  这里就涉及了绝对导入和相对导入了,下面直接说一下

绝对导入和相对导入

  在linux里可以通过cd ..回到上一层目录 ,cd ../.. 往上回2层,这个..就是指相对路径,在python里,导入也可以通过..

  例如:

├── __init__.py
├── crm
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   ├── views.py  #from ..proj import settings 
├── manage.py   
└── proj
    ├── __init__.py
    ├── settings.py #from .import urls  
    ├── urls.py
    └── wsgi.py

  views.py里代码

from ..proj import settings
def sayhi():
    print('hello world!')

print(settings.DATABASES)

  执行结果报错了(linux)

Traceback (most recent call last):
File "my_proj/crm/views.py", line 4, in <module>
 from ..proj import settings
SystemError: Parent module '' not loaded, cannot perform relative import

  或者报错如下(windows):

Traceback (most recent call last):
D:/模块学习
  File "D:/crm/views.py", line 9, in <module>
    from .. import settings
ValueError: attempted relative import beyond top-level package

  

  其实这两个错误的原因归根结底是一样的:在涉及到相对导入时,package所对应的文件夹必须正确的被python解释器视作package,而不是普通文件夹。否则由于不被视作package,无法利用package之间的嵌套关系实现python中包的相对导入。

文件夹被python解释器视作package需要满足两个条件:

  1. 文件夹中必须有__init__.py文件,该文件可以为空,但必须存在该文件。
  2. 不能作为顶层模块来执行该文件夹中的py文件(即不能作为主函数的入口)。

所以这个问题的解决办法就是,既然你在views.py里执行了相对导入,那就不要把views.py当作入口程序,可以通过上一级的manage.py调用views.py

.
├── __init__.py
├── crm
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   ├── views.py  #from ..proj import settings 
├── manage.py  #from crm import views 
└── proj
    ├── __init__.py
    ├── settings.py #from .import urls  
    ├── urls.py
    └── wsgi.py

  事实证明还是不行,报错

ValueError: attempted relative import beyond top-level package

  但把from ..proj import settings 改成from . import models 后却执行成功了,为什么呢?

  from .. import models会报错的原因是,这句代码会把manage.py所在的这一层视作package,但实际上它不是,因为package不能是顶层入口代码,若想不出错,只能把manage.py往上再移一层。

正确的代码目录结构如下

packages/
    ├── __init__.py
    ├── manage.py #from my_proj.crm  import views
    └── my_proj
        ├── crm
        │   ├── admin.py
        │   ├── apps.py
        │   ├── models.py
        │   ├── tests.py
        │   ├── views.py  #from . import models;  from ..proj import settings 
        └── proj
            ├── __init__.py
            ├── settings.py
            ├── urls.py
            └── wsgi.py

  再执行manage.py就不会报错了。

       注意:虽然python支持相对导入,但对模块间的路径关系要求比较严格,处理不当就容易出错,所以并不建议在项目里经常使用。

三,If __name__ == "__main__" 函数

  我在写python,会常常见到if __name__ == "__main__"这行代码。但是它的目的是什么呢?

 

  在Python中,if __name__ == "__main__": 是一个常见的代码结构,用于判断一个Python脚本是否正在作为主程序运行,还是被作为模块导入到其他脚本中

3.1 if __name__ == "__main__":是什么意思

 

  当一个Python脚本被直接执行时,其特定的内置变量__name__会被设置为__main__。但如果这个脚本被导入到其他脚本中,那么__name__将会被设置为模块的名称(通常是文件名去掉后缀)。

 

  使用 if __name__ == "__main__": 结构的主要目的是为了在一个脚本中定义一些仅在该脚本作为主程序运行时才会执行的代码块,而当脚本被导入为模块时,这些代码块不会被执行。这样可以避免在导入模块时不必要的副作用。

  例如,考虑以下脚本文件”example.py“:

 

def some_function():
    print("This is a function.")

print("This is not in a function.")

if __name__ == "__main__":
    print("This will only be printed when the script is run directly.")
    some_function()

 

  当你直接运行 example.py 时,会看到输出:

 

 

This is not in a function.
This will only be printed when the script is run directly.
This is a function.

 

  但如果你在另一个脚本中导入 example.py,那么在导入的脚本中,if __name__ == "__main__": 下面的代码块将不会被执行。这样就可以防止不必要的输出或执行。

  总之,if __name__ == "__main__": 是一个Python的惯用用法,用于将脚本的主要执行逻辑与模块导入时的行为分隔开来。

3.2 如果不设置if __name__ == "__main__"在一个脚本里面

 

  首先呢,如果我们只是简单在一个脚本里面编写一些顺序执行的代码,并不需要将其封装成可重用的模块,那么可能没有必要使用 if __name__ == "__main__":。但在大多数情况下,遵循这个惯用法是一个好的实践,可以避免一些潜在的问题。所以我们常看到的脚本,有些人写,有些人不写,容易让初学者混淆。

  其次呢,如果你写的脚本复杂一些呢,就一个文件,有很多函数,如果在脚本中不设置 if __name__ == "__main__":,那么脚本中的所有代码将在脚本被导入为模块时都会被执行,而不管它是不是作为主程序直接运行的。这意味着脚本中的所有代码,包括定义的函数、变量、语句等,在导入该脚本为模块时都会立即执行。这可能会导致意外的结果,因为你预计只是想导入脚本的一部分功能,但实际上整个脚本都被执行了。

  为了避免这种情况,通常建议在脚本中使用 if __name__ == "__main__": 结构,将希望只在脚本作为主程序运行时执行的代码放在这个结构中。这样做可以确保在模块导入时不会执行不必要的代码,同时保持代码的清晰性和模块化。

posted @ 2017-09-12 15:06  战争热诚  阅读(1672)  评论(1编辑  收藏  举报