譯文出自:掘金翻譯計畫
譯者:emilyqirabbit
校對者:allenlongbaobao,sunhaokk
python 的物件模型令人難以置信的強大;實際上,你可以重寫所有(物件),或者向任何人分發奇怪的物件,並讓他們像對待正常的物件的那樣接受它。
python 的物件導向是 smalltalk 物件導向的乙個後裔。在 python 中,一切都是物件,甚至物件集和物件型別都是如此;特別的,函式也是物件。這讓我很好奇:不使用類建立乙個類是否可能?
這個想法的關鍵性**如下所示。這是乙個很基礎的實現,但它支援__call__
這樣的邊緣情況(但不支援其他魔術方法,因為他們需要載入依賴)。後文將會解說。
這是一些很先進的 python 輪子,它用一種和物件的設計初衷絕不相同的方法使用了一些物件。我們將分段解說**。
第乙個 helper
def _suspend_self(namespace, suspended):
複製**
這是個讓人有點害怕的函式名。暫停?這可不好,但我們是可以解決問題的。_suspend_self
函式是functools.partial
的乙個簡單應用,它的工作原理是:通過從外部函式作用域中捕獲namespace
,並把它懸停在內部函式中。
def suspender(*args, **kwargs):
return suspended(namespace, *args, **kwargs)
複製**
接下來,這個內部的函式呼叫了和第乙個引數 namespace 一起傳遞進來的函式 suspended,實際上這是將方法又包了一層,這樣它就可以應用在乙個普通的 python 類上。_suspend_self
餘下的部分就只是設定一些屬性,這些屬性在某些時候可能會被對映(reflection)用到(我可能漏掉一些內容)。
猛獸(beast)
下乙個函式是make_class
。從它的簽名中我們能知道什麼?
def make_class(locals: dict):
""" 在被呼叫者的本地建立乙個類。
引數 locals:建立類的本地。
"""複製**
如果其他方法請求或者直接取得了你的本地變數,可不是什麼好事。通常情況下,這是為了在之前的棧中搜尋什麼東西,或者就是在黑你的本機。我們當前的例項屬於前面一種,搜尋本地函式並加入到類中。
# 試著找到乙個 `__call__` 來執行 call 函式
# 它將作為乙個函式,這樣命名空間和被呼叫者可以引用彼此
def call_maker():
if'__call__'
in locals and callable(locals['__call__']):
return _suspend_self(namespace, locals['__call__'])
def _not_callable(*args, **kwargs):
raise typeerror('this is not callable')
return _not_callable
複製**
這個函式相當簡單,它是乙個將函式作為返回值的函式! 它實際上做了如下這些事:
命名空間 namespace
namespace 是關鍵的部分,然而我還沒有解說。類中的每乙個(或者絕大部分)方法都會將self
作為第乙個引數,這個self
就是函式執行的時候類的例項。
乙個類的例項實際上就是乙個你可以用.
符號而不是數字索引訪問其內容的字典。所以需要乙個可以傳入我們期望的函式的物件來模仿這個字典。於是我們就說,這個例項是乙個namespace
,我們在namespace
上設定變數等等。後文提到namespace
的地方,就把它當作我們的例項。通過呼叫類的物件自身,你可以獲取這個類的例項:obb = someclass()
。
標準的建立點式訪問的字典的方法是 attrdict:
attrdict = type("attrdict", (dict,), )
複製**
但是既然它建立了乙個類,這就有點欺騙性了。其他的方法包括typing.******namespace
,或者建立乙個無哨兵(sentinel)的類。但是這兩種方法都還是欺騙性的建立了類,我們都不能用。
解決方案
namespace 的解決方案是另乙個函式。函式的行為可以像可呼叫的點式訪問字典,所以我們就簡單的建立乙個namespace
函式,假設它就是 self。
# 這個就充當了 self 物件
# 所有的屬性都建立在此之上
def namespace():
return called()
複製**
需要注意呼叫called()
的用法 - 這是為了正常模擬例項上__call__
的行為。
建立__init__
python 中的所有類都有__init__
(不包括預設提供空 init 的類),所以我們需要去模仿這一點並確保使用者定義的 init 被呼叫。
# 建立乙個 init 的替代方法
def new_class(*args, **kwargs):
init = locals.get("__init__")
if init is not none:
init(namespace, *args, **kwargs)
return namespace
複製**
這段**就是簡單的從本地獲取使用者定義的__init__
,如果找到了,就呼叫它。然後,它返回 namespace(就是假的例項),有效地模擬了迴圈:(metaclass.)__call__
->__new__
->__init__
。
清理接下來要做的就是在類的基礎上建立方法,這可以用超級簡單的迴圈掃瞄來完成:
# 更新 namespace
for name, item in locals.items():
if callable(item):
fn = _suspend_self(namespace, item)
setattr(namespace, name, fn)
複製**
和上文提到的相似,所有可呼叫的函式都被_suspend_self
包裹來將函式變成類的方法,在 namespace 完成設定。
獲取到類
最後要做的就是簡單的return new_class
。獲取到類的例項的最後一輪迴圈是:
現在我們就得到它了,乙個完全沒用類的類。打賭你會實際應用它。
matlab如何寫乙個類
類是一種資料型別,與普通的資料型別不同的是類不僅包含資料,還包含對資料的操作,類把資料和資料操作方法封裝在一起,作為乙個整體參與程式的執行。類具有可繼承性,建立乙個新的類的時候,可以在乙個基類中新增成員派生出新類。類的變數和類的例項是不同的,類的例項是動態分配的記憶體區域,通常稱類的例項維 物件 同...
如何寫乙個Stack?
1.棧是陣列 2.先進後出 3.出棧 4.入棧 手寫乙個雙向鍊錶 棧 public class stackpopandpush public stackpopandpush int lens 返回元素個數 public intsize 返回陣列長度,容量,棧資料長 private intcapaci...
如何寫乙個鍊錶
有的時候,處於記憶體中的資料並不是連續的。那麼這時候,我們就需要在 資料結構中新增乙個屬性,這個屬性會記錄下面乙個資料的位址。有了這個位址之後,所有的資料就像一條鍊子一樣串起來了,那麼這個位址屬性就起到了穿線鏈結的作用。相比較普通的線性結構,鍊錶結構的優勢是什麼呢?我們可以總結一下 1 單個節點建立...