Python · numba 的基本應用

2021-02-19 CVKH

我們都知道 Python 比較慢,但很多時候我們都不知道為什麼。雖然我用 Python 也有那麼兩年左右了,但也只能模模糊糊地感受到這麼兩點:

* Python 太動態了

* 如果能事先編譯一下 Python,讓它靜態一點,速度應該就會上來

於是我們就有了 cython。然而 cython 畢竟不是原生的 Python 代碼,使用起來還是有諸多不便的。為此,numba 就成了一個功能強大又容易上手的替代選擇。下面我們就先來看一下它的基本用法,在最後我們則會用卷積神經網絡(CNN)的卷積和池化操作來直觀感受一下其威力

(注意:以下代碼的運行環境均為 Jupyter Notebook、Python3.6.1)

使用 jit 加速 Python 低效的 for 語句

jit 的全稱是 Just-in-time,在 numba 裡面則特指 Just-in-time compilation(即時編譯)。它背後的原理我們就不細說了,總之我們看到「編譯」兩個字大概就能感受到它是幹什麼的對吧(餵

那麼來看一個簡單的慄子——給數組中的每個數加上一個常數 c:

import numba as nb
import numpy as np

# 普通的 for
def add1(x, c):
rs = [0.] * len(x)
for i, xx in enumerate(x):
rs[i] = xx + c
return rs

# list comprehension
def add2(x, c):
return [xx + c for xx in x]

# 使用 jit 加速後的 for
@nb.jit(nopython=True)
def add_with_jit(x, c):
rs = [0.] * len(x)
for i, xx in enumerate(x):
rs[i] = xx + c
return rs

y = np.random.random(10**5).astype(np.float32)
x = y.tolist()

assert np.allclose(add1(x, 1), add2(x, 1), add_with_jit(x, 1))
%timeit add1(x, 1)
%timeit add2(x, 1)
%timeit add_with_jit(x, 1)
print(np.allclose(wrong_add(x, 1), 1))

以下是程序運行結果:

9.92 ms ± 188 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
5.77 ms ± 347 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
3.48 ms ± 171 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

需要注意的是:

* numba不支持 list comprehension,詳情可參見這裡

* jit能夠加速的不限於for,但一般而言加速for會比較常見、效果也比較顯著。我在我實現的numpy版本的卷積神經網絡(CNN)中用了jit後、可以把代碼加速 20 倍左右。具體代碼可以參見這裡,不過如果不想看原始碼的話,可以參見CNN.ipynb,我在其中做了一些相應的、比較簡單的實驗

* jit會在某種程度上「預編譯」你的代碼,這意味著它會在某種程度上固定住各個變量的數據類型;所以在jit下定義數組時,如果想要使用的是float數組的話,就不能用[0] * len(x)定義、而應該像上面那樣在0後面加一個小數點:[0.] * len(x)

使用 vectorize 實現 numpy 的 Ufunc 功能

雖然jit確實能讓我們代碼加速不少,但比之numpy的Ufunc還是要差很多:

assert np.allclose(y + 1, add_with_jit(x, 1))
%timeit add_with_jit(x, 1)
%timeit y + 1

結果將會是:

3.76 ms ± 292 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
19.8 µs ± 426 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

可以看到幾乎有 200 倍的差距,這當然是無法忍受的。為此,我們可以用vectorize來定義出類似於Ufunc的函數:

@nb.vectorize(nopython=True)
def add_with_vec(yy, c):
return yy + c

assert np.allclose(y + 1, add_with_vec(y, 1), add_with_vec(y, 1.))
%timeit add_with_vec(y, 1)
%timeit add_with_vec(y, 1.)
%timeit y + 1
%timeit y + 1.

上述程序的運行結果將會是:

72.5 µs ± 3.46 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
64.2 µs ± 1.9 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
24.6 µs ± 1.81 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
25.3 µs ± 1.61 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

雖然還是慢了 2 倍左右,但已經好很多了

然後有幾點需要注意的地方:

* vectorize下的函數所接受的參數都是一個個的數而非整個數組。所以上述add_with_vec的參數yy其實是輸入數組y中的元素,而不是y本身。更詳細的說明可以參見官方文檔)

