[ TL; DR? Anda dapat melewati bagian akhir untuk contoh kode .]
Saya sebenarnya lebih suka menggunakan idiom yang berbeda, yang sedikit terlibat untuk digunakan sebagai salah satu, tetapi bagus jika Anda memiliki kasus penggunaan yang lebih kompleks.
Sedikit latar belakang terlebih dahulu.
Properti berguna karena memungkinkan kita menangani pengaturan dan mendapatkan nilai secara terprogram tetapi tetap memungkinkan atribut diakses sebagai atribut. Kita dapat mengubah 'menjadikan' menjadi 'perhitungan' (pada dasarnya) dan kita dapat mengubah 'set' menjadi 'acara'. Jadi katakanlah kita memiliki kelas berikut, yang saya kodekan dengan getter dan setter seperti Java.
class Example(object):
def __init__(self, x=None, y=None):
self.x = x
self.y = y
def getX(self):
return self.x or self.defaultX()
def getY(self):
return self.y or self.defaultY()
def setX(self, x):
self.x = x
def setY(self, y):
self.y = y
def defaultX(self):
return someDefaultComputationForX()
def defaultY(self):
return someDefaultComputationForY()
Anda mungkin bertanya-tanya mengapa saya tidak menelepon defaultX
dan defaultY
menggunakan __init__
metode objek. Alasannya adalah untuk kasus kami, saya ingin mengasumsikan bahwa someDefaultComputation
metode mengembalikan nilai yang bervariasi dari waktu ke waktu, katakan cap waktu, dan setiap kali x
(atau y
) tidak disetel (di mana, untuk tujuan contoh ini, "tidak disetel" berarti "set ke Tidak Ada ") Saya ingin nilai perhitungan default x
(atau y
).
Jadi ini lumpuh karena sejumlah alasan yang dijelaskan di atas. Saya akan menulis ulang menggunakan properti:
class Example(object):
def __init__(self, x=None, y=None):
self._x = x
self._y = y
@property
def x(self):
return self.x or self.defaultX()
@x.setter
def x(self, value):
self._x = value
@property
def y(self):
return self.y or self.defaultY()
@y.setter
def y(self, value):
self._y = value
# default{XY} as before.
Apa yang telah kita peroleh? Kami memperoleh kemampuan untuk menyebut atribut ini sebagai atribut meskipun, di balik layar, kami akhirnya menjalankan metode.
Tentu saja kekuatan sebenarnya dari properti adalah kita umumnya ingin metode ini melakukan sesuatu selain hanya mendapatkan dan menetapkan nilai (jika tidak ada gunanya menggunakan properti). Saya melakukan ini dalam contoh rajin saya. Kami pada dasarnya menjalankan badan fungsi untuk mengambil default kapan pun nilainya tidak ditetapkan. Ini adalah pola yang sangat umum.
Tapi apa yang kita kehilangan, dan apa yang tidak bisa kita lakukan?
Gangguan utama, dalam pandangan saya, adalah bahwa jika Anda mendefinisikan pengambil (seperti yang kita lakukan di sini), Anda juga harus menentukan pembuat. [1] Itu suara ekstra yang mengacaukan kode.
Gangguan lain adalah bahwa kita masih harus menginisialisasi x
dan y
nilai - nilai di __init__
. (Yah, tentu saja kita bisa menambahkannya menggunakan setattr()
tapi itu kode tambahan.)
Ketiga, tidak seperti pada contoh mirip-Java, getter tidak dapat menerima parameter lain. Sekarang saya dapat mendengar Anda berkata, ya, jika itu mengambil parameter, itu bukan pengambil! Dalam pengertian resmi, itu benar. Tetapi dalam arti praktis tidak ada alasan kita tidak seharusnya dapat parameter parameter atribut bernama - seperti x
- dan mengatur nilainya untuk beberapa parameter tertentu.
Alangkah baiknya jika kita bisa melakukan sesuatu seperti:
e.x[a,b,c] = 10
e.x[d,e,f] = 20
sebagai contoh. Cara terdekat yang bisa kita dapatkan adalah mengganti tugas untuk menyiratkan beberapa semantik khusus:
e.x = [a,b,c,10]
e.x = [d,e,f,30]
dan tentu saja memastikan bahwa setter kami tahu cara mengekstraksi tiga nilai pertama sebagai kunci ke kamus dan mengatur nilainya ke angka atau sesuatu.
Tetapi bahkan jika kita melakukan itu, kita masih tidak dapat mendukungnya dengan properti karena tidak ada cara untuk mendapatkan nilai karena kita tidak bisa melewatkan parameter sama sekali kepada pengambil. Jadi kami harus mengembalikan semuanya, memperkenalkan asimetri.
Para pengambil / penyetel gaya Java tidak membiarkan kami menangani ini, tapi kami kembali membutuhkan pengambil / penentu.
Dalam benak saya apa yang benar-benar kita inginkan adalah sesuatu yang menangkap persyaratan berikut:
Pengguna mendefinisikan hanya satu metode untuk atribut yang diberikan dan dapat menunjukkan di sana apakah atribut tersebut hanya baca atau baca-tulis. Properti gagal tes ini jika atribut dapat ditulisi.
Tidak perlu bagi pengguna untuk mendefinisikan variabel tambahan yang mendasari fungsi, jadi kami tidak memerlukan __init__
atau setattr
dalam kode. Variabel hanya ada oleh fakta kami telah membuat atribut gaya baru ini.
Kode default apa pun untuk atribut dijalankan dalam tubuh metode itu sendiri.
Kita dapat mengatur atribut sebagai atribut dan merujuknya sebagai atribut.
Kita dapat parameterisasi atribut.
Dalam hal kode, kami ingin cara menulis:
def x(self, *args):
return defaultX()
dan kemudian dapat melakukan:
print e.x -> The default at time T0
e.x = 1
print e.x -> 1
e.x = None
print e.x -> The default at time T1
Dan seterusnya.
Kami juga ingin cara untuk melakukan ini untuk kasus khusus dari atribut parameterable, tetapi masih memungkinkan kasus penetapan default berfungsi. Anda akan melihat bagaimana saya menangani ini di bawah.
Sekarang ke intinya (yay! Intinya!). Solusi saya datang untuk ini adalah sebagai berikut.
Kami membuat objek baru untuk menggantikan gagasan tentang properti. Objek dimaksudkan untuk menyimpan nilai set variabel untuk itu, tetapi juga mempertahankan pegangan pada kode yang tahu cara menghitung default. Tugasnya adalah menyimpan set value
atau menjalankannya method
jika nilai itu tidak disetel.
Sebut saja sebuah UberProperty
.
class UberProperty(object):
def __init__(self, method):
self.method = method
self.value = None
self.isSet = False
def setValue(self, value):
self.value = value
self.isSet = True
def clearValue(self):
self.value = None
self.isSet = False
Saya berasumsi di method
sini adalah metode kelas, value
adalah nilai dari UberProperty
, dan saya telah menambahkan isSet
karena None
mungkin nilai nyata dan ini memungkinkan kita cara yang bersih untuk menyatakan memang ada "tidak ada nilai". Cara lain adalah semacam penjaga.
Ini pada dasarnya memberi kita sebuah objek yang dapat melakukan apa yang kita inginkan, tetapi bagaimana kita sebenarnya meletakkannya di kelas kita? Nah, properti menggunakan dekorator; kenapa kita tidak bisa? Mari kita lihat bagaimana kelihatannya (mulai sekarang saya akan tetap menggunakan hanya satu 'atribut', x
).
class Example(object):
@uberProperty
def x(self):
return defaultX()
Ini sebenarnya belum berfungsi, tentu saja. Kita harus menerapkan uberProperty
dan memastikan itu menangani keduanya mendapat dan set.
Mari kita mulai dengan get.
Upaya pertama saya adalah membuat objek UberProperty baru dan mengembalikannya:
def uberProperty(f):
return UberProperty(f)
Saya dengan cepat menemukan, tentu saja, bahwa ini tidak berfungsi: Python tidak pernah mengikat callable ke objek dan saya membutuhkan objek untuk memanggil fungsi. Bahkan membuat dekorator di kelas tidak bekerja, karena meskipun sekarang kita memiliki kelas, kita masih tidak memiliki objek untuk dikerjakan.
Jadi kita harus bisa berbuat lebih banyak di sini. Kita tahu bahwa suatu metode hanya perlu diwakili satu kali, jadi mari kita lanjutkan dan simpan dekorator kita, tetapi modifikasi UberProperty
hanya menyimpan method
referensi:
class UberProperty(object):
def __init__(self, method):
self.method = method
Itu juga tidak bisa dipanggil, jadi saat ini tidak ada yang berfungsi.
Bagaimana kita melengkapi gambar? Nah, apa yang kita dapatkan ketika kita membuat kelas contoh menggunakan dekorator baru kita:
class Example(object):
@uberProperty
def x(self):
return defaultX()
print Example.x <__main__.UberProperty object at 0x10e1fb8d0>
print Example().x <__main__.UberProperty object at 0x10e1fb8d0>
dalam kedua kasus kami mendapatkan kembali UberProperty
yang tentu saja tidak bisa dipanggil, jadi ini tidak banyak berguna.
Yang kita butuhkan adalah beberapa cara untuk secara dinamis mengikat UberProperty
instance yang dibuat oleh dekorator setelah kelas telah dibuat ke objek kelas sebelum objek tersebut dikembalikan ke pengguna untuk digunakan. Um, ya, itu __init__
panggilan telepon, kawan.
Mari kita tuliskan apa yang kita inginkan hasil temuan kita menjadi yang pertama. Kami mengikat sebuah UberProperty
contoh, jadi hal yang jelas untuk dikembalikan adalah BoundUberProperty. Di sinilah kita akan benar-benar mempertahankan status x
atribut.
class BoundUberProperty(object):
def __init__(self, obj, uberProperty):
self.obj = obj
self.uberProperty = uberProperty
self.isSet = False
def setValue(self, value):
self.value = value
self.isSet = True
def getValue(self):
return self.value if self.isSet else self.uberProperty.method(self.obj)
def clearValue(self):
del self.value
self.isSet = False
Sekarang kita representasi; bagaimana cara mendapatkan ini ke suatu objek? Ada beberapa pendekatan, tetapi yang termudah untuk dijelaskan hanya menggunakan __init__
metode untuk melakukan pemetaan itu. Pada saat __init__
ini disebut dekorator kami telah berjalan, jadi hanya perlu melihat melalui objek __dict__
dan memperbarui atribut mana pun di mana nilai atribut adalah tipe UberProperty
.
Sekarang, properti-uber itu keren dan kita mungkin ingin menggunakannya banyak, jadi masuk akal untuk membuat kelas dasar yang melakukan ini untuk semua subclass. Saya pikir Anda tahu apa yang akan disebut kelas dasar.
class UberObject(object):
def __init__(self):
for k in dir(self):
v = getattr(self, k)
if isinstance(v, UberProperty):
v = BoundUberProperty(self, v)
setattr(self, k, v)
Kami menambahkan ini, mengubah contoh kami untuk mewarisi UberObject
, dan ...
e = Example()
print e.x -> <__main__.BoundUberProperty object at 0x104604c90>
Setelah dimodifikasi x
menjadi:
@uberProperty
def x(self):
return *datetime.datetime.now()*
Kami dapat menjalankan tes sederhana:
print e.x.getValue()
print e.x.getValue()
e.x.setValue(datetime.date(2013, 5, 31))
print e.x.getValue()
e.x.clearValue()
print e.x.getValue()
Dan kami mendapatkan hasil yang kami inginkan:
2013-05-31 00:05:13.985813
2013-05-31 00:05:13.986290
2013-05-31
2013-05-31 00:05:13.986310
(Wah, saya bekerja lembur.)
Perhatikan bahwa saya telah menggunakan getValue
, setValue
dan clearValue
di sini. Ini karena saya belum menautkan cara untuk mengembalikannya secara otomatis.
Tetapi saya pikir ini adalah tempat yang baik untuk berhenti untuk saat ini, karena saya mulai lelah. Anda juga dapat melihat bahwa fungsionalitas inti yang kami inginkan ada di tempat; sisanya adalah ganti jendela. Ganti kegunaan jendela penting, tapi itu bisa menunggu sampai saya memiliki perubahan untuk memperbarui posting.
Saya akan menyelesaikan contoh di posting berikutnya dengan membahas hal-hal ini:
Kita perlu memastikan bahwa UberObject __init__
selalu dipanggil oleh subclass.
- Jadi kita memaksanya dipanggil di suatu tempat atau kita mencegahnya dari diterapkan.
- Kami akan melihat bagaimana melakukan ini dengan metaclass.
Kita perlu memastikan bahwa kita menangani kasus umum di mana seseorang 'alias' berfungsi untuk sesuatu yang lain, seperti:
class Example(object):
@uberProperty
def x(self):
...
y = x
Kami harus e.x
kembali e.x.getValue()
secara default.
- Apa yang sebenarnya akan kita lihat adalah ini adalah salah satu area di mana model gagal.
- Ternyata kita akan selalu perlu menggunakan pemanggilan fungsi untuk mendapatkan nilai.
- Tapi kita bisa membuatnya tampak seperti pemanggilan fungsi biasa dan menghindari keharusan untuk menggunakannya
e.x.getValue()
. (Melakukan ini sudah jelas, jika Anda belum memperbaikinya.)
Kita perlu mendukung pengaturan e.x directly
, seperti pada e.x = <newvalue>
. Kita juga bisa melakukan ini di kelas induk, tetapi kita harus memperbarui __init__
kode kita untuk menanganinya.
Akhirnya, kami akan menambahkan atribut parameter. Seharusnya cukup jelas bagaimana kita akan melakukan ini juga.
Berikut kode yang ada hingga sekarang:
import datetime
class UberObject(object):
def uberSetter(self, value):
print 'setting'
def uberGetter(self):
return self
def __init__(self):
for k in dir(self):
v = getattr(self, k)
if isinstance(v, UberProperty):
v = BoundUberProperty(self, v)
setattr(self, k, v)
class UberProperty(object):
def __init__(self, method):
self.method = method
class BoundUberProperty(object):
def __init__(self, obj, uberProperty):
self.obj = obj
self.uberProperty = uberProperty
self.isSet = False
def setValue(self, value):
self.value = value
self.isSet = True
def getValue(self):
return self.value if self.isSet else self.uberProperty.method(self.obj)
def clearValue(self):
del self.value
self.isSet = False
def uberProperty(f):
return UberProperty(f)
class Example(UberObject):
@uberProperty
def x(self):
return datetime.datetime.now()
[1] Saya mungkin ketinggalan tentang apakah ini masih terjadi.