อะไรคือความแตกต่างระหว่าง“ classmethod” และวิธี metaclass?


12

ใน Python ฉันสามารถสร้างวิธีการเรียนโดยใช้@classmethoddecorator:

>>> class C:
...     @classmethod
...     def f(cls):
...             print(f'f called with cls={cls}')
...
>>> C.f()
f called with cls=<class '__main__.C'>

อีกวิธีหนึ่งฉันสามารถใช้วิธีปกติ (ตัวอย่าง) ใน metaclass:

>>> class M(type):
...     def f(cls):
...             print(f'f called with cls={cls}')
...
>>> class C(metaclass=M):
...     pass
...
>>> C.f()
f called with cls=<class '__main__.C'>

ตามที่แสดงโดยผลลัพธ์ของC.f()วิธีการทั้งสองนี้ให้การทำงานที่คล้ายกัน

อะไรคือความแตกต่างระหว่างการใช้@classmethodและการใช้วิธีปกติบน metaclass

คำตอบ:


5

เนื่องจากคลาสเป็นอินสแตนซ์ของ metaclass ดังนั้นจึงไม่เป็นที่คาดหวังว่า "เมธอดอินสแตนซ์" บนเมตาคลาสจะทำงานเหมือนคลาสเมธอด

อย่างไรก็ตามใช่มีความแตกต่าง - และบางส่วนมีความหมายมากกว่า:

  1. ความแตกต่างที่สำคัญที่สุดคือวิธีการใน metaclass ไม่ได้เป็น "มองเห็น" จากชั้นเรียนเช่น สิ่งนี้เกิดขึ้นเนื่องจากการค้นหาแอททริบิวต์ใน Python (ในทางที่เรียบง่าย - ผู้บรรยายอาจจะมีความสำคัญกว่า) ค้นหาแอททริบิวในอินสแตนซ์ - หากไม่มีอยู่ในอินสแตนซ์ Python จะค้นหาในคลาสของอินสแตนซ์นั้น ซูเปอร์คลาสของคลาสแต่ไม่ได้อยู่ในคลาสของคลาส Python stdlib ใช้ประโยชน์จากคุณสมบัตินี้ในabc.ABCMeta.registerวิธีการ คุณลักษณะนั้นสามารถใช้งานได้ดีเนื่องจากวิธีการที่เกี่ยวข้องกับคลาสนั้นสามารถนำกลับมาใช้ใหม่เป็นแอตทริบิวต์ของอินสแตนซ์ได้โดยไม่มีข้อขัดแย้งใด ๆ (แต่วิธีการจะยังคงขัดแย้งกัน)
  2. ความแตกต่างอื่น ๆ ที่เห็นได้ชัดคือวิธีการที่ประกาศใน metaclass สามารถใช้ได้ในหลาย ๆ คลาสไม่เกี่ยวข้องกัน - ถ้าคุณมีลำดับชั้นของคลาสที่แตกต่างกันไม่เกี่ยวข้องเลยในสิ่งที่พวกเขาจัดการ แต่ต้องการฟังก์ชั่นทั่วไปบางอย่างสำหรับชั้นเรียนทั้งหมด คุณต้องมีคลาส mixin ซึ่งจะต้องรวมเป็นฐานในลำดับชั้นทั้งสอง (พูดเพื่อรวมคลาสทั้งหมดในแอปพลิเคชันรีจิสตรี) (NB. the mixin บางครั้งอาจเป็นการเรียกที่ดีกว่า metaclass)
  3. classmethod เป็นวัตถุ "classmethod" แบบพิเศษในขณะที่เมธอดใน metaclass เป็นฟังก์ชันปกติ

ดังนั้นจึงเกิดขึ้นว่ากลไกที่ classmethods ใช้คือ " descriptor protocol " ในขณะที่ฟังก์ชั่นปกติมี__get__วิธีการที่จะแทรกselfอาร์กิวเมนต์เมื่อพวกเขาถูกดึงออกมาจากอินสแตนซ์และปล่อยให้อาร์กิวเมนต์นั้นว่างเปล่าเมื่อดึงมาจากชั้นเรียน classmethodวัตถุมีความแตกต่าง__get__ที่จะแทรกชั้นเรียนเอง ("เจ้าของ") เป็น พารามิเตอร์แรกในทั้งสองสถานการณ์

