คำอธิบายที่ถูกต้องจาก Armin Ronacher ด้านบนขยายคำตอบของเขาเพื่อให้ผู้เริ่มต้นอย่างฉันเข้าใจดี:
ความแตกต่างในวิธีการที่กำหนดไว้ในคลาสไม่ว่าจะเป็นวิธีการคงที่หรืออินสแตนซ์ (ยังมีวิธีการเรียนแบบอื่น - ไม่ได้กล่าวถึงที่นี่เพื่อข้ามมัน) วางในความเป็นจริงว่าพวกเขาจะผูกพันกับอินสแตนซ์ชั้นหรือไม่ ตัวอย่างเช่นพูดว่าวิธีการได้รับการอ้างอิงถึงอินสแตนซ์ของชั้นเรียนในช่วงรันไทม์
class C:
a = []
def foo(self):
pass
C # this is the class object
C.a # is a list object (class property object)
C.foo # is a function object (class property object)
c = C()
c # this is the class instance
__dict__
คุณสมบัติในพจนานุกรมของวัตถุชั้นถืออ้างอิงถึงคุณสมบัติและวิธีการของวัตถุชั้นและทำให้
>>> C.__dict__['foo']
<function foo at 0x17d05b0>
วิธีการ foo สามารถเข้าถึงได้ดังกล่าวข้างต้น จุดสำคัญที่ควรทราบที่นี่คือทุกอย่างในหลามเป็นวัตถุดังนั้นการอ้างอิงในพจนานุกรมด้านบนจึงเป็นตัวชี้ไปยังวัตถุอื่น ให้ฉันเรียกพวกเขาว่า Class Property Objects - หรือเป็น CPO ภายในขอบเขตของคำตอบของฉันเพื่อความกระชับ
หาก CPO เป็นตัวบ่งชี้ตัวแปลภาษาไพ ธ อนจะเรียก__get__()
เมธอดของ CPO เพื่อเข้าถึงค่าที่มีอยู่
เพื่อตรวจสอบว่า CPO เป็น descriptor หรือไม่งูเหลือมล่ามตรวจสอบว่ามันใช้โปรโตคอล descriptor หรือไม่ ในการนำโพรโทคอล descriptor ไปใช้นั้นมี 3 วิธี
def __get__(self, instance, owner)
def __set__(self, instance, value)
def __delete__(self, instance)
สำหรับเช่น
>>> C.__dict__['foo'].__get__(c, C)
ที่ไหน
self
คือ CPO (อาจเป็นตัวอย่างของรายการ str ฟังก์ชั่น ฯลฯ ) และจัดทำโดยรันไทม์
instance
เป็นตัวอย่างของคลาสที่มีการกำหนด CPO นี้ (วัตถุ 'c' ด้านบน) และจะต้องมีการอธิบายโดยเรา
owner
เป็นคลาสที่กำหนด CPO นี้ (คลาสอ็อบเจ็กต์ 'C' ด้านบน) และจำเป็นต้องจัดหาโดยเรา อย่างไรก็ตามนี่เป็นเพราะเรากำลังเรียกมันบน CPO เมื่อเราเรียกมันว่าบนอินสแตนซ์เราไม่จำเป็นต้องให้สิ่งนี้เนื่องจากรันไทม์สามารถจัดหาอินสแตนซ์หรือคลาสของมัน (polymorphism)
value
คือคุณค่าที่ตั้งใจไว้สำหรับ CPO และเราต้องจัดหาให้
CPO ไม่ใช่ทุกคนที่เป็นตัวอธิบาย ตัวอย่างเช่น
>>> C.__dict__['foo'].__get__(None, C)
<function C.foo at 0x10a72f510>
>>> C.__dict__['a'].__get__(None, C)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__get__'
นี่เป็นเพราะคลาสลิสต์ไม่ได้ใช้โปรโตคอล descriptor
ดังนั้นจึงc.foo(self)
จำเป็นต้องมีการโต้แย้งตัวเองเพราะลายเซ็นของวิธีการนี้เป็นจริงC.__dict__['foo'].__get__(c, C)
(ตามที่อธิบายไว้ข้างต้น C ไม่จำเป็นเนื่องจากสามารถพบได้หรือ polymorphed) และนี่คือสาเหตุที่คุณได้รับ TypeError ถ้าคุณไม่ผ่านอาร์กิวเมนต์อินสแตนซ์ที่ต้องการ
หากคุณสังเกตเห็นว่าวิธีการนั้นยังคงถูกอ้างอิงผ่านคลาส Object C และการเชื่อมโยงกับอินสแตนซ์ของคลาสทำได้โดยผ่านบริบทในรูปแบบของวัตถุอินสแตนซ์ในฟังก์ชันนี้
สิ่งนี้ยอดเยี่ยมมากเพราะถ้าคุณเลือกที่จะไม่เก็บบริบทหรือไม่ผูกมัดกับอินสแตนซ์ทั้งหมดที่จำเป็นต้องมีคือการเขียนคลาสเพื่อตัดคำอธิบาย CPO และแทนที่__get__()
เมธอดของมันเพื่อไม่ต้องใช้บริบท คลาสใหม่นี้คือสิ่งที่เราเรียกว่ามัณฑนากรและใช้ผ่านคำสำคัญ@staticmethod
class C(object):
@staticmethod
def foo():
pass
การไม่มีบริบทใน CPO ที่ปิดใหม่foo
ไม่ได้เกิดข้อผิดพลาดและสามารถตรวจสอบได้ดังนี้:
>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>
กรณีการใช้งานของวิธีการแบบคงที่เป็นมากกว่าหนึ่ง namespacing และการบำรุงรักษารหัสหนึ่ง (นำออกจากชั้นเรียนและทำให้มันใช้ได้ตลอดโมดูล ฯลฯ )
มันอาจจะดีกว่าที่จะเขียนวิธีการคงที่แทนที่จะเป็นวิธีการอินสแตนซ์ที่เป็นไปได้เว้นแต่ ofcourse คุณต้อง contexualise วิธีการ (เช่นการเข้าถึงตัวแปรอินสแตนซ์ตัวแปรคลาส ฯลฯ ) เหตุผลหนึ่งคือทำให้การเก็บขยะง่ายขึ้นโดยไม่เก็บการอ้างอิงวัตถุ