Python系列-5-模块

概述

关于模块的概念,都已有了概念,本章我们学习下python的模块, 看看它的模块化怎么玩儿。

python中的模块

在Python中模块分为以下几种:

  • 系统内置模块,例如:sys、time、json模块等等;
  • 自定义模块,自定义模块是自己写的模块,对某段逻辑或某些函数进行封装后供其他函数调用。注意:自定义模块的命名一定不能和系统内置的模块重名了,否则将不能再导入系统的内置模块了。例如:自定义了一个sys.py模块后,再想使用系统的sys模块是不能使用的;
  • 第三方的开源模块:这部分模块可以通过pip install进行安装,有开源的代码。pip 类似node中的npm。 pip 默认源为https://pypi.org,使用时可以设置国内镜像源 pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple进行加速。PS: python3 使用 pip3

定义模块

我们先看下如何定义一个模块,不同于nodejs,py中不需要特定的语法。模块内部的作用域是在模块内,如果两个不同的模块如果需要共享变量,只需要通过引入对应变量即可。我们先定义一个模块hello.py:

#!/usr/bin/env python3
# -*- coding=utf-8 -*-

listdata=[1,2,3]

def sayHi(name):
  print("Hello,"+name)
  listdata.append(name)

def foo(name):
  print("Hi! "+ name + ",I am foo")

这样,就定义好了一个模块,我们接下来看如何引用模块

引入模块

Python中引入模块的方式有以下几种:

import 语句

import module1[, module2[,... moduleN]

对于hello.py模块:

import hello

hello.sayHi('main') 
print(hello.listdata)

我们可以通过模块名称访问模块内部所有的变量与函数。

from .. import 语句

from 语句让你从模块中导入一个指定的部分到当前命名空间中:

from modname import name1[, name2[, ... nameN]]

对于hello.py模块:

from hello import listdata, sayHi

sayHi('main') 
print(listdata)

即可引入变量listdata 与 函数sayHi,可以直接调用,无须再加模块名称。

导入时也支持别名

from modname import name as otherName

对于hello.py模块:

from hello import sayHi as sayHello, listdata as listArr

sayHello('Tom')
print(listArr)

from .. import *

如果需要将模块中所有内容全部导入到当前命名空间中,需要使用:

from modname import *

对于hello.py模块:

from hello import *

sayHi('Tom')
print(listdata)

但是尽量避免这种引用方式,这样会将引用模块中所有的变量及函数引入到当前模块内,极大概率的会污染当前命名空间。

  • import * 无法导入以下划线开头的变量名
  • 如果定义了__all__, import * 只会导入__all__中指定的变量,无论是否以下划线开头

推荐直接使用 import, 语法简单且基本不会造成命名冲突

作用域

上面提到模块内部的作用域是在模块内,如果模块之前共享数据的话只需要引入对应变量即可:

import hello

# 执行sayHi函数
hello.sayHi('Tom')

# 修改listdata
hello.listdata.append("main")

print(hello.listdata)

运行结果:

 hello Tom
 [1, 2, 3, 'Tom', 'main']

可以看到 listdata 数据在hello.py模块中增加一条数据Tom,在当前模块内又被追加上数据main,相当于两个模块“共享”了这个数据。当然这种方式在nodejs中与ES6中都是通用的。

在python中没有类似public,private等关键词来修饰成员函数和成员变量,那怎么才能将函数或变量作为作为非公开的(private)?

非类定义

很多博客跟文档反复提到’_’开头的命名,但对于非类成员的定义,下划线开头的命名方式只对于import * 有效(上面有提到),其他引入方式仍是可以正常访问的,这个只能作为规范,只是“不建议”直接引用,而不是“不能”直接引用。

类定义

对于类成员的定义

我们定义一个类,放在hi.py中

class Hi(object):
  def __init__(self):
      self.__list=[1,2,3]
      self.list=[-1,-2]

  def add(self,name):
      self.__list.append(name)
      self.list.append(name)
      print("Hello,"+name)
      print(self.__list)

然后我们在main.py中执行

    from hello import Hi

    p = Hi()

    p.add("Tom")

    print(p.list)

运行结果为:

  Hello,Tom
  [1, 2, 3, 'Tom']
  [-1, -2, 'Tom']

如果我们再打印下内部__list属性:

   print(p.__list)   

此时就会报错:

  Traceback (most recent call last):
    File "main.py", line 19, in <module>
      print(p.__list)
  AttributeError: 'Hi' object has no attribute '__list'

导入模块的搜索路径

用import hello时,python会搜寻hello.py文件,搜索顺序如下:

  • 首先搜寻内置模块是否有hello(所以我们定义的模块名不要和内置模块相同)
  • 如果内置模块没有,则看下面这些目录里有没有(以下结果通过内置sys模块的sys.path属性获取):
      ['F:\\python-demo',
       'C:\\Users\\wmh\\AppData\\Local\\Programs\\Python\\Python37\\python37.zip', 
       'C:\\Users\\wmh\\AppData\\Local\\Programs\\Python\\Python37\\DLLs', 
       'C:\\Users\\wmh\\AppData\\Local\\Programs\\Python\\Python37\\lib', 
       'C:\\Users\\wmh\\AppData\\Local\\Programs\\Python\\Python37', 
       'C:\\Users\\wmh\\AppData\\Roaming\\Python\\Python37\\site-packages', 
       'C:\\Users\\wmh\\AppData\\Local\\Programs\\Python\\Python37\\lib\\site-packages']
    

执行导入模块命令时,会首先检查待导入的模块是否在当前已有模块之中,如果有则跳过import。因此模块之间相互引用不会导致无限循环

相对引用与绝对引用

python中的import分为绝对引用和相对引用两种。绝对引用就是上面我们采用的引用方式,对与相对引用,在nodejs中也经常遇到,不同的是:Python中只采用.来拼接

对于如下结构:

    home
    ├─── hello.py

我们可以这样引用

    from home.hello import Hi

.只能放在from后,不能放import后,更多用法请参考:python模块详解

if __name__ == '__main__'

经常会在别人的代码中发现if __name__ == '__main__', 我们接着上面的例子,执行如下代码:

import hello 

print(__name__)
print(hello.__name__)

运行结果:

__main__
hello

其实,if __name__ == '__main__'的含义就是:该模块直接执行时运行的代码,如果被引用时则不执行;这算是一种约定俗成的写法。

参考