花下貓語: 先祝你們假期快樂!今天,我要分享一篇長文,選自 Python 的官方文檔。它列舉了 27 個設計及歷史的問題,其中有些問題我曾經分享過,例如爲何使用顯式的 self、浮點數的問題、len(x) 而非 x.len() 等等。大部分的回答很簡略精要,適合在空閒之餘翻閱。建議你先收藏起來,隨時查看,溫故知新。html
Guido van Rossum 認爲使用縮進進行分組很是優雅,而且大大提升了普通Python程序的清晰度。大多數人在一段時間後就學會並喜歡上這個功能。python
因爲沒有開始/結束括號,所以解析器感知的分組與人類讀者之間不會存在分歧。偶爾C程序員會遇到像這樣的代碼片斷:git
if (x <= y) x++; y--; z++;
若是條件爲真,則只執行 x++
語句,但縮進會使你認爲狀況並不是如此。即便是經驗豐富的C程序員有時會長時間盯着它,想知道爲何即便 x > y
, y
也在減小。程序員
由於沒有開始/結束括號,因此Python不太容易發生編碼式衝突。在C中,括號能夠放到許多不一樣的位置。若是您習慣於閱讀和編寫使用一種風格的代碼,那麼在閱讀(或被要求編寫)另外一種風格時,您至少會感到有些不安。github
許多編碼風格將開始/結束括號單獨放在一行上。這使得程序至關長,浪費了寶貴的屏幕空間,使得更難以對程序進行全面的瞭解。理想狀況下,函數應該適合一個屏幕(例如,20--30行)。 20行Python能夠完成比20行C更多的工做。這不只僅是因爲缺乏開始/結束括號 -- 缺乏聲明和高級數據類型也是其中的緣由 -- 但縮進基於語法確定有幫助。正則表達式
請看下一個問題。算法
用戶常常對這樣的結果感到驚訝:express
>>> 1.2 - 1.0 0.19999999999999996
而且認爲這是 Python中的一個 bug。其實不是這樣。這與 Python 關係不大,而與底層平臺如何處理浮點數字關係更大。編程
CPython 中的 float
類型使用C語言的 double
類型進行存儲。 float
對象的值是以固定的精度(一般爲 53 位)存儲的二進制浮點數,因爲 Python 使用 C 操做,然後者依賴於處理器中的硬件實現來執行浮點運算。 這意味着就浮點運算而言,Python 的行爲相似於許多流行的語言,包括 C 和 Java。數組
許多能夠輕鬆地用十進制表示的數字不能用二進制浮點表示。例如,在輸入如下語句後:
>>> x = 1.2
爲 x
存儲的值是與十進制的值 1.2
(很是接近) 的近似值,但不徹底等於它。 在典型的機器上,實際存儲的值是:
1.0011001100110011001100110011001100110011001100110011 (binary)
它對應於十進制數值:
1.1999999999999999555910790149937383830547332763671875 (decimal)
典型的 53 位精度爲 Python 浮點數提供了 15-16 位小數的精度。
要得到更完整的解釋,請參閱 Python 教程中的 浮點算術 一章。
有幾個優勢。
一個是性能:知道字符串是不可變的,意味着咱們能夠在建立時爲它分配空間,而且存儲需求是固定不變的。這也是元組和列表之間區別的緣由之一。
另外一個優勢是,Python 中的字符串被視爲與數字同樣「基本」。 任何動做都不會將值 8 更改成其餘值,在 Python 中,任何動做都不會將字符串 "8" 更改成其餘值。
這個想法借鑑了 Modula-3 語言。 出於多種緣由它被證實是很是有用的。
首先,更明顯的顯示出,使用的是方法或實例屬性而不是局部變量。 閱讀 self.x
或 self.meth()
能夠清楚地代表,即便您不知道類的定義,也會使用實例變量或方法。在 C++ 中,能夠經過缺乏局部變量聲明來判斷(假設全局變量不多見或容易識別) —— 可是在 Python 中沒有局部變量聲明,因此必須查找類定義才能肯定。 一些 C++ 和 Java 編碼標準要求實例屬性具備 m_
前綴,所以這種顯式性在這些語言中仍然有用。
其次,這意味着若是要顯式引用或從特定類調用該方法,不須要特殊語法。 在 C++ 中,若是你想使用在派生類中重寫基類中的方法,你必須使用 ::
運算符 -- 在 Python 中你能夠編寫 baseclass.methodname(self, <argumentlist>)
。 這對於 __init__()
方法很是有用,特別是在派生類方法想要擴展同名的基類方法,而必須以某種方式調用基類方法時。
最後,它解決了變量賦值的語法問題:爲了 Python 中的局部變量(根據定義!)在函數體中賦值的那些變量(而且沒有明確聲明爲全局)賦值,就必須以某種方式告訴解釋器一個賦值是爲了分配一個實例變量而不是一個局部變量,它最好是經過語法實現的(出於效率緣由)。 C++ 經過聲明來作到這一點,可是 Python 沒有聲明,僅僅爲了這個目的而引入它們會很惋惜。 使用顯式的 self.var
很好地解決了這個問題。 相似地,對於使用實例變量,必須編寫 self.var
意味着對方法內部的非限定名稱的引用沒必要搜索實例的目錄。 換句話說,局部變量和實例變量存在於兩個不一樣的命名空間中,您須要告訴 Python 使用哪一個命名空間。
許多習慣於C或Perl的人抱怨,他們想要使用C 的這個特性:
while (line = readline(f)) { // do something with line }
但在Python中被強制寫成這樣:
while True: line = f.readline() if not line: break ... # do something with line
不容許在 Python 表達式中賦值的緣由是這些其餘語言中常見的、很難發現的錯誤,是由這個結構引發的:
if (x = 0) { // error handling } else { // code that only works for nonzero x }
錯誤是一個簡單的錯字: x = 0
,將0賦給變量 x
,而比較 x == 0
確定是能夠預期的。
已經有許多替代方案提案。 大多數是爲了少打一些字的黑客方案,但使用任意或隱含的語法或關鍵詞,並不符合語言變動提案的簡單標準:它應該直觀地向還沒有被介紹到這一律唸的人類讀者提供正確的含義。
一個有趣的現象是,大多數有經驗的Python程序員都認識到 while True
的習慣用法,也不太在乎是否能在表達式構造中賦值; 只有新人表達了強烈的願望但願將其添加到語言中。
有一種替代的拼寫方式看起來頗有吸引力,但一般不如"while True"解決方案可靠:
line = f.readline() while line: ... # do something with line... line = f.readline()
問題在於,若是你改變主意(例如你想把它改爲 sys.stdin.readline()
),如何知道下一行。你必須記住改變程序中的兩個地方 -- 第二次出現隱藏在循環的底部。
最好的方法是使用迭代器,這樣能經過 for
語句來循環遍歷對象。例如 file objects 支持迭代器協議,所以能夠簡單地寫成:
for line in f: ... # do something with line...
正如Guido所說:
(a) 對於某些操做,前綴表示法比後綴更容易閱讀 -- 前綴(和中綴!)運算在數學中有着悠久的傳統,就像在視覺上幫助數學家思考問題的記法。比較一下咱們將 x (a+b) 這樣的公式改寫爲 xa+x*b 的容易程度,以及使用原始OO符號作相同事情的笨拙程度。(b) 當讀到寫有len(X)的代碼時,就知道它要求的是某件東西的長度。這告訴咱們兩件事:結果是一個整數,參數是某種容器。相反,當閱讀x.len()時,必須已經知道x是某種實現接口的容器,或者是從具備標準len()的類繼承的容器。當沒有實現映射的類有get()或key()方法,或者不是文件的類有write()方法時,咱們偶爾會感到困惑。
—https://mail.python.org/pipermail/python-3000/2006-November/004643.html
從Python 1.6開始,字符串變得更像其餘標準類型,當添加方法時,這些方法提供的功能與始終使用String模塊的函數時提供的功能相同。這些新方法中的大多數已被普遍接受,但彷佛讓一些程序員感到不舒服的一種方法是:
", ".join(['1', '2', '4', '8', '16'])
結果以下:
"1, 2, 4, 8, 16"
反對這種用法有兩個常見的論點。
第一條是這樣的:「使用字符串文本(String Constant)的方法看起來真的很難看」,答案是也許吧,可是字符串文本只是一個固定值。若是在綁定到字符串的名稱上容許使用這些方法,則沒有邏輯上的理由使其在文字上不可用。
第二個異議一般是這樣的:「我其實是在告訴序列使用字符串常量將其成員鏈接在一塊兒」。遺憾的是並不是如此。出於某種緣由,把 split()
做爲一個字符串方法彷佛要容易得多,由於在這種狀況下,很容易看到:
"1, 2, 4, 8, 16".split(", ")
是對字符串文本的指令,用於返回由給定分隔符分隔的子字符串(或在默認狀況下,返回任意空格)。
join()
是字符串方法,由於在使用該方法時,您告訴分隔符字符串去迭代一個字符串序列,並在相鄰元素之間插入自身。此方法的參數能夠是任何遵循序列規則的對象,包括您本身定義的任何新的類。對於字節和字節數組對象也有相似的方法。
若是沒有引起異常,則try/except塊的效率極高。實際上捕獲異常是昂貴的。在2.0以前的Python版本中,一般使用這個習慣用法:
try: value = mydict[key] except KeyError: mydict[key] = getvalue(key) value = mydict[key]
只有當你指望dict在任什麼時候候都有key時,這纔有意義。若是不是這樣的話,你就是應該這樣編碼:
if key in mydict: value = mydict[key] else: value = mydict[key] = getvalue(key)
對於這種特定的狀況,您還可使用 value = dict.setdefault(key, getvalue(key))
,但前提是調用 getvalue()
足夠便宜,由於在全部狀況下都會對其進行評估。
你能夠經過一系列 if... elif... elif... else
.輕鬆完成這項工做。對於switch語句語法已經有了一些建議,但還沒有就是否以及如何進行範圍測試達成共識。有關完整的詳細信息和當前狀態,請參閱 PEP 275 。
對於須要從大量可能性中進行選擇的狀況,能夠建立一個字典,將case 值映射到要調用的函數。例如:
def function_1(...): ... functions = {'a': function_1, 'b': function_2, 'c': self.method_1, ...} func = functions[value] func()
對於對象調用方法,能夠經過使用 getattr()
內置檢索具備特定名稱的方法來進一步簡化:
def visit_a(self, ...): ... ... def dispatch(self, value): method_name = 'visit_' + str(value) method = getattr(self, method_name) method()
建議對方法名使用前綴,例如本例中的 visit_
。若是沒有這樣的前綴,若是值來自不受信任的源,攻擊者將可以調用對象上的任何方法。
答案1: 不幸的是,解釋器爲每一個Python堆棧幀推送至少一個C堆棧幀。此外,擴展能夠隨時回調Python。所以,一個完整的線程實現須要對C的線程支持。
答案2: 幸運的是, Stackless Python 有一個徹底從新設計的解釋器循環,能夠避免C堆棧。
Python的 lambda表達式不能包含語句,由於Python的語法框架不能處理嵌套在表達式內部的語句。然而,在Python中,這並非一個嚴重的問題。與其餘語言中添加功能的lambda表單不一樣,Python的 lambdas只是一種速記符號,若是您懶得定義函數的話。
函數已是Python中的第一類對象,能夠在本地範圍內聲明。 所以,使用lambda而不是本地定義的函數的惟一優勢是你不須要爲函數建立一個名稱 -- 這只是一個分配了函數對象(與lambda表達式生成的對象類型徹底相同)的局部變量!
Cython 將帶有可選註釋的Python修改版本編譯到C擴展中。 Nuitka 是一個將Python編譯成 C++ 代碼的新興編譯器,旨在支持完整的Python語言。要編譯成Java,能夠考慮 VOC 。
Python 內存管理的細節取決於實現。 Python 的標準實現 CPython 使用引用計數來檢測不可訪問的對象,並使用另外一種機制來收集引用循環,按期執行循環檢測算法來查找不可訪問的循環並刪除所涉及的對象。 gc
模塊提供了執行垃圾回收、獲取調試統計信息和優化收集器參數的函數。
可是,其餘實現(如 Jython 或 PyPy ),)能夠依賴不一樣的機制,如徹底的垃圾回收器 。若是你的Python代碼依賴於引用計數實現的行爲,則這種差別可能會致使一些微妙的移植問題。
在一些Python實現中,如下代碼(在CPython中工做的很好)可能會耗盡文件描述符:
for file in very_long_list_of_files: f = open(file) c = f.read(1)
實際上,使用CPython的引用計數和析構函數方案, 每一個新賦值的 f 都會關閉前一個文件。然而,對於傳統的GC,這些文件對象只能以不一樣的時間間隔(可能很長的時間間隔)被收集(和關閉)。
若是要編寫可用於任何python實現的代碼,則應顯式關閉該文件或使用 with
語句;不管內存管理方案如何,這都有效:
for file in very_long_list_of_files: with open(file) as f: c = f.read(1)
首先,這不是C標準特性,所以不能移植。(是的,咱們知道Boehm GC庫。它包含了 大多數 常見平臺(但不是全部平臺)的彙編代碼,儘管它基本上是透明的,但也不是徹底透明的; 要讓Python使用它,須要使用補丁。)
當Python嵌入到其餘應用程序中時,傳統的GC也成爲一個問題。在獨立的Python中,能夠用GC庫提供的版本替換標準的malloc()和free(),嵌入Python的應用程序可能但願用 它本身 替代malloc()和free(),而可能不須要Python的。如今,CPython能夠正確地實現malloc()和free()。
當Python退出時,從全局命名空間或Python模塊引用的對象並不老是被釋放。 若是存在循環引用,則可能發生這種狀況 C庫分配的某些內存也是不可能釋放的(例如像Purify這樣的工具會抱怨這些內容)。 可是,Python在退出時清理內存並嘗試銷燬每一個對象。
若是要強制 Python 在釋放時刪除某些內容,請使用 atexit
模塊運行一個函數,強制刪除這些內容。
雖然列表和元組在許多方面是類似的,但它們的使用方式一般是徹底不一樣的。能夠認爲元組相似於Pascal記錄或C結構;它們是相關數據的小集合,能夠是不一樣類型的數據,能夠做爲一個組進行操做。例如,笛卡爾座標適當地表示爲兩個或三個數字的元組。
另外一方面,列表更像其餘語言中的數組。它們傾向於持有不一樣數量的對象,全部對象都具備相同的類型,而且逐個操做。例如, os.listdir('.')
返回表示當前目錄中的文件的字符串列表。若是向目錄中添加了一兩個文件,對此輸出進行操做的函數一般不會中斷。
元組是不可變的,這意味着一旦建立了元組,就不能用新值替換它的任何元素。列表是可變的,這意味着您始終能夠更改列表的元素。只有不變元素能夠用做字典的key,所以只能將元組和非列表用做key。
CPython的列表其實是可變長度的數組,而不是lisp風格的鏈表。該實現使用對其餘對象的引用的連續數組,並在列表頭結構中保留指向該數組和數組長度的指針。
這使得索引列表 a[i]
的操做成本與列表的大小或索引的值無關。
當添加或插入項時,將調整引用數組的大小。並採用了一些巧妙的方法來提升重複添加項的性能; 當數組必須增加時,會分配一些額外的空間,以便在接下來的幾回中不須要實際調整大小。
CPython的字典實現爲可調整大小的哈希表。與B-樹相比,這在大多數狀況下爲查找(目前最多見的操做)提供了更好的性能,而且實現更簡單。
字典的工做方式是使用 hash()
內置函數計算字典中存儲的每一個鍵的hash代碼。hash代碼根據鍵和每一個進程的種子而變化很大;例如,"Python" 的hash值爲-539294296,而"python"(一個按位不一樣的字符串)的hash值爲1142331976。而後,hash代碼用於計算內部數組中將存儲該值的位置。假設您存儲的鍵都具備不一樣的hash值,這意味着字典須要恆定的時間 -- O(1),用Big-O表示法 -- 來檢索一個鍵。
字典的哈希表實現使用從鍵值計算的哈希值來查找鍵。若是鍵是可變對象,則其值可能會發生變化,所以其哈希值也會發生變化。可是,因爲不管誰更改鍵對象都沒法判斷它是否被用做字典鍵值,所以沒法在字典中修改條目。而後,當你嘗試在字典中查找相同的對象時,將沒法找到它,由於其哈希值不一樣。若是你嘗試查找舊值,也不會找到它,由於在該哈希表中找到的對象的值會有所不一樣。
若是你想要一個用列表索引的字典,只需先將列表轉換爲元組;用函數 tuple(L)
建立一個元組,其條目與列表 L
相同。 元組是不可變的,所以能夠用做字典鍵。
已經提出的一些不可接受的解決方案:
哈希按其地址(對象ID)列出。這不起做用,由於若是你構造一個具備相同值的新列表,它將沒法找到;例如:
mydict = {[1, 2]: '12'} print(mydict[[1, 2]])
會引起一個 KeyError
異常,由於第二行中使用的 [1, 2]
的 id 與第一行中的 id 不一樣。換句話說,應該使用 ==
來比較字典鍵,而不是使用 is
。
d.keys()
中的每一個值均可用做字典的鍵。若是須要,可使用如下方法來解決這個問題,但使用它須要你自擔風險:你能夠將一個可變結構包裝在一個類實例中,該實例同時具備 __eq__()
和 __hash__()
方法。而後,你必須確保駐留在字典(或其餘基於 hash 的結構)中的全部此類包裝器對象的哈希值在對象位於字典(或其餘結構)中時保持固定。:
class ListWrapper: def __init__(self, the_list): self.the_list = the_list def __eq__(self, other): return self.the_list == other.the_list def __hash__(self): l = self.the_list result = 98767 - len(l)*555 for i, el in enumerate(l): try: result = result + (hash(el) % 9999999) * 1001 + i except Exception: result = (result % 7777777) + i * 333 return result
注意,哈希計算因爲列表的某些成員可能不可用以及算術溢出的可能性而變得複雜。
此外,必須始終如此,若是 o1 == o2
(即 o1.__eq__(o2) is True
)則 hash(o1) == hash(o2)
`(即`o1.__hash__() == o2.__hash__()
),不管對象是否在字典中。 若是你不能知足這些限制,字典和其餘基於 hash 的結構將會出錯。
對於 ListWrapper ,只要包裝器對象在字典中,包裝列表就不能更改以免異常。除非你準備好認真考慮需求以及不正確地知足這些需求的後果,不然不要這樣作。請留意。
在性能很重要的狀況下,僅僅爲了排序而複製一份列表將是一種浪費。所以, list.sort()
對列表進行了適當的排序。爲了提醒您這一事實,它不會返回已排序的列表。這樣,當您須要排序的副本,但也須要保留未排序的版本時,就不會意外地覆蓋列表。
若是要返回新列表,請使用內置 sorted()
函數。此函數從提供的可迭代列表中建立新列表,對其進行排序並返回。例如,下面是如何迭代遍歷字典並按keys排序:
for key in sorted(mydict): ... # do whatever with mydict[key]...
由C++和Java等語言提供的模塊接口規範描述了模塊的方法和函數的原型。許多人認爲接口規範的編譯時強制執行有助於構建大型程序。
Python 2.6添加了一個 abc
模塊,容許定義抽象基類 (ABCs)。而後可使用 isinstance()
和 issubclass()
來檢查實例或類是否實現了特定的ABC。 collections.abc
模塊定義了一組有用的ABCs 例如 Iterable
, Container
, 和 MutableMapping
對於Python,經過對組件進行適當的測試規程,能夠得到接口規範的許多好處。還有一個工具PyChecker,可用於查找因爲子類化引發的問題。
一個好的模塊測試套件既能夠提供迴歸測試,也能夠做爲模塊接口規範和一組示例。許多Python模塊能夠做爲腳本運行,以提供簡單的「自我測試」。即便是使用複雜外部接口的模塊,也經常可使用外部接口的簡單「樁代碼(stub)」模擬進行隔離測試。可使用 doctest
和 unittest
模塊或第三方測試框架來構造詳盡的測試套件,以運行模塊中的每一行代碼。
適當的測試規程能夠幫助在Python中構建大型的、複雜的應用程序以及接口規範。事實上,它可能會更好,由於接口規範不能測試程序的某些屬性。例如, append()
方法將向一些內部列表的末尾添加新元素;接口規範不能測試您的 append()
實現是否可以正確執行此操做,可是在測試套件中檢查這個屬性是很簡單的。
編寫測試套件很是有用,您可能但願設計代碼時着眼於使其易於測試。一種日益流行的技術是面向測試的開發,它要求在編寫任何實際代碼以前,首先編寫測試套件的各個部分。固然,Python容許您草率行事,根本不編寫測試用例。
可使用異常捕獲來提供 「goto結構」 ,甚至能夠跨函數調用工做的 。許多人認爲異常捕獲能夠方便地模擬C,Fortran和其餘語言的 "go" 或 "goto" 結構的全部合理用法。例如:
class label(Exception): pass # declare a label try: ... if condition: raise label() # goto label ... except label: # where to goto pass ...
可是不容許你跳到循環的中間,這一般被認爲是濫用goto。謹慎使用。
更準確地說,它們不能以奇數個反斜槓結束:結尾處的不成對反斜槓會轉義結束引號字符,留下未結束的字符串。
原始字符串的設計是爲了方便想要執行本身的反斜槓轉義處理的處理器(主要是正則表達式引擎)建立輸入。此類處理器將不匹配的尾隨反斜槓視爲錯誤,所以原始字符串不容許這樣作。反過來,容許經過使用引號字符轉義反斜槓轉義字符串。當r-string用於它們的預期目的時,這些規則工做的很好。
若是您正在嘗試構建Windows路徑名,請注意全部Windows系統調用都使用正斜槓:
f = open("/mydir/file.txt") # works fine!
若是您正在嘗試爲DOS命令構建路徑名,請嘗試如下示例
dir = r"\this\is\my\dos\dir" "\\" dir = r"\this\is\my\dos\dir\ "[:-1] dir = "\\this\\is\\my\\dos\\dir\\"
Python有一個 'with' 語句,它封裝了塊的執行,在塊的入口和出口調用代碼。有些語言的結構是這樣的:
with obj: a = 1 # equivalent to obj.a = 1 total = total + 1 # obj.total = obj.total + 1
在Python中,這樣的結構是不明確的。
其餘語言,如ObjectPascal、Delphi和C++ 使用靜態類型,所以能夠絕不含糊地知道分配給什麼成員。這是靜態類型的要點 -- 編譯器 老是 在編譯時知道每一個變量的做用域。
Python使用動態類型。事先不可能知道在運行時引用哪一個屬性。能夠動態地在對象中添加或刪除成員屬性。這使得沒法經過簡單的閱讀就知道引用的是什麼屬性:局部屬性、全局屬性仍是成員屬性?
例如,採用如下不完整的代碼段:
def foo(a): with a: print(x)
該代碼段假設 "a" 必須有一個名爲 "x" 的成員屬性。然而,Python中並無告訴解釋器這一點。假設 "a" 是整數,會發生什麼?若是有一個名爲 "x" 的全局變量,它是否會在with塊中使用?如您所見,Python的動態特性使得這樣的選擇更加困難。
然而,Python 能夠經過賦值輕鬆實現 "with" 和相似語言特性(減小代碼量)的主要好處。代替:
function(args).mydict[index][index].a = 21 function(args).mydict[index][index].b = 42 function(args).mydict[index][index].c = 63
寫成這樣:
ref = function(args).mydict[index][index] ref.a = 21 ref.b = 42 ref.c = 63
這也具備提升執行速度的反作用,由於Python在運行時解析名稱綁定,而第二個版本只須要執行一次解析。
冒號主要用於加強可讀性(ABC語言實驗的結果之一)。考慮一下這個:
if a == b print(a)
與
if a == b: print(a)
注意第二種方法稍微容易一些。請進一步注意,在這個FAQ解答的示例中,冒號是如何設置的;這是英語中的標準用法。
另外一個次要緣由是冒號使帶有語法突出顯示的編輯器更容易工做;他們能夠尋找冒號來決定什麼時候須要增長縮進,而沒必要對程序文本進行更精細的解析。
Python 容許您在列表,元組和字典的末尾添加一個尾隨逗號:
[1, 2, 3,] ('a', 'b', 'c',) d = { "A": [1, 5], "B": [6, 7], # last trailing comma is optional but good style }
有幾個理由容許這樣作。
若是列表,元組或字典的字面值分佈在多行中,則更容易添加更多元素,由於沒必要記住在上一行中添加逗號。這些行也能夠從新排序,而不會產生語法錯誤。
不當心省略逗號會致使難以診斷的錯誤。例如:
x = [ "fee", "fie" "foo", "fum" ]
這個列表看起來有四個元素,但實際上包含三個 : "fee", "fiefoo" 和 "fum" 。老是加上逗號能夠避免這個錯誤的來源。
容許尾隨逗號也可使編程代碼更容易生成。
公衆號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫做、優質英文推薦與翻譯等等,歡迎關注哦。