Python速成
關於 Python
Python 是一種目前已在世界上廣泛使用的解釋型面嚮對象語言,非常適合用來測試算法片段和原型,也可以用來刷一些 OJ。
爲什麼要學習 Python
- Python 是一種 解釋型 語言:類似於 PHP 與 Perl,它在開發過程中無需編譯,即開即用,跨平臺兼容性好。
- Python 是一種 交互式 語言:您可以在命令行的提示符
>>>
後直接輸入代碼,這將使您的代碼更易於調試。 - Python 易學易用,且覆蓋面廣:從簡單的輸入輸出到科學計算甚至於大型 WEB 應用,Python 可以幫助您在 極低的學習成本 下快速寫出適合自己的程序,從而讓您的程序生涯如虎添翼,爲以後的學習和工作增加一項實用能力。
- Python 易讀性強,且在世界廣泛使用:這意味着您能夠在使用過程中比其他語言 更快獲得支持 , 更快解決問題 。
- 哦,還有一個最重要的:它在各平臺下的環境易於配置,並且目前市面上大部分流行的 Linux 發行版(甚至於
NOI Linux
)中也大都 內置 了個版本比較舊的 Python,這意味着您能真正在考場上使用它,讓它成爲您的最佳拍檔。
學習 Python 時需要注意的事項
- 目前的 Python 分爲 Python 2 和 Python 3 兩個版本,其中 Python 2 雖然 幾近廢棄 ,但是仍被一些老舊系統和代碼所使用。我們通常不能確定在考場上可以使用的版本,因而會 介紹較新版本的 Python ,但還是建議讀者瞭解一下 Python 2 的相關語法,並比較兩者之間的差異。
- 如果您之前使用 C++ 語言,那麼很遺憾地告訴您,Python 的語法結構與 C++ 差異還是比較大的,請注意使用的時候不要混淆。
- 由於 Python 是高度動態的解釋型語言,因此其程序運行有大量的額外開銷。通常而言,實現同樣功能時 Python 代碼越少速度越快(但不要追求極端)。尤其是 for 循環在 Python 中運行的奇慢無比 。因此在使用 Python 時若想獲得高性能,儘量使用
filter
,map
等內置函數,或者使用 “列表理解” 語法的手段來避免循環。
環境安裝
Windows
訪問 https://www.python.org/downloads/ ,下載自己需要的版本並安裝。
另外爲了方便,請務必勾選 ** Add Python 3.x to PATH
** 以確保將 Python 加入環境變量!
如在如下的 Python 3.7.4 安裝界面中,應該如圖勾選最下一項複選框。
安裝完成後,您可以在開始菜單找到安裝好的 Python。
如果您按上圖勾選了加入環境變量,您還可以通過 命令提示符 ( Win + R
-> cmd
)的方式使用 Python。
正常啓動後,它會先顯示歡迎信息與版本信息,再顯示版權聲明,之後就會出現提示符 ** >>>
** ,一般情況下如下所示:
$ python3
Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
這就是 Python 的 IDLE 。
“何謂 IDLE?”
Python 的 IDE,“集成開發與學習環境”的英文縮寫。是 Python 標準發行版附帶的基本編程器和解釋器環境。在其他 Python 發行版(如 Anaconda)中還包含 IPython , Spyder 等更加先進的 IDE。
macOS/Linux
通常情況下,正如上文所說,大部分的 Linux 發行版中已經自帶了 Python,如果您只打算學學語法並無特別需求,一般情況下不用再另外安裝。通常而言,在 Linux 終端中運行 python
進入的是 Python 2,而運行 python3
進入的是 Python 3。
而由於種種依賴問題(如 CentOS 的 yum ),自行編譯安裝後通常還要處理種種問題,這已經超出了本文的討論範疇。
而在這種情況下您一般能直接通過軟件包管理器來進行安裝,如在 Ubuntu 下安裝 Python 3
:
sudo apt install python3
更多詳情您可以直接在搜索引擎上使用關鍵字 系統名稱(標誌版本) 安裝 Python 2/3
來找到對應教程。
“運行
python
還是python3
?”
根據 Python 3 官方文檔 的說法,在 Unix 系統中,Python 3.X
解釋器 默認安裝 (指使用軟件包管理器安裝)後的執行文件並不叫作python
,這樣纔不會與同時安裝的Python 2.X
衝突。同樣的,默認安裝的 pip 軟件也是類似的情況,Python 3 包管理器的文件名爲pip3
您可以根據自己的使用習慣自建軟鏈或者 shell 別名,但還請注意不要與自帶的衝突。
關於鏡像和 pip
目前國內關於 源碼 的鏡像緩存主要是 北京交通大學 和 華爲開源鏡像站 在做,如果您有下載問題的話可以到那裏嘗試一下。
如果您還有使用 pip 安裝其他模塊的需求,請參照 TUNA 的鏡像更換幫助 。
note “pip 是什麼?”
Python 的默認包管理器,用來安裝第三方 Python 庫。它的功能很強大,能夠處理版本依賴關係,還能通過 wheel 文件支持二進制安裝。pip 的庫現在託管在 PyPI (即“Python 包索引”)平臺上,用戶也可以指定第三方的包託管平臺。
關於 PyPI 的鏡像,可以使用如下大鏡像站的資源:
基本語法
Python 以其簡潔易懂的語法而出名。它基本的語法結構可以非常容易地在網上找到,例如 菜鳥教程 就有不錯的介紹。這裏僅介紹一些對 OIer 比較實用的語言特性。
關於註釋
在此提前聲明一下 Python 中註釋的寫法,因爲在後面的講解中會頻繁用到。
# 用 # 字符開頭的是單行註釋
""" 跨多行字符串會用三個引號
包裹,但也常被用來做多
行註釋.(NOTE: 在字符串中不會考慮縮進問題)
"""
加入註釋代碼並不會影響程序的正常運行。我們鼓勵加入註釋來使您的代碼更加易懂易用。
基本數據類型與運算
有人說,你可以把你係統裏裝的 Python 當作一個多用計算器,這是事實。
你可以在提示符 >>>
後面輸入一個表達式,就像其他大部分語言(如 C++)一樣使用運算符 +
、 -
、 *
、 /
來對數字進行運算;還可以使用 ()
來進行符合結合律的分組,例如:
>>> 233 # 整數就是整數
233
>>> 5 + 6 # 算術也沒有什麼出乎意料的
11
>>> 50 - 4 * 8
18
>>> (50 - 4) * 8
368
>>> 15 / 3 # 但是除法除外,它會永遠返回浮點 float 類型
5.0
>>> (50 - 4 * 8) / 9
2.0
>>> 5 / 3
1.6666666666666667
>>> 5.0 * 6 # 浮點數的運算結果也是浮點數
30.0
整數(比如 5
、 8
、 16
)有 int
類型,有小數部分的(如 2.33
、 6.0
)則有 float
類型。隨着更深入的學習你可能會接觸到更多的類型,但是在速成階段這些已經足夠使用。
在上面的實踐中你也看到了,除法運算( /
)永遠返回浮點類型(在 Python 2 中返回整數)。如果你想要整數或向下取整的結果的話,可以使用整數除法( //
)。同樣的,你也可以像 C++ 中一樣,使用模( %
)來計算餘數。
>>> 5 / 3 # 正常的運算會輸出浮點數
1.6666666666666667
>>> 5 // 3 # 使用整數除法則會向下取整,輸出整數類型
1
>>> -5 // 3 # 符合向下取整原則,注意與C/C++不同
-2
>>> 5.0 // 3.0 # 如果硬要浮點數向下取整也可以這麼做
1.0
>>> 5 % 3 # 取模
2
>>> -5 % 3 # 負數取模結果一定是非負數,這點也與C/C++不同,不過都滿足 (a//b)*b+(a%b)==a
1
特別的,Python 封裝了乘方( **
)的算法,這也表明 Python 附有 大整數支持 。值得一提的是,Python 還通過內置的 pow(a, b, mod)
提供了 快速冪 的高效實現。
>>> 5 ** 2
25
>>> 2 ** 16
65536
>>> 2 ** 512
13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096
>>> pow(2, 512, 10000) # 即 2**512 % 10000 的快速實現
4096
>>> 2048 ** 2048 # 在IDLE裏試試大整數?
輸入輸出
Python 中的輸入輸出主要通過內置函數 raw_input
(Python 2)/ input
(Python 3) 和 print
完成,這一部分內容可以參考 Python 的官方文檔 。 input
函數用來從標準輸入流中讀取一行, print
則是向標準輸出流中輸出一行。在 Python 3 中對 print
增加了 end
參數指定結尾符,可以用來避免 print
自動換行。如果需要更靈活的輸入輸出操作,可以在引入 sys
包之後利用 sys.stdin
和 sys.stdout
操標準作輸入輸出流。
另外,如果要進行格式化的輸出的話可以利用 Python 中字符串的語法。格式化有兩種方法,一種是利用 %
操作符,另一種是利用 format
函數。前者語法與 C 兼容,後者語法比較複雜,可以參考 官方文檔 。
>>> print(12)
12
>>> print(12, 12) # 該方法在 Python 2 和 Python 3 中的表現不同
12 12
>>> print("%d" % 12) # 與C語法兼容
12
>>> print("%04d %.3f" % (12, 1.2))
0012 1.200
>>> print("{name} is {:b}".format(5, name="binary of 5"))
binary of 5 is 101
開數組
從 C++ 轉過來的同學可能很迷惑怎麼在 Python 中開數組,這裏就介紹在 Python 開數組的語法。
使用 list
主要用到的是 Python 中列表( list
)的特性,值得注意的是 Python 中列表的實現方式類似於 C++ 的 vector
。
>>> [] # 空列表
[]
>>> [1] * 10 # 開一個10個元素的數組
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
>>> [1, 1] + [2, 3] # 數組拼接
[1, 1, 2, 3]
>>> a1 = list(range(8)) # 建立一個自然數數組
>>> a1
[0, 1, 2, 3, 4, 5, 6, 7]
>>> [[1] * 3] * 3 # 開一個3*3的數組
[[1, 1, 1], [1, 1, 1], [1, 1, 1]]
>>> [[1] * 3 for _ in range(3)] # 同樣是開一個3*3的數組
[[1, 1, 1], [1, 1, 1], [1, 1, 1]]
>>> a2 = [[1]] * 5; a[0][0] = 2; # 猜猜結果是什麼?
>>> a2
[[2], [2], [2], [2], [2]]
>>> # 以下是數組操作的方法
>>> len(a1) # 獲取數組長度
8
>>> a1.append(8) # 向末尾添加一個數
>>> a1[0] = 0 # 訪問和賦值
>>> a1[-1] = 7 # 從末尾開始訪問
>>> a1[2:5] # 提取數組的一段
[2, 3, 4]
>>> a1[5:2:-1] # 倒序訪問
[5, 4, 3]
>>> a1.sort() # 數組排序
>>> a2[0][0] = 10 # 訪問和賦值二維數組
>>> for i, a3 in enumerate(a2):
for j, v in enumerate(a3):
temp = v # 這裏的v就是a[i][j]
注意上面案例裏提到的多維數組的開法。由於列表的乘法只是拷貝引用,因此 [[1]] * 3
這樣的代碼生成的三個 [1]
實際上是同一個對象,修改其內容時會導致所有數組都被修改。所以開多維數組時使用 for 循環可以避免這個問題。
使用 Numpy
“什麼是 Numpy”
Numpy 是著名的 Python 科學計算庫,提供高性能的數值及矩陣運算。在測試算法原型時可以利用 Numpy 避免手寫排序、求最值等算法。Numpy
的核心數據結構是ndarray
,即 n 維數組,它在內存中連續存儲,是定長的。此外 Numpy 核心是用 C 編寫的,運算效率很高。
下面的代碼將介紹如何利用 Numpy 建立多維數組並進行訪問。
>>> import numpy as np # Numpy 是第三方庫,需要安裝和引用
>>> np.empty(3) # 開容量爲3的空數組
array([0.00000000e+000, 0.00000000e+000, 2.01191014e+180])
>>> np.empty((3, 3)) # 開3*3的空數組
array([[6.90159178e-310, 6.90159178e-310, 0.00000000e+000],
[0.00000000e+000, 3.99906161e+252, 1.09944918e+155],
[6.01334434e-154, 9.87762528e+247, 4.46811730e-091]])
>>> np.zeros((3, 3)) # 開3*3的數組,並初始化爲0
array([[0., 0., 0.],
[0., 0., 0.],
[0., 0., 0.]])
>>> a1 = np.zeros((3, 3), dtype=int) # 開3×3的整數數組
>>> a1[0][0] = 1 # 訪問和賦值
>>> a1[0, 0] = 1 # 更友好的語法
>>> a1.shape # 數組的形狀
(3, 3)
>>> a1[:2, :2] # 取前兩行、前兩列構成的子陣,無拷貝
array([[1, 0],
[0, 0]])
>>> a1[0, 2] # 獲取第1和3列,無拷貝
array([[1, 0],
[0, 0],
[0, 0]])
>>> np.max(a1) # 獲取數組最大值
1
>>> a1.flatten() # 將數組展平
array([1, 0, 0, 0, 0, 0, 0, 0, 0])
>>> np.sort(a1, axis=1) # 沿行方向對數組進行排序,返回排序結果
array([[0, 0, 1],
[0, 0, 0],
[0, 0, 0]])
>>> a1.sort(axis=1) # 沿行方向對數組進行原地排序
常用內置庫
在這裏介紹一些寫算法可能用得到的內置庫,具體用法可以自行搜索或者閱讀 官方文檔 。
包名 | 用途 |
---|---|
array |
定長數組 |
argparse |
命令行參數處理 |
bisect |
二分查找 |
collections |
提供有序字典、雙端隊列等數據結構 |
fractions |
有理數 |
heapq |
基於堆的優先級隊列 |
io |
文件流、內存流 |
itertools |
迭代器相關 |
math |
常用數學函數 |
os.path |
系統路徑相關 |
random |
隨機數 |
re |
正則表達式 |
struct |
轉換結構體和二進制數據 |
sys |
系統信息 |
對比 C++ 與 Python
相信大部分算法競賽選手已經熟練掌握了 C++98 的語法。接下來我們展示一下 Python 語法的一些應用。
接下來的例子是 Luogu P4779「【模板】單源最短路徑(標準版)」 的代碼。我們將 C++ 代碼與 Python 代碼做出對比:
從聲明一些常量開始:
C++:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5, M = 2e5 + 5;
Python:
try: # 引入優先隊列模塊
import Queue as pq #python version < 3.0
except ImportError:
import queue as pq #python3.*
N = int(1e5 + 5)
M = int(2e5 + 5)
INF = 0x3f3f3f3f
然後是聲明前向星結構體和一些其他變量。
C++:
struct qxx {
int nex, t, v;
};
qxx e[M];
int h[N], cnt;
void add_path(int f, int t, int v) { e[++cnt] = (qxx){h[f], t, v}, h[f] = cnt; }
typedef pair<int, int> pii;
priority_queue<pii, vector<pii>, greater<pii>> q;
int dist[N];
Python:
class qxx: # 前向星類(結構體)
def __init__(self):
self.nex = 0
self.t = 0
self.v = 0
e = [qxx() for i in range(M)] # 鏈表
h = [0 for i in range(N)]
cnt = 0
dist = [INF for i in range(N)]
q = pq.PriorityQueue() # 定義優先隊列,默認第一元小根堆
def add_path(f, t, v): # 在前向星中加邊
# 如果要修改全局變量,要使用global來聲名
global cnt, e, h
# 調試時的輸出語句,多個變量使用元組
# print("add_path(%d,%d,%d)" % (f,t,v))
cnt += 1
e[cnt].nex = h[f]
e[cnt].t = t
e[cnt].v = v
h[f] = cnt
然後是求解最短路的 Dijkstra 算法代碼:
C++:
void dijkstra(int s) {
memset(dist, 0x3f, sizeof(dist));
dist[s] = 0, q.push(make_pair(0, s));
while (q.size()) {
pii u = q.top();
q.pop();
if (dist[u.second] < u.first) continue;
for (int i = h[u.second]; i; i = e[i].nex) {
const int &v = e[i].t, &w = e[i].v;
if (dist[v] <= dist[u.second] + w) continue;
dist[v] = dist[u.second] + w;
q.push(make_pair(dist[v], v));
}
}
}
Python:
def nextedgeid(u): # 生成器,可以用在for循環裏
i = h[u]
while i:
yield i
i = e[i].nex
def dijkstra(s):
dist[s] = 0
q.put((0, s))
while not q.empty():
u = q.get() # get函數會順便刪除堆中對應的元素
if dist[u[1]] < u[0]:
continue
for i in nextedgeid(u[1]):
v = e[i].t
w = e[i].v
if dist[v] <= dist[u[1]]+w:
continue
dist[v] = dist[u[1]]+w
q.put((dist[v], v))
最後是主函數部分
C++:
int n, m, s;
int main() {
scanf("%d%d%d", &n, &m, &s);
for (int i = 1; i <= m; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add_path(u, v, w);
}
dijkstra(s);
for (int i = 1; i <= n; i++) printf("%d ", dist[i]);
return 0;
}
Python:
# 如果你直接運行這個python代碼(不是模塊調用什麼的)就執行命令
if __name__ == '__main__':
# 一行讀入多個整數。注意它會把整行都讀進來
n, m, s = map(int, input().split())
for i in range(m):
u, v, w = map(int, input().split())
add_path(u, v, w)
dijkstra(s)
for i in range(1, n+1):
# 兩種輸出語法都是可以用的
print("{}".format(dist[i]), end=' ')
# print("%d" % dist[i],end=' ')
print() # 結尾換行
完整的代碼如下:
C++:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5, M = 2e5 + 5;
struct qxx {
int nex, t, v;
};
qxx e[M];
int h[N], cnt;
void add_path(int f, int t, int v) { e[++cnt] = (qxx){h[f], t, v}, h[f] = cnt; }
typedef pair<int, int> pii;
priority_queue<pii, vector<pii>, greater<pii>> q;
int dist[N];
void dijkstra(int s) {
memset(dist, 0x3f, sizeof(dist));
dist[s] = 0, q.push(make_pair(0, s));
while (q.size()) {
pii u = q.top();
q.pop();
if (dist[u.second] < u.first) continue;
for (int i = h[u.second]; i; i = e[i].nex) {
const int &v = e[i].t, &w = e[i].v;
if (dist[v] <= dist[u.second] + w) continue;
dist[v] = dist[u.second] + w;
q.push(make_pair(dist[v], v));
}
}
}
int n, m, s;
int main() {
scanf("%d%d%d", &n, &m, &s);
for (int i = 1; i <= m; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add_path(u, v, w);
}
dijkstra(s);
for (int i = 1; i <= n; i++) printf("%d ", dist[i]);
return 0;
}
Python:
try: # 引入優先隊列模塊
import Queue as pq # python version < 3.0
except ImportError:
import queue as pq # python3.*
N = int(1e5+5)
M = int(2e5+5)
INF = 0x3f3f3f3f
class qxx: # 前向星類(結構體)
def __init__(self):
self.nex = 0
self.t = 0
self.v = 0
e = [qxx() for i in range(M)] # 鏈表
h = [0 for i in range(N)]
cnt = 0
dist = [INF for i in range(N)]
q = pq.PriorityQueue() # 定義優先隊列,默認第一元小根堆
def add_path(f, t, v): # 在前向星中加邊
# 如果要修改全局變量,要使用global來聲名
global cnt, e, h
# 調試時的輸出語句,多個變量使用元組
# print("add_path(%d,%d,%d)" % (f,t,v))
cnt += 1
e[cnt].nex = h[f]
e[cnt].t = t
e[cnt].v = v
h[f] = cnt
def nextedgeid(u): # 生成器,可以用在for循環裏
i = h[u]
while i:
yield i
i = e[i].nex
def dijkstra(s):
dist[s] = 0
q.put((0, s))
while not q.empty():
u = q.get()
if dist[u[1]] < u[0]:
continue
for i in nextedgeid(u[1]):
v = e[i].t
w = e[i].v
if dist[v] <= dist[u[1]]+w:
continue
dist[v] = dist[u[1]]+w
q.put((dist[v], v))
# 如果你直接運行這個python代碼(不是模塊調用什麼的)就執行命令
if __name__ == '__main__':
# 一行讀入多個整數。注意它會把整行都讀進來
n, m, s = map(int, input().split())
for i in range(m):
u, v, w = map(int, input().split())
add_path(u, v, w)
dijkstra(s)
for i in range(1, n+1):
# 兩種輸出語法都是可以用的
print("{}".format(dist[i]), end=' ')
# print("%d" % dist[i],end=' ')
print() # 結尾換行
參考文檔
- Python 官方中文文檔, https://docs.python.org/zh-cn/3/tutorial/
- Learn Python3 In Y Minutes, https://learnxinyminutes.com/docs/python3/