python质数列_现代化程序开发笔记(3)——多文件与模块
本系列文章以我的個人博客的搭建為線索(GitHub 倉庫:Evian-Zhang/evian-blog),記錄我在現代化程序設計中的一些筆記。在這篇文章中,我將對現代編程語言的多文件和模塊部分進行一些介紹。
模塊化編程
隨著現代編程開發項目的代碼量越來越大,參與開發維護的人數越來越多,模塊化編程這一理念變得十分重要。就像我所說的,模塊化編程實際上是一個理念,它倡導的是開發者利用各種手段,將不同作用的代碼塊隔離。比方說,眾所周知,巫師三的兩個核心功能是昆特牌功能和與女術士增進感情的功能。假設我們是簡陋版巫師三的開發者,就在開發這兩個功能。為了簡化,假設昆特牌功能有函數playGwent, useMonsterCard,與女術士增進感情功能有函數talk, fight等。那么,任何一個懂得規劃的開發者都會知道,不管使用什么編程語言,我們的代碼順序應該是
// Gwent partvoid playGwent(Person person);
void useMonsterCard(Card monsterCard);
// ... many more functions
// Sorceress partvoid talk(Sorceress sorceress);
void fight(Sorceress sorceress);
// ... many more functions
而不是
void playGwent(Person person);
void talk(Sorceress sorceress);
// ... many more functionsvoid fight(Sorceress sorceress);
void useMonsterCard(Card monsterCard);
// ... many more functions
這樣把各種功能,毫不相關的函數交錯放置。只有通過合理地有序組織代碼,才能使代碼的開發和維護變得輕松一些。試想,如果一個開發者想維護我們的這個代碼,他想找到和女術士交談的函數,與在整個項目代碼中一行一行找相比,那必然是直接在女術士相應的代碼部分尋找更為輕松。
總而言之,模塊化實際上是一種開發分配、代碼組織的理念,就是將整個項目的代碼分成許多有獨立功能的模塊,將毫不相關的模塊分開。同時,對于沒有功能依賴的模塊,可以多位開發者并行開發。通過模塊化的措施,可以最大程度降低開發、維護的成本和時間。
多文件編程
將項目分為許多模塊,由開發者并行開發,這是模塊化的理念。那么實際操作中,應該如何貫徹呢?最直觀的想法,就是將不同的模塊歸屬到不同的文件、目錄下去。假如我們的簡陋版巫師三是用JavaScript寫的,那么如果和女術士增進感情部分的代碼比較少,我們直接將這部分的代碼歸入到sorceress.js這個文件中;如果昆特牌部分的代碼比較多,放不到一個文件中,那就單獨設置一個目錄gwent, 在其中可能會有monster.js, play.js等多個文件。也就是說,我們的代碼結構可能會是
project
├── main.js
├── sorceress.js
└── gwent
├── monster.js
└── play.js
這樣的層次結構。此外,我們還可能會使用別人寫的庫的功能。假設我們有一個庫height用于計算跳落的傷害, 其代碼結構是
height
├── damage.js
└── distance.js
將代碼分到不同的文件、目錄中,這不僅需要開發者遵守,編程語言也需要有相應的多文件支持。也就是說,編程語言需要支持多文件編程。
此外,當編程語言支持多文件編程時,還有一個問題引刃而解了——命名沖突。比如說,我們在昆特牌功能中有一個win函數,表示比賽獲勝,而在和女術士增進感情的功能中,也有一個win函數,表示獲得其芳心。那么,最簡單的解決方案,就是一個函數叫winGwent, 一個函數叫winSorceress. 但是,通過編程語言對多文件的支持,或者說其對多模塊的支持,只要將其分屬于兩個模塊內,那么都叫win也就沒有關系了。
在討論大多數編程語言的多文件編程支持時,會涉及到三個概念:文件級別,目錄級別,以及庫級別。根據我們之前的討論,每個文件都包含一些單獨的功能。而對于那些功能有聯系的文件,會將其組織在同一個目錄下。而有的目錄具有的功能是一些輔助性的,可以復用的功能,所以有些目錄會被作為一個庫發布出去,供別的人使用,也就像我們這里的height庫一樣。在大多數編程語言的認知中,一個文件被稱為一個「模塊」,一個目錄被稱為一個「包」。因此,一個項目本身也是一個包,它是由許多文件和包組成。而組成它的包又有下一層次的文件和包組成。而一個庫可能由許多包組成,也可能就是一個包。
對于庫而言,我會在后面關于包管理器的文章中專門提到,這里就先只討論模塊和包。在下面具體編程語言的討論中,模塊級別和包級別是我們剛才講的文件和目錄的概念,而非語言具體的名詞概念。
Python^1
Python是區分模塊級別和包級別的。在Python中,一個文件就是一個模塊,而一個目錄則是一個包。
一個目錄如果要聲明自己是一個包,則必須要在目錄中包含__init__.py文件。一個文件自動是一個模塊,不需要聲明。
如果要用Python完成我們的簡陋版巫師三,那么代碼結構應該為
project
├── main.py
├── sorceress.py
└── gwent
├── __init__.py
├── monster.py
└── play.py
在main.py中,需要按模塊引入。同時,引入別的庫的模塊和引入自己的模塊沒有差別。引入別的模塊的方法是
import sorceress
import gwent.monster
import gwent.play
import height.damage
Kotlin^2
使用Kotlin編寫的Android項目依然有模塊級別和包級別的概念。但是,從名詞術語的角度來看,Kotlin的模塊并不指單個文件,而是一個項目;Kotlin的包則指一個目錄。
一個目錄不需要聲明,自動是一個包。一個文件需要在開頭用package關鍵詞表明自己所屬的包。
如果要用Kotlin完成我們的簡陋版巫師三,那么代碼結構應該為
project
├── main.kt
├── sorceress.kt
└── gwent
├── monster.kt
└── play.kt
在main.kt及sorceress.kt的第一行,需要
package project
而在monster.kt和play.kt的第一行,則要
package project.gwent
在main.kt中,需要按包引入。一個文件會自動引入同一個包下的別的文件,如果要引入別的包或者庫(實際上也是包),則需要
import project.gwent
import height
JavaScript/TypeScript^3
自ECMAScript 2015之后,JavaScript有了模塊的概念。在JavaScript的視角下,目錄僅僅是目錄的作用,并沒有特殊的包的作用。因此,JavaScript只有模塊的概念。
一個文件如果要表明自己是一個模塊,則必須有export語句。
如果要用JavaScript或TypeScript完成我們的簡陋版巫師三,那么代碼結構應該為
project
├── main.js
├── sorceress.js
└── gwent
├── monster.js
└── play.js
對于main.js,引入別的模塊的方法是
import './sorceress.js';
import './gwent/monster.js';
import './gwent/play.js';
import 'path/to/height/damage.js';
值得注意的是,TypeScript在進行import的時候,不需要帶擴展名,也就是
import './sorceress';
import './gwent/monster';
import './gwent/play';
import 'path/to/height/damage';
Swift^5
Swift沒有模塊級別和包級別的概念,其「模塊」指的是庫的概念。
如果要用Swift完成我們的簡陋版巫師三,其代碼結構與之前無異:
project
├── main.swift
├── sorceress.swift
└── gwent
├── monster.swift
└── play.swift
在同一個模塊下(也就是我們理解的在同一個庫內),所有文件都是默認導入的,我們不需要import來導入同項目下別的文件或目錄。但是,需要使用import語句導入別的庫,也就是Swift中的模塊:
import height
Rust^6
Rust的模塊系統和JavaScript相近,沒有包的概念。但其目錄和文件的地位是相同的,都是一個模塊,而模塊可以擁有子模塊。
具體而言,就是Rust把「模塊」和「包」的概念等同了,gwent目錄實際上就是gwent模塊,其下有子模塊monster和play.
一個Rust的文件自動是一個模塊,但需要在其父模塊中聲明。一個Rust的目錄必須包含mod.rs作為當前模塊。
如果要用Rust來完成我們的簡陋版巫師三,其代碼結構為
project
├── main.rs
├── sorceress.rs
└── gwent
├── mod.rs
├── monster.rs
└── play.rs
或
project
├── main.rs
├── sorceress.rs
├── gwent.rs
└── gwent
├── monster.rs
└── play.rs
在gwent/mod.rs或gwent.rs中,必須要有
mod monster;mod play;
來聲明其子模塊。
在main.rs中如果要想引入別的模塊,需要
mod sorceress;// declare sub modulemod gwent;// declare sub moduleusegwent::monster;// use sub moduleusegwent::play;// use sub moduleuseheight::damage;// use library module
由于Rust中目錄和文件的地位都是模塊,所以我們也可以同時use gwent和use gwent::monster.
訪問控制
使用模塊化編程后,會帶來更進一步的好處,就是訪問控制。所謂訪問控制,就是誰能對誰干什么。一個訪問控制規則可以用一個三元組表示:主體,客體,訪問權限。我們的生活中常常會有訪問控制的存在,比如說,QQ空間中,僅好友可見,就是一種訪問控制規則,表明只有主體為我的好友的人,才能對客體——我的這條說說,進行「讀」這一訪問權限。
在模塊化編程中,訪問控制就體現在我此時處在的代碼塊中,能否調用別的代碼塊中的函數。為什么要進行訪問控制呢?這主要是為了貫徹封裝的理念。在一個庫中,有的函數也許只是作為庫內的輔助函數使用,不暴露給外部,這時候就要對這些函數進行訪問控制的保護。
最簡單的訪問控制,就是private和public. 被標記為private的代碼只能被邏輯上處于同一個代碼塊的別的代碼調用,而被標記為public的代碼卻能被所有的代碼訪問到。這一個思想作為基礎,在此之上有許多的變種。
Rust^7
最符合邏輯的訪問控制操作是Rust. 它將模塊視作訪問控制的最小單元,其的原則只有一個:如果一個模塊能訪問某些代碼,那么它的所有子模塊都能訪問該代碼。根據這一宗旨,Rust的訪問控制實際上只分為兩種:pub(in path::to::module)在適當的代碼前加上這個限定符,代表當前的代碼能夠被指定的模塊和其子模塊訪問。比如說以下的代碼:
```rust mod A { mod B { mod C { } } }
mod D { pub(in super::A::B) struct Foo { } } ```
那么,Foo這個結構體本身位于D這個模塊,但它指定B模塊可以訪問自己,那么總共可以訪問Foo結構體的模塊有B, C, D.
不加訪問控制限定符不加訪問控制限定符則默認為私有。私有的代碼只能被當前模塊和其子模塊訪問。比如說以下的代碼:
```rust mod A { mod B { struct Foo { } mod C { } } }
mod D { } ```
那么,能夠訪問到Foo這個結構體的模塊只有B和C.
為了方便開發者,pub(in path)會有許多的語法糖,比如說pub(crate)代表在當前crate內能訪問,pub(super)代表父模塊能訪問,pub(self)代表只有本模塊能訪問,也就等同于不加訪問控制限定符。
Kotlin^8
Kotlin的訪問控制限定符則有4個:private, protected, internal和public. 由于Rust并不具有OOP的全部特性,所以其訪問控制可以通過簡單的修飾符達到完美的效果。但是,Kotlin等語言則是OOP更強的語言,所以其訪問控制的修飾符也更多了一些。
首先,我們來看每個符號的定義:private對于頂層代碼(即不寫在class內部的代碼),是僅能由本文件內部訪問
對于class內的代碼,僅能由該類內部訪問
protected對于class內的代碼,僅能由該類及其子類訪問internal對于頂層代碼,能由整個Kotlin語義下的模塊(即我們眼中的庫級別)訪問
對于class內的代碼,能由該Kotlin語義下的模塊內,能訪問該類的代碼訪問
public對于頂層代碼,能被所有代碼訪問
對于class內的代碼,能由能訪問該類的代碼訪問
就像我們剛剛所說的,正是由于擁有了繼承關系,Kotlin的訪問限定符因此多了一個protected, 以及其他每個符號也多了class內的含義。但是,其與Rust相比,僅能表示當前文件內(即private),或當前庫級別內(即internal)的訪問控制,不能做到任意包路徑的訪問控制。
Swift^9
Swift比Kotlin多了一個訪問控制限定符,共有5個:open, public, internal, fileprivate, private. 首先,我們還是先來看其定義:open能被所有代碼訪問,并且能被Swift語義下的模塊(即我們眼中的庫級別)外的類繼承
public能被所有代碼訪問internal能被Swift語義下的模塊內的代碼訪問
fileprivate能被當前文件內的代碼訪問private僅能被當前邏輯上的實體訪問
這樣看來,實際上和Kotlin是類似的,只不過open多了一個能不能被繼承的限定。
上述的三種語言使用的是傳統意義上的訪問控制,我們可以看到,Rust由于沒有OOP的完整特性,所以比較靈活。但雖然Kotlin和Swift不能指定路徑上的訪問控制,但在實際工程中,庫級別和文件級別的訪問控制實際已經能夠勝任了。
JavaScript/TypeScript^10
JavaScript的訪問控制就比較粗糙了,但也是一個很有效的策略,它的思想就是:只要我export的,你就能用;只要我沒有export的,你就不能用。
我們之前講過,JavaScript和TypeScript中一個文件就是一個模塊。在JavaScript或TypeScript中,所有代碼都可以被同一模塊內的其他代碼使用。但是,如果想讓別的模塊使用某些代碼,則必須將相應的代碼導出去。比如說,我們有下面的代碼:
// foobar.ts
function foo() { }
export interface Foo { }
export function bar() { }
export default function baz() { }
那么,在別的模塊中,我們可以使用
import baz, { bar, Foo } from 'foobar'
來導入相應的代碼。
Python
Python是最奇葩的一種訪問控制策略,它是少數的幾種通過變量名來控制訪問策略的語言。
首先是對于模塊來說,以_開頭的代碼不能被import^11. 比方說,我們有如下的一個模塊:
# foo.py
def bar():
print("bar")
def _baz():
print("barz")
那么,當我們在別的模塊使用
from foo import *
這個語法的時候,并不會將_baz也一并導入。但是,要注意的是,如果我們單獨
from foo import _baz
是可以成功的。
其次,是對于類來說,以雙下劃線__開頭的代碼被認為是不能被子類改寫的^12。比方說以官方文檔中的代碼為例:
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # private copy of original update() method
class MappingSubclass(Mapping):
def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)
那么,當我們實例化一個MappingSubclass的時候,即使子類提供了__update, 其構造函數也不會調用子類的__update。
總結
以上是生活随笔為你收集整理的python质数列_现代化程序开发笔记(3)——多文件与模块的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数据显示,特斯拉 Model 3 / Y
- 下一篇: 从mysql读取数据保存成excel_小