สิ่งนี้ทำให้ไม่มีความแตกต่างในทางปฏิบัติส่วนใหญ่ แต่ถ้าคุณต้องการเข้าถึงวิธีการเป็นฟังก์ชั่นสำหรับวัตถุประสงค์ในการเพิ่มการเพิ่มมัณฑนากรแบบไดนามิกหรืออื่น ๆ สำหรับวิธีการใน metaclass meta.methodดึงฟังก์ชั่นพร้อมที่จะใช้ ในขณะที่คุณต้องใช้cls.my_classmethod.__func__ เพื่อดึงมันออกมาจาก classmethod (แล้วคุณจะต้องสร้างclassmethodวัตถุอื่นและกำหนดกลับถ้าคุณทำห่อบาง)

โดยทั่วไปเหล่านี้เป็น 2 ตัวอย่าง:


class M1(type):
    def clsmethod1(cls):
        pass

class CLS1(metaclass=M1):
    pass

def runtime_wrap(cls, method_name, wrapper):
    mcls = type(cls)
    setattr(mcls, method_name,  wrapper(getatttr(mcls, method_name)))

def wrapper(classmethod):
    def new_method(cls):
        print("wrapper called")
        return classmethod(cls)
    return new_method

runtime_wrap(cls1, "clsmethod1", wrapper)

class CLS2:
    @classmethod
    def classmethod2(cls):
        pass

 def runtime_wrap2(cls, method_name, wrapper):
    setattr(cls, method_name,  classmethod(
                wrapper(getatttr(cls, method_name).__func__)
        )
    )

runtime_wrap2(cls1, "clsmethod1", wrapper)

ในคำอื่น ๆ :นอกเหนือจากความแตกต่างที่สำคัญที่วิธีการที่กำหนดใน metaclass สามารถมองเห็นได้จากอินสแตนซ์และclassmethodวัตถุไม่ได้ความแตกต่างอื่น ๆ ที่รันไทม์จะดูเหมือนชัดเจนและไร้ความหมาย - แต่ที่เกิดขึ้นเพราะภาษาไม่จำเป็นต้องไป ด้วยกฎพิเศษสำหรับ classmethods: ทั้งสองวิธีในการประกาศ class class เป็นไปได้ซึ่งเป็นผลมาจากการออกแบบภาษา - หนึ่งเนื่องจากข้อเท็จจริงที่ว่าคลาสนั้นเป็นวัตถุตัวเองและอีกวิธีหนึ่งเป็นไปได้ในหมู่คนจำนวนมากของ การใช้โพรโทคอล descriptor ซึ่งอนุญาตให้หนึ่งเข้าถึงการเข้าถึงคุณลักษณะเฉพาะในอินสแตนซ์และในคลาส:

classmethodbuiltin ถูกกำหนดไว้ในรหัสพื้นเมือง แต่มันอาจจะเป็นเพียงแค่จะเขียนในหลามบริสุทธิ์และจะทำงานในลักษณะเดียวกันแน่นอน ร้องระดับ 5 บรรทัดสามารถใช้เป็นclassmethodมัณฑนากรที่ไม่มีความแตกต่างแบบรันไทม์สำหรับการแก้ไข@classmethod" at all (though distinguishable through introspection such as calls toisinstance ในตัว, and even`):


class myclassmethod:
    def __init__(self, func):
        self.__func__ = func
    def __get__(self, instance, owner):
        return lambda *args, **kw: self.__func__(owner, *args, **kw)

และนอกเหนือจากวิธีการเป็นที่น่าสนใจที่จะทราบว่าคุณลักษณะพิเศษเช่น@propertyบน metaclass จะทำงานเป็นคุณลักษณะคลาสเฉพาะเช่นเดียวกันโดยไม่มีพฤติกรรมที่น่าแปลกใจเลย


2

