後悔沒早知道這些Python特性
寫 Python 也好幾年時間了。講道理,在工作中大家肯定遇到過這樣的場景:
這個故事告訴我們什麼?先造輪子再去 GitHub?還是提高下 GitHub 搜尋技巧?
都不是!
實際上,在日常的工作中,我們很多需求,無論是常見的、還是不常見的,Python 都為我們提供了一些獨特的解決方案,既不需要自己造輪子,也不需要引入新的依賴(引入新的依賴勢必會增加專案的複雜度)。
但是 Python 有太多功能和特性被我們忽略了,導致我們在遇到問題的時候,沒法第一時間作出良好的決策。
所以,乾脆來一起掃清這些被我們忽略的 Python 死角。
裝飾器的妙用
我們經常會想完成一些註冊&呼叫的功能,比如我們有四個函式:
現在我們想將這四個函式和 +、-、*、/ 四個操作符繫結,那麼我們該怎麼做?
可能我們第一反應是這樣:
operator_map = {}
def add(a: int, b: int) -> float:
return a + b
def sub(a: int, b: int) -> float:
return a - b
def mul(a: int, b: int) -> float:
return a * b
def div(a: int, b: int) -> float:
return a / b
operator_map["+"] = add
operator_map["-"] = sub
operator_map["*"] = mul
operator_map["/"] = div
但這樣寫起來,有一個很大的問題就是太不美觀了。因為直接對於 dict
的操作從實際上來講可維護性是很差的,那麼我們這個地方應該怎麼做?
在改進這段程式碼之前,我們首先要明確 Python 中一個很重要的概念,即:函式/方法是:First Class Member 。用不精確的話來講,就是函式/方法可以作為引數被傳遞、被使用。
舉個例子:
import typing
def execute(func: typing.Callable, *args, **kwargs) -> typing.Any:
return func(*args, **kwargs)
def print_func(data: int) -> None:
print(data)
execute(print, 2)
大家可以看到我們將 print_func
這個函式作為引數傳遞給 execute 函式並被呼叫。
那麼我們來改造下之前的程式碼:
好了,大家看看,目前整體程式碼的可讀性以及可維護性是不是改了很多?
但是我們現在的問題在於,每次都需要在單獨呼叫一次 register_operator 函式,這樣也太煩了吧!要不要再改進一下?要得。我們可以用裝飾器來改進一下。
首先,看一個最簡單的裝飾器例子:
import functools
import typing
import time
def execute(func: typing.Callable) -> typing.Callable:
@functools.wraps(func)
def wraps(*args, **kwargs) -> typing.Any:
start_time = time.time()
result = func(*args, **kwargs)
print("{}".format(time.time() - start_time))
return result
return wraps
@execute
def add(a: int, b: int) -> float:
return a + b
我們能看到這段函式的意義是計算函式的執行時間。那麼這個原理是什麼?
實際上裝飾器是一個語法糖,具體可以參見 PEP318 Decorators for Functions and Methods。
簡而言之,實際上是 Python 替我們做了一個替換過程。以上面的例子為例,這個替換過程就是 add=execute(add)
。
好了,我們就用這個知識點來改進下之前的程式碼:
import typing
operator_map = {}
def register_operator(operator: str) -> typing.Callable:
def wraps(func: typing.Callable) -> typing.Callable:
operator_map[operator] = func
return func
return wraps
@register_operator("+")
def add(a: int, b: int) -> float:
return a + b
@register_operator("-")
def sub(a: int, b: int) -> float:
return a - b
@register_operator("*")
def mul(a: int, b: int) -> float:
return a * b
@register_operator("/")
def div(a: int, b: int) -> float:
return a / b
聊聊 OrderedDict
import typing
class OrderedDict:
def __init__(self, *args, **kwargs):
self._data = {}
self._ordered_key = []
def __getitem__(self, key: typing.Any) -> typing.Any:
return self._data[key]
def __setitem__(self, key: typing.Any, value: typing.Any) -> None:
if key not in self._data:
return
self._data[key] = value
self._ordered_key.append(key)
def __delitem__(self, key: typing.Any):
del self._data[key]
self._ordered_key.remove(key)
通過額外維護一個 list 來維護 key 插入的順序。這段程式碼,看似完成了我們的需求,但是實則存在很大問題。大家可以猜猜問題在哪?
3,2,1!
揭曉答案,這段程式碼利用 list 來保證 key 的有序性,在刪除的時候, list 的刪除操作,是一個時間複雜度 O(n) 的操作。換句話說,我們的刪除操作隨著內部資料的增多,所需的刪除時間也變得越長。這對於某些效能敏感的場景是無法接受的。
那要怎麼辦呢?事實上,Python 在很早之前就已經內建了有序字典,即很多人可能都用過的 collections.OrderedDict 。
在 OrderedDict 中, Python
維護了一個雙向連結串列解構,來保證插入的有序性,如下圖所示:
在最左側維護一個衛兵節點,衛兵節點的 next 指標恆指向於資料中最後插入的節點。那麼插入新的資料時,我們將新的資料插入到衛兵節點之後,從而達成維護插入順序的目的。
import typing
class Node:
def __init__(self, key: typing.Any, value: typing.Any) -> None:
self.key = key
self.value = value
self.prev = None
self.next = None
class OrderedDict:
def __init__(self, *args, **kwargs):
self._data = {}
self._head = Node(None, None)
self._last = self._head
def __getitem__(self, key: typing.Any) -> typing.Any:
if key in self._data:
return self._data[key].value
raise ValueError
def __setitem__(self, key: typing.Any, value: typing.Any) -> None:
if key not in self._data:
return
value_node = Node(key, value)
next_node = self._head.next
if not next_node:
self._head.next = value_node
value_node.prev = self._head
self._last = value_node
else:
value_node.next = next_node
next_node.prev = value_node
value_node.prev = self._head
self._head.next = value_node
self._data[key] = value_node
def __delitem__(self, key: typing.Any):
if key not in self._data:
return
value_node = self._data[key]
if value_node == self._last:
self._last = value_node.prev
self._last.next = None
else:
prev_node = value_node.prev
next_node = value_node.next
prev_node.next = next_node
next_node.prev = prev_node
del self._data[key]
del value_node
(此段程式碼,如有錯亂,煩請將瀏覽字型調小几號)
這只是一個 OrderedDict 的簡化版,如果想完成一個完整的 OrderedDict 還有很多很多的 corner case 要去處理。不過現在,我們可以使用內建的資料結構去完成我們需求。怎麼樣,是不是有了一種幸福的感覺?
隨意聊聊
通過今天的兩個例子,我們發現 Python 提供了相當多的功能去幫助我們完成日常的工作與學習任務。同時通過去深入地瞭解 Python 內部的一些功能實現,以便我們能更好地去學習一些知識。比如,上文提到的 OrderedDict 的實現,會讓我們學到雙頭連結串列的一種非常典型的應用,與此同時,雙頭連結串列也會用於諸如 LRU 這樣非常常用的資料解構的實現。所以,多去深入瞭解 Python 的方方面面,有助於我們整體能力的提升。
那麼既然電腦上已經有了 Python,為何不將這些 tricks 用起來,讓 Python 更 6 呢?安利這位來自德國的大咖,資深養蛇玩家 Dan Bader。
他的部落格 dbader.org 每月有超過 20 萬的瀏覽量,Real Python 視訊累計瀏覽量超過百萬。訂閱他的郵件,每天都會準時收到他更新 Python 技巧,一日不差,是一個極其自律的人。
精進Python不二之選
作為碼書商店的運營人員,誠邀你們進入我們的“CSDN碼書福利群”,進入群后,你想要的書籍都有推薦,你想要的優惠也都可以實現(當然不能讓我賣掉我自己),你也可以在學習累的時候和大家吹吹牛放鬆放鬆
相關文章
- 你後悔進入IT行業嗎?懂這些技術只會後悔沒早點搞IT!行業
- laracon 2018 演講《後悔沒有早知道的 Linux 命令》筆記Linux筆記
- 沒有選擇PHP培訓這條路,是我現在後悔的PHP
- 下代Apple Watch如果沒有這些特性 果粉發話就不買APP
- C,java,Python,這些名字背後的江湖!JavaPython
- 職場必備技能-BI資料視覺化,後悔沒早學視覺化
- 沒有基礎學習Python有這些方法和建議Python
- 不看後悔的Vue系列Vue
- git上的後悔藥Git
- 學完了Python基礎後,你可以嘗試這些方向!Python
- 華瑞IT教育:2021年這些網際網路趨勢要趁早知道!
- 沒想到 Python 中竟然還藏著這些稀奇古怪的東西...Python
- 小程式沒有入口?這些“場景”你可能還沒用上
- 06 萬萬沒想到,Github還有這些功能Github
- 面試”作弊“,不看絕對會後悔!面試
- 步入IT行業,你後悔了麼?行業
- BIEE視屏整理分享,不看後悔。。。
- 天天寫 SQL,這些神奇的特性你知道嗎?SQL
- 超大福利 | 這款免費 Java 線上診斷利器,不用真的會後悔!Java
- 阿里員工吐槽:工作壓力真的大,年假都沒心情用,後悔來阿里阿里
- 這都是些啥??asp.net!你搞懂了沒!ASP.NET
- Python——你應該知道這些Python
- Java8新特性,你應該瞭解這些!Java
- 小特性 大用途 —— YashanDB JDBC驅動的這些特性你都get了嗎?JDBC
- 十個Python練手的實戰專案,學會這些Python就基本沒問題了Python
- ES6的這些新知識你記住了沒?
- 跟我們這些屌絲根本沒有任何區別
- Java這個高階特性,很多人還沒用過!Java
- Android 8.0 的這些新特性你都有了解過嗎Android
- Java中的這些String特性可能需要了解下Java
- 這些Python騷操作,你知道嗎?Python
- 黑客用漏洞清除債務 這種漏洞如何“早知道”黑客
- 史上最強程式碼註釋(不看後悔)
- 做前端的你還沒用這些軟體?? out 啦前端
- 網購時先看“好評”? 這些陷阱坑你沒商量!
- 這些奇怪的排序演算法,你沒見過吧?排序演算法
- 這都Java15了,Java7特性還沒整明白?Java
- Linux系統擁有哪些特性?這些你必須知道!Linux