Bir 'sınıf yöntemi' ve bir metasınıf yöntemi arasındaki farklar nelerdir?


12

Python, @classmethoddekoratör kullanarak bir sınıf yöntemi oluşturabilir :

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

Alternatif olarak, bir metasınıfta normal (örnek) bir yöntem kullanabilirim:

>>> 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'>

Sonuç olarak gösterildiği gibi C.f(), bu iki yaklaşım benzer işlevsellik sağlar.

@classmethodMetasınıfta normal yöntem kullanma ve kullanma arasındaki farklar nelerdir ?

Yanıtlar:


5

Sınıflar bir metasınıfın örnekleri olduğu için, metasınıftaki bir "örnek yönteminin" bir sınıf yöntemi gibi davranması beklenmez.

Ancak, evet, farklılıklar var - ve bazıları semantikten daha fazlası:

  1. En önemli fark, metasınıftaki bir yöntemin bir sınıf örneğinden "görünür" olmamasıdır . Bunun nedeni, Python'daki öznitelik aramasının (basitleştirilmiş bir şekilde - tanımlayıcıların önceliği olabilir) örnekteki bir özniteliği aramasıdır - örnekte yoksa, Python daha sonra bu örneğin sınıfına bakar ve arama devam eder sınıfın üst sınıfları, ama değil sınıfları, sınıfın sınıfları üzerinde . Python stdlib abc.ABCMeta.registeryöntemde bu özelliği kullanır . Sınıfla ilgili yöntemlerin herhangi bir çakışma olmadan örnek öznitelikleri olarak yeniden kullanılabilmeleri için bu özellik iyi olarak kullanılabilir (ancak bir yöntem yine de çakışma olur).
  2. Başka bir fark, açık olsa da, metasınıfta bildirilen bir yöntemin başka sınıflarla ilgili olmayan birkaç sınıfta mevcut olması - farklı sınıf hiyerarşilerine sahipseniz, uğraştıkları şeyle hiç ilgili değilse , ancak tüm sınıflar için bazı ortak işlevsellik istiyorsanız , her iki hiyerarşiye de temel olarak dahil edilmesi gereken bir mixin sınıfı bulmanız gerekir (örneğin, bir uygulama kayıt defterine tüm sınıfları dahil etmek için). (Not: mixin bazen bir metasınıftan daha iyi bir çağrı olabilir)
  3. Sınıf metodu özel bir "sınıf metodu" nesnesidir, oysa metasınıftaki bir yöntem sıradan bir fonksiyondur.

Böylece, sınıf yöntemlerinin kullandığı mekanizma " tanımlayıcı protokolü " olur. Normal işlevler , bir örnekten alındığında bağımsız değişkeni __get__ekleyecek ve selfbir sınıftan alındığında bu bağımsız değişkeni boş bırakacak bir yöntem içerirken, bir classmethodnesnenin farklı__get__ sınıfın kendisini ("sahip") her iki durumda da ilk parametre.

Bu çoğu zaman pratik bir fark yaratmaz, ancak metoda bir işlev olarak erişmek istiyorsanız, ona dinamik olarak dekoratör eklemek amacıyla veya metasınıftaki bir yöntem için meta.methodkullanıma hazır olan bir işlevi alır. , cls.my_classmethod.__func__ bir sınıf yönteminden almak için kullanmanız gerekirken (ve sonra başka birclassmethod nesne ve biraz kaydırma yaparsanız geri atamanız gerekir).

Temel olarak, bunlar 2 örnektir:


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)

Başka bir deyişle: metasınıfta tanımlanan bir yöntemin örnekten görülebildiği ve bir classmethodnesnenin görmediği önemli farkın yanı sıra, çalışma zamanında diğer farklılıklar belirsiz ve anlamsız görünecektir - ancak bu, dilin gitmesi gerekmediği için olur sınıf metodları için özel kurallarla kendi yolundan: Sınıf tasarımını ilan etmenin her iki yolu da dil tasarımından kaynaklanabilir - birincisi, bir sınıfın kendisinin bir nesne olması ve bir diğerinin de birçokları arasında bir olasılık olarak kişinin bir örnekte ve bir sınıfta öznitelik erişimini özelleştirmesine izin veren tanımlayıcı protokolünün kullanımı:

classmethodBuiltin yerli kodlarında tanımlanan, ama sadece saf python kodlanmış olabilir ve aynı şekilde çalışmaya devam eder. 5 satır sınıfı körük classmethod, dahili @classmethod" at all (though distinguishable through introspection such as calls toisinstance , and evenrepr'ında çalışma zamanı farkı olmayan bir dekoratör olarak kullanılabilir ):


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

Ve yöntemlerin ötesinde @property, metasınıfta gibi özel niteliklerin, şaşırtıcı bir davranış olmadan, aynı şekilde, özel sınıf nitelikleri olarak çalışacağını akılda tutmak ilginçtir .


2

Soruda yaptığınız gibi ifade ettiğinizde, @classmethodmetasınıflar benzer görünebilir, ancak oldukça farklı amaçları vardır. @classmethod'Argümanına enjekte edilen sınıf genellikle bir örnek (yani alternatif bir kurucu) oluşturmak için kullanılır. Öte yandan, metasınıflar genellikle sınıfın kendisini değiştirmek için kullanılır (örneğin, Django'nun DSL modelleri ile yaptığı gibi).

Bu, bir sınıf yönteminin içindeki sınıfı değiştiremeyeceğiniz anlamına gelmez. Ama sonra soru neden sınıfı ilk etapta değiştirmek istediğiniz şekilde tanımlayamadınız? Değilse, birden fazla sınıfın kullanılması için bir refactor önerebilir.

İlk örneği biraz genişletelim.

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

Python belgelerinden ödünç alındığında, yukarıdakiler aşağıdaki gibi bir şeye genişleyecektir:

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)

Nasıl Not __get__örneği veya sınıfı (veya her ikisini) alabilir ve böylece her ikisini birden yapabilirsiniz C.fve C().f. Bu, bir atacağım hangi vermek metaclass örneğin benzemez AttributeErroriçin C().f.

Üstelik metasınıf örneğinde, fyoktur C.__dict__. Öznitelik aranırken file C.fen tercüman görünüyor C.__dict__ve sonra bulmak başarısız, bakar type(C).__dict__(ki M.__dict__). Eğer esneklik geçersiz kılmak istiyorsanız bu önemli olabilir fde Cben bu şimdiye kadar pratik kullanımı olacak şüphe rağmen.


0

Örneğinizde, fark M'yi metasınıfları olarak ayarlayacak diğer bazı sınıflarda olacaktır.

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'

İşte metasınıflar hakkında daha fazla bilgi Metasınıflar için bazı (somut) kullanım örnekleri nelerdir?


0

Hem @classmethod hem de Metaclass farklıdır.

Python'daki her şey bir nesnedir. Her şey her şey demektir.

Metaclass nedir?

Söylendiği gibi her şey bir nesnedir. Sınıflar aynı zamanda nesnelerdir aslında sınıflar resmi olarak meta sınıf olarak adlandırılan diğer gizemli nesnelerin örnekleridir. Python'daki varsayılan metasınıf belirtilmezse "type" dır

Varsayılan olarak, tanımlanan tüm sınıflar tür örnekleridir.

Sınıflar Meta Sınıflarının örnekleridir

Metrik davranışları anlamak için çok az önemli nokta

  • Sınıflar meta sınıfların örnekleridir.
  • Her örneklenen nesne gibi, nesneler (örnekler) de özniteliklerini sınıftan alır. Sınıf, özniteliklerini Meta-Class'tan alacak

Aşağıdaki Kodu Düşünün

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()

Nerede,

  • C sınıfı Meta sınıfının bir örneğidir
  • "sınıf C", "sınıf Meta" örneği olan sınıf nesnesidir
  • Diğer herhangi bir nesne (örnek) gibi "C sınıfı" erişime sahiptir, "sınıf Meta" sınıfında tanımlanan öznitelikleri / yöntemleri
  • Yani, "C.foo ()" kodunun çözülmesi. "C", "Meta" örneğidir ve "foo", "C" olan "Meta" örneğiyle çağrılan yöntemdir.
  • "Foo" yönteminin ilk argümanı "classmethod" 'dan farklı olarak sınıfa değil, örneğe gönderme

"Sınıf C" nin "Sınıf Meta" örneği olup olmadığını doğrulayabiliriz

  isinstance(C, Meta)

Sınıf metodu nedir?

Python yöntemlerinin bağlı olduğu söylenir. Python kısıtlama getirdiği için, yöntemin yalnızca örnekle çağrılması gerekir. Bazen, herhangi bir örnek oluşturmak zorunda kalmadan, yöntemleri herhangi bir örnek olmadan (java'daki statik üyeler gibi) doğrudan sınıf aracılığıyla çağırmak isteyebiliriz. Geçici çözüm olarak python, verilen yöntemi örnek yerine sınıfa bağlamak için yerleşik işlev sınıfı yöntemi sağlar.

Sınıf yöntemleri sınıfa bağlıdır. Örnek (benlik) yerine sınıfın kendisine referans olan en az bir argüman alır

yerleşik işlev / dekoratör sınıf yöntemi kullanılıyorsa. İlk argüman örnek yerine sınıfa başvurulacaktır

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

"Classmethod" kullandığımız için, aşağıdaki gibi bir örnek oluşturmadan "foo" yöntemini çağırıyoruz.

ClassMethodDemo.foo()

Yukarıdaki yöntem çağrısı True döndürecektir. İlk argüman cls gerçekten "ClassMethodDemo" referans olduğundan

Özet:

  • Classmethod'un ilk argümanı, "sınıfa referans (geleneksel olarak cls olarak adlandırılır)"
  • Meta-sınıfların yöntemleri sınıf yöntemleri değildir. Meta sınıflarının yöntemleri ilk argümanı alır: "sınıf değil, örnek (geleneksel olarak öz olarak adlandırılır) referansı"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.