接下来聊聊 Python 中的模组 (Module) ,毕竟 JavaScript 也有所谓的模组观念,
如果你有使用过 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 改成中文是不是又要改一百次?或许你可能会想说:「哼哼,我用取代功能就好了!」,对你真棒!
阿不是,上面的迷因只是开玩笑而已。
但是如果善加利用模组化的话,你只需要改那一只模组的档案就好了,所以就让我们来了解一下该如何将上面范例程序码模组化吧!
前面老样子,我们先讲讲 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!
有没有觉得超简单~
关於 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
是不是超级方便,再来看一次海绵宝宝
接下来聊聊比较简单的东西,也就是汇入语法 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.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('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))
这时候或许有人会问该如何像 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))
相信看到这边你应该就知道 dir
与 help
这两个函式有多方便,那麽今天就先到这边结束吧 :D
这几天收到动保处的通知,主要内容是在讲我家狗狗没有结紮请尽快带去结紮,否则将会开罚,虽然我家狗狗年纪已经约 7~8 岁了,但是经过医生专业评估後还是决定让她结紮会比较好,不得不说我一开始是保持反对态度,但是听完医生建议後还是乖乖照做比较好,也是为了狗儿健康为主。
>>: Day1 - 序言 - 成为Canvas Ninja ~ 理解2D渲染的精髓
原本今天是想写解析文章列表的,不过思考了一下,为了让脉络顺一点,决定把今天的内容放到解析文章列表之前...
第十一天 各位点进来的朋友,你们好阿 因为我还是新手不能够直接回覆,所以在这边回覆前两篇的留言。 第...
一、前言 想要进行资料分析,要做的第一件事当然是收集资料,所幸现在是2021,我们不需要为了股票资料...
连续型机率 连续型机率的随机变数介於一个区间之间,而在某一特定区间内发生的机率皆相同,则此特定区间外...
前言: 今天是铁人赛的第二天,要来说说大叔的自学经历与心得 内容预计分成两篇...(满满的回忆 XD...