譯 不用 Class,如何寫乙個類

2021-09-11 12:00:18 字數 3567 閱讀 4878

譯文出自:掘金翻譯計畫

譯者: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 單個節點建立...