Apa cara pythonic untuk menggunakan getter dan setter?

341

Saya melakukannya seperti:

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

atau

object.property = value  
value = object.property

Saya baru mengenal Python, jadi saya masih menjelajahi sintaks, dan saya ingin saran untuk melakukan ini.

Jorge Guberte
sumber

Jawaban:

684

Coba ini: Properti Python

Kode sampel adalah:

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
Grissiom
sumber
2
Apakah setter untuk x dipanggil di initializer saat instantiating _x?
Casey
7
@Casey: Tidak. Referensi ke ._x(yang bukan properti, hanya atribut biasa) memotong propertypembungkus. Hanya referensi untuk .xmelalui property.
ShadowRanger
278

Apa cara pythonic untuk menggunakan getter dan setter?

Cara "Pythonic" bukanlah menggunakan "getter" dan "setters", tetapi menggunakan atribut polos, seperti yang diperlihatkan oleh pertanyaan, dan deluntuk menghapus (tetapi namanya diubah untuk melindungi yang tidak bersalah ... bawaan):

value = 'something'

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

Jika nanti, Anda ingin mengubah pengaturan dan mendapatkan, Anda dapat melakukannya tanpa harus mengubah kode pengguna, dengan menggunakan propertydekorator:

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

(Setiap dekorator menggunakan salinan dan memperbarui objek properti sebelumnya, jadi perhatikan bahwa Anda harus menggunakan nama yang sama untuk setiap set, mendapatkan, dan menghapus fungsi / metode.

Setelah mendefinisikan di atas, pengaturan asli, mendapatkan, dan menghapus kode adalah sama:

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

Anda harus menghindari ini:

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

Pertama, hal di atas tidak berfungsi, karena Anda tidak memberikan argumen untuk contoh bahwa properti akan diatur ke (biasanya self), yang akan menjadi:

class Obj:

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

Kedua, ini menggandakan tujuan dari dua metode khusus, __setattr__dan __getattr__.

Ketiga, kami juga memiliki fungsi setattrdan getattrbuiltin.

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

The @propertydekorator adalah untuk menciptakan getter dan setter.

Misalnya, kita bisa mengubah perilaku pengaturan untuk menempatkan batasan nilai yang ditetapkan:

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

Secara umum, kami ingin menghindari menggunakan propertydan hanya menggunakan atribut langsung.

Inilah yang diharapkan oleh pengguna Python. Mengikuti aturan yang paling tidak mengejutkan, Anda harus mencoba memberikan apa yang mereka harapkan kepada pengguna kecuali Anda memiliki alasan yang sangat kuat untuk melakukan hal sebaliknya.

Demonstrasi

Misalnya, kami membutuhkan atribut objek yang kami lindungi untuk menjadi bilangan bulat antara 0 dan 100 inklusif, dan mencegah penghapusannya, dengan pesan yang sesuai untuk memberi tahu pengguna tentang penggunaannya yang benar:

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

(Catatan yang __init__mengacu pada self.protected_valuetetapi metode properti merujuk self._protected_value. Ini adalah agar __init__menggunakan properti melalui API publik, memastikan itu "dilindungi".)

Dan penggunaan:

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

Apakah nama itu penting?

Ya mereka lakukan . .setterdan .deletermembuat salinan dari properti asli. Ini memungkinkan subclass untuk memodifikasi perilaku dengan benar tanpa mengubah perilaku pada induknya.

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

Agar ini berfungsi, Anda harus menggunakan nama masing-masing:

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

Saya tidak yakin di mana ini akan berguna, tetapi kasus penggunaannya adalah jika Anda menginginkan properti get, set, dan / atau delete-only. Mungkin lebih baik tetap pada properti semantik yang sama dengan nama yang sama.

Kesimpulan

Mulai dengan atribut sederhana.

Jika nanti Anda membutuhkan fungsionalitas di sekitar pengaturan, mendapatkan, dan menghapus, Anda dapat menambahkannya dengan dekorator properti.

Hindari fungsi bernama set_...dan get_...- itulah gunanya properti.

Aaron Hall
sumber
Selain menduplikasi fungsi yang tersedia, mengapa menulis setter dan getter Anda sendiri harus dihindari? Saya mengerti itu mungkin bukan cara Pythonic, tetapi apakah ada masalah serius yang mungkin ditemui orang sebaliknya?
user1350191
4
Dalam demo Anda, __init__metode ini merujuk self.protected_valuetetapi pengambil dan penunjuk mengacu padanya self._protected_value. Bisakah Anda jelaskan bagaimana cara kerjanya? Saya menguji kode Anda dan berfungsi sebagaimana mestinya - jadi ini bukan kesalahan ketik.
codeforester
2
@ kodeforester Saya berharap untuk menjawab jawaban saya sebelumnya, tetapi sampai saya bisa, komentar ini sudah cukup. Saya harap Anda dapat melihat bahwa itu menggunakan properti melalui api publik, memastikan itu "dilindungi". Tidak masuk akal untuk "melindunginya" dengan properti dan kemudian menggunakan api non-publik sebagai gantinya __init__?
Aaron Hall
2
Ya, @ Harun. Sekarang kita mengerti. Saya tidak menyadari self.protected_value = start_protected_valuesebenarnya memanggil fungsi setter; Saya pikir itu adalah tugas.
codeforester
1
imho ini harus menjadi jawaban yang diterima, jika saya mengerti benar python hanya mengambil titik berlawanan dibandingkan dengan misalnya java. Alih-alih menjadikan semuanya pribadi secara default dan menulis beberapa kode tambahan saat dibutuhkan secara publik dengan python, Anda dapat menjadikan semuanya publik dan menambah privasi nanti
idclev 463035818
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
Autoplektik
sumber
17

Menggunakan @propertydan @attribute.settermembantu Anda untuk tidak hanya menggunakan cara "pythonic" tetapi juga untuk memeriksa validitas atribut baik saat membuat objek maupun ketika mengubahnya.

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

Dengan ini, Anda sebenarnya 'menyembunyikan' _nameatribut dari pengembang klien dan juga melakukan pemeriksaan pada tipe properti nama. Perhatikan bahwa dengan mengikuti pendekatan ini bahkan selama inisiasi setter dipanggil. Begitu:

p = Person(12)

Akan mengarah ke:

Exception: Invalid value for name

Tapi:

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

Lihat @propertydekoratornya .

Kevin Little
sumber
34
Ini hanya jawaban tautan saja.
Aaron Hall
7
Bagaimana ini jawaban yang lengkap? Tautan bukan jawaban.
Andy_A̷n̷d̷y̷
Saya pikir ini adalah jawaban yang baik, karena dokumentasi di sana dengan jelas menyatakan bagaimana menggunakannya (dan akan tetap berlaku jika implementasi python berubah, dan mengarahkan OP ke metode yang disarankan oleh jawaban-er. @ Jean-FrançoisCorbett dengan jelas menyatakan 'bagaimana' itu adalah jawaban yang lengkap
HunnyBear
Bagaimanapun, jawaban ini tidak menambahkan apa pun yang berguna untuk jawaban lain dan idealnya harus dihapus.
Georgy
5

Anda dapat menggunakan accessors / mutators (yaitu @attr.setterdan @property) atau tidak, tetapi yang paling penting adalah konsisten!

Jika Anda menggunakan @propertyuntuk mengakses atribut, mis

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

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

gunakan untuk mengakses setiap atribut * ! Ini akan menjadi praktik yang buruk untuk mengakses beberapa atribut menggunakan @propertydan membiarkan beberapa properti lainnya publik (yaitu nama tanpa garis bawah) tanpa pengakses , misalnya jangan lakukan

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

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

Catatan yang self.btidak memiliki pengakses eksplisit di sini meskipun itu publik.

Demikian pula dengan setter (atau mutator ), jangan ragu untuk menggunakan @attribute.settertetapi konsisten! Ketika Anda melakukan mis

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

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

Sulit bagi saya untuk menebak niat Anda. Di satu sisi Anda mengatakan bahwa keduanya adan bpublik (tidak ada garis bawah terkemuka dalam nama mereka) jadi saya secara teoritis harus diizinkan untuk mengakses / bermutasi (dapatkan / atur) keduanya. Tapi kemudian Anda menentukan mutator eksplisit hanya untuk a, yang memberi tahu saya bahwa mungkin saya seharusnya tidak dapat mengatur b. Karena Anda telah menyediakan mutator eksplisit, saya tidak yakin apakah kurangnya accessor eksplisit ( @property) berarti saya seharusnya tidak dapat mengakses salah satu dari variabel-variabel tersebut atau Anda cukup hemat dalam menggunakan @property.

* Pengecualiannya adalah ketika Anda secara eksplisit ingin membuat beberapa variabel dapat diakses atau diubah tetapi tidak keduanya atau Anda ingin melakukan beberapa logika tambahan saat mengakses atau bermutasi atribut. Ini adalah ketika saya secara pribadi menggunakan @propertydan @attribute.setter(jika tidak tidak ada aksesor / mutator eksplisit untuk atribut publik).

Terakhir, saran PEP8 dan Google Style Guide:

PEP8, Merancang untuk Warisan mengatakan:

Untuk atribut data publik sederhana, itu terbaik hanya mengekspos nama atribut, tanpa metode accessor / mutator yang rumit . Perlu diingat bahwa Python menyediakan jalur yang mudah ke peningkatan di masa mendatang, jika Anda menemukan bahwa atribut data sederhana perlu menumbuhkan perilaku fungsional. Dalam hal itu, gunakan properti untuk menyembunyikan implementasi fungsional di belakang sintaks akses atribut data sederhana.

Di sisi lain, menurut Google Style Guide Aturan / Properti Bahasa Python rekomendasinya adalah untuk:

Gunakan properti dalam kode baru untuk mengakses atau mengatur data di mana Anda biasanya menggunakan metode pengakses atau penyetel yang sederhana dan ringan. Properti harus dibuat dengan @propertydekorator.

Kelebihan dari pendekatan ini:

Keterbacaan ditingkatkan dengan menghilangkan get eksplisit dan mengatur panggilan metode untuk akses atribut sederhana. Mengizinkan penghitungan menjadi malas. Dianggap sebagai cara Pythonic untuk mempertahankan antarmuka kelas. Dalam hal kinerja, memungkinkan properti mem-bypass yang memerlukan metode aksesor sepele ketika akses variabel langsung masuk akal. Ini juga memungkinkan metode accessor untuk ditambahkan di masa depan tanpa merusak antarmuka.

dan kontra:

Harus mewarisi dari objectdalam Python 2. Dapat menyembunyikan efek samping seperti operator overloading. Dapat membingungkan untuk subclass.

Tomasz Bartkowiak
sumber
1
Saya sangat tidak setuju. Jika saya memiliki 15 atribut pada objek saya, dan saya ingin satu untuk dihitung @property, membuat sisanya juga menggunakan @propertysepertinya keputusan yang buruk.
Quelklef
Setuju tetapi hanya jika ada sesuatu yang spesifik tentang atribut khusus ini yang Anda perlukan @property(misalnya, mengeksekusi beberapa logika khusus sebelum mengembalikan atribut). Kalau tidak, mengapa Anda mendekorasi satu atribut dengan yang @properylain?
Tomasz Bartkowiak
@Quelklef melihat sidenote di pos (ditandai dengan tanda bintang).
Tomasz Bartkowiak
Nah ... Jika Anda tidak melakukan salah satu hal yang disebutkan oleh sidenote, maka Anda seharusnya tidak menggunakannya @propertyuntuk memulainya, kan? Jika rajin rajin return this._xdan rajin rajin this._x = new_x, maka menggunakannya @propertysama sekali konyol.
Quelklef
1
Hmm, mungkin. Saya pribadi akan mengatakan itu tidak baik --- itu sepenuhnya berlebihan. Tapi saya bisa melihat dari mana Anda berasal. Saya kira saya baru saja membaca posting Anda yang mengatakan bahwa "hal terpenting ketika menggunakan @propertyadalah konsisten."
Quelklef
-1

Anda dapat menggunakan metode sulap __getattribute__dan __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

Sadarilah itu __getattr__dan __getattribute__tidak sama. __getattr__hanya dipanggil ketika atribut tidak ditemukan.

Pika sang Penyihir Paus
sumber