Memahami deskriptor __get__ dan __set__ dan Python


310

Saya mencoba untuk memahami apa itu deskriptor Python dan apa manfaatnya. Saya mengerti bagaimana mereka bekerja, tetapi inilah keraguan saya. Pertimbangkan kode berikut:

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. Mengapa saya membutuhkan kelas deskriptor?

  2. Apa instancedan ownerdi sini? (dalam __get__). Apa tujuan dari parameter ini?

  3. Bagaimana saya menelepon / menggunakan contoh ini?

Jawaban:


147

Deskriptor adalah bagaimana propertytipe Python diimplementasikan. Deskriptor hanya mengimplementasikan __get__,, __set__dll. Dan kemudian ditambahkan ke kelas lain dalam definisinya (seperti yang Anda lakukan di atas dengan kelas Suhu). Sebagai contoh:

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

Mengakses properti tempat Anda menugaskan deskriptor ( celsiusdalam contoh di atas) memanggil metode deskriptor yang sesuai.

instancein __get__adalah instance dari kelas (jadi di atas, __get__akan menerima temp, sedangkan ownerkelas dengan deskriptor (jadi itu akan Temperature).

Anda perlu menggunakan kelas deskriptor untuk merangkum logika yang mendukungnya. Dengan begitu, jika deskriptor digunakan untuk me-cache beberapa operasi mahal (misalnya), ia bisa menyimpan nilai pada dirinya sendiri dan bukan kelasnya.

Artikel tentang deskriptor dapat ditemukan di sini .

EDIT: Seperti yang ditunjukkan jchl dalam komentar, jika Anda hanya mencoba Temperature.celsius, instanceakan None.


6
Apa perbedaan antara selfdan instance?
Lemma Prism

2
'instance' dapat menjadi instance dari setiap kelas, self akan menjadi instance dari kelas yang sama.
TheBeginner

3
@LemmaPrism selfadalah turunan descriptor, instanceadalah turunan dari kelas (jika instantiated) descriptor ada di ( instance.__class__ is owner).
Tcll

Temperature.celsiusmemberi nilai 0.0sesuai dengan kode celsius = Celsius(). Deskriptor Celsius dipanggil, jadi instansinya memiliki nilai init yang 0.0ditetapkan ke atribut kelas Temperature, celsius.
Angel Salazar

109

Mengapa saya membutuhkan kelas deskriptor?

Ini memberi Anda kontrol ekstra atas cara kerja atribut. Jika Anda terbiasa dengan getter dan setter di Jawa, misalnya, maka itulah cara Python untuk melakukan itu. Satu keuntungan adalah tampilannya bagi pengguna seperti atribut (tidak ada perubahan dalam sintaksis). Jadi Anda bisa mulai dengan atribut biasa dan kemudian, ketika Anda perlu melakukan sesuatu yang mewah, beralihlah ke deskriptor.

Atribut hanyalah nilai yang bisa berubah. Deskriptor memungkinkan Anda mengeksekusi kode arbitrer saat membaca atau mengatur (atau menghapus) suatu nilai. Jadi Anda bisa membayangkan menggunakannya untuk memetakan atribut ke bidang dalam database, misalnya - semacam ORM.

Penggunaan lain mungkin menolak untuk menerima nilai baru dengan melemparkan pengecualian secara __set__efektif membuat "atribut" hanya baca.

Apa instancedan ownerdi sini? (dalam __get__). Apa tujuan dari parameter ini?

Ini cukup halus (dan alasan saya menulis jawaban baru di sini - saya menemukan pertanyaan ini sambil bertanya-tanya hal yang sama dan tidak menemukan jawaban yang ada yang hebat).

Deskriptor didefinisikan pada kelas, tetapi biasanya dipanggil dari instance. Ketika itu dipanggil dari instance keduanya instancedan ownerditetapkan (dan Anda dapat bekerja ownerdari instancesehingga sepertinya tidak ada gunanya). Tetapi ketika dipanggil dari kelas, hanya ownerdiatur - itulah sebabnya itu ada di sana.

Ini hanya diperlukan __get__karena itu satu-satunya yang dapat dipanggil di kelas. Jika Anda menetapkan nilai kelas Anda mengatur deskriptor itu sendiri. Demikian pula untuk penghapusan. Itu sebabnya ownertidak diperlukan di sana.

Bagaimana saya menelepon / menggunakan contoh ini?

Nah, inilah trik keren menggunakan kelas serupa:

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)

