Skip to content

Python

Fluent Python 讀書筆記(二)

一級函式

  • 在 Python 所有函式都是一級物件:
    • 可在執行階段建立
    • 可以指派給變數,或資料結構內的元素
    • 可以當成引數傳給函式
    • 可以當成函式的結果回傳
  • 如果一個函式的引數是包含函式,或回傳的物件是函式,它就是高階函式 (higher-order function),經典的例子是 mapfilterreduce
  • listcomp、genexp 可作 mapfilter 的工作,且更容易閱讀,後兩者已經沒有那麼重要了
  • 除了用來處理高階函式的引數外,匿名函式在 Python 並沒什麼其他用處,且通常難以閱讀
  • Python 的七種 callable
    1. User-defined functions: deflambda
    2. Built-in functions:以 C 寫成的函式,如 lentime.strftime
    3. Built-in methods:以 C 寫成的方法,如 dict.get
    4. Methods
    5. Classes: 透過 __new__ 建立,再經 __init__ 初始化
    6. Class instances:須實作 __call__(任何物件都能有函式的行為)
    7. Generator functions
  • 如同自訂類別的實例,函式會使用 __dict__ 儲存特定的使用者屬性
  • 幾個重要的函式專用特殊方法:`
    • __annotations__:參數與回傳註解
    • __closure__:綁定自由變數(free variables)的空間
    • __code__:中繼資料、及編碼後的函式內文
    • __defaults__:以 tuple 儲存正式參數的預設值
    • __kwdefaults__:以 dict 儲存限關鍵字的正式參數的預設值
  • 要知道函式需要什麼參數、以及有沒有預設值,使用 inspect 模組會比較方便。因為 __(kw)defaults__ 雖然儲存了預設值,但參數名稱卻是放在 __code__ 裡面,必須由後往前掃描一次,才能將每一個值與各自的參數連結

  • inspect.Signature 物件有一個 bind 方法可以拿來測試傳入的參數組合
  • 函式註釋(Function Annotations)常見的型態是類別,如 strint,或字串如 int > 0,註釋不會處理任何工作,會被保存在 __annotation__ 屬性裡
  • 對解譯器來說,註釋沒有意義,它只們是可能會被工具所使用的中繼資料
  • Guido 清楚地表示不想讓 Python 成為 Funtional Programming 語言(但是因為有 operatorfunctools 模組,可以善加運用在 FP 風格上)
  • 列出一些有用的 FP 工具:operator.itemgetteroperator.attrgetteroperator.methodcallerfunctools.partial
  • 在 Python 中廣泛採用 FP 語法的最大障礙,就是缺乏尾部遞迴消除 (tail-recursion elimination) 的最佳化功能
  • 「所有匿名函式都有一個嚴重的缺點:它們沒有名字」

Read More »Fluent Python 讀書筆記(二)

Fluent Python 讀書筆記(一)

  • Python

特殊方法

  • 資料模型對 Python 來說是一種「框架」,這個框架(可以想成 Python 的解譯器 Interpreter)會呼叫特定的方法(指 method),這個特定的方法就是 Dunder Method
  • 當你實作了 __getitem__,就代表類別實例可以是可迭代(iterable),且支援了 indexing、slicing、函式庫如 random.choice
  • 若可迭代物件沒有實作 __contains__,在 in 運算子下的預設的行為是循序掃描
  • len() 對於內建型態(如 list、str、bytearray),解譯器並非呼叫 __len__,而是回到底層的 C 結構查詢,因為速度快很多 —— 這也說明了為什麼是 len(collection) 而不是 collection.len,這是一種「在內建物件效率與語言一致性之間取得平衡」
  • for 語句私下是呼叫 iter() 再呼叫 __iter__()
  • 一般而言,你的程式不該自己呼叫特殊方法,而是透過內建方法(如 len、iter、str)
  • 交互式終端與除錯程式會對運算式的結果呼叫 repr()
  • __repr__ 回傳的字串必須精確,盡可能匹配原始碼,好表示「物件如何建立的」以利再次重建
  • 沒有自訂的 __str__,解譯器會呼叫 __repr__ 提供回饋
  • 反向運算子 (reverse operator) 應用在交換律的後備機制如 a * bb * a
  • 擴增賦值運算子 (augmented assignment operator) 意在結合中輟運算 (infix) 與賦值行為,如 a <<= b
  • 「讓使用者使用核心開發人員所使用的工具」,我的理解是,將底層 API 開放給上層使用
  • 「特殊方法其實是魔術方法的相反」,魔術功能通常意指「你不能在自己定義的物件中模擬這些功能」

序列

  • 牢記可變 VS. 不可變、容器序列 VS. 一般序列的不同
  • List comprehension 只做一件事:建構新序列
  • tuple 可以當成「不可變序列」來用,也可當成沒有欄位名稱的「紀錄」(有 unpacking 的優勢)
  • 變數名 _ 翻譯成「啞變數」(使用底線有缺點,會被當成 gettext 的函數別名)
  • 當賦值的對象是切片時,賦值物件必須是 iterable,如 l[2:5] = [20, 30]
  • 序列的 +* 一定都是建立新物件
  • += (原地算法)的特殊方法是 __iadd__, i 代表 in-place,如果 __iadd__沒有被實作的話,Python 會退而呼叫 __add__,可以解讀成「是否真的是原地算法,取決於運算哪種物件」—— 同一個概念可以套用到 *=
  • 檢視 Python 的 bytecode: dis.dis('<your code here>')
  • 重要的 Python API 慣例:當函式或方法就地改變物件(沒有建立新的物件)時,必須回傳 None,簡單的範例是 list.sort(),反例是 sorted(list)
  • 另一種快速的數值儲存方式,是使用 pickle 模組來做物件序列化,使用 pickle.dump 來儲存一個陣列的浮點數幾乎和 array.tofile 一樣快
  • 內建的 memoryview 類別是 Python 的一個廣義 NumPy 陣列結構,可以用它在資料結構間共用記憶體(PIL、SQLite、NumPy 陣列等),而不需要先做複製
  • 移除 deque 中間的項目並不是那麼快,它其實最適合在兩端進行操作
  • queue 函式庫實作的佇列如 Queue、LifoQueue 用來在執行緒之間進行安全通訊
  • Python 的 sort 是使用 Timesort,根據資料的排序狀況來決定採用 MergeSort 或是 InsertionSort

Read More »Fluent Python 讀書筆記(一)

Generator as Coroutines

  • Python

Generator as Coroutines

  • cooperative multitasking (cooperative routines)
  • concurrent not parallel (python program execute on a single thread)

The way to create coroutines:

  • generators (asyncio)
  • native coroutines (using async /await)

Concepts

  • concurrency: tasks start, run and complete in overlapping time periods
  • parallelism: tasks run simultaneousely

image

  • cooperative: control relinquished to other task voluntarily, control by application(developer)
  • preemptive: control relinquished to other task involuntarily, control by the OS.

    some sort of scheduler involved

image

  • Global Interpreter Lock(GIL)

    Only one native thread excutes at a time.

    Use Process based parallelism to avoid GIL. Not Thread based.

    The Python threading module uses threads instead of processes. Threads uniquely run in the same unique memory heap. Whereas Processes run in separate memory heaps. This makes sharing information harder with processes and object instances. One problem arises because threads use the same memory heap, multiple threads can write to the same location in the memory heap which is why the global interpreter lock(GIL) in CPython was created as a mutex to prevent it from happening.

Make the right choice

  • CPU Bound => Multi processing
  • I/O Bound, Fast I/O, Limit Connections => Muilti Threading
  • I/O Bound, Slow I/O, Many Connections => Concurrency

Use deque

Much more efficient way to implement the stack and queue.

Operate 10,000 items take 1,000 times average:

(times in seconds) list deque
append(right) 0.87 0.87
pop(right) 0.002 0.0005
insert(left) 20.8 0.84
pop(left) 0.012 0.0005

Use unlimited deque with deque() or deque(iterable)
Use limited deque with deque(maxlen=n). If full, a corresponding number of items are discarded from the opposite end.

Implement producer / consumer coroutine using deque

Implement simple event loop

Read More »Generator as Coroutines

Context Manager

  • Python

Context Manager

what is context

the state surrounding a section of code

why we need a context manager

  • writing try/finally every time can get cumbersom
  • easy to forget closing the file

use cases

Useful for program that needs Enter / Exit handeling

  • create / releasing resources
  • database transaction
  • set and reset decimal context

Common patterns

  • open / close
  • lock / release
  • change / reset
  • start / stop
  • enter / exit

protocal

implement these two dunder methods:

  • __enter__

    perform the setup, optionally return an object

  • __exit__

    receives error (silence or propagate)

    • need arguments exc_type, exc_value, exc_trace to handle exception
    • return True to silence exception

    perform clean up

examples

contextlib

nested contexts

Generator

  • Python

Generator

  • A type of iterator
  • generator function: function that uses yield statement
  • implement the iterator protocal, call next
  • raise StopIteration exhausted

Less code

Implement an iterator

Implement a generator

More efficient

Generator Comprehensions

  • local scope
  • lazy evaluation
  • is an iterator, can be exhausted

Delegating Generator

Use the syntax yield from to yield items in a generator

Iterable and Iterator

  • Python

Iterator & Iterable

iterator

  • get next item (__next__)
  • no indexes needed (Don’t need to be Sequence type)
  • consumable

iterable

  • collections that implement iterator

Protocal

Python need to count on certain funcionality: __next____iter__StopIteration

compare to sequence type

iteration can be more general than sequential indexing, we only need:

  • a bucket of items: collection, container
  • a way to get the next item, no need to care about ordering
  • an exception to raise if there is no next item

try to custom an iterator ourselfs:

Why re-create?

Seperate the Collection from the iterator

Iterable object

  • Maintaining the data of the collection is one object
  • Created once
  • implements __iter__, return a new iterator instance

Iterator object

  • Iterating over that data should be another object
  • throw away the iterator but don’t throw away the collection
  • Created every time
  • implements __iter__, return itself
  • implements __next__, return next item

iterable can be lazy

Caculate the next itme in an iterable until it’s actually requested

lazy evaluation

  • often used in class properties
  • properties of classes may not always populated when the object is created
  • value of property only becomes known when the property is requested/deferred

infnite iterables

  • itertools.cycle

Python Built-ins

  • range: return iterable
  • zip: return iterator
  • enumerate: return iterator
  • open: return iterator
  • reversed: return iterator

The type is important. Iterator object can be only iter over once.

iter()

when iter is called:

  • Python first looks for __iter__, if not then:
  • look for __getitem__ and create an iterator, if not then:
  • raise TypeError

Test it:

The __iter__ must return an iterator!

Iterating callable

iterator delegation

Example 1

Example 2