Alıcıları ve ayarlayıcıları kullanmanın pitonik yolu nedir?


341

Bunu şöyle yapıyorum:

def set_property(property,value):  
def get_property(property):  

veya

object.property = value  
value = object.property

Python için yeniyim, bu yüzden hala sözdizimini keşfediyorum ve bunu yapmak için bazı tavsiyeler istiyorum.

Yanıtlar:


684

Bunu deneyin: Python Özelliği

Örnek kod:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        print("getter of x called")
        return self._x

    @x.setter
    def x(self, value):
        print("setter of x called")
        self._x = value

    @x.deleter
    def x(self):
        print("deleter of x called")
        del self._x


c = C()
c.x = 'foo'  # setter called
foo = c.x    # getter called
del c.x      # deleter called

2
_X somutlaştırılırken x için ayarlayıcı başlatıcıda çağrılıyor mu?
Casey

7
@Casey: No. Referanslar ._xbaypas (bir özellik, sadece düz bir özellik değildir) propertyambalaj. Sadece referanslar için .xgeçmesi property.
ShadowRanger

278

Alıcıları ve ayarlayıcıları kullanmanın pitonik yolu nedir?

"Pythonic" yoludur değil "alıcılar" ve "belirleyiciler" kullanmak, ama soru gösterir gibi, düz özelliklerini kullanmak ve delsilme (ancak isimler masum ... yerleşiklerini korumak için değiştirilir):

value = 'something'

obj.attribute = value  
value = obj.attribute
del obj.attribute

Daha sonra, ayarı değiştirmek ve almak istiyorsanız, bunu propertydekoratör kullanarak kullanıcı kodunu değiştirmek zorunda kalmadan yapabilirsiniz :

class Obj:
    """property demo"""
    #
    @property            # first decorate the getter method
    def attribute(self): # This getter method name is *the* name
        return self._attribute
    #
    @attribute.setter    # the property decorates with `.setter` now
    def attribute(self, value):   # name, e.g. "attribute", is the same
        self._attribute = value   # the "value" name isn't special
    #
    @attribute.deleter     # decorate with `.deleter`
    def attribute(self):   # again, the method name is the same
        del self._attribute