(Saya menggunakan Python 3; untuk python 2 Anda perlu memastikan divisi-divisi itu / 5.0dan / 9.0). Itu memberi:

100.0
32.0

Sekarang ada cara lain yang bisa dibilang lebih baik untuk mencapai efek yang sama dalam python (misalnya jika celsius adalah properti, yang merupakan mekanisme dasar yang sama tetapi menempatkan semua sumber di dalam kelas Temperatur), tetapi itu menunjukkan apa yang bisa dilakukan ...


2
Konversi salah: konversi harus C = 5 (F − 32) / 9, F = 32 + 9C / 5.
musiphil

1
Pastikan Anda memiliki satu objek Temperatur. Melakukan hal-hal berikut mengacaukan hal-hal. t1 = Suhu (190) cetak t1.celsius t1.celsius = 100 cetak t1.fahrenheit Sekarang ketika Anda memeriksa t.celcius dan t.fahrenheit, mereka juga akan dimodifikasi. t.celcius adalah 115 dan t.fahrenheit adalah 32. yang jelas-jelas salah. @Eric
Ishan Bhatt

1
@IshanBhatt: Saya pikir itu karena kesalahan yang ditunjukkan oleh musiphil di atas. Juga, bukan ini bukan jawaban saya
Eric

69

Saya mencoba memahami apa itu deskriptor Python dan apa manfaatnya.

Deskriptor adalah atribut kelas (seperti properti atau metode) dengan salah satu metode khusus berikut:

  • __get__ (metode deskriptor non-data, misalnya pada metode / fungsi)
  • __set__ (metode deskriptor data, misalnya pada instance properti)
  • __delete__ (metode deskriptor data)

Objek deskriptor ini dapat digunakan sebagai atribut pada definisi kelas objek lainnya. (Yaitu, mereka hidup di __dict__objek kelas.)

Objek deskriptor dapat digunakan untuk secara terprogram mengelola hasil pencarian putus-putus (mis. foo.descriptor) Dalam ekspresi normal, tugas, dan bahkan penghapusan.

Fungsi / metode, metode terikat, property, classmethod, dan staticmethodsemua menggunakan metode-metode khusus untuk kontrol bagaimana mereka diakses melalui pencarian putus-putus.

Sebuah deskripsi Data , seperti property, dapat memungkinkan untuk evaluasi malas atribut berdasarkan keadaan sederhana objek, sehingga contoh untuk menggunakan lebih sedikit memori daripada jika Anda Precomputed setiap atribut mungkin.

Deskriptor data lain, a member_descriptor, dibuat oleh __slots__, memungkinkan penghematan memori dengan memungkinkan kelas untuk menyimpan data dalam struktur data tuple-suka bisa berubah bukan lebih fleksibel tetapi memakan ruang __dict__.

Deskriptor non-data, biasanya instance, class, dan metode statis, mendapatkan argumen pertama implisit mereka (biasanya dinamai clsdan self, masing-masing) dari metode deskriptor non-data __get__,.

Sebagian besar pengguna Python hanya perlu mempelajari penggunaannya yang sederhana, dan tidak perlu mempelajari atau memahami implementasi deskriptor lebih lanjut.

Dalam Kedalaman: Apa Itu Penjelas?

Deskriptor adalah objek dengan salah satu metode berikut ( __get__,, __set__atau __delete__), yang dimaksudkan untuk digunakan melalui pencarian bertitik seolah-olah itu adalah atribut khas dari sebuah instance. Untuk objek pemilik obj_instance,, dengan descriptorobjek:

  • obj_instance.descriptormeminta
    descriptor.__get__(self, obj_instance, owner_class)pengembalian suatu value
    Ini adalah bagaimana semua metode dan getpada properti bekerja.

  • obj_instance.descriptor = valuememinta
    descriptor.__set__(self, obj_instance, value)pengembalian None
    Ini adalah cara setterkerja properti.

  • del obj_instance.descriptormeminta
    descriptor.__delete__(self, obj_instance)pengembalian None
    Ini adalah cara deleterkerja properti.

obj_instanceadalah instance yang kelasnya berisi instance objek deskriptor. selfadalah contoh dari deskriptor (mungkin hanya satu untuk kelas obj_instance)

Untuk mendefinisikan ini dengan kode, sebuah objek adalah deskriptor jika himpunan atributnya bersinggungan dengan salah satu atribut yang diperlukan:

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

Sebuah data Descriptor memiliki __set__dan / atau __delete__.
Sebuah Non-Data-Descriptor memiliki tidak __set__atau __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))

