【Python】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)中還包含 IPythonSpyder 等更加先進的 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

整數(比如 5816 )有 int 類型,有小數部分的(如 2.336.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.stdinsys.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()  # 結尾換行

參考文檔

  1. Python 官方中文文檔, https://docs.python.org/zh-cn/3/tutorial/
  2. Learn Python3 In Y Minutes, https://learnxinyminutes.com/docs/python3/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章