(Her dekoratör kullanımı önceki özellik nesnesini kopyalar ve günceller, bu nedenle her küme, alma ve silme işlevi / yöntemi için aynı adı kullanmanız gerektiğini unutmayın.

Yukarıdakileri tanımladıktan sonra, orijinal ayar, alma ve kod silme aynıdır:

obj = Obj()
obj.attribute = value  
the_value = obj.attribute
del obj.attribute

Bundan kaçınmalısınız:

def set_property(property,value):  
def get_property(property):  

Birincisi, yukarıdakiler işe yaramaz, çünkü özelliğin (genellikle self) olarak ayarlanacağı örnek için bir argüman sağlamazsınız ;

class Obj:

    def set_property(self, property, value): # don't do this
        ...
    def get_property(self, property):        # don't do this either
        ...

İkincisi, bu iki özel yöntemin amacını çoğaltır __setattr__ve __getattr__.

Üçüncüsü, setattrve getattryerleşik fonksiyonlara da sahibiz .

setattr(object, 'property_name', value)
getattr(object, 'property_name', default_value)  # default is optional

@propertyDekoratör alıcılar ve ayarlayıcılar oluşturmak içindir.

Örneğin, ayarlanan değeri kısıtlamalar koymak için ayar davranışını değiştirebiliriz:

class Protective(object):

    @property
    def protected_value(self):
        return self._protected_value

    @protected_value.setter
    def protected_value(self, value):
        if acceptable(value): # e.g. type or range check
            self._protected_value = value

Genel olarak, propertydoğrudan özellikleri kullanmaktan kaçınmak ve kullanmak istiyoruz .

Python kullanıcıları tarafından beklenen budur. En az sürpriz kuralına uyarak, aksine çok zorlayıcı bir nedeniniz yoksa kullanıcılarınıza bekledikleri şeyi vermeye çalışmalısınız.

gösteri

Örneğin, nesnemizin korumalı özelliğinin 0 ile 100 arasında bir tamsayı olması ve silinmesini önlemek için kullanıcıyı uygun kullanımını bildiren uygun mesajlarla ihtiyacımız olduğunu varsayalım:

class Protective(object):
    """protected property demo"""
    #
    def __init__(self, start_protected_value=0):
        self.protected_value = start_protected_value
    # 
    @property
    def protected_value(self):
        return self._protected_value
    #
    @protected_value.setter
    def protected_value(self, value):
        if value != int(value):
            raise TypeError("protected_value must be an integer")
        if 0 <= value <= 100:
            self._protected_value = int(value)
        else:
            raise ValueError("protected_value must be " +
                             "between 0 and 100 inclusive")
    #
    @protected_value.deleter
    def protected_value(self):
        raise AttributeError("do not delete, protected_value can be set to 0")

(Not bu __init__belirtmektedir self.protected_valueama özelliği yöntemleri bakınız self._protected_value. Bu şekilde bir __init__kullanım da temin ortak API yoluyla maddi "korumalı").

Ve kullanım:

>>> p1 = Protective(3)
>>> p1.protected_value
3
>>> p1 = Protective(5.0)
>>> p1.protected_value
5
>>> p2 = Protective(-5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __init__
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> p1.protected_value = 7.3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 17, in protected_value
TypeError: protected_value must be an integer
>>> p1.protected_value = 101
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 15, in protected_value
ValueError: protectected_value must be between 0 and 100 inclusive
>>> del p1.protected_value
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 18, in protected_value
AttributeError: do not delete, protected_value can be set to 0

İsimler önemli mi?

Evet biliyorlar . .setterve .deleterorijinal mülkün kopyalarını oluşturun. Bu, alt sınıfların üst öğedeki davranışı değiştirmeden davranışı düzgün bir şekilde değiştirmesini sağlar.

class Obj:
    """property demo"""
    #
    @property
    def get_only(self):
        return self._attribute
    #
    @get_only.setter
    def get_or_set(self, value):
        self._attribute = value
    #
    @get_or_set.deleter
    def get_set_or_delete(self):
        del self._attribute

Şimdi bunun çalışması için ilgili isimleri kullanmalısınız:

obj = Obj()
# obj.get_only = 'value' # would error
obj.get_or_set = 'value'  
obj.get_set_or_delete = 'new value'
the_value = obj.get_only
del obj.get_set_or_delete
# del obj.get_or_set # would error

Bunun yararlı olacağından emin değilim, ancak get, set ve / veya salt silme özelliği istiyorsanız use-case. Muhtemelen aynı ada sahip semantik olarak aynı mülke bağlı kalmak en iyisidir.

Sonuç

Basit özelliklerle başlayın.

Daha sonra ayar, alma ve silme etrafında işlevselliğe ihtiyacınız varsa, özelliği dekoratörle ekleyebilirsiniz.

set_...Ve get_...- adlı fonksiyonlardan kaçının .


Mevcut işlevselliği kopyalamanın yanı sıra, kendi ayarlayıcılarınızı ve alıcılarınızı yazmaktan neden kaçınmalısınız? Bunun Pythonic bir yol olmadığını anlıyorum, ama başka türlü karşılaşabileceğiniz gerçekten ciddi sorunlar var mı?
user1350191

4
Senin tanıtımda, __init__yöntem anlamına gelir self.protected_valueama alıcı ve ayarlayıcılar bakınız self._protected_value. Bunun nasıl çalıştığını açıklar mısınız? Kodunuzu test ettim ve olduğu gibi çalışıyor - bu bir yazım hatası değil.
codeforester

2
@codeforester Daha önce cevabımda cevap vermeyi umuyordum, ama yapabilinceye kadar bu yorum yeterli olmalı. Umarım bu özelliği "korunmuş" olduğundan emin olarak, kamu API'sı aracılığıyla mülkü kullanır. Bunu bir mülkle "korumak" ve daha sonra kamuya açık olmayan API'ları kullanmak __init__mantıklı olmaz mıydı?
Aaron Hall

2
Evet, @AaronHall şimdi anladı. self.protected_value = start_protected_valueAslında setter fonksiyonunu çağırdığını fark etmedim ; Bunun bir görev olduğunu düşündüm.
codeforester

1
imho bu kabul edilen cevap olmalıdır, eğer doğru anladım python örneğin java ile karşılaştırıldığında tam tersi bir nokta alır. Her şeyi varsayılan olarak özel yapmak ve
python'da

27
In [1]: class test(object):
    def __init__(self):
        self.pants = 'pants'
    @property
    def p(self):
        return self.pants
    @p.setter
    def p(self, value):
        self.pants = value * 2
   ....: 
In [2]: t = test()
In [3]: t.p
Out[3]: 'pants'
In [4]: t.p = 10
In [5]: t.p
Out[5]: 20

17

Kullanılması @propertyve @attribute.settersiz "pythonic" yolunu kullanmak sadece yardımcı değil, aynı zamanda niteliklerin geçerliliğini kontrol etmek hem nesne oluşturulurken ve onu değiştirerek zaman.

class Person(object):
    def __init__(self, p_name=None):
        self.name = p_name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_name):
        if type(new_name) == str: #type checking for name property
            self._name = new_name
        else:
            raise Exception("Invalid value for name")

Böylece, aslında _nameistemci geliştiricilerin özniteliğini 'gizler' ve ayrıca name özellik türü üzerinde denetimler gerçekleştirirsiniz. Başlatma sırasında bile bu yaklaşımı izleyerek ayarlayıcının çağrıldığına dikkat edin. Yani:

p = Person(12)

Aşağıdakilere yol açacaktır:

Exception: Invalid value for name

Fakat:

>>>p = person('Mike')
>>>print(p.name)
Mike
>>>p.name = 'George'
>>>print(p.name)
George
>>>p.name = 2.3 # Causes an exception

16

34
Bu hemen hemen sadece linke verilen bir cevap.
Aaron Hall


7
Bu nasıl tam bir cevap? Bağlantı bir cevap değildir.
Andy_A̷n̷d̷y̷

Bence bu iyi bir cevap, çünkü orada belgelerin nasıl kullanılacağını açıkça belirtiyor (ve python uygulaması değişirse güncel kalacak ve OP'yi yanıtlayıcının önerdiği yönteme yönlendiriyor. @ Jean-FrançoisCorbett açıkça belirtildi 'nasıl' tam bir cevaptır
HunnyBear

Her durumda, bu cevap diğer cevaplara faydalı bir şey eklemez ve ideal olarak silinmelidir.
Georgy

5

Erişimcileri / mutasyonları kullanabilirsiniz (yani @attr.setterve @propertydeğil), ancak en önemli şey tutarlı olmaktır!

Bir @propertyözelliğe erişmek için kullanıyorsanız , ör.

class myClass:
    def __init__(a):
        self._a = a

    @property
    def a(self):
        return self._a

her * özelliğe erişmek için kullanın ! Bazı özellikleri kullanarak bazı özelliklere erişmek @propertyve diğer bazı özellikleri herkese açık bırakmak kötü bir uygulamadır. , örneğin bir erişgeç olmadan (bir alt çizgi olmadan yani ad) yapmayın

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b

    @property
    def a(self):
        return self.a

self.bHerkese açık olmasına rağmen burada açık bir erişimci olmadığını unutmayın .

Benzer şekilde sahip belirleyiciler (veya mutators ), kullanmaktan çekinmeyin @attribute.setterama tutarlı olmak! Örneğin;

class myClass:
    def __init__(a, b):
        self.a = a
        self.b = b 

    @a.setter
    def a(self, value):
        return self.a = value

Niyetini tahmin etmek benim için zor. Bir yandan her iki söylüyorsun ave bben teorik olarak erişim / mutasyona (get / set) hem izin verilmelidir böylece (adlarında hiçbir lider çizgi) kamuya açıktır. Ancak, yalnızcaa , bu da bana ayarlayamamam gerektiğini söyler b. Açık bir mutatör sağladığınızdan, açık erişimcinin ( @property) eksikliğinin bu değişkenlerden herhangi birine erişememem gerektiği anlamına mı geldiğini ya da sadece kullanımda tutumlu olduğunuzu bilmiyorum @property.

* İstisna, bazı değişkenleri açıkça erişilebilir veya değiştirilebilir hale getirmek istediğinizde, ancak her ikisini birden yapmamanız veya bir özniteliğe erişirken veya bir mutasyon geçirirken ek mantık gerçekleştirmek istediğiniz durumdur . Ben şahsen kullanıyorum @propertyve @attribute.setter(aksi takdirde kamu öznitelikleri için hiçbir açık acessors / mutasyon) budur.

Son olarak, PEP8 ve Google Stil Kılavuzu önerileri:

PEP8, Kalıtım için Tasarım :

Basit genel veri özellikleri için, karmaşık erişimci / mutasyon aracı yöntemleri olmadan yalnızca özellik adını göstermek en iyisidir . Basit bir veri özelliğinin işlevsel davranışı arttırması gerektiğini fark ederseniz, Python'un gelecekteki geliştirmelere kolay bir yol sağladığını unutmayın. Bu durumda, basit veri özniteliği erişim sözdiziminin arkasındaki işlevsel uygulamayı gizlemek için özellikleri kullanın.

Öte yandan, Google Stil Kılavuzu Python Dil Kurallarına / Özelliklerine göre göre öneri:

Normalde basit, hafif erişimci veya ayarlayıcı yöntemleri kullanacağınız verilere erişmek veya bu verileri ayarlamak için yeni koddaki özellikleri kullanın. Özellikler aşağıdakilerle oluşturulmalıdır:@property dekoratör .

Bu yaklaşımın artıları:

Basit öznitelik erişimi için açık alma ve ayarlama yöntemi çağrılarını ortadan kaldırarak okunabilirlik artırılır. Hesaplamaların tembel olmasını sağlar. Bir sınıfın arayüzünü korumanın Pythonic yolu olarak kabul edildi. Performans açısından, özelliklere izin vermek, doğrudan değişken erişim makul olduğunda önemsiz erişimci yöntemlerine ihtiyaç duymaz. Bu ayrıca gelecekte arabirimi bozmadan erişimci yöntemlerinin eklenmesine izin verir.

ve eksileri:

objectPython 2'den miras alınmalıdır. Operatör aşırı yüklenmesi gibi yan etkileri gizleyebilir. Alt sınıflar için kafa karıştırıcı olabilir.


1
Kesinlikle katılmıyorum. Nesnemde 15 özniteliğim varsa ve birisinin hesaplanmasını istiyorsam @property, geri kalanını da kullanmak @propertykötü bir karar gibi görünüyor.
Quelklef

Kabul edin, ancak yalnızca bu özel özellik hakkında ihtiyaç duyduğunuz belirli bir şey varsa @property(örneğin, bir özellik döndürmeden önce bazı özel mantık yürütmek). Aksi halde neden bir özelliği @properydiğerleriyle değil de süslersiniz?
Tomasz Bartkowiak

@Quelklef yazıdaki sidenote (yıldız işaretiyle işaretlenmiştir) bakın.
Tomasz Bartkowiak

Şey ... Sidenote'un bahsettiği şeylerden birini yapmıyorsanız, @propertybaşlamak için kullanmamalısınız , değil mi? Getteriniz return this._xve setteriniz ise this._x = new_x, o zaman kullanmak @propertybiraz saçmadır.
Quelklef

1
Hmm, belki. Şahsen iyi olmadığını söyleyebilirim --- tamamen gereksiz. Ama nereden geldiğini görebiliyorum. Sanırım yazınızı "kullanırken en önemli şey @propertytutarlı olmak " diyerek okudum .
Quelklef

-1

Sihirli yöntemleri __getattribute__ve kullanabilirsiniz __setattr__.

class MyClass:
    def __init__(self, attrvalue):
        self.myattr = attrvalue
    def __getattribute__(self, attr):
        if attr == "myattr":
            #Getter for myattr
    def __setattr__(self, attr):
        if attr == "myattr":
            #Setter for myattr

Bunun farkında olun __getattr__ve __getattribute__aynı değil. __getattr__yalnızca özellik bulunmadığında çağrılır.

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.