เมื่อคุณพูดอย่างที่คุณทำในคำถาม@classmethodและ metaclasses อาจมีลักษณะคล้ายกัน แต่มีวัตถุประสงค์ที่แตกต่างกัน ชั้นเรียนที่ถูกฉีดใน@classmethodอาร์กิวเมนต์ของมักจะใช้สำหรับการสร้างตัวอย่าง (เช่นตัวสร้างทางเลือก) ในทางกลับกัน metaclasses มักจะใช้ในการปรับเปลี่ยนระดับของตัวเอง (เช่นสิ่งที่ Django ทำกับรุ่น DSL)

ไม่ได้บอกว่าคุณไม่สามารถปรับเปลี่ยนคลาสได้ภายในคลาสเมธอด แต่คำถามก็กลายเป็นว่าทำไมคุณไม่กำหนดคลาสในแบบที่คุณต้องการจะแก้ไขในตอนแรก? หากไม่เป็นเช่นนั้นอาจแนะนำให้ผู้ปรับโครงสร้างใช้หลายคลาส

ลองขยายตัวอย่างแรกหน่อย

class C:
    @classmethod
    def f(cls):
        print(f'f called with cls={cls}')

การยืมจากเอกสาร Pythonดังกล่าวจะขยายไปสู่สิ่งต่อไปนี้:

class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def newfunc(*args):
            return self.f(klass, *args)
        return newfunc

class C:
    def f(cls):
        print(f'f called with cls={cls}')
    f = ClassMethod(f)

หมายเหตุวิธี__get__สามารถใช้อย่างใดอย่างหนึ่งอินสแตนซ์หรือระดับ (หรือทั้งสอง) และทำให้คุณสามารถทำทั้งสองอย่างและC.f C().fนี้จะแตกต่างเช่น metaclass ที่คุณให้ซึ่งจะโยนสำหรับAttributeErrorC().f

นอกจากนี้ในตัวอย่าง metaclass ที่ไม่อยู่ในf C.__dict__เมื่อค้นหาคุณลักษณะfด้วยC.fล่ามจะมองC.__dict__แล้วหลังจากล้มเหลวในการค้นหาให้ดูที่type(C).__dict__(ซึ่งก็คือM.__dict__) นี้อาจจะสำคัญว่าถ้าคุณต้องการความยืดหยุ่นในการแทนที่fในCแต่ฉันสงสัยนี้จะเคยมีการใช้ในทางปฏิบัติ


0

ในตัวอย่างของคุณความแตกต่างจะอยู่ในคลาสอื่น ๆ ที่จะมี M ตั้งเป็นเมตาคลาสของพวกเขา

class M(type):
    def f(cls):
        pass

class C(metaclass=M):
    pass

class C2(metaclass=M):
    pass

C.f()
C2.f()
class M(type):
     pass

class C(metaclass=M):
     @classmethod
     def f(cls):
        pass

class C2(metaclass=M):
    pass

C.f()
# C2 does not have 'f'

ต่อไปนี้เป็นข้อมูลเพิ่มเติมเกี่ยวกับ metaclasses กรณีการใช้ metaclasses อะไรบางอย่าง (คอนกรีต)?


0

ทั้ง @classmethod และ Metaclass นั้นแตกต่างกัน

ทุกอย่างในหลามเป็นวัตถุ ทุกสิ่งหมายถึงทุกสิ่ง

Metaclass คืออะไร

อย่างที่กล่าวไว้ทุกอย่างเป็นวัตถุ คลาสยังเป็นวัตถุในคลาสจริง ๆ เป็นอินสแตนซ์ของวัตถุลึกลับอื่น ๆ ที่เรียกอย่างเป็นทางการว่าเมตาคลาส metaclass เริ่มต้นใน python คือ "type" หากไม่ได้ระบุไว้

โดยค่าเริ่มต้นชั้นเรียนทั้งหมดที่กำหนดไว้เป็นอินสแตนซ์ของประเภท

คลาสเป็นอินสแตนซ์ของ Meta-Classes