Contoh Objek Deskriptor Builtin:

  • classmethod
  • staticmethod
  • property
  • fungsi secara umum

Penjelas Non-Data

Kita bisa melihat itu classmethoddan staticmethodNon-Data-Deskriptor:

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

Keduanya hanya memiliki __get__metode:

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

Perhatikan bahwa semua fungsi juga Non-Data-Deskriptor:

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

Deskriptor data, property

Namun, propertyadalah deskriptor data:

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

Urutan Pencarian Bertitik

Ini adalah perbedaan penting , karena mereka memengaruhi urutan pencarian untuk pencarian putus-putus.

obj_instance.attribute
  1. Pertama di atas terlihat untuk melihat apakah atributnya adalah Data-Descriptor pada kelas instance,
  2. Jika tidak, akan terlihat apakah atributnya ada di obj_instance's __dict__, lalu
  3. akhirnya jatuh kembali ke Non-Data-Descriptor.

Konsekuensi dari urutan pencarian ini adalah Non-Data-Descriptors seperti fungsi / metode dapat ditimpa oleh instance .

Rekap dan Langkah Selanjutnya

Kami telah belajar bahwa deskriptor adalah obyek dengan salah __get__, __set__atau __delete__. Objek deskriptor ini dapat digunakan sebagai atribut pada definisi kelas objek lainnya. Sekarang kita akan melihat bagaimana mereka digunakan, menggunakan kode Anda sebagai contoh.


Analisis Kode dari Pertanyaan

Inilah kode Anda, diikuti dengan pertanyaan dan jawaban Anda untuk masing-masing:

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. Mengapa saya membutuhkan kelas deskriptor?

Deskriptor Anda memastikan Anda selalu memiliki float untuk atribut kelas ini Temperature, dan yang tidak dapat Anda gunakan deluntuk menghapus atribut:

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

Jika tidak, deskriptor Anda mengabaikan kelas pemilik dan instance pemilik, sebagai gantinya, menyimpan status dalam deskriptor. Anda bisa dengan mudah membagikan status di semua instance dengan atribut kelas sederhana (selama Anda selalu menetapkannya sebagai float ke kelas dan tidak pernah menghapusnya, atau merasa nyaman dengan pengguna kode Anda yang melakukannya):

class Temperature(object):
    celsius = 0.0

Ini memberi Anda perilaku yang persis sama dengan contoh Anda (lihat respons untuk pertanyaan 3 di bawah), tetapi menggunakan Python builtin ( property), dan akan dianggap lebih idiomatis:

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. Apa instance dan pemilik di sini? (di dapatkan ). Apa tujuan dari parameter ini?

instanceadalah contoh dari pemilik yang memanggil deskriptor. Pemilik adalah kelas di mana objek deskriptor digunakan untuk mengelola akses ke titik data. Lihat deskripsi metode khusus yang mendefinisikan deskriptor di sebelah paragraf pertama dari jawaban ini untuk nama variabel yang lebih deskriptif.

  1. Bagaimana saya menelepon / menggunakan contoh ini?

Ini sebuah demonstrasi:

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

Anda tidak dapat menghapus atribut:

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

Dan Anda tidak dapat menetapkan variabel yang tidak dapat dikonversi ke float:

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

Kalau tidak, yang Anda miliki di sini adalah keadaan global untuk semua instance, yang dikelola dengan menetapkan instance apa pun.

Cara yang diharapkan bahwa programmer Python paling berpengalaman akan mencapai hasil ini adalah dengan menggunakan propertydekorator, yang menggunakan deskriptor yang sama di bawah tenda, tetapi membawa perilaku ke dalam implementasi kelas pemilik (sekali lagi, sebagaimana didefinisikan di atas):

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)

Yang memiliki perilaku yang diharapkan sama persis dengan potongan kode asli:

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

Kesimpulan

Kami telah membahas atribut yang mendefinisikan deskriptor, perbedaan antara deskriptor data dan non-data, objek bawaan yang menggunakannya, dan pertanyaan spesifik tentang penggunaan.

Jadi sekali lagi, bagaimana Anda akan menggunakan contoh pertanyaan? Saya harap kamu tidak akan melakukannya. Saya harap Anda akan mulai dengan saran pertama saya (atribut kelas sederhana) dan beralih ke saran kedua (dekorator properti) jika Anda merasa perlu.