* 可以看到當常數 c 是整數和是浮點數時、速度是不同的。個人猜測這是因為若常數 c 為整數,那麼實際運算時需要將它轉化為浮點數,從而導致速度變慢

* 上述代碼中我們沒有顯式地定義函數的參數類型和返回類型,但我們可以預先定義。比如說,如果我確定常數 c 就是整數的話,我就可以這樣寫:

@nb.vectorize("float32(float32, int32)", nopython=True)
def add_with_vec(yy, c):
return yy + c

而如果我確定常數 c 就是浮點數的話,我就可以這樣寫:

@nb.vectorize("float32(float32, float32)", nopython=True)
def add_with_vec(yy, c):
return yy + c

而如果我確定常數 c 不是整數就是浮點數的話(這個人好囉嗦!),我就可以這樣寫:

@nb.vectorize([
"float32(float32, int32)",
"float32(float32, float32)"
], nopython=True)
def add_with_vec(yy, c):
return yy + c

注意,float32 和 float64、int32 和 int64 是不同的,需要小心

此外,vectorize最炫酷的地方在於,它可以「並行」:

@nb.vectorize("float32(float32, float32)", target="parallel", nopython=True)
def add_with_vec(y, c):
return y + c

assert np.allclose(y+1, add_with_vec(y,1.))
%timeit add_with_vec(y, 1.)
%timeit y + 1

雖說在普通的 Python3.6.1 下、運行結果將如下:

73.5 µs ± 4.22 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
21.2 µs ± 734 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

似乎還變慢了;不過如果使用 Intel Distribution for Python 的話,會發現parallel版本甚至會比numpy原生的版本要稍快一些

那麼是否有用parallel總會更好的慄子呢?當然是有的:

# 將數組所有元素限制在某個區間[a, b]內
# 小於 a 則置為 a,大於 b 則置為 b
# 經典應用:ReLU

@nb.vectorize("float32(float32, float32, float32)", target="parallel", nopython=True)
def clip_with_parallel(y, a, b):
if y < a:
return a
if y > b:
return b
return y

@nb.vectorize("float32(float32, float32, float32)", nopython=True)
def clip(y, a, b):
if y < a:
return a
if y > b:
return b
return y

assert np.allclose(np.clip(y, 0.1, 0.9), clip(y, 0.1, 0.9), clip_with_parallel(y, 0.1, 0.9))
%timeit clip_with_parallel(y, 0.1, 0.9)
%timeit clip(y, 0.1, 0.9)
%timeit np.clip(y, 0.1, 0.9)

上述程序的運行結果將會是:

95.2 µs ± 5.6 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
104 µs ± 4.52 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
377 µs ± 62 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

這個慄子中的性能提升就是實打實的了。總之,使用parallel時不能一概而論,還是要做些實驗

需要指出的是,vectorize中的參數target一共有三種取值:cpu(默認)、parallel和cuda。關於選擇哪個取值,官方文檔上有很好的說明:

A general guideline is to choose different targets for different data sizes and algorithms. The 「cpu」 target works well for small data sizes (approx. less than 1KB) and low compute intensity algorithms. It has the least amount of overhead. The 「parallel」 target works well for medium data sizes (approx. less than 1MB). Threading adds a small delay. The 「cuda」 target works well for big data sizes (approx. greater than 1MB) and high compute intensity algorithms. Transfering memory to and from the GPU adds significant overhead.
使用 jit(nogil=True) 實現高效並發(多線程)

我們知道,Python 中由於有 GIL 的存在,所以多線程用起來非常不舒服。不過 numba 的 jit 裡面有一項參數叫 nogil,想來聰明的觀眾老爺們都猜到了它是幹什麼的了……

下面就來看一個慄子:

import math
from concurrent.futures import ThreadPoolExecutor

# 計算類似於 Sigmoid 的函數
def np_func(a, b):
return 1 / (a + np.exp(-b))

# 參數中的 result 代表的即是我們想要的結果,後同
# 第一個 kernel,nogil 參數設為了 False
@nb.jit(nopython=True, nogil=False)
def kernel1(result, a, b):
for i in range(len(result)):
result[i] = 1 / (a[i] + math.exp(-b[i]))

