Contoh dunia nyata tentang cara menggunakan fitur properti dalam python?


142

Saya tertarik dengan cara menggunakan @propertyPython. Saya sudah membaca dokumen python dan contohnya di sana, menurut saya, hanyalah kode mainan:

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

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

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

Saya tidak tahu manfaat apa yang bisa saya dapatkan dari membungkus yang _xdiisi dengan dekorator properti. Mengapa tidak menerapkan saja sebagai:

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

Saya pikir, fitur properti mungkin berguna dalam beberapa situasi. Tapi ketika? Bisakah seseorang tolong beri saya beberapa contoh dunia nyata?

Terima kasih.


9
Ini adalah penjelasan terbaik dan terbersih yang saya temukan tentang dekorator properti [klik di sini ]
Sumudu

2
@Anubis dalam contoh terakhir di tautan yang Anda berikan, pengaturan c = Celsius (-500) tidak membuang ValueError, yang saya pikir tidak mencapai hasil yang diinginkan.
Sajuuk

Setuju dengan @Anubis. Diimplementasikan dengan benar di sini: python-course.eu/python3_properties.php
anon01

Jawaban:


91

Contoh lain adalah validasi / penyaringan atribut yang ditetapkan (memaksa mereka untuk berada dalam batas atau dapat diterima) dan evaluasi malas dari istilah yang kompleks atau berubah dengan cepat.

Perhitungan kompleks yang tersembunyi di balik atribut:

class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

Validasi:

class Pedometer(object)
    ...
    @property
    def stride_length(self):
        return self._stride_length

    @stride_length.setter
    def stride_length(self, value):
        if value > 10:
            raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
        else:
            self._stride_length = value

1
Saya suka contoh PDB_Calculator - hal-hal rumit disarikan, semuanya berfungsi dan pengguna dapat menikmati kesederhanaan!
Adam Kurkiewicz

2
mungkin, dari sudut pandang pro, ini adalah contoh yang sangat bagus. Tetapi, sebagai noobie, saya menemukan contoh ini sangat tidak efektif.
salahku

77

Satu use case sederhana adalah dengan menetapkan atribut instance read only, seperti yang Anda tahu memimpin nama variabel dengan satu garis bawah _xpada python biasanya berarti itu pribadi (penggunaan internal) tetapi kadang-kadang kita ingin dapat membaca atribut instance dan tidak menulis jadi kita bisa gunakan propertyuntuk ini:

>>> class C(object):

        def __init__(self, x):
            self._x = x

        @property
        def x(self):
            return self._x

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute

6
Satu masih dapat mengatur c._x, jika pengguna menginginkannya. Python sebenarnya tidak memiliki atribut pribadi.

20

Lihatlah artikel ini untuk penggunaan yang sangat praktis. Singkatnya, ini menjelaskan bagaimana dengan Python Anda biasanya dapat membuang metode pengambil / penyetel eksplisit, karena jika Anda membutuhkannya pada tahap tertentu Anda dapat menggunakan propertyuntuk implementasi yang mulus.


15

Satu hal yang saya gunakan untuk itu adalah caching nilai yang lambat untuk dicari, tetapi tidak berubah, disimpan dalam database. Ini bersifat umum untuk situasi apa pun di mana atribut Anda memerlukan perhitungan atau operasi panjang lainnya (mis. Pemeriksaan basis data, komunikasi jaringan) yang hanya ingin Anda lakukan sesuai permintaan.

class Model(object):

  def get_a(self):
    if not hasattr(self, "_a"):
      self._a = self.db.lookup("a")
    return self._a

  a = property(get_a)

Ini ada di aplikasi web di mana setiap tampilan halaman yang diberikan mungkin hanya memerlukan satu atribut tertentu dari jenis ini, tetapi objek yang mendasarinya sendiri mungkin memiliki beberapa atribut seperti itu - menginisialisasi mereka semua pada konstruksi akan sia-sia, dan properti memungkinkan saya untuk fleksibel di mana atributnya malas dan mana yang tidak.


1
Tidak bisakah kamu menggunakan @cached_propertyini?
adarsh

@adarsh ​​- Kedengarannya menarik. Dimanakah itu?
detly