จุดสำคัญน้อยที่จะเข้าใจพฤติกรรม metioned

  • เนื่องจากคลาสเป็นอินสแตนซ์ของคลาสเมตา
  • เช่นเดียวกับทุกวัตถุที่ยกตัวอย่างเช่นวัตถุ (อินสแตนซ์) ได้รับคุณลักษณะจากคลาส Class จะได้รับคุณสมบัติจาก Meta-Class

พิจารณารหัสต่อไปนี้

class Meta(type):
    def foo(self):
        print(f'foo is called self={self}')
        print('{} is instance of {}: {}'.format(self, Meta, isinstance(self, Meta)))

class C(metaclass=Meta):
    pass

C.foo()

ที่ไหน

  • คลาส C เป็นอินสแตนซ์ของคลาส Meta
  • "class C" เป็นวัตถุคลาสซึ่งเป็นตัวอย่างของ "class Meta"
  • เช่นเดียวกับวัตถุอื่น (อินสแตนซ์) "คลาส C" มีการเข้าถึงคุณลักษณะ / วิธีการที่กำหนดไว้ในคลาส "Meta Meta"
  • ดังนั้นถอดรหัส "C.foo ()" "C" เป็นตัวอย่างของ "Meta" และ "foo" เป็นวิธีการเรียกผ่านอินสแตนซ์ของ "Meta" ซึ่งเป็น "C"
  • อาร์กิวเมนต์แรกของเมธอด "foo" เป็นการอ้างอิงถึงอินสแตนซ์ที่ไม่ใช่คลาสซึ่งแตกต่างจาก "classmethod"

เราสามารถตรวจสอบได้ว่า "class C" เป็นตัวอย่างของ "Class Meta

  isinstance(C, Meta)

classmethod คืออะไร?

วิธีการที่ไพ ธ อนกล่าวจะผูกพัน ในฐานะที่เป็นงูหลามกำหนดข้อ จำกัด วิธีการที่จะต้องมีการเรียกใช้กับอินสแตนซ์เท่านั้น บางครั้งเราอาจต้องการเรียกใช้เมธอดโดยตรงผ่านคลาสโดยไม่มีอินสแตนซ์ใด ๆ (เหมือนกับสมาชิกแบบคงที่ในจาวา) โดยไม่จำเป็นต้องสร้างอินสแตนซ์ใด ๆ ในฐานะที่เป็นวิธีการแก้ปัญหาหลามให้ฟังก์ชั่นในตัวคลาสวิธีการผูกวิธีการที่กำหนดให้กับชั้นเรียนแทนอินสแตนซ์

เป็นวิธีการเรียนที่ถูกผูกไว้กับชั้นเรียน ใช้เวลาอย่างน้อยหนึ่งอาร์กิวเมนต์ที่อ้างอิงถึงคลาสเองแทนที่จะเป็นอินสแตนซ์ (ตัวเอง)

ถ้าใช้ classmethod ฟังก์ชั่น / มัณฑนากรในตัว อาร์กิวเมนต์แรกจะอ้างอิงกับคลาสแทนอินสแตนซ์

class ClassMethodDemo:
    @classmethod
    def foo(cls):
        print(f'cls is ClassMethodDemo: {cls is ClassMethodDemo}')

เนื่องจากเราได้ใช้ "classmethod" เราเรียกวิธีการ "foo" โดยไม่ต้องสร้างอินสแตนซ์ใด ๆ ดังต่อไปนี้

ClassMethodDemo.foo()

การเรียกวิธีการด้านบนจะส่งคืน True เนื่องจากอาร์กิวเมนต์ cls แรกนั้นอ้างอิงถึง "ClassMethodDemo"

สรุป:

  • Classmethod รับอาร์กิวเมนต์แรกซึ่งก็คือ "การอ้างอิงถึงคลาส (ตามธรรมเนียมเรียกว่า cls)"
  • วิธีการของเมตาคลาสไม่ได้เป็นวิธีการเรียน เมธอดของคลาส Meta จะได้รับอาร์กิวเมนต์แรกซึ่งเป็น "การอ้างอิงถึงอินสแตนซ์ (ตามธรรมเนียมเรียกว่าตนเอง) ไม่ใช่คลาส"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.