# 第二個 kernel,nogil 參數設為了 True
@nb.jit(nopython=True, nogil=True)
def kernel2(result, a, b):
for i in range(len(result)):
result[i] = 1 / (a[i] + math.exp(-b[i]))

def make_single_task(kernel):
def func(length, *args):
result = np.empty(length, dtype=np.float32)
kernel(result, *args)
return result
return func

def make_multi_task(kernel, n_thread):
def func(length, *args):
result = np.empty(length, dtype=np.float32)
args = (result,) + args
# 將每個線程接受的參數定義好
chunk_size = (length + n_thread - 1) // n_thread
chunks = [[arg[i*chunk_size:(i+1)*chunk_size] for i in range(n_thread)] for arg in args]
# 利用 ThreadPoolExecutor 進行並發
with ThreadPoolExecutor(max_workers=n_thread) as e:
for _ in e.map(kernel, *chunks):
pass
return result
return func

length = 10 ** 6
a = np.random.rand(length).astype(np.float32)
b = np.random.rand(length).astype(np.float32)

nb_func1 = make_single_task(kernel1)
nb_func2 = make_multi_task(kernel1, 4)
nb_func3 = make_single_task(kernel2)
nb_func4 = make_multi_task(kernel2, 4)

rs_np = np_func(a, b)
rs_nb1 = nb_func1(length, a, b)
rs_nb2 = nb_func2(length, a, b)
rs_nb3 = nb_func3(length, a, b)
rs_nb4 = nb_func4(length, a, b)
assert np.allclose(rs_np, rs_nb1, rs_nb2, rs_nb3, rs_nb4)
%timeit np_func(a, b)
%timeit nb_func1(length, a, b)
%timeit nb_func2(length, a, b)
%timeit nb_func3(length, a, b)
%timeit nb_func4(length, a, b)

這個慄子有點長,不過我們只需要知道如下兩點即可:

* make_single_task和make_multi_task分別生成單線程函數和多線程函數

* 生成的函數會調用相應的kernel來完成計算

上述程序的運行結果將會是:

14.9 ms ± 538 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
8.32 ms ± 259 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
10.2 ms ± 368 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
8.25 ms ± 279 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
4.68 ms ± 114 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

一般來說,數據量越大、並發的效果越明顯。反之,數據量小的時候,並發很有可能會降低性能

numba 的應用實例 —— 卷積與池化

如果只想看效果的話倒沒什麼關係,不過如果想知道我具體在幹什麼的話,可以參見這篇文章

首先是卷積操作:

import numba as nb
import numpy as np

# 普通的卷積
def conv_kernel(x, w, rs, n, n_channels, height, width, n_filters, filter_height, filter_width, out_h, out_w):
for i in range(n):
for j in range(out_h):
for p in range(out_w):
window = x[i, ..., j:j+filter_height, p:p+filter_width]
for q in range(n_filters):
rs[i, q, j, p] += np.sum(w[q] * window)
return rs

# 簡單地加了個 jit 後的卷積
@nb.jit(nopython=True)
def jit_conv_kernel(x, w, rs, n, n_channels, height, width, n_filters, filter_height, filter_width, out_h, out_w):
for i in range(n):
for j in range(out_h):
for p in range(out_w):
window = x[i, ..., j:j+filter_height, p:p+filter_width]
for q in range(n_filters):
rs[i, q, j, p] += np.sum(w[q] * window)

# 卷積操作的封裝
def conv(x, w, kernel, args):
n, n_filters = args[0], args[4]
out_h, out_w = args[-2:]
rs = np.zeros([n, n_filters, out_h, out_w], dtype=np.float32)
kernel(x, w, rs, *args)
return rs

# 64 個 3 x 28 x 28 的圖像輸入(模擬 mnist)
x = np.random.randn(64, 3, 28, 28).astype(np.float32)
# 16 個 5 x 5 的 kernel
w = np.random.randn(16, x.shape[1], 5, 5).astype(np.float32)

n, n_channels, height, width = x.shape
n_filters, _, filter_height, filter_width = w.shape
out_h = height - filter_height + 1
out_w = width - filter_width + 1
args = (n, n_channels, height, width, n_filters, filter_height, filter_width, out_h, out_w)