Saya telah menggunakannya tetapi saya lupa bahwa itu bukan built-in, tetapi Anda dapat menggunakannya dengan ini, pypi.python.org/pypi/cached-property/0.1.5
adarsh

2
Menarik. Saya pikir ini pertama kali diterbitkan setelah jawaban ini, tetapi siapa pun yang membaca ini mungkin harus menggunakannya sebagai gantinya.
Detly

10

Membaca melalui jawaban dan komentar, tema utama tampaknya adalah jawaban yang sepertinya tidak ada contoh sederhana namun bermanfaat. Saya telah memasukkan yang sangat sederhana di sini yang menunjukkan penggunaan sederhana @propertydekorator. Ini adalah kelas yang memungkinkan pengguna untuk menentukan dan mendapatkan pengukuran jarak menggunakan berbagai unit yang berbeda, yaitu in_feetatau in_metres.

class Distance(object):
    def __init__(self):
        # This private attribute will store the distance in metres
        # All units provided using setters will be converted before
        # being stored
        self._distance = 0.0

    @property
    def in_metres(self):
        return self._distance

    @in_metres.setter
    def in_metres(self, val):
        try:
            self._distance = float(val)
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_feet(self):
        return self._distance * 3.2808399

    @in_feet.setter
    def in_feet(self, val):
        try:
            self._distance = float(val) / 3.2808399
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_parsecs(self):
        return self._distance * 3.24078e-17

    @in_parsecs.setter
    def in_parsecs(self, val):
        try:
            self._distance = float(val) / 3.24078e-17
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

Pemakaian:

>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14

bagi saya pribadi, contoh terbaik pengambil / pemukim adalah menunjukkan kepada orang-orang jenis perubahan yang perlu Anda lakukan nanti, tetapi jelas, itu membutuhkan waktu sedikit lebih lama.
dtc

7

Properti hanyalah abstraksi di sekitar bidang yang memberi Anda lebih banyak kontrol pada cara-cara bidang tertentu dapat dimanipulasi dan untuk melakukan perhitungan middleware. Beberapa penggunaan yang terlintas dalam pikiran adalah validasi dan inisialisasi sebelumnya serta pembatasan akses

@property
def x(self):
    """I'm the 'x' property."""
    if self._x is None:
        self._x = Foo()

    return self._x

6

Ya, untuk contoh asli yang diposkan, properti akan bekerja persis sama dengan hanya memiliki variabel instan 'x'.

Ini adalah hal terbaik tentang properti python. Dari luar, mereka bekerja persis seperti variabel instan! Yang memungkinkan Anda untuk menggunakan variabel instan dari luar kelas.

Ini berarti contoh pertama Anda sebenarnya bisa menggunakan variabel instan. Jika semuanya berubah, dan kemudian Anda memutuskan untuk mengubah implementasi Anda dan properti berguna, antarmuka ke properti akan tetap sama dari kode di luar kelas. Perubahan dari variabel instan ke properti tidak berdampak pada kode di luar kelas.

Banyak bahasa dan kursus pemrograman lain akan menginstruksikan bahwa seorang programmer tidak boleh mengekspos variabel instan, dan sebagai gantinya menggunakan 'getter' dan 'setter' untuk nilai apa pun untuk diakses dari luar kelas, bahkan kasus sederhana seperti dikutip dalam pertanyaan.

Kode di luar kelas dengan banyak bahasa (misalnya Java) digunakan

object.get_i()
    #and
object.set_i(value)

#in place of (with python)
object.i
    #and 
object.i = value

Dan ketika mengimplementasikan kelas ada banyak 'getter' dan 'setter' yang melakukan persis seperti contoh pertama Anda: mereplikasi variabel instance sederhana. Getter dan setter ini diperlukan karena jika implementasi kelas berubah, semua kode di luar kelas perlu diubah. Tapi properti python memungkinkan kode di luar kelas sama dengan variabel instan. Jadi kode di luar kelas tidak perlu diubah jika Anda menambahkan properti, atau memiliki variabel instance sederhana. Jadi tidak seperti kebanyakan bahasa Berorientasi Objek, untuk contoh sederhana Anda, Anda dapat menggunakan variabel instan alih-alih 'getter' dan 'setter' yang benar-benar tidak diperlukan, aman dalam pengetahuan bahwa jika Anda mengubah ke properti di masa depan, kode menggunakan kelasmu tidak perlu berubah.