1
Bagus, saya paling banyak belajar dari jawaban ini (tentu saja belajar dari orang lain juga). Sebuah pertanyaan tentang pernyataan ini "Cara yang diharapkan oleh programmer Python paling berpengalaman untuk mencapai hasil ini ..." Kelas Temeperature yang Anda tetapkan sebelum dan sesudah pernyataan identik. Apakah saya ketinggalan apa yang Anda dapatkan di sini?
Yolo Voe

1
@ YoloVo tidak, itu benar, saya menambahkan beberapa verbiage tanda kurung untuk menekankan bahwa ini adalah pengulangan dari yang di atas.
Aaron Hall

1
Ini jawaban yang LUAR BIASA. Saya perlu membacanya beberapa kali lagi, tetapi saya merasa pemahaman saya tentang Python hanya meningkat beberapa tingkat
Lucas Young

20

Sebelum masuk ke detail deskriptor, penting untuk mengetahui bagaimana pencarian atribut dalam Python bekerja. Ini mengasumsikan bahwa kelas tidak memiliki metaclass dan bahwa itu menggunakan implementasi default __getattribute__(keduanya dapat digunakan untuk "menyesuaikan" perilaku).

Ilustrasi terbaik pencarian atribut (dalam Python 3.x atau untuk kelas gaya baru di Python 2.x) dalam hal ini adalah dari Memahami metaclasses Python (ionel's codelog) . Gambar digunakan :sebagai pengganti "pencarian atribut yang tidak dapat disesuaikan".

Ini merupakan pencarian dari atribut foobarpada instancedari Class:

masukkan deskripsi gambar di sini

Dua kondisi penting di sini:

  • Jika kelas instancememiliki entri untuk nama atribut dan memiliki __get__dan __set__.
  • Jika instancememiliki tidak ada entri untuk nama atribut tetapi kelas memiliki satu dan memiliki __get__.

Di situlah deskriptor masuk ke dalamnya:

  • Deskriptor data yang memiliki keduanya __get__dan __set__.
  • Deskriptor non-data yang hanya dimiliki __get__.

Dalam kedua kasus nilai yang dikembalikan __get__disebut dengan instance sebagai argumen pertama dan kelas sebagai argumen kedua.

Pencarian bahkan lebih rumit untuk pencarian atribut kelas (lihat misalnya Pencarian atribut kelas (di blog yang disebutkan di atas) ).

Mari kita beralih ke pertanyaan spesifik Anda:

Mengapa saya membutuhkan kelas deskriptor?

Dalam kebanyakan kasus, Anda tidak perlu menulis kelas deskriptor! Namun Anda mungkin adalah pengguna akhir yang sangat teratur. Misalnya fungsi. Fungsi adalah deskriptor, dengan begitu fungsi dapat digunakan sebagai metode yang selfsecara implisit dilewatkan sebagai argumen pertama.

def test_function(self):
    return self

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

Jika Anda melihat test_methodcontoh Anda akan mendapatkan kembali "metode terikat":

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

Demikian pula Anda juga dapat mengikat fungsi dengan menjalankan __get__metode secara manual (tidak benar-benar direkomendasikan, hanya untuk tujuan ilustrasi):

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

Anda bahkan dapat menyebutnya "metode terikat sendiri":

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

Perhatikan bahwa saya tidak memberikan argumen apa pun dan fungsinya mengembalikan contoh yang telah saya ikat!

Fungsinya adalah deskriptor non-data !

Beberapa contoh built-in dari deskriptor data adalah property. Mengabaikan getter, setterdan deleteryang propertydescriptor adalah (dari Descriptor HowTo Panduan "Properties" ):

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)

Karena itu data deskriptor itu dipanggil setiap kali Anda mencari "nama" dari propertydan itu hanya delegasi untuk fungsi dihiasi dengan @property, @name.setter, dan @name.deleter(jika ada).

Ada beberapa penjelas lainnya di perpustakaan standar, misalnya staticmethod, classmethod.

Titik deskriptor mudah (walaupun Anda jarang membutuhkannya): Kode umum abstrak untuk akses atribut. propertyadalah abstraksi untuk akses variabel instan, functionmenyediakan abstraksi untuk metode, staticmethodmenyediakan abstraksi untuk metode yang tidak memerlukan akses instance dan classmethodmenyediakan abstraksi untuk metode yang memerlukan akses kelas daripada akses instance (ini sedikit disederhanakan).

Contoh lain adalah properti kelas .

Satu contoh yang menyenangkan (menggunakan __set_name__dari Python 3.6) juga bisa menjadi properti yang hanya memungkinkan jenis tertentu:

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