print(np.linalg.norm((conv(x, w, conv_kernel, args) - conv(x, w, jit_conv_kernel, args)).ravel()))
%timeit conv(x, w, conv_kernel, args)
%timeit conv(x, w, jit_conv_kernel, args)

上述程序的運行結果將會是:

0.00112681
3.63 s ± 194 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
300 ms ± 20.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

可以看到,僅僅是加了一個jit、速度就直接提升了十多倍

有細心的觀眾老爺可能已經發現,我這裡沒有使用np.allclose;這是因為卷積涉及到的運算太多,僅僅是將數組的dtype從float64變成float32、精度就會大幅下降,所以使用np.allclose的話會過不了assert

同時需要特別注意的是,使用jit和使用純numpy進行編程的很大一點不同就是,不要畏懼用for;事實上一般來說,代碼「長得越像 C」、速度就會越快:

@nb.jit(nopython=True)
def jit_conv_kernel2(x, w, rs, n, n_channels, height, width, n_filters, filter_height, filter_width, out_h, out_w):
for i in range(n):
for j in range(out_h):
for p in range(out_w):
for q in range(n_filters):
for r in range(n_channels):
for s in range(filter_height):
for t in range(filter_width):
rs[i, q, j, p] += x[i, r, j+s, p+t] * w[q, r, s, t]

assert np.allclose(conv(x, w, jit_conv_kernel, args), conv(x, w, jit_conv_kernel, args))
%timeit conv(x, w, jit_conv_kernel, args)
%timeit conv(x, w, jit_conv_kernel2, args)

那麼程序的運行結果將會是:

281 ms ± 12.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
66.2 ms ± 2.32 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

可以看到這又快了 3 倍左右

接下來是池化操作(我們選用的是 MaxPool):

# 普通的 MaxPool
def max_pool_kernel(x, rs, *args):
n, n_channels, pool_height, pool_width, out_h, out_w = args
for i in range(n):
for j in range(n_channels):
for p in range(out_h):
for q in range(out_w):
window = x[i, j, p:p+pool_height, q:q+pool_width]
rs[i, j, p, q] += np.max(window)

# 簡單地加了個 jit 後的 MaxPool
@nb.jit(nopython=True)
def jit_max_pool_kernel(x, rs, *args):
n, n_channels, pool_height, pool_width, out_h, out_w = args
for i in range(n):
for j in range(n_channels):
for p in range(out_h):
for q in range(out_w):
window = x[i, j, p:p+pool_height, q:q+pool_width]
rs[i, j, p, q] += np.max(window)

# 不懼用 for 的、「更像 C」的 MaxPool
@nb.jit(nopython=True)
def jit_max_pool_kernel2(x, rs, *args):
n, n_channels, pool_height, pool_width, out_h, out_w = args
for i in range(n):
for j in range(n_channels):
for p in range(out_h):
for q in range(out_w):
_max = x[i, j, p, q]
for r in range(pool_height):
for s in range(pool_width):
_tmp = x[i, j, p+r, q+s]
if _tmp > _max:
_max = _tmp
rs[i, j, p, q] += _max

# MaxPool 的封裝
def max_pool(x, kernel, args):
n, n_channels = args[:2]
out_h, out_w = args[-2:]
rs = np.zeros([n, n_filters, out_h, out_w], dtype=np.float32)
kernel(x, rs, *args)
return rs

pool_height, pool_width = 2, 2
n, n_channels, height, width = x.shape
out_h = height - pool_height + 1
out_w = width - pool_width + 1
args = (n, n_channels, pool_height, pool_width, out_h, out_w)

assert np.allclose(max_pool(x, max_pool_kernel, args), max_pool(x, jit_max_pool_kernel, args))
assert np.allclose(max_pool(x, jit_max_pool_kernel, args), max_pool(x, jit_max_pool_kernel2, args))
%timeit max_pool(x, max_pool_kernel, args)
%timeit max_pool(x, jit_max_pool_kernel, args)
%timeit max_pool(x, jit_max_pool_kernel2, args)

上述程序的運行結果將會是:

586 ms ± 38 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
8.25 ms ± 526 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
1.4 ms ± 57 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

可以看到最快的比最慢的要快整整 400 倍有多

