▌使用 pathlib 模塊來更好地處理路徑
pathlib 是 Python 3默認的用於處理數據路徑的模塊,它能夠幫助我們避免使用大量的 os.path.joins語句:
from pathlib import Path dataset = 'wiki_images' datasets_root = Path('/path/to/datasets/') train_path = datasets_root / dataset / 'train' test_path = datasets_root / dataset / 'test' for image_path in train_path.iterdir(): with image_path.open() as f: # note, open is a method of Path object # do something with an image
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
向左滑動查看完整代碼
在Python2中,我們需要通過級聯字符串的形成來實現路徑的拼接。而現在有了pathlib模塊后,數據路徑處理將變得更加安全、准確,可讀性更強。
此外,pathlib.Path含有大量的方法,這樣Python的初學者將不再需要搜索每個方法:
p.exists() p.is_dir() p.parts() p.with_name('sibling.png') # only change the name, but keep the folder p.with_suffix('.jpg') # only change the extension, but keep the folder and the name p.chmod(mode) p.rmdir()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
使用pathlib還將大大節約你的時間。更多功能請查看:
官方文檔 - https://docs.python.org/3/library/pathlib.html
參考信息 - https://pymotw.com/3/pathlib/
▌類型提示(Type hinting)成為Python3中的新成員
下面是在編譯器PyCharm 中,類型提示功能的一個示例:

Python 不只是一門腳本的語言,如今的數據流程還包括大量的邏輯步驟,每一步都包括不同的框架(有時也包括不同的邏輯)。
Python3中引入了類型提示工具包來處理復雜的大型項目,使機器可以更好地對代碼進行驗證。而在這之前,不同的模塊需要使用自定義的方式,對文檔中的字符串指定類型 (注意:PyCharm可以將舊的文檔字符串轉換成新的類型提示)。
下面是一個簡單的代碼示例,利用類型提示功能來處理不同類型的數據:
def repeat_each_entry(data): """ Each entry in the data is doubled <blah blah nobody reads the documentation till the end> """ index = numpy.repeat(numpy.arange(len(data)), 2) return data[index]
- 1
- 2
- 3
- 4
- 5
- 6
上述代碼對多維的 numpy.array、astropy.Table 和 astropy.Column、bcolz、cupy、mxnet.ndarray 等操作同樣適用。
這段代碼還可用於 pandas.Series 操作,但是這種形式是錯誤的:
repeat_each_entry(pandas.Series(data=[0, 1, 2], index=[3, 4, 5])) # returns Series with Nones inside
- 1
這僅僅是一段兩行的代碼。所以,復雜系統的行為是非常難預測的,有時一個函數就可能導致整個系統的錯誤。因此,明確地了解哪些類型方法,並在這些類型方法未得到相應參數的時候發出錯誤提示,這對於大型系統的運作是很有幫助的。
def repeat_each_entry(data: Union[numpy.ndarray, bcolz.carray]):
- 1
如果你有一個很棒的代碼庫,諸如 MyPy這樣的類型提示工具將可能成為一個大型項目的集成流程中的一部分。不幸的是,類型提示功能還沒辦法強大到為 ndarrays/tensors 這種細粒度類型發出提示。或許,不久的將來我們就可以擁有這樣全面的的類型提示工具,這將成為數據科學領域需要的強大功能。
▌從類型提示(運行前)到類型檢查(運行時)
默認情況下,函數的注釋對於代碼的運行是沒有影響的,它只是幫你指出每段代碼所要做的工作。
在代碼運行階段,很多時候類型提示工具是不起作用的。這種情況你可以使用 enforce 等工具,強制性對代碼進行類型檢查,同時也可以幫助你調試代碼。
@enforce.runtime_validation def foo(text: str) -> None: print(text) foo('Hi') # ok foo(5) # fails @enforce.runtime_validation def any2(x: List[bool]) -> bool: return any(x) any ([False, False, True, False]) # True any2([False, False, True, False]) # True any (['False']) # True any2(['False']) # fails any ([False, None, "", 0]) # False any2([False, None, "", 0]) # fails
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
▌函數注釋的其他用途
正如上面我們提到的,函數的注釋部分不僅不會影響代碼的執行,還會提供可以隨時使用的一些元信息(meta-information)。
例如,計量單位是科學界的一個普遍難題,Python3中的astropy包提供了一個簡單的裝飾器(Decorator)來控制輸入的計量單位,並將輸出轉換成相應的單位。
#Python 3 from astropy import units as u @u.quantity_input() def frequency(speed: u.meter / u.s, wavelength: u.m) -> u.terahertz: return speed / wavelength frequency(speed=300_000 * u.km / u.s, wavelength=555 * u.nm) # output: 540.5405405405404 THz, frequency of green visible light
- 1
- 2
- 3
- 4
- 5
- 6
- 7
如果你需要用Python處理表格類型的科學數據,你可以嘗試astropy包,體驗一下計量單位隨意轉換的方便性。你還可以針對某個應用專門定義一個裝飾器,用同樣的方式來控制或轉換輸入和輸出的計量單位。
▌通過 @ 實現矩陣乘法
下面,我們實現一個最簡單的機器學習模型,即帶 L2 正則化的線性回歸 (如嶺回歸模型),來對比 Python2 和 Python3 之間的差別:
# l2-regularized linear regression: || AX - b ||^2 + alpha * ||x||^2 -> min # Python 2 X = np.linalg.inv(np.dot(A.T, A) + alpha * np.eye(A.shape[1])).dot(A.T.dot(b)) # Python 3 X = np.linalg.inv(A.T @ A + alpha * np.eye(A.shape[1])) @ (A.T @ b)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在 Python3 中,以@作為矩陣乘法符號使得代碼整體的可讀性更強,且更容易在不同的深度學習框架間進行轉譯:因為一些代碼如 X @ W + b[None, :]在 numpy、cupy、pytorch 和 tensorflow 等不同庫中都表示單層感知機。
▌使用**作為通配符
Python2 中使用遞歸文件夾的通配符並不是很方便,因此可以通過定制的 glob2 模塊來解決這個問題。遞歸 flag 在 Python 3.6 中得到了支持。
import glob # Python 2 found_images = \ glob.glob('/path/*.jpg') \ + glob.glob('/path/*/*.jpg') \ + glob.glob('/path/*/*/*.jpg') \ + glob.glob('/path/*/*/*/*.jpg') \ + glob.glob('/path/*/*/*/*/*.jpg') # Python 3 found_images = glob.glob('/path/**/*.jpg', recursive=True)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
Python3 中更好的選擇是使用 pathlib:(缺少個import)
# Python 3 found_images = pathlib.Path('/path/').glob('**/*.jpg')
- 1
- 2
▌Python3中的print函數
誠然,print 在 Python3 中是一個函數,使用 print 需要加上圓括弧(),雖然這是個麻煩的操作,但它還是具有一些優點:
使用文件描述符的簡單句法:
print >>sys.stderr, "critical error" # Python 2 print("critical error", file=sys.stderr) # Python 3
- 1
- 2
在不使用str.join情況下能夠輸出 tab-aligned 表格:
# Python 3 print(*array, sep='\t') print(batch, epoch, loss, accuracy, time, sep='\t')
- 1
- 2
- 3
修改與重新定義 print 函數的輸出:
# Python 3 _print = print # store the original print function def print(*args, **kargs): pass # do something useful, e.g. store output to some file
- 1
- 2
- 3
- 4
在 Jupyter notebook 中,這種形式能夠記錄每一個獨立的文檔輸出,並在出現錯誤的時候追蹤到報錯的文檔。這能方便我們快速定位並解決錯誤信息。因此我們可以重寫 print 函數。
在下面的代碼中,我們可以使用上下文管理器來重寫 print 函數的行為:
@contextlib.contextmanager def replace_print(): import builtins _print = print # saving old print function # or use some other function here builtins.print = lambda *args, **kwargs: _print('new printing', *args, **kwargs) yield builtins.print = _print with replace_print(): <code here will invoke other print function>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
但是,重寫print函數的行為,我們並不推薦,因為它會引起系統的不穩定。
print函數可以結合列表生成器或其它語言結構一起使用。
# Python 3 result = process(x) if is_valid(x) else print('invalid item: ', x)
- 1
- 2
▌f-strings 可作為簡單和可靠的格式化
默認的格式化系統提供了一些靈活性操作。但在數據實驗中這些操作不僅不是必須的,還會導致代碼的修改變得冗長和瑣碎。
而數據科學通常需要以固定的格式,迭代地打印出一些日志信息,所使用的代碼如下:
# Python 2 print('{batch:3} {epoch:3} / {total_epochs:3} accuracy: {acc_mean:0.4f}±{acc_std:0.4f} time: {avg_time:3.2f}'.format( batch=batch, epoch=epoch, total_epochs=total_epochs, acc_mean=numpy.mean(accuracies), acc_std=numpy.std(accuracies), avg_time=time / len(data_batch) )) # Python 2 (too error-prone during fast modifications, please avoid): print('{:3} {:3} / {:3} accuracy: {:0.4f}±{:0.4f} time: {:3.2f}'.format( batch, epoch, total_epochs, numpy.mean(accuracies), numpy.std(accuracies), time / len(data_batch) ))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
樣本輸出為:
120 12 / 300 accuracy: 0.8180±0.4649 time: 56.60
- 1
Python 3.6 中引入了格式化字符串 (f-strings):
# Python 3.6+ print(f'{batch:3} {epoch:3} / {total_epochs:3} accuracy: {numpy.mean(accuracies):0.4f}±{numpy.std(accuracies):0.4f} time: {time / len(data_batch):3.2f}')
- 1
- 2
另外,這對於查詢語句的書寫也是非常方便的:
query = f"INSERT INTO STATION VALUES (13, '{city}', '{state}', {latitude}, {longitude})"
- 1
▌「true division」和「integer division」之間的明顯區別
雖然說對於系統編程來說,Python3所提供的改進還遠遠不夠,但這些便利對於數據科學來說已經足夠。
data = pandas.read_csv('timing.csv') velocity = data['distance'] / data['time']
- 1
- 2
Python 2 中的結果依賴於『時間』和『距離』(例如,以米和秒為單位),關注其是否被保存為整數。
而在 Python 3 中,結果的表示都是精確的,因為除法運算得到的都是精確的浮點數。
另一個例子是整數除法,現在已經作為明確的運算:
n_gifts = money // gift_price # correct for int and float arguments
- 1
值得注意的是,整除運算可以應用到Python的內建類型和由numpy、pandas等數據包提供的自定義類型。
▌嚴格排序
下面是一個嚴格排序的例子:
# All these comparisons are illegal in Python 3 3 < '3' 2 < None (3, 4) < (3, None) (4, 5) < [4, 5] # False in both Python 2 and Python 3 (4, 5) == [4, 5]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
嚴格排序的主要功能有:
防止不同類型實例之間的偶然性排序。
“`
sorted([2, ‘1’, 3]) # invalid for Python 3, in Python 2 returns [2, 3, ‘1’]
在處理原始數據時幫助我們發現存在的問題。此外,嚴格排序對None值的合適性檢查是(這對於兩個版本的 Python 都適用): ``` if a is not None: pass if a: # WRONG check for None pass
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
▌自然語言處理中的Unicode編碼
下面來看一個自然語言處理任務:
s = '您好' print(len(s)) print(s[:2])
- 1
- 2
- 3
比較兩個版本Python的輸出:
Python2: 6\n��
Python3: 2\n 您好
再來看個例子:
x = u'со' x += 'co' # ok x += 'со' # fail
- 1
- 2
- 3
在這里,Python 2 會報錯,而 Python 3 能夠正常工作。因為我在字符串中使用了俄文字母,對於Python2 是無法識別或編碼這樣的字符。
Python 3 中的 strs 是 Unicode 字符串,這對非英語文本的自然語言處理任務來說將更加地方便。還有些其它有趣的應用,例如:
'a' < type < u'a' # Python 2: True 'a' < u'a' # Python 2: False
- 1
- 2
和
from collections import Counter Counter('Möbelstück')
- 1
- 2
Python 2: Counter({‘\xc3’: 2, ‘b’: 1, ‘e’: 1, ‘c’: 1, ‘k’: 1, ‘M’: 1, ‘l’: 1, ‘s’: 1, ‘t’: 1, ‘\xb6’: 1, ‘\xbc’: 1})
Python 3: Counter({‘M’: 1, ‘ö’: 1, ‘b’: 1, ‘e’: 1, ‘l’: 1, ‘s’: 1, ‘t’: 1, ‘ü’: 1, ‘c’: 1, ‘k’: 1})
對於這些,Python 2 也能正常地工作,但 Python 3 的支持更為友好。
▌保留詞典和**kwargs 的順序
CPython 3.6+ 的版本中字典的默認行為是一種類似 OrderedDict 的類,但最新的 Python3.7 版本,此類已經得到了全面的支持。這就要求在字典理解、json 序列化/反序列化等操作中保持字典原先的順序。
下面來看個例子:
import json x = {str(i):i for i in range(5)} json.loads(json.dumps(x)) # Python 2 {u'1': 1, u'0': 0, u'3': 3, u'2': 2, u'4': 4} # Python 3 {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
這種保順性同樣適用於 Python3.6 版本中的 **kwargs:它們的順序就像參數中顯示的那樣。當設計數據流程時,參數的順序至關重要。
以前,我們必須以這樣繁瑣的方式來編寫:
from torch import nn # Python 2 model = nn.Sequential(OrderedDict([ ('conv1', nn.Conv2d(1,20,5)), ('relu1', nn.ReLU()), ('conv2', nn.Conv2d(20,64,5)), ('relu2', nn.ReLU()) ])) # Python 3.6+, how it *can* be done, not supported right now in pytorch model = nn.Sequential( conv1=nn.Conv2d(1,20,5), relu1=nn.ReLU(), conv2=nn.Conv2d(20,64,5), relu2=nn.ReLU()) )
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
注意到了嗎?名稱的唯一性也會被自動檢查。
▌迭代拆封
Python3 中引入迭代式拆封功能,下面來看一段代碼:
# handy when amount of additional stored info may vary between experiments, but the same code can be used in all cases
model_paramteres, optimizer_parameters, *other_params = load(checkpoint_name) # picking two last values from a sequence *prev, next_to_last, last = values_history # This also works with any iterables, so if you have a function that yields e.g. qualities, # below is a simple way to take only last two values from a list *prev, next_to_last, last = iter_train(args)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
▌默認的 pickle 引擎為數組提供更好的壓縮
Python3 中引入 pickle 引擎,為數組提供更好的壓縮,節省參數空間:
# Python 2 import cPickle as pickle import numpy print len(pickle.dumps(numpy.random.normal(size=[1000, 1000]))) # result: 23691675 # Python 3 import pickle import numpy len(pickle.dumps(numpy.random.normal(size=[1000, 1000]))) # result: 8000162
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
這個小的改進節省了3倍的空間,而且運行階段速度更快。實際上,如果不關心速度的話,類似的壓縮性能也可以通過設置參數 protocol=2 來實現,但是用戶經常會忽略這個選項或者根本不了解這個功能。
▌更安全的解析功能
Python3 能為代碼提供更安全的解析,提高代碼的可讀性。具體如下段代碼所示:
labels = <initial_value>
predictions = [model.predict(data) for data, labels in dataset] # labels are overwritten in Python 2 # labels are not affected by comprehension in Python 3
- 1
- 2
- 3
- 4
- 5
關於 super(),simply super()
Python2 中的 super() 方法,是常見的錯誤代碼。我們來看這段代碼:
# Python 2 class MySubClass(MySuperClass): def __init__(self, name, **options): super(MySubClass, self).__init__(name='subclass', **options) # Python 3 class MySubClass(MySuperClass): def __init__(self, name, **options): super().__init__(name='subclass', **options)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
有關 super() 方法及方法解析順序的更多內容,參見 stackoverflow:
https://stackoverflow.com/questions/576169/understanding-python-super-with-init-methods
▌更好的 IDE 會給出變量注釋
編程過程中使用一個好的IDE,能夠給初學者一種更好的編程體驗。一個好的IDE能夠給不同的編程語言如Java、C#等,提供友好的編程環境及非常有用的編程建議,因為在執行代碼之前,所有標識符的類型都是已知的。
對於 Python,雖然這些 IDE 的功能是很難實現,但是代碼的注釋能夠在編程過程幫助到我們:
- 以清晰的形式提示你下一步想要做的
- 從 IDE 獲取良好的建議
這是 PyCharm IDE 的一個示例。雖然例子中所使用的函數不帶注釋,但是這些帶注釋的變量,利用代碼的后向兼容性,也能保證程序的正常工作。
▌多種拆封(unpacking)
下面是 Python3 中字典融合的代碼示例:
x = dict(a=1, b=2) y = dict(b=3, d=4) # Python 3.5+ z = {**x, **y} # z = {'a': 1, 'b': 3, 'd': 4}, note that value for `b` is taken from the latter dict.
- 1
- 2
- 3
- 4
- 5
如果你想對比兩個版本之間的差異性,可以參考以下這個鏈接來了解更多的信息:
https://stackoverflow.com/questions/38987/how-to-merge-two-dictionaries-in-a-single-expression
aame 方法對於 Python 中的列表(list)、元組(tuple)和集合(set)等類型都是有效的,通過下面這段代碼我們能夠更清楚地了解它們的工作原理,其中a、b、c是任意的可迭代對象:
[*a, *b, *c] # list, concatenating (*a, *b, *c) # tuple, concatenating {*a, *b, *c} # set, union
- 1
- 2
- 3
此外,函數同樣支持 *args 和 **kwargs 的 unpacking 過程:
Python 3.5+ do_something(**{**default_settings, **custom_settings}) # Also possible, this code also checks there is no intersection between keys of dictionaries do_something(**first_args, **second_args)
- 1
- 2
- 3
- 4
▌不會過時的技術—只帶關鍵字參數的 API
我們來看這段代碼:
model = sklearn.svm.SVC(2, 'poly', 2, 4, 0.5)
- 1
顯而易見,這段代碼的作者還不熟悉 Python 的代碼風格,很可能剛從 C++ 或 rust語言轉 Python。代碼風格不僅是個人偏好的問題,還因為在 SVC 接口中改變參數順序(adding/deleting)會使代碼無效。特別是對於 sklearn,經常要通過重新排序或重命名大量的算法參數以提供一致的 API。而每次的重構都可能使代碼失效。
在 Python3中依賴庫的編寫者通常會需要使用*以明確地命名參數:
class SVC(BaseSVC):
def __init__(self, *, C=1.0, kernel='rbf', degree=3, gamma='auto', coef0=0.0, ... )
- 1
- 2
使用時,用戶需要明確規定 sklearn.svm.SVC(C=2, kernel=’poly’, degree=2, gamma=4, coef0=0.5) 中參數的命名。
這種參數命名機制使得 API 同時兼具可靠性和靈活性。
▌微調:math模塊中的常量
Python3 中 math 模塊的改動,可以查看下面這段代碼:
# Python 3 math.inf # 'largest' number math.nan # not a number max_quality = -math.inf # no more magic initial values! for model in trained_models: max_quality = max(max_quality, compute_quality(model, data))
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
▌微調:單精度整數類型
Python 2 中提供了兩種基本的整數類型,即 int(64 位符號整數)和用於長整型數值計算的 long 類型(長整型)。而在 Python 3 中對單精度的整型數據有個微小的改動,使其包含長整型(long) 的運算。下面這段代碼教你如何查看整型值:
isinstance(x, numbers.Integral) # Python 2, the canonical way isinstance(x, (long, int)) # Python 2 isinstance(x, int) # Python 3, easier to remember
- 1
- 2
- 3
▌其他改動
- Enums 的改動具有理論價值,是因為字符串輸入已廣泛應用在 python 數據棧中。Enums - 雖然不與 numpy 庫交互,但是在 pandas 中有良好的兼容性。
- 協同程序將很有可能用於數據流程的處理,雖然目前還沒有大規模應用的出現。
- Python 3 有穩定的 ABI。
- Python 3 支持 unicode 編碼格式,如 ω = Δφ / Δt 也是可以允許的,但最好使用兼容性更好的舊 ASCII 名稱。
- 一些庫比如 jupyterhub(jupyter in cloud)、django 和新版 ipython 都只支持 Python 3,因此這些用處不大的庫對你來講,可能只會偶爾使用一次。
▌數據科學中代碼遷移所會碰到的問題及解決方案
放棄對嵌套參數的支持:
map(lambda x, (y, z): x, z, dict.items())
- 1
然而,它依然能夠完美地適用於不同的理解:
{x:z for x, (y, z) in d.items()}
- 1
通常,理解在 Python2 和 3 之間差異能夠幫助我們更好地‘轉義’代碼。
map(), .keys(), .values(), .items() 等等,返回的是迭代器而不是列表。迭代器的主要問題包括:沒有瑣碎的分割,以及無法進行二次迭代。將返回的結果轉化為列表幾乎可以解決所有問題。
如遇到其他問題請參見這篇有關 Python 的問答:“如何將 Python3 移植到我的程序中?”( https://eev.ee/blog/2016/07/31/python-faq-how-do-i-port-to-python-3/)
▌Python 機器學習和 python 數據科學領域所會碰到的主要問題
這些課程的作者首先要花點時間解釋 python 中什么是迭代器,為什么它不能像字符串那樣被分片/級聯/相乘/二次迭代(以及如何處理它)。
我相信大多數課程的作者都很希望能夠避開這些繁瑣的細節,但是現在看來這幾乎是個不可避免的話題。
▌結論
Python 的兩個版本( Python2 與 Python3 )共存了近10年的時間。時至今日,我們不得不說:是時候該轉向 Python 3 了。
科學研究和實際生產中,代碼應該更短,可讀性更強,並且在遷移到 Python 3 后的代碼庫將更加得安全。
目前 Python 的大多數庫仍同時支持 2.x 和 3.x 兩個版本。但我們不應等到這些依賴庫開始停止支持 Python 2 才開始轉向 Python3,我們現在就可以享受新語言的功能。
遷移到 Python3 后,我敢保證你的程序運行會更加順暢:「我們不會再做向后不兼容的事情了(https://snarky.ca/why-python-3-exists/)」。
參考內容:
Key differences between Python 2.7 and Python 3.x
http://sebastianraschka.com/Articles/2014_python_2_3_key_diff.html
Python FAQ: How do I port to Python 3?
https://eev.ee/blog/2016/07/31/python-faq-how-do-i-port-to-python-3/
10 awesome features of Python that you can’t use because you refuse to
upgrade to Python 3
http://www.asmeurer.com/python3-presentation/slides.html Trust me,
python 3.3 is better than 2.7 (video)
http://pyvideo.org/pycon-us-2013/python-33-trust-me-its-better-than-27.html
Python 3 for scientists
http://python-3-for-scientists.readthedocs.io/en/latest/
原文鏈接:
https://github.com/arogozhnikov/python3_with_pleasure#future-proof-apis-with-keyword-only-arguments
