__Get__ ve __set__ ve Python tanımlayıcılarını anlama


310

Ben am çalışırken onlar için yararlı ne Python'un tanımlayıcılarıdır ve ne olduğunu anlamak için. Nasıl çalıştıklarını anlıyorum, ama şüphelerim burada. Aşağıdaki kodu göz önünde bulundurun:

class Celsius(object):
    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)


class Temperature(object):
    celsius = Celsius()
  1. Neden tanımlayıcı sınıfına ihtiyacım var?

  2. Nedir instanceve ownerburada? (in __get__). Bu parametrelerin amacı nedir?

  3. Bu örneği nasıl arayabilirim / kullanabilirim?

Yanıtlar:


147

Tanımlayıcı, Python'un propertytürünün nasıl uygulandığıdır. Bir tanımlayıcı basitçe uygular __get__, __set__vb ve daha sonra (eğer Sıcaklık sınıfı ile yukarıda yaptığımız gibi) tanımında başka bir sınıfa eklenir. Örneğin:

temp=Temperature()
temp.celsius #calls celsius.__get__

Tanımlayıcıya atadığınız özelliğe ( celsiusyukarıdaki örnekte) erişmek, uygun tanımlayıcı yöntemini çağırır.

instanceiçinde __get__sınıfının örneğidir (yukarıda açıklanan, __get__alacak tempise, ownerolacağını, böylece (tanımlayıcısı ile sınıftır Temperature).

Güç veren mantığı kapsüllemek için bir tanımlayıcı sınıf kullanmanız gerekir. Bu şekilde, tanımlayıcı bazı pahalı işlemleri önbelleğe almak için kullanılırsa (örneğin), değeri sınıfına değil, kendisine depolayabilir.

Tanımlayıcılarla ilgili bir makaleyi burada bulabilirsiniz .

EDIT: jchl yorumlarda belirtildiği gibi, sadece denerseniz Temperature.celsius, instanceolacak None.


6
Arasındaki fark nedir selfve instance?
Lemma Prism

2
'örnek' herhangi bir sınıfın örneği olabilir, benlik aynı sınıfın örneği olacaktır.
TheBeginner

3
@LemmaPrism self, tanımlayıcı örneğidir, instancetanımlayıcının ( instance.__class__ is owner) içinde olduğu sınıfın örneğidir (somutlaştırılmışsa ).
Tcll

Temperature.celsius0.0koda göre değeri verir celsius = Celsius(). Celsius tanımlayıcısı çağrılır, dolayısıyla örneğinin 0.0celsius adlı Sıcaklık sınıfı özniteliğine atanmış init değeri vardır .
Angel Salazar

109

Neden tanımlayıcı sınıfına ihtiyacım var?

Özelliklerin nasıl çalıştığı üzerinde ekstra kontrol sağlar. Örneğin, Java'da alıcı ve ayarlayıcılara alışkınsanız, Python'un bunu yapmanın yolu. Bir avantajı, kullanıcılara tıpkı bir özellik gibi görünmesidir (sözdiziminde bir değişiklik yoktur). Böylece sıradan bir nitelikle başlayabilir ve daha sonra süslü bir şey yapmanız gerektiğinde bir tanımlayıcıya geçebilirsiniz.

Bir özellik sadece değiştirilebilir bir değerdir. Bir tanımlayıcı, bir değeri okurken veya ayarlarken (veya silerken) rastgele kod yürütmenizi sağlar. Böylece, bir özelliği bir veritabanındaki bir alanla eşleştirmek için bunu kullanabilirsiniz, örneğin bir tür ORM.

Başka bir kullanım __set__, "özniteliği" salt okunur kılmak için bir istisna atarak yeni bir değeri kabul etmeyi reddetmek olabilir .

Nedir instanceve ownerburada? (in __get__). Bu parametrelerin amacı nedir?

Bu oldukça ince (ve burada yeni bir cevap yazmamın nedeni - Aynı soruyu merak ederken bu soruyu buldum ve mevcut cevabı bu kadar büyük bulamadım).

Bir sınıfta tanımlayıcı tanımlanır, ancak genellikle bir örnekten çağrılır. O bir örneği hem denir zaman instanceve ownerayarlanır (ve halledebiliriz ownergelen instancebu tür anlamsız görünüyor böylece). Ancak bir sınıftan çağrıldığında, sadece ownerayarlanır - bu yüzden oradadır.

Bu sadece __get__bir sınıfta çağrılabilen tek kişi olduğu için gereklidir. Sınıf değerini ayarlarsanız tanımlayıcının kendisini ayarlarsınız. Benzer şekilde silinmek için. Bu yüzden ownerorada gerekli değil.

Bu örneği nasıl arayabilirim / kullanabilirim?

İşte benzer sınıfları kullanan harika bir numara:

class Celsius:

    def __get__(self, instance, owner):
        return 5 * (instance.fahrenheit - 32) / 9

    def __set__(self, instance, value):
        instance.fahrenheit = 32 + 9 * value / 5


class Temperature:

    celsius = Celsius()

    def __init__(self, initial_f):
        self.fahrenheit = initial_f


t = Temperature(212)
print(t.celsius)
t.celsius = 0
print(t.fahrenheit)

(Python 3 kullanıyorum; python 2 için bu bölümlerin / 5.0ve olduğundan emin olmalısınız / 9.0). Bu şunu verir:

100.0
32.0

Şimdi python'da aynı etkiyi elde etmenin başka, tartışmasız daha iyi yolları var (örneğin, santigrat aynı temel mekanizma olan ancak tüm kaynağı Sıcaklık sınıfının içine yerleştiren bir özellik olsaydı), ancak bu ne yapılabileceğini gösterir ...


2
Dönüşümler yanlış: C = 5 (F − 32) / 9, F = 32 + 9C / 5 olmalıdır.
musiphil

1
Bir Sıcaklık nesneniz olduğundan emin olun. Aşağıdaki şeyler yapmak berbat. t1 = Sıcaklık (190) baskı t1.celsius t1.celsius = 100 baskı t1.fahrenheit Şimdi t.celcius ve t.fahrenheit'i kontrol ettiğinizde, onlar da değiştirilir. t.celcius 115 ve t.fahrenheit 32'dir. @Eric
Ishan Bhatt

1
@IshanBhatt: Bunun bence yukarıdaki musiphil tarafından belirtilen hata yüzünden. Ayrıca, bu benim cevabım değil
Eric

69

Python'un tanımlayıcılarının ne olduğunu ve ne için yararlı olabileceklerini anlamaya çalışıyorum.

Tanımlayıcılar, aşağıdaki özel yöntemlerden herhangi birine sahip sınıf nitelikleridir (özellikler veya yöntemler gibi):

  • __get__ (örneğin bir yöntem / işlevde veri olmayan tanımlayıcı yöntem)
  • __set__ (örneğin bir özellik örneğinde veri tanımlayıcı yöntemi)
  • __delete__ (veri tanımlayıcı yöntem)

Bu tanımlayıcı nesneler, diğer nesne sınıfı tanımlarında öznitelik olarak kullanılabilir. (Yani, __dict__sınıf nesnesinin içinde yaşarlar .)

Tanımlayıcı nesneler, foo.descriptornormal ifadede, atamada ve hatta silmede noktalı aramanın (örn. ) Sonuçlarını programlı olarak yönetmek için kullanılabilir .

Fonksiyonlar / yöntemleri, sınır yöntemleri, property, classmethodve staticmethodtüm kullanım onlar noktalı araması ile erişilen nasıl kontrole bu özel yöntemler.

Örneğin , bir veri tanımlayıcı , propertynesnelerin daha basit bir durumuna dayalı olarak tembel özelliklerin değerlendirilmesine izin vererek, örneklerin olası her özniteliği önceden hesapladığınızdan daha az bellek kullanmasına izin verebilir.

Başka bir veri tanımlayıcısı olan a member_descriptor, __slots__sınıfın verileri daha esnek ancak yer kaplayan yerine değiştirilebilir bir grup benzeri veri yapısında depolamasına izin vererek bellek tasarrufuna izin verir __dict__.

Veri olmayan tanımlayıcılar, genellikle örnek, sınıf ve statik yöntemler, örtük ilk bağımsız değişkenlerini (genellikle adlandırılmış clsve selfsırasıyla) veri olmayan tanımlayıcı yöntemlerinden alırlar __get__.

Çoğu Python kullanıcısının sadece basit kullanımı öğrenmesi ve tanımlayıcıların uygulanmasını daha fazla öğrenmesi veya anlaması gerekmez.

Derinlemesine: Tanımlayıcılar Nelerdir?

Bir tanımlayıcı, bir örneğin tipik bir özniteliği gibi noktalı arama yoluyla kullanılması amaçlanan aşağıdaki yöntemlerden ( __get__, __set__veya __delete__) herhangi birine sahip bir nesnedir . obj_instanceBir descriptornesne ile birlikte bir sahip- nesne için:

  • obj_instance.descriptor
    descriptor.__get__(self, obj_instance, owner_class)döndürme çağırır a Bir özellik value
    tüm yöntemleri ve açık nasıl getçalışır.

  • obj_instance.descriptor = value
    descriptor.__set__(self, obj_instance, value)döndürme çağırır None
    Bu setterbir özellik üzerinde çalışır.

  • del obj_instance.descriptor
    descriptor.__delete__(self, obj_instance)döndürme çağırır None
    Bu deleterbir özellik üzerinde çalışır.

obj_instancesınıfı tanımlayıcı nesnenin örneğini içeren örnektir. tanımlayıcınınself örneğidir (muhtemelen sınıfının yalnızca biri )obj_instance

Bunu kodla tanımlamak için, öznitelikler kümesi gerekli özniteliklerden herhangi biriyle kesişiyorsa bir nesne bir tanımlayıcıdır:

def has_descriptor_attrs(obj):
    return set(['__get__', '__set__', '__delete__']).intersection(dir(obj))

def is_descriptor(obj):
    """obj can be instance of descriptor or the descriptor class"""
    return bool(has_descriptor_attrs(obj))

Bir Veri Tanımlayıcı bir sahiptir __set__ve / veya __delete__.
Bir Sigara Veri tanımlayıcısı ikiside yok __set__ne de __delete__.

def has_data_descriptor_attrs(obj):
    return set(['__set__', '__delete__']) & set(dir(obj))

def is_data_descriptor(obj):
    return bool(has_data_descriptor_attrs(obj))

Yerleşik Tanımlayıcı Nesne Örnekleri:

  • classmethod
  • staticmethod
  • property
  • genel olarak fonksiyonlar

Veri Dışı Tanımlayıcılar

Bunu görebiliriz classmethodve staticmethodVeri Tanımlayıcı Olmayanlardır:

>>> is_descriptor(classmethod), is_data_descriptor(classmethod)
(True, False)
>>> is_descriptor(staticmethod), is_data_descriptor(staticmethod)
(True, False)

Her ikisi de sadece __get__yönteme sahiptir:

>>> has_descriptor_attrs(classmethod), has_descriptor_attrs(staticmethod)
(set(['__get__']), set(['__get__']))

Tüm işlevlerin aynı zamanda Veri Tanımlayıcı Olmayanlar olduğuna dikkat edin:

>>> def foo(): pass
... 
>>> is_descriptor(foo), is_data_descriptor(foo)
(True, False)

Veri Tanımlayıcı, property

Ancak, propertybir Veri Tanımlayıcıdır:

>>> is_data_descriptor(property)
True
>>> has_descriptor_attrs(property)
set(['__set__', '__get__', '__delete__'])

Noktalı Arama Sırası

Bunlar, noktalı bir aramanın arama sırasını etkilediği için önemli ayrımlardır .

obj_instance.attribute
  1. İlk olarak yukarıdakiler, özelliğin örneğin sınıfında bir Veri Tanımlayıcı olup olmadığını görmek için,
  2. Değilse, özelliğin obj_instance's'de olup olmadığına bakar __dict__,
  3. Sonunda bir Veri Tanımlayıcı Olmayan'a geri döner.

Bu arama sırasının sonucu, işlevler / yöntemler gibi Veri Tanımlayıcı Olmayanların örnekler tarafından geçersiz kılınabilmesidir .

Özet ve Sonraki Adımlar

Biz tanımlayıcılar herhangi biriyle nesneler olduğunu öğrendik __get__, __set__ya __delete__. Bu tanımlayıcı nesneler, diğer nesne sınıfı tanımlarında öznitelik olarak kullanılabilir. Şimdi kodunuzu örnek olarak kullanarak nasıl kullanıldıklarına bakacağız.


Kodun Sorudan Analizi

İşte kodunuz, ardından sorularınız ve her birine cevaplarınız:

class Celsius(object):
    def __init__(self, value=0.0):
        self.value = float(value)
    def __get__(self, instance, owner):
        return self.value
    def __set__(self, instance, value):
        self.value = float(value)

class Temperature(object):
    celsius = Celsius()
  1. Neden tanımlayıcı sınıfına ihtiyacım var?

Tanımlayıcınız, bu sınıf özniteliği için her zaman bir kayan noktaya sahip olmanızı ve özelliği silmek için Temperaturekullanamayacağınızı garanti eder del:

>>> t1 = Temperature()
>>> del t1.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __delete__

Aksi takdirde, tanımlayıcılarınız durumu tanımlayıcıda depolamak yerine sahip sınıfını ve sahibin örneklerini yok sayar. Basit bir sınıf özniteliğiyle tüm örneklerde durumu kolayca paylaşabilirsiniz (bunu her zaman sınıfa bir kayan nokta olarak ayarlayıp asla silmediğiniz veya kodunuzun kullanıcıları ile rahat olduğunuz sürece):

class Temperature(object):
    celsius = 0.0

Bu, örneğinizle tam olarak aynı davranışı sağlar (aşağıdaki 3. soruya verilen cevaba bakın), ancak bir Pythons yerleşik ( property) kullanır ve daha deyimsel olarak kabul edilir:

class Temperature(object):
    _celsius = 0.0
    @property
    def celsius(self):
        return type(self)._celsius
    @celsius.setter
    def celsius(self, value):
        type(self)._celsius = float(value)
  1. Burada örnek ve sahip nedir? ( olsun ). Bu parametrelerin amacı nedir?

instancetanımlayıcıyı çağıran sahibin örneğidir. Sahip, tanımlayıcı nesnenin veri noktasına erişimi yönetmek için kullanıldığı sınıftır. Daha açıklayıcı değişken adları için bu cevabın ilk paragrafının yanındaki tanımlayıcıları tanımlayan özel yöntemlerin açıklamalarına bakın.

  1. Bu örneği nasıl arayabilirim / kullanabilirim?

İşte bir gösteri:

>>> t1 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1
>>> 
>>> t1.celsius
1.0
>>> t2 = Temperature()
>>> t2.celsius
1.0

Özelliği silemezsiniz:

>>> del t2.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __delete__

Ve bir kayan noktaya dönüştürülemeyen bir değişken atayamazsınız:

>>> t1.celsius = '0x02'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __set__
ValueError: invalid literal for float(): 0x02

Aksi takdirde, burada bulunanlar, tüm örnekler için, herhangi bir örneğe atanarak yönetilen küresel bir durumdur.

En deneyimli Python programcılarının bu sonucu elde etmesinin beklenen yolu property, başlık altında aynı tanımlayıcıları kullanan ancak davranışı sahip sınıfının uygulanmasına getiren dekoratörü kullanmak olacaktır (yine yukarıda tanımlandığı gibi):

class Temperature(object):
    _celsius = 0.0
    @property
    def celsius(self):
        return type(self)._celsius
    @celsius.setter
    def celsius(self, value):
        type(self)._celsius = float(value)

Orijinal kod parçasının beklenen davranışı tam olarak aynıdır:

>>> t1 = Temperature()
>>> t2 = Temperature()
>>> t1.celsius
0.0
>>> t1.celsius = 1.0
>>> t2.celsius
1.0
>>> del t1.celsius
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't delete attribute
>>> t1.celsius = '0x02'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in celsius
ValueError: invalid literal for float(): 0x02

Sonuç

Tanımlayıcıları tanımlayan özellikleri, veri ve veri tanımlayıcı olmayanlar arasındaki farkı, bunları kullanan yerleşik nesneleri ve kullanımla ilgili belirli soruları ele aldık.

Peki yine, sorunun örneğini nasıl kullanırsınız? Umarım yapmazsın. Umarım ilk önerim (basit bir sınıf özniteliği) ile başlar ve gerekli olduğunu düşünüyorsanız ikinci öneriye (özellik dekoratörü) geçersiniz.


1
Güzel, en çok bu cevaptan öğrendim (kesinlikle başkalarından da öğrendim). Bu açıklama hakkında bir soru "En deneyimli Python programcılarının bu sonucu elde etmesinin beklenen yolu ...". İfadeden önce ve sonra tanımladığınız Temeperature sınıfı aynıdır. Burada ne aldığını özledim mi?
Yolo Voe

1
@YoloVoe hayır, bu doğru, yukarıdakilerin tekrarı olduğunu vurgulamak için bazı parantezsel ayrıntılar ekledim.
Aaron Hall

1
Bu İNANILMAZ bir cevaptır. Birkaç kez daha okumam gerekecek ama Python anlayışımın birkaç çentik açmış gibi hissediyorum
Lucas Young

20

Tanımlayıcıların ayrıntılarına girmeden önce Python'da özellik aramanın nasıl çalıştığını bilmek önemli olabilir. Bu, sınıfın metasınıfının olmadığını ve varsayılan uygulamasını kullandığını varsayar __getattribute__(her ikisi de davranışı "özelleştirmek" için kullanılabilir).

Bu durumda öznitelik aramanın en iyi gösterimi (Python 3.x'te veya Python 2.x'te yeni stil sınıfları için) Python metasınıflarını (ionel'in kod günlüğü) Anlamak'tır . Resim, :"özelleştirilemeyen özellik araması" yerine kullanılır.

Bu bir özelliğin arama temsil foobarbir on instancearasında Class:

resim açıklamasını buraya girin

Burada iki koşul önemlidir:

  • Sınıfının instanceöznitelik adı için bir girdisi varsa __get__ve ve öğesinde ise __set__.
  • Eğer instancesahip hiçbir özellik adı için giriş ama sınıf birine sahiptir ve sahip olduğu __get__.

Burada tanımlayıcılar devreye girer:

  • Her ikisi de olan veri tanımlayıcıları__get__ ve __set__.
  • Yalnızca veri içermeyen tanımlayıcılar__get__ .

Her iki durumda da döndürülen değer __get__ilk argüman olarak örnek ve ikinci argüman olarak sınıf ile çağrılır.

Sınıf özelliği araması için arama daha da karmaşıktır (bkz. Örneğin Sınıf özelliği araması (yukarıda belirtilen blogda) ).

Özel sorularınıza geçelim:

Neden tanımlayıcı sınıfına ihtiyacım var?

Çoğu durumda tanımlayıcı sınıflar yazmanıza gerek yoktur! Ancak muhtemelen son derece düzenli bir kullanıcısınız. Örneğin işlevler. İşlevler tanımlayıcılardır, işlevler selfdolaylı olarak ilk argüman olarak geçirilen yöntemler olarak kullanılabilir .

def test_function(self):
    return self

class TestClass(object):
    def test_method(self):
        ...

Bir test_methodörneğe bakarsanız , "bağlı yöntem" i geri alırsınız:

>>> instance = TestClass()
>>> instance.test_method
<bound method TestClass.test_method of <__main__.TestClass object at ...>>

Benzer şekilde, bir işlevi __get__yöntemini manuel olarak çağırarak da bağlayabilirsiniz (gerçekten açıklama amaçlı değildir, yalnızca açıklama amacıyla):

>>> test_function.__get__(instance, TestClass)
<bound method test_function of <__main__.TestClass object at ...>>

Hatta bu "kendine bağlı yöntem" de diyebilirsiniz:

>>> test_function.__get__(instance, TestClass)()
<__main__.TestClass at ...>

Herhangi bir argüman sağlamadığımı ve fonksiyonun bağladığım örneği döndürdüğünü unutmayın!

Fonksiyonlar Veri olmayan tanımlayıcılardır !

Bir veri tanımlayıcısının bazı yerleşik örnekleri olacaktır property. İhmal getter, setterve (gelen tanımlayıcıdır Tanımlayıcı HowTo Kılavuzu "Özellikler" ):deleterproperty

class Property(object):
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

Eğer bir "adı" aramak her bir veri bunun çağrılan tanıtıcıya var yana propertyve süslenmiş fonksiyonlara basitçe delegeler @property, @name.setterve @name.deleter(varsa).

Örneğin standart kütüphanede birkaç diğer tanımlayıcılar vardır staticmethod, classmethod.

Tanımlayıcıların noktası kolaydır (nadiren ihtiyacınız olmasına rağmen): Öznitelik erişimi için soyut ortak kod. propertyörneğin değişken erişimi functioniçin bir soyutlamadır, yöntemler için bir soyutlama staticmethodsağlar , örnek erişimi classmethodgerektirmeyen yöntemler için bir soyutlama sağlar ve örnek erişimi yerine sınıf erişimi gerektiren yöntemler için bir soyutlama sağlar (bu biraz basitleştirilmiştir).

Başka bir örnek, bir sınıf özelliği olabilir .

Eğlenceli bir örnek ( __set_name__Python 3.6'dan kullanma) yalnızca belirli bir türe izin veren bir özellik olabilir:

class TypedProperty(object):
    __slots__ = ('_name', '_type')
    def __init__(self, typ):
        self._type = typ

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        return instance.__dict__[self._name]

    def __set__(self, instance, value):
        if not isinstance(value, self._type):
            raise TypeError(f"Expected class {self._type}, got {type(value)}")
        instance.__dict__[self._name] = value

    def __delete__(self, instance):
        del instance.__dict__[self._name]

    def __set_name__(self, klass, name):
        self._name = name

Ardından tanımlayıcıyı bir sınıfta kullanabilirsiniz:

class Test(object):
    int_prop = TypedProperty(int)

Ve onunla biraz oynamak:

>>> t = Test()
>>> t.int_prop = 10
>>> t.int_prop
10

>>> t.int_prop = 20.0
TypeError: Expected class <class 'int'>, got <class 'float'>

Veya "tembel bir mülk":

class LazyProperty(object):
    __slots__ = ('_fget', '_name')
    def __init__(self, fget):
        self._fget = fget

    def __get__(self, instance, klass=None):
        if instance is None:
            return self
        try:
            return instance.__dict__[self._name]
        except KeyError:
            value = self._fget(instance)
            instance.__dict__[self._name] = value
            return value

    def __set_name__(self, klass, name):
        self._name = name

class Test(object):
    @LazyProperty
    def lazy(self):
        print('calculating')
        return 10

>>> t = Test()
>>> t.lazy
calculating
10
>>> t.lazy
10

Bunlar, mantığın ortak bir tanımlayıcıya taşınmasının mantıklı olabileceği durumlardır, ancak biri bunları başka yollarla da çözebilir (ancak belki de bazı kodları tekrarlayarak).

Nedir instanceve ownerburada? (in __get__). Bu parametrelerin amacı nedir?

Bu özelliği nasıl aradığınıza bağlıdır. Özniteliği bir örnekte ararsanız:

  • ikinci argüman, niteliğe baktığınız örnektir
  • üçüncü argüman örneğin sınıfıdır

Sınıftaki özniteliğe bakmanız durumunda (tanımlayıcının sınıfta tanımlandığı varsayılarak):

  • ikinci argüman None
  • üçüncü argüman, niteliğe baktığınız sınıftır

Eğer sınıf düzeyindeki taramalı (çünkü ne zaman davranışını özelleştirmek istiyorsanız Yani temelde üçüncü argüman gereklidir instanceolan None).

Bu örneği nasıl arayabilirim / kullanabilirim?

Örneğin, temel olarak yalnızca floatsınıfın tüm örnekleri arasında (ve sınıfta) dönüştürülebilen ve paylaşılabilen değerlere izin veren bir özelliktir, ancak sınıfta yalnızca "okuma" erişimini kullanabilirsiniz, aksi takdirde tanımlayıcı örneğini değiştirirsiniz ):

>>> t1 = Temperature()
>>> t2 = Temperature()

>>> t1.celsius = 20   # setting it on one instance
>>> t2.celsius        # looking it up on another instance
20.0

>>> Temperature.celsius  # looking it up on the class
20.0

Bu nedenle tanımlayıcılar instance, değeri paylaşmaktan kaçınmak için genellikle ikinci bağımsız değişkeni ( ) kullanır. Ancak bazı durumlarda örnekler arasında bir değer paylaşmak istenebilir (şu anda bir senaryo düşünemiyorum). Bununla birlikte, sıcaklık sınıfında bir celsius özelliği için neredeyse hiç mantıklı değildir ... belki sadece akademik egzersizler hariç.


karanlık modda gerçekten acı çeken grafiğin saydam arka planının yığın akışına bir hata olarak bildirilmesi gerekip gerekmediğinden emin değilim.
Tshirtman

@Tshirtman Sanırım bu görüntünün kendisiyle ilgili bir sorun. Tamamen şeffaf değil ... Blog gönderisini aldım ve uygun şeffaf arka planla nasıl yeniden oluşturacağımı bilmiyorum. Koyu arka planla çok tuhaf görünüyor :(
MSeifert

9

Neden tanımlayıcı sınıfına ihtiyacım var?

Akıcı Python'dan esinlenerek Buciano Ramalho

Bunun gibi bir sınıfınız olduğunu görüntüleme

class LineItem:
     price = 10.9
     weight = 2.1
     def __init__(self, name, price, weight):
          self.name = name
          self.price = price
          self.weight = weight

item = LineItem("apple", 2.9, 2.1)
item.price = -0.9  # it's price is negative, you need to refund to your customer even you delivered the apple :(
item.weight = -0.8 # negative weight, it doesn't make sense

Negatif bir sayı atamaktan kaçınmak için ağırlığı ve fiyatı doğrulamalıyız, tanımlayıcıyı proxy olarak bu şekilde kullanırsak daha az kod yazabiliriz

class Quantity(object):
    __index = 0

    def __init__(self):
        self.__index = self.__class__.__index
        self._storage_name = "quantity#{}".format(self.__index)
        self.__class__.__index += 1

    def __set__(self, instance, value):
        if value > 0:
            setattr(instance, self._storage_name, value)
        else:
           raise ValueError('value should >0')

   def __get__(self, instance, owner):
        return getattr(instance, self._storage_name)

sonra LineItem sınıfını şu şekilde tanımlayın:

class LineItem(object):
     weight = Quantity()
     price = Quantity()

     def __init__(self, name, weight, price):
         self.name = name
         self.weight = weight
         self.price = price

ve daha yaygın doğrulama yapmak için Miktar sınıfını genişletebiliriz


1
Çok sayıda kullanıcıyla etkileşim kurmak için tanımlayıcıyı nasıl kullanacağını gösteren ilginç kullanım durumu. Bir tanımlayıcısı olan bir özellik: Başlangıçta önemli bir noktayı anlamadı gerekir mesela sınıf ad (oluşturulabilir weight = Quantity(), ancak değerler sadece kullanarak örnek ad alanında ayarlanmalıdır self(örn self.weight = 4, başka özellik yeni değere ribaund olurdu) ve tanımlayıcı atılır. Güzel!
dakika

Bir şeyi anlayamıyorum. Sen tanımlayan weight = Quantity()sınıf değişkeni ve olarak onun __get__ve __set__örnek değişkeni üzerinde çalışıyoruz. Nasıl?
Teknokrat

0

Andrew Cooke'un cevabındaki kodu (önerilen küçük değişikliklerle) denedim. (Python 2.7 kullanıyorum).

Kod:

#!/usr/bin/env python
class Celsius:
    def __get__(self, instance, owner): return 9 * (instance.fahrenheit + 32) / 5.0
    def __set__(self, instance, value): instance.fahrenheit = 32 + 5 * value / 9.0

class Temperature:
    def __init__(self, initial_f): self.fahrenheit = initial_f
    celsius = Celsius()

if __name__ == "__main__":

    t = Temperature(212)
    print(t.celsius)
    t.celsius = 0
    print(t.fahrenheit)

Sonuç:

C:\Users\gkuhn\Desktop>python test2.py
<__main__.Celsius instance at 0x02E95A80>
212

Python öncesinde 3'e ile emin olarak doğru açıklayıcısı işi yapacak nesneden alt sınıf yapmak olsun sihirli eski tarz sınıfları için çalışma yapmaz.


1
Tanımlayıcılar yalnızca yeni stil sınıflarıyla çalışır. Python 2.x için bu, sınıfınızı Python 3'te varsayılan olan "nesne" den türetmek anlamına gelir.
Ivo van der Wijk

0

Sen görürdük https://docs.python.org/3/howto/descriptor.html#properties

class Property(object):
    "Emulate PyProperty_Type() in Objects/descrobject.c"

    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        self.fget = fget
        self.fset = fset
        self.fdel = fdel
        if doc is None and fget is not None:
            doc = fget.__doc__
        self.__doc__ = doc

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.fget is None:
            raise AttributeError("unreadable attribute")
        return self.fget(obj)

    def __set__(self, obj, value):
        if self.fset is None:
            raise AttributeError("can't set attribute")
        self.fset(obj, value)

    def __delete__(self, obj):
        if self.fdel is None:
            raise AttributeError("can't delete attribute")
        self.fdel(obj)

    def getter(self, fget):
        return type(self)(fget, self.fset, self.fdel, self.__doc__)

    def setter(self, fset):
        return type(self)(self.fget, fset, self.fdel, self.__doc__)

    def deleter(self, fdel):
        return type(self)(self.fget, self.fset, fdel, self.__doc__)

1
Bu soruya cevap vermez veya faydalı bilgi sağlamaz.
Sebastian Nielsen
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.