Kemudian Anda bisa menggunakan deskriptor di kelas:

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

Dan bermain sedikit dengannya:

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

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

Atau "properti malas":

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

Ini adalah kasus di mana memindahkan logika ke deskriptor umum mungkin masuk akal, namun orang juga bisa menyelesaikannya (tapi mungkin dengan mengulang beberapa kode) dengan cara lain.

Apa instancedan ownerdi sini? (dalam __get__). Apa tujuan dari parameter ini?

Itu tergantung pada bagaimana Anda mencari atribut. Jika Anda mencari atribut pada instance maka:

  • argumen kedua adalah contoh di mana Anda mencari atribut
  • argumen ketiga adalah kelas instance

Jika Anda mencari atribut di kelas (dengan asumsi deskriptor didefinisikan pada kelas):

  • argumen kedua adalah None
  • argumen ketiga adalah kelas tempat Anda mencari atribut

Jadi pada dasarnya argumen ketiga diperlukan jika Anda ingin menyesuaikan perilaku ketika Anda melakukan pencarian tingkat kelas (karena instanceada None).

Bagaimana saya menelepon / menggunakan contoh ini?

Contoh Anda pada dasarnya adalah properti yang hanya memungkinkan nilai yang dapat dikonversi floatdan yang dibagikan di antara semua instance kelas (dan di kelas - meskipun orang hanya dapat menggunakan akses "baca" di kelas jika tidak, Anda akan mengganti instance deskriptor) ):

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

Itu sebabnya deskriptor umumnya menggunakan argumen kedua ( instance) untuk menyimpan nilai untuk menghindari berbagi. Namun dalam beberapa kasus berbagi nilai antar instance mungkin diinginkan (walaupun saya tidak bisa memikirkan skenario saat ini). Namun praktis tidak masuk akal untuk properti celsius pada kelas suhu ... kecuali mungkin sebagai murni kegiatan akademis.


tidak yakin apakah latar belakang transparan dari grafik benar-benar menderita dalam mode gelap harus dilaporkan sebagai bug ke stackoverflow.
Tshirtman

@ Tshirt saya pikir ini adalah masalah dengan gambar itu sendiri. Ini tidak sepenuhnya transparan ... Saya mengambilnya dari posting blog dan tidak tahu bagaimana membuatnya kembali dengan latar belakang transparan yang tepat. Sayang sekali terlihat sangat aneh dengan latar belakang yang gelap :(
MSeifert

9

Mengapa saya membutuhkan kelas deskriptor?

Terinspirasi oleh Fluent Python oleh Buciano Ramalho

Gambar Anda memiliki kelas seperti ini

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

Kita harus memvalidasi bobot dan harga untuk menghindari memberi mereka angka negatif, kita dapat menulis lebih sedikit kode jika kita menggunakan deskriptor sebagai proxy karena ini

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)

kemudian definisikan kelas LineItem seperti ini:

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

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

dan kita dapat memperluas kelas Kuantitas untuk melakukan validasi yang lebih umum


1
Use case yang menarik, karena menunjukkan cara menggunakan deskriptor untuk berinteraksi dengan beberapa contoh pengguna. Saya awalnya tidak mengerti poin penting: Atribut dengan deskriptor harus dibuat di namespace kelas (misalnya weight = Quantity(), tetapi nilai-nilai harus ditetapkan dalam contoh namespace hanya menggunakan self(misalnya self.weight = 4), kalau tidak atribut akan kembali ke nilai baru dan deskriptor akan dibuang. Bagus!
mnt

Saya tidak bisa mengerti satu hal. Anda mendefinisikan weight = Quantity()sebagai variabel kelas dan itu __get__dan __set__bekerja pada variabel instan. Bagaimana?
Teknokrat

0

Saya mencoba (dengan perubahan kecil seperti yang disarankan) kode dari jawaban Andrew Cooke. (Saya menjalankan python 2.7).

Kode:

#!/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)

Hasil:

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

Dengan Python sebelum 3, pastikan Anda subkelas dari objek yang akan membuat deskriptor berfungsi dengan benar karena get magic tidak berfungsi untuk kelas gaya lama.


1
Deskriptor hanya bekerja dengan kelas gaya baru. Untuk python 2.x ini berarti menurunkan kelas Anda dari "objek", yang merupakan default dalam Python 3.
Ivo van der Wijk

0

Anda akan melihat 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
Ini tidak menjawab pertanyaan atau memberikan informasi yang bermanfaat.
Sebastian Nielsen
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.