以上就是 numba 的基本應用。

相關焦點

  • Numba:基於CUDA加速的高性能Python
    導致python如此流行的原因有很多,主要有其簡潔且易於理解的語法和標準的數據結構,廣泛的功能齊備的標準庫,優秀的文檔,庫和工具構成的良好的生態系統,專業支持的可用性以及大而開放的社區。也許最重要的是,像Python這樣的動態類型解釋語言的高效率。Python是靈活的,這使其成為可用於快速原型設計的一種很好的語言,同時也能用於構建完整的系統。
  • Numba:用CUDA加速的高性能Python編譯器
    【IT168 編譯】Python是一種高效的動態程式語言,廣泛應用於科學、工程和數據分析應用領域。使python如此受歡迎的因素有很多,包括其乾淨的、表達性的語法和標準的數據結構,綜合的「內置電池」標準庫,優秀的文檔,庫和工具的廣泛生態系統,專業支持的可用性,以及大而開放的社區。
  • 一行代碼讓你的Python運行速度提高100倍!Python真強!
    python一直被病垢運行速度太慢,但是實際上python的執行效率並不慢,慢的是python用的解釋器Cpython運行效率太差。「一行代碼讓python的運行速度提高100倍」這絕不是譁眾取寵的論調。我們來看一下這個最簡單的例子,從1一直累加到1億。
  • 讓Python代碼更快運行的 5 種方法
    Python很簡單易用,但大多數人使用Python都知道在處理密集型cpu工作時,它的數量級依然低於C、Java和JavaScript。但不少第三方不願贅述Python的優點,而是決定自內而外提高其性能。
  • 四、python編程基礎之循環語句:基本語法以及應用實例
    實驗目的:學習python的循環語句實驗環境:已正確安裝python3.51、循環語句的語法循環語句主要應用於多次有規律的重複操作,python中有兩種循環語句,(1)for...in循環的基本用法對一個可遍歷對象利用for循環進行遍歷輸出,其中range([strat,end,step])函數產生給定範圍的整數。
  • 【技巧總結】Python 使用和高性能技巧完結
    使用numba.jit我們沿用上面介紹過的例子,在此基礎上使用numba.jit。 numba可以將 Python 函數 JIT 編譯為機器碼執行,大大提高代碼運行速度。關於numba的更多信息見下面的主頁:http://numba.pydata.orgPython 內置的數據結構如str, tuple, list, set, dict底層都是 C 實現的,速度非常快,自己實現新的數據結構想在性能上達到內置的速度幾乎是不可能的。
  • Python函數的基本特徵詳解
    函數是python為了代碼最大程度的重用和最小化代碼冗餘而提供的最基本的程序結構。函數有關的內容主要分為函數的基本概念、作用域以及參數傳遞,還有一些相關的高級概念,如裝飾器。那這一節我們先學習函數的最基本用法吧首先我們先學習一下在python中如何去編寫一個函數。
  • Python真實應用案例
    Python的應用範圍非常廣泛,在自動化、人工智慧、Web開發、軟體開發、數據科學、數據分析等領域都非常出色。這裡主要和大家分享一下Python的一些真實應用案例。對於Python真實應用案例最直觀而且目前已經非常成熟的領域屬於Web開發和軟體開發。
  • Python-random庫的基本應用
    偽隨機數:計算機中通過採用梅森旋轉算法生成的(偽)隨機序列元素,python中用於生成偽隨機數的函數庫是random概述這裡放幾個random庫常用函數:
  • 如何讓Python像Julia一樣快地運行
    包含下面的所有基準測試的完整代碼的 Notebook 可在此處(https://www.ibm.com/developerworks/community/blogs/jfp/resource/julia_python.zip)找到。 鑑於各種社交媒體上的評論,我添加了這樣一句話:我沒有在這裡使用 Python 的替代性實現。我沒有編寫任何 C代碼:如果您不信,可試試尋找分號。
  • python語言在全球廣泛應用
    下面就跟大家一起詳細聊聊,如何搭建一個實用的python編程環境,做到從零開始從linux到jupyternotebook來進行機器學習項目的學習。一、python初步了解先來談談python吧。python語言用得越來越多,幾乎可以代替c和c++,已經成為了一門通用語言,對於項目開發來說,首要作用就是利用。
  • Python 四大基本語法
    四大基本語法分別從變量命名規則、縮進原則、特殊關鍵字和特殊運算符四個方面,總結 Python 的基本語法。book_name self.book_store_count = book_store_count # 重寫加法操作 def __add__(self,book): return self.book_store_count + book.book_store_count# 創建兩個 Book 類的實例:python_intro_book
  • python可視化之matplotlib庫餅形圖的基本用法與應用實例
    實驗環境:已正確安裝python3.5、matplotlib1、餅形圖概念餅狀圖顯示一個整體中各項大小佔總體的百分比,可以清楚反應部分與總體的關係。在餅形圖上標出百分比為了在平面上觀察各項的比例,可以設置x與y的比例為1進行觀察。利用axes方法創建或激活軸,aspect為一個數字時,表示屏幕空間中y與x之比。
  • 科悟學院講解Python是什麼以及Python的應用
    今天科悟學院的講師就為你介紹一下Python是什麼以及Python的應用,希望對你有所幫助。Python 是一種解釋型、面向對象、動態數據類型的強類型高級程序設計語言。1.程式語言簡介和特點程式語言主要從以下幾個角度為進行分類,編譯型和解釋型、靜態語言和動態語言、強類型定義語言和弱類型定義語言,每個分類代表什麼意思呢,我們一起來看一下。
  • python量化投資(一):量化投資的基本流程
    本文將會講解量化投資過程中的基本流程,量化投資無非這幾個流程:數據輸入-策略書寫-回測輸出其中策略書寫部分還涉及到程式語言的選擇,如果不想苦惱數據輸入和回測輸出的話,還要選擇回測平臺。筆者一開始用的是matlab,但最終選擇了pythonpython:庫很多,只有你找不到的,沒有你想不到,和量化這塊結合比較緊密的有:Numpy&Scipy:科學計算庫,矩陣計算Pandas:金融數據分析神器,原AQR資本員工寫的一個庫,處理時間序列的標配pandas_datareader:國外股票宏觀數據接口Matplotlib
  • python+C、C++混合編程的應用
    我看到的一個很好的Python與c/c++混合編程的應用是NS3(Network Simulator3)一款網絡模擬軟體,它的內部計算引擎需要用高性能,但在用戶建模部分需要靈活易用。NS3的選擇是使用C/C++來模擬核心部件和協議,用python來建模和擴展。這篇文章介紹python和c/c++三種混合編程的方法,並對性能加以分析。
  • 淺析Python五大領域應用
    隨著國家戰略對「新基建」實施提上日程,大數據將會得到進一步推廣和應用。那麼在作為大數據開發語言之一的Python語言,又有哪些用武之地呢,我們可以用一張圖來簡單闡述。requests模塊在python內置模塊的基礎上進行了高度的封裝,從而使得python進行網絡請求時,變得人性化,使用Requests可以輕而易舉完成瀏覽器可有的任何操作。Beautiful Soup 是用Python寫的一個HTML/XML的解析器,它可以大大節省你的編程時間。
  • python String模塊在實際應用的代碼介紹
    python String模塊在實際應用的代碼介紹 本文以python String模塊的相關代碼的方式來介紹python String模塊的具體應用方法的介紹,那麼下面的文章就讓我們一起分享吧!
  • Python基本語法V1.5
    介紹1.1 介紹Python這種腳本語言福哥已經給大家介紹過了,現在童鞋們應該已經會安裝python的開發環境了,接下來我們需要針對python語言進行一個系統的學習。首先學習一門程式語言最先要學習的是語言的語法,學習程式語言的語法包括常量與變量、輸入與輸出、條件語句(if)、循環語句(for,while)、交換語句(switch,case)、函數(function)、對象(class),這個和之前福哥帶著童鞋們接觸c/c++、java、php三種主流程式語言的基本語法的順序是一樣的。
  • python能否開發安卓應用app?當然可以,python助你輕鬆搞定
    python是非常簡單方便的程式語言,你可以用python很簡潔的實現很多功能,今天就來說說如何用python開發安卓應用app。 app的開發有兩種方式: 第一種,混合開發。