Ini berarti Anda hanya perlu membuat properti jika ada perilaku kompleks, dan untuk kasus sederhana yang sangat umum di mana, seperti yang dijelaskan dalam pertanyaan, hanya variabel instan yang diperlukan, Anda bisa menggunakan variabel instan.


6

fitur bagus lain dari properti dibandingkan menggunakan setter dan getter sehingga mereka memungkinkan Anda untuk terus menggunakan OP = operator (misalnya + =, - =, * = dll) pada atribut Anda sambil tetap mempertahankan validasi, kontrol akses, caching, dll yang setter dan getter akan memasok.

misalnya jika Anda menulis kelas Persondengan setter setage(newage), dan pengambil getage(), maka untuk menambah usia Anda harus menulis:

bob = Person('Robert', 25)
bob.setage(bob.getage() + 1)

tetapi jika Anda membuat ageproperti, Anda bisa menulis yang jauh lebih bersih:

bob.age += 1

5

Jawaban singkat untuk pertanyaan Anda, adalah bahwa dalam contoh Anda, tidak ada manfaatnya. Anda mungkin harus menggunakan formulir yang tidak melibatkan properti.

Alasan properti ada, adalah bahwa jika kode Anda berubah di masa mendatang, dan Anda tiba-tiba perlu berbuat lebih banyak dengan data Anda: nilai cache, melindungi akses, meminta beberapa sumber daya eksternal ... apa pun, Anda dapat dengan mudah memodifikasi kelas Anda untuk menambahkan getter dan setters untuk data tanpa mengubah antarmuka, sehingga Anda tidak perlu menemukan di mana-mana dalam kode Anda di mana data itu diakses dan mengubahnya juga.


4

Sesuatu yang banyak orang tidak perhatikan pada awalnya adalah Anda dapat membuat subclass properti Anda sendiri. Ini saya temukan sangat berguna untuk mengekspos atribut objek hanya baca atau atribut yang dapat Anda baca dan tulis tetapi tidak menghapus. Ini juga merupakan cara terbaik untuk membungkus fungsionalitas seperti pelacakan modifikasi ke bidang objek.

class reader(property):
    def __init__(self, varname):
        _reader = lambda obj: getattr(obj, varname)
        super(reader, self).__init__(_reader)

class accessor(property):
    def __init__(self, varname, set_validation=None):
        _reader = lambda obj: getattr(obj, varname)
        def _writer(obj, value):
            if set_validation is not None:
               if set_validation(value):
                  setattr(obj, varname, value)
        super(accessor, self).__init__(_reader, _writer)

#example
class MyClass(object):
   def __init__(self):
     self._attr = None

   attr = reader('_attr')

Saya suka ini. Apakah saya membaca ini dengan benar karena pembaca hanya membaca ketika accessor dibaca / ditulis tanpa kemampuan penghapusan? Bagaimana Anda menambahkan validasi data? Saya cukup baru untuk Python tapi saya pikir mungkin ada cara untuk menambahkan panggilan balik ke attr = reader('_attr')baris atau semacam prechecking seperti attr = if self.__isValid(value): reader('_attr'). Saran?
Gabe Spradlin

Maaf baru sadar saya menanyakan validasi data untuk variabel hanya baca. Tetapi jelas ini hanya akan berlaku untuk bagian setter dari kelas accessor. Jadi ubah attr = reader('_attr')ke attr = accessor('_attr'). Terima kasih
Gabe Spradlin

Anda benar bahwa jika Anda ingin validasi maka Anda akan menambahkan fungsi untuk memvalidasi dan meningkatkan Pengecualian jika tidak valid (atau perilaku apa pun yang Anda suka termasuk tidak melakukan apa-apa) ke init . Saya memodifikasi di atas dengan satu pola yang mungkin. Validator harus mengembalikan Benar | Salah untuk memandu apakah set terjadi atau tidak.
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.