首頁  >  文章  >  後端開發  >  Python中的self參數是什麼?

Python中的self參數是什麼?

王林
王林轉載
2023-05-08 17:49:081082瀏覽

Python 的"self"参数是什么?

讓我們從我們已經知道的開始:self - 方法中的第一個參數- 指的是類別實例:

class MyClass:
┌─────────────────┐
▼ │
def do_stuff(self, some_arg): │
print(some_arg)▲│
 ││
 ││
 ││
 ││
instance = MyClass() ││
instance.do_stuff("whatever") │
│ │
└───────────────────────────────┘

此外,這個論點實際上不必稱為self - 它只是一個約定。例如,你可以像其他語言中常見的那樣使用它。

上面的程式碼可能是自然而明顯的,因為你一直在使用,但是我們只給了.do_stuff() 一個參數(some_arg),但該方法聲明了兩個(self 和, some_arg) ,好像也說不通。片段中的箭頭顯示 self 被翻譯成實例,但它是如何真正傳遞的呢?

instance = MyClass()
MyClass.do_stuff(instance, "whatever")

Python 在內部所做的是將 instance.do_stuff("whatever") 轉換為 MyClass.do_stuff(instance, "whatever")。我們可以在這裡稱之為“Python 魔法”,但如果我們想真正了解幕後發生的事情,我們需要了解 Python 方法是什麼以及它們與函數的關係。

類別屬性/方法

在 Python 中,沒有「方法」物件之類的東西——實際上方法只是常規函數。函數和方法之間的區別在於,方法是在類別的命名空間中定義的,使它們成為該類別的屬性。

這些屬性儲存在類別字典__dict__ 中,我們可以直接存取或使用vars 內建函數存取:

MyClass.__dict__["do_stuff"]
# <function MyClass.do_stuff at 0x7f132b73d550>
vars(MyClass)["do_stuff"]
# <function MyClass.do_stuff at 0x7f132b73d550>

存取它們的最常見方法是「類別方法」方式:

print(MyClass.do_stuff)
# <function MyClass.do_stuff at 0x7f132b73d550>

在這裡,我們使用類別屬性存取函數,正如預期的那樣列印do_stuff 是MyClass 的函數。然而,我們也可以使用實例屬性來存取它:

print(instance.do_stuff)
# <bound method MyClass.do_stuff of <__main__.MyClass object at 0x7ff80c78de50>

但在這種情況下,我們得到的是一個「綁定方法」而不是原始函數。 Python 在這裡為我們所做的是,它將類別屬性綁定到實例,創建了所謂的「綁定方法」。這個「綁定方法」是底層函數的包裝,該函數已經將實例插入為第一個參數(self)。

因此,方法是普通函數,它們的其他參數前附加了類別實例(self)。

要了解這是如何發生的,我們需要看一下描述符協定。

描述子協定

描述子是方法背後的機制,它們是定義 __get__()、__set__() 或 __delete__() 方法的物件(類別)。為了理解 self 是如何運作的,我們只考慮 __get__(),它有一個簽名:

descr.__get__(self, instance, type=None) -> value

但是 __get__() 方法實際上做了什麼?它允許我們自訂類別中的屬性查找 - 或者換句話說 - 自訂使用點符號存取類別屬性時發生的情況。考慮到方法實際上只是類別的屬性,這非常有用。這意味著我們可以使用 __get__ 方法來建立一個類別的「綁定方法」。

為了讓它更容易理解,讓我們透過使用描述符實作一個「方法」來示範這一點。首先,我們建立一個函數物件的純 Python 實作:

import types
class Function:
def __get__(self, instance, objtype=None):
if instance is None:
return self
return types.MethodType(self, instance)
def __call__(self):
return

上面的 Function 類別實作了 __get__ ,這使它成為一個描述符。這個特殊方法在實例參數中接收類別實例 - 如果這個參數是 None,我們知道 __get__ 方法是直接從一個類別(例如 MyClass.do_stuff)呼叫的,所以我們只回傳 self。但是,如果它是從類別實例中呼叫的,例如 instance.do_stuff,那麼我們傳回 types.MethodType,這是一種手動建立「綁定方法」的方式。

此外,我們也提供了 __call__ 特殊方法。 __init__ 是在呼叫類別來初始化實例時呼叫的(例如 instance = MyClass()),而 __call__ 是在呼叫實例時呼叫的(例如 instance())。我們需要用這個,是因為 types.MethodType(self, instance) 中的 self 必須是可呼叫的。

現在我們有了自己的函數實現,我們可以使用它將方法綁定到類別:

class MyClass:
do_stuff = Function()
print(MyClass.__dict__["do_stuff"])# __get__ not invoked
# <__main__.Function object at 0x7f229b046e50>
print(MyClass.do_stuff)# __get__ invoked, but "instance" is None, "self" is returned
print(MyClass.do_stuff.__get__(None, MyClass))
# <__main__.Function object at 0x7f229b046e50>
instance = MyClass()
print(instance.do_stuff)#__get__ invoked and "instance" is not None, "MethodType" is returned
print(instance.do_stuff.__get__(instance, MyClass))
# <bound method ? of <__main__.MyClass object at 0x7fd526a33d30>

透過給MyClass 一個Function 類型的屬性do_stuff,我們大致模擬了Python 在類別的命名空間中定義方法時所做的事情。

綜上所述,在instance.do_stuff等屬性存取時,do_stuff在instance的屬性字典(__dict__)中尋找。如果do_stuff 定義了__get__ 方法,則調用do_stuff.__get__ ,最終調用:

# For class invocation:
print(MyClass.__dict__['do_stuff'].__get__(None, MyClass))
# <__main__.Function object at 0x7f229b046e50>
# For instance invocation:
print(MyClass.__dict__['do_stuff'].__get__(instance, MyClass))
# Alternatively:
print(type(instance).__dict__['do_stuff'].__get__(instance, type(instance)))
# <bound method ? of <__main__.MyClass object at 0x7fd526a33d30>

正如我們現在所知- 將返回一個綁定方法- 一個圍繞原始函數的可調用包裝器,它的參數前面有self !

如果想進一步探索這一點,可以類似地實作靜態和類別方法(https://docs.python.org/3.7/howto/descriptor.html#static-methods-and-class-methods)

為什麼self在方法定義中?

我們現在知道它是如何運作的,但還有一個更哲學的問題——「為什麼它必須出現在方法定義中?」

明確self 方法參數是有爭議的設計選擇,但它是一種有利於簡單性的選擇。

Python 的自我體現了「越差越好」的設計理念——在此處進行了描述。這種設計理念的優先順序是“簡單”,定義為:

設計必須簡單,包括實作和介面。實現比介面簡單更重要...

這正是 self 的情況——一個簡單的實現,以介面為代價,其中方法簽名與其呼叫不匹配。

當然還有更多的原因為什麼我們要明確的寫self,或者說為什麼它必須保留, Guido van Rossum 在博客文章中描述了其中一些(http://neopythonic.blogspot.com/ 2008/10/why-explicit-self-has-to-stay.html),文章回覆了要求刪除的提議。

Python 抽象化了許多複雜性,但在我看來,深入研究低階細節和複雜性對於更好地理解語言的工作原理非常有價值,當事情發生故障和高級故障排除/調試時,它可以派上用場不夠。

此外,理解描述符實際上可能非常實用,因為它們有一些用例。雖然大多數時候你真的只需要@property 描述符,但在某些情況下自訂的描述符是有意義的,例如 SLQAlchemy 中的或者 e.g.自訂驗證器。

以上是Python中的self參數是什麼?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:51cto.com。如有侵權,請聯絡admin@php.cn刪除