Bir sınıfta tanımlayıcı ayarlamak neden tanımlayıcının üzerine yazıyor?


10

Basit repro:

class VocalDescriptor(object):
    def __get__(self, obj, objtype):
        print('__get__, obj={}, objtype={}'.format(obj, objtype))
    def __set__(self, obj, val):
        print('__set__')

class B(object):
    v = VocalDescriptor()

B.v # prints "__get__, obj=None, objtype=<class '__main__.B'>"
B.v = 3 # does not print "__set__", evidently does not trigger descriptor
B.v # does not print anything, we overwrote the descriptor

Bu sorunun etkili bir kopyası var , ancak kopyası cevaplanmadı ve CPython kaynağına bir öğrenme alıştırması olarak biraz daha girdim. Uyarı: Yabancı otların içine girdim. Gerçekten bu suları bilen bir kaptandan yardım alabileceğimi umuyorum . Kendi gelecekteki ve gelecekteki okuyucuların yararına baktığım çağrıların izlenmesinde mümkün olduğunca açık olmaya çalıştım.

__getattribute__Tanımlayıcılara uygulanan davranışlar üzerine dökülen çok fazla mürekkep gördüm , örneğin arama önceliği. Python pasajı "çağırma Açıklayıcılar" hemen altında For classes, the machinery is in type.__getattribute__()...kabaca ben tekabül inandığımız ile zihnimde kabul eder CPython kaynak içinde type_getattroben bakarak aşağı izlenen "tp_slots" sonra tp_getattro doldurulur . Ve B.vbaşlangıçta basan gerçeğin __get__, obj=None, objtype=<class '__main__.B'>benim için anlamı var.

Anlamadığım şey, ödev neden B.v = 3tetikleyiciden ziyade tanımlayıcının üzerine körü körüne yazılıyor v.__set__? Ben bir kez daha başlayarak CPython Numarayı tespit etmeye çalıştılar "tp_slots" ardından bakarak, tp_setattro doldurulur nerede o bakarak, type_setattro . type_setattro görünmektedir etrafında ince bir sargı _PyObject_GenericSetAttrWithDict . Ve karışıklığımın temel noktası var: bir tanımlayıcının yöntemine öncelik veren mantığa_PyObject_GenericSetAttrWithDict sahip gibi görünüyor !! Bunu akılda tutarak, neden tetiklemek yerine körü körüne yazıldığını anlayamıyorum .__set__B.v = 3vv.__set__

Yasal Uyarı 1: Python'u printfs ile kaynaktan yeniden oluşturmadım, bu yüzden type_setattrosırasında ne denildiğinden tam olarak emin değilim B.v = 3.

Feragat 2: VocalDescriptor"tipik" veya "önerilen" tanımlayıcı tanımını örneklemek amacında değildir. Metodların ne zaman çağrıldığını bana söylemek, kapsamlı bir hayır.


1
Benim için bu son satırda 3 yazdırıyor ... Kod iyi çalışıyor
Jab

3
Tanımlayıcılar , sınıfın kendisinden değil, bir örnekten özniteliklere erişirken uygulanır . Bana göre, gizem neden __get__işe yaramadı , neden işe __set__yaramadı.
jasonharper

1
@Jab OP hala __get__yöntemi çağırmayı bekliyor . B.v = 3ile etkin bir şekilde özelliğin üzerine yazmıştır int.
r.ook

2
@jasonharper Öznitelik erişimi çağrılıp __get__çağrılmayacağını ve bir örneği veya sınıfı kullanırken varsayılan uygulamalarını belirler object.__getattribute__ve type.__getattribute__çağırır __get__. Üzerinden atama__set__ sadece örnek içindir.
chepner

@jasonharper Tanımlayıcıların __get__yöntemlerinin sınıfın kendisinden çağrıldığında tetiklenmesi gerektiğine inanıyorum . Nasıl yapılır kılavuzuna göre @classmethods ve @staticmethods bu şekilde uygulanır . @Jab B.v = 3Sınıf tanımlayıcısının neden üzerine yazılabileceğini merak ediyorum . CPython uygulamasına dayanarak, B.v = 3tetiklemeyi de bekliyordum __set__.
Michael Carilli

Yanıtlar:


6

B.v = 3Tanımlayıcının üzerine bir tamsayı (yalnızca olması gerektiği gibi) üzerine yazan doğrudur .

İçin B.v = 3bir tanımlayıcı çağırmak için, açıklayıcısı üzerine yani metaclass, üzerinde tanımlı olması gerekirdi type(B).

>>> class BMeta(type): 
...     v = VocalDescriptor() 
... 
>>> class B(metaclass=BMeta): 
...     pass 
... 
>>> B.v = 3 
__set__

Açıklayıcıyı etkinleştirmek için Bbir örnek kullanırsınız: B().v = 3bunu yaparsınız.

B.vAlıcıyı çağırmanın nedeni , tanımlayıcı örneğin kendisinin döndürülmesine izin vermektir. Genellikle sınıf nesnesi üzerinden tanımlayıcıya erişime izin vermek için bunu yaparsınız:

class VocalDescriptor(object):
    def __get__(self, obj, objtype):
        if obj is None:
            return self
        print('__get__, obj={}, objtype={}'.format(obj, objtype))
    def __set__(self, obj, val):
        print('__set__')

Şimdi etkileşimde bulunabileceğiniz B.vgibi bir örnek döndürür <mymodule.VocalDescriptor object at 0xdeadbeef>. Kelimenin tam anlamıyla bir sınıf özniteliği olarak tanımlanan tanımlayıcı nesnedir ve durumu B.v.__dict__tüm örnekleri arasında paylaşılır B.

Tabii ki tam olarak ne B.vyapmak istediklerini tanımlamak kullanıcının koduna bağlıdır , geri dönmek selfsadece ortak kalıptır.


1
Bu cevabı tamamlamak __get__için, örnek niteliği veya sınıf niteliği __set__olarak adlandırılmak üzere tasarlanmış , ancak yalnızca örnek niteliği olarak adlandırılmak üzere tasarlanmış ekleyeceğim . Ve ilgili dokümanlar: docs.python.org/3/reference/datamodel.html#object.__get__
sanyash

@wim Muhteşem !! Paralel olarak bir kez daha type_setattro çağrı zincirine bakıyordum. _PyObject_GenericSetAttrWithDict çağrısının türü sağladığını görüyorum (bu noktada B, benim durumumda).
Michael Carilli

İçinde _PyObject_GenericSetAttrWithDict, bu B'nin Py_TYPE çeker olarak tp(B metaclass olduğunu, typebenim durumumda), o zaman var metaclass tp tarafından tedavi edilir açıklayıcı kısa devre mantığı . Doğrudan üzerinde tanımlı açıklayıcısı Yani B olmayan bu kısa devre mantığı tarafından görülen (bu nedenle benim orijinal kodda __set__çağrılmaz), ancak metaclass üzerinde tanımlı bir açıklayıcıdır olduğu kısa devre mantığı tarafından görülen.
Michael Carilli

Bu nedenle, Metaclass bir tanımlayıcı vardır senin durumunda, __set__o tanımlayıcısı yöntem olduğunu denir.
Michael Carilli

@sanyash doğrudan düzenleme yapmaktan çekinmeyin.
wim

3

Herhangi geçersiz kılmaları olmazsa söz B.veşdeğerdir type.__getattribute__(B, "v")iken, b = B(); b.veşdeğerdir object.__getattribute__(b, "v"). Her iki tanım __get__da tanımlanırsa sonucun yöntemini çağırır .

Dikkat, çağrı __get__her durumda farklıdır. B.vgeçer Noneiken, ilk değişken olarak B().vörneği kendisi geçer. Her iki durumda Bda ikinci argüman olarak geçilir.

B.v = 3Öte yandan, eşdeğerdir type.__setattr__(B, "v", 3), hangi gelmez değil çağırmak __set__.

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.