从 JavaScript 角度学 Python(15) - 模组 (Module)

前言

接下来聊聊 Python 中的模组 (Module) ,毕竟 JavaScript 也有所谓的模组观念,

模组 (Module)

如果你有使用过 Webpack、Vue Cli 等等的工具,想必对於模组的概念就会有一定的认知与熟悉度,如果你还没有使用过 Webpack 等工具的话,或许你有看到 export default 这段程序码,例如:

/* app.js */
export default {
  sayHello() {
    console.log('Hello Ray');
  },
  myName: 'Ray',
  obj: {
    myName: 'Ray',
  },
  arr: [1, 2, 3],
};

不然就是 Node.js 开发者常用的 module.exports 语法:

/* exports.js */
module.exports = 'Hello World'

/* main.js */
const hello = require('exports');

console.log(hello); // Hello World

而现阶段比较有名的应该是 ESM (ES6 Modules or JavaScript Modules),如果你不熟悉 ESM 的话,你也可以考虑阅读我先前写的文章 什麽是 ESM(ES6 Modules or JavaScript Modules) 呢? 这边会有更详细的介绍。

所以我这边也简单的做一下关於模组的小结论:

模组的概念简单来讲就是一个模组代表着一个档案,而这个档案内通常会包含许多可以给予我们使用的函式也可以说是功能等等,而这个档案可以让我们在不同的之间档案之间引入使用。

举例来讲,我们可能有五个 Python 档案,都会使用到加法与打招呼这个功能:

# ch1.py
print(1 + 2)
print(f'Hello Ray!')

# ch2.py
print(10 + 23)
print(f'Hello Mike!')

# ch3.py
print(12 + 6)
print(f'Hello Ming!')

# ch4.py
print(105+ 9)
print(f'Hello Jack!')

# ch5.py
print(189 + 256)
print(f'Hello Charles!')

看起来是不是好像没有什麽了不起的对吧?那麽你试想一下,当我们如果要使用的的页面高达 100 页,那麽势必你可能要写相同的程序码 100 次,当然你也有可能想说,好像也还好一直复制贴上就好了,假设今天你已经复制贴完 100 页了,然後隔天老板跟你说要你把 Hello 改成中文是不是又要改一百次?或许你可能会想说:「哼哼,我用取代功能就好了!」,对你真棒!

https://ithelp.ithome.com.tw/upload/images/20210915/201194868qoB7Pkg3o.png

阿不是,上面的迷因只是开玩笑而已。

但是如果善加利用模组化的话,你只需要改那一只模组的档案就好了,所以就让我们来了解一下该如何将上面范例程序码模组化吧!

汇出模组

前面老样子,我们先讲讲 JavaScript 的模组汇出方式,这边先举例 ESM 的汇出:

/* app.js */
export default {
  sayHello() {
    console.log('Hello Ray');
  },
  myName: 'Ray',
  obj: {
    myName: 'Ray',
  },
  arr: [1, 2, 3],
};

汇入的时候则必须使用 import 汇入模组:

<script type="module">
  import app from './app.js';
  console.log(app.myName); // 'Ray'
  app.sayHello(); // Hello Ray
  console.log(app.obj.myName); // Ray
  console.log(app.arr[0]); // 0
</script>

oh!对了,ESM 还有一个特色,也就是作用域都是独立的,意指你无法跨 script module 取得另一个 script module 的变数。

将镜头转回到 Python 吧!

那麽 Python 该如何撰写模组呢?其实非常非常的简单,只需要讲相关函式拆成另一个档案,然後使用 import 引入就可以了:

# module.py
def add(a, b):
  return int(a + b)

def sayHi(name):
  return f'Hello {name}!'

# example.py
import module

print(module.add(100, 25)) # 125

print(module.sayHi('Ray')) # Hello Ray!

有没有觉得超简单~

https://ithelp.ithome.com.tw/upload/images/20210915/20119486eAPo7HlzvU.png

关於 import 的部分我们稍後再聊。

透过上面的范例我们可以得知,Python 在模组化确实是非常方便,只需要建立一个档案并宣告函式,然後将其结果 return 回去就可以了,相对 JavaScript 模组化时还必须使用 export 语法来汇出。

当然在做模组可能不只有函式也有可能是字典,那 Python 呢?写法会因此有所变化吗?

其实没有,一样宣告一个字典就可以了,而且不用 return 就可以直接使用:

# module.py
def add(a, b):
  return int(a + b)

def sayHi(name):
  return f'Hello {name}!'

dic = {
  'myName': 'Ray',
}

# example.py
import module

print(module.add(100, 25)) # 125

print(module.sayHi('Ray')) # Hello Ray!

print(module.dic['myName']) # Ray

是不是超级方便,再来看一次海绵宝宝

https://ithelp.ithome.com.tw/upload/images/20210915/20119486eAPo7HlzvU.png

汇入模组

接下来聊聊比较简单的东西,也就是汇入语法 import 的部分。

在上面范例中就已经有开始使用 import,而这种单纯只是 import 特定档案的方式其实就是全部汇入的一种,所以你可以直接使用 module.py 档案中所有的函式与字典。

但是其实 import 还有其他种用法,举例来讲在前面范例中的 import module,我们可以看到每次要使用模组中的函式时,都必须 module.add or module.dic 等等,这时候 module 这个名字就会显得很长很麻烦,因此这时候你可以使用 import 中的 as 来重新命名模组要汇入的名称,届时你就可以直接使用新的名称呼叫:

# example.py
import module as mu

print(mu.add(100, 25)) # 125

print(mu.sayHi('Ray')) # Hello Ray!

print(mu.dic['myName']) # Ray

前面也有讲到,如果只是单纯的 import 模组,代表着你是将一整包模组汇入到这个档案中,可是有时候我们只是要使用里面的特定功能,所以这时候就可以使用 form 来解决这个需求。

举例来讲,我要指定只汇入 dis 字典的话就只需要这样写:

# example.py
from module import dic

print(dic['myName']) # Ray

上面的意思就是「从 xxx 汇入 xxx 方法」的意思,相较 JavaScript 的汇入则是:

import app from './app.js';

两者写法可以看出是不同的,一个是 form 开始,另一个则是 import 开始,从 JavaScript 角度来讲大意就是:「汇入 xxx 来源是 xxx」。

我绝对不会说我在写 Python 的时候一直写长 import ... from ...

dir

最後我想额外补充一个我觉得满实用的东西,也就是 dir(),这个函式有什麽用途呢?简单来讲就是它可以列出当前作用域范围内有哪些变数与方法:

# dir.py
def add(a, b):
  print(a, b)
  return int(a + b)

def sayHi(name):
  return f'Hello {name}!'

dic = {
  'myName': 'Ray',
}

print(dir()) # ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'add', 'dic', 'sayHi']

这边先不看 __xxx__ 这种,直接看没有双下底线的类型,你可以看到在这个页面上宣告的方法与字典都会被列出来,而 dir 除了用於看当前执行范围的变数与范围之外,你也可以拿来查看模组:

# module.py
def add(a, b):
  return int(a + b)

def sayHi(name):
  return f'Hello {name}!'

dic = {
  'myName': 'Ray',
}

# example.py
import module

print(module) # ['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'add', 'dic', 'sayHi']

除此之外 dir 还可以查看串列与字典有哪些方法可以使用:

(下述输出结果我已经有稍微整理过了。)

print(dir([])) # ['append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

print(dir({})) # ['clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']

有没有觉得 Python 的 dir 方法与 JavaScript 的 console.dir 有异曲同工之妙呢?

help

最後来讲讲 help 这个函式,相信看到这个函式名称的人应该大部分都已经知道它的用途了,它可以用於查看模组与函式的详细说明:

help('def')

我先说如果你查看 def 的话内容会超级无敌详细又超级无敌的长,所以我只截取部分:

Function definitions
********************

A function definition defines a user-defined function object (see
section The standard type hierarchy):

   funcdef                   ::= [decorators] "def" funcname "(" [parameter_list] ")"
               ["->" expression] ":" suite
   decorators                ::= decorator+
   decorator                 ::= "@" assignment_expression NEWLINE
   parameter_list            ::= defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]]
                        | parameter_list_no_posonly
   parameter_list_no_posonly ::= defparameter ("," defparameter)* ["," [parameter_list_starargs]]
                                 | parameter_list_starargs
   parameter_list_starargs   ::= "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
                               | "**" parameter [","]
   parameter                 ::= identifier [":" expression]
   defparameter              ::= parameter ["=" expression]
   funcname                  ::= identifier

A function definition is an executable statement.  Its execution binds
the function name in the current local namespace to a function object
(a wrapper around the executable code for the function).  This
function object contains a reference to the current global namespace

那如果用於我们的模组也可以有类似的效果,但是就不会出现这麽详细的介绍了:

# module.py
def add(a, b):
  return int(a + b)

def sayHi(name):
  return f'Hello {name}!'

dic = {
  'myName': 'Ray',
}

# example.py
import module

print(help(module))

https://ithelp.ithome.com.tw/upload/images/20210915/20119486MDh9EcjODX.png

这时候或许有人会问该如何像 help(def) 一样可以出现说明,其实非常简单使用注解就可以了:

# NAME: 这一行会成为模组的描述说明

# FUNCTIONS: 非常简单的计算,但回传会是整数
def add(a, b):
  return int(a + b)

# FUNCTIONS: 这是一个跟人打招呼的方法
def sayHi(name):
  # 注解说明必须放在 def 之前,放在 def 之内不会出现
  return f'Hello {name}!'

# DATA 类的不会出现注解说明
dic = {
  # 就算写在这里也一样
  'myName': 'Ray',
}

# example.py
import module

print(help(module))

相信看到这边你应该就知道 dirhelp 这两个函式有多方便,那麽今天就先到这边结束吧 :D

作者的话

这几天收到动保处的通知,主要内容是在讲我家狗狗没有结紮请尽快带去结紮,否则将会开罚,虽然我家狗狗年纪已经约 7~8 岁了,但是经过医生专业评估後还是决定让她结紮会比较好,不得不说我一开始是保持反对态度,但是听完医生建议後还是乖乖照做比较好,也是为了狗儿健康为主。

关於兔兔们

兔法无边


<<:  终於要来开赛了~第一天

>>:  Day1 - 序言 - 成为Canvas Ninja ~ 理解2D渲染的精髓

Day13 - 使用Chip和ChipGroup显示搜寻项目

原本今天是想写解析文章列表的,不过思考了一下,为了让脉络顺一点,决定把今天的内容放到解析文章列表之前...

Kotlin 语言和你 SAY HELLO!!

第十一天 各位点进来的朋友,你们好阿 因为我还是新手不能够直接回覆,所以在这边回覆前两篇的留言。 第...

[Day 3] 取得台股资料(基本篇)

一、前言 想要进行资料分析,要做的第一件事当然是收集资料,所幸现在是2021,我们不需要为了股票资料...

DAY5-EXCEL统计分析:认识连续型机率

连续型机率 连续型机率的随机变数介於一个区间之间,而在某一特定区间内发生的机率皆相同,则此特定区间外...

DAY02 - 那些当年很想做但就是写不出来的Side Project...囧

前言: 今天是铁人赛的第二天,要来说说大叔的自学经历与心得 内容预计分成两篇...(满满的回忆 XD...