Menggunakan @ properti versus getter dan setter


728

Inilah pertanyaan murni desain khusus Python:

class MyClass(object):
    ...
    def get_my_attr(self):
        ...

    def set_my_attr(self, value):
        ...

dan

class MyClass(object):
    ...        
    @property
    def my_attr(self):
        ...

    @my_attr.setter
    def my_attr(self, value):
        ...

Python memungkinkan kita melakukannya dengan cara apa pun. Jika Anda akan merancang program Python, pendekatan mana yang akan Anda gunakan dan mengapa?

Jawaban:


613

Lebih suka properti . Untuk itulah mereka ada di sana.

Alasannya adalah bahwa semua atribut bersifat publik dalam Python. Nama awal dengan satu atau dua garis bawah hanyalah peringatan bahwa atribut yang diberikan adalah detail implementasi yang mungkin tidak tetap sama di versi kode yang akan datang. Itu tidak mencegah Anda untuk benar-benar mendapatkan atau mengatur atribut itu. Oleh karena itu, akses atribut standar adalah cara normal, Pythonic, yah, mengakses atribut.

Keuntungan dari properti adalah sintaksis identik dengan akses atribut, sehingga Anda dapat mengubah dari satu ke yang lain tanpa ada perubahan pada kode klien. Anda bahkan dapat memiliki satu versi kelas yang menggunakan properti (misalnya, untuk kode-kontrak-atau debug) dan yang tidak untuk produksi, tanpa mengubah kode yang menggunakannya. Pada saat yang sama, Anda tidak harus menulis getter dan setter untuk semua kalau-kalau Anda mungkin perlu mengontrol akses lebih baik nanti.


90
Nama atribut dengan garis bawah ganda ditangani secara khusus oleh Python; itu bukan hanya konvensi belaka. Lihat docs.python.org/py3k/tutorial/classes.html#private-variables
6502

63
Mereka ditangani secara berbeda, tetapi itu tidak menghalangi Anda untuk mengaksesnya. PS: 30 C0 M
hati

4
dan karena karakter "@" jelek dalam kode python, dan dekorator dereferencing memberikan perasaan yang sama seperti kode spaghetti.
Berry Tsakala

18
Saya tidak setuju. Bagaimana kode terstruktur sama dengan kode spageti? Python adalah bahasa yang indah. Tetapi akan lebih baik dengan dukungan yang lebih baik untuk hal-hal sederhana seperti enkapsulasi yang tepat dan kelas terstruktur.

69
Sementara saya setuju dalam banyak kasus, berhati-hatilah menyembunyikan metode lambat di balik dekorator @ properti. Pengguna API Anda mengharapkan akses properti berkinerja seperti akses variabel, dan menyimpang terlalu jauh dari harapan itu dapat membuat API Anda tidak nyaman untuk digunakan.
defrex

153

Dalam Python Anda tidak menggunakan getter atau setter atau properti hanya untuk bersenang-senang. Pertama-tama Anda hanya menggunakan atribut dan kemudian, hanya jika diperlukan, akhirnya bermigrasi ke properti tanpa harus mengubah kode menggunakan kelas Anda.

Memang ada banyak kode dengan ekstensi .py yang menggunakan getter dan setter dan kelas inheritance dan pointless di mana-mana di mana misalnya tuple sederhana akan dilakukan, tapi itu kode dari orang yang menulis dalam C ++ atau Java menggunakan Python.

Itu bukan kode Python.


46
@ 6502, ketika Anda mengatakan "[...] kelas tidak berguna di mana-mana di mana misalnya tuple sederhana akan dilakukan": keuntungan dari sebuah kelas daripada tuple, adalah bahwa instance kelas menyediakan nama eksplisit untuk mengakses bagian-bagiannya, sementara tupel tidak . Nama-nama lebih baik pada keterbacaan dan menghindari kesalahan, daripada tuple subscript, terutama ketika ini akan dilewatkan di luar modul saat ini.
Hibou57

15
@ Hibou57: Saya tidak mengatakan kelas tidak berguna. Tetapi terkadang tuple lebih dari cukup. Namun masalahnya adalah siapa yang berasal dari mengatakan Java atau C ++ tidak punya pilihan selain membuat kelas untuk semuanya karena kemungkinan lain hanya mengganggu untuk digunakan dalam bahasa tersebut. Gejala khas lain dari pemrograman Java / C ++ menggunakan Python adalah membuat kelas abstrak dan hierarki kelas kompleks tanpa alasan di mana dalam Python Anda bisa menggunakan kelas independen berkat mengetik bebek.
6502

39
@ Hibou57 untuk itu Anda juga dapat menggunakan namedtuple: doughellmann.com/PyMOTW/collections/namedtuple.html
hugo24

5
@JonathonReinhart: ADA di perpustakaan standar sejak 2.6 ... lihat docs.python.org/2/library/collections.html
6502

1
Ketika "akhirnya bermigrasi ke properti jika diperlukan", sangat mungkin Anda memecahkan kode menggunakan kelas Anda. Properti sering memberlakukan pembatasan - kode apa pun yang tidak mengharapkan pembatasan ini akan rusak segera setelah Anda memperkenalkannya.
yaccob

118

Menggunakan properti memungkinkan Anda mulai dengan akses atribut normal dan kemudian mencadangkannya dengan getter dan setter sesudahnya sebagaimana diperlukan .


3
@GregKrsak Sepertinya aneh karena itu. "Hal yang disetujui orang dewasa" adalah meme python dari sebelum properti ditambahkan. Itu adalah respon stok untuk orang-orang yang mengeluh tentang kurangnya pengubah akses. Ketika properti ditambahkan, tiba-tiba enkapsulasi menjadi diinginkan. Hal yang sama terjadi dengan kelas dasar abstrak. "Python selalu berperang dengan pemecahan enkapsulasi. Kebebasan adalah perbudakan. Lambdas hanya cocok pada satu baris."
johncip

71

Jawaban singkatnya adalah: properti menang dengan mudah . Selalu.

Kadang-kadang ada kebutuhan untuk getter dan setter, tetapi bahkan kemudian, saya akan "menyembunyikan" mereka ke dunia luar. Ada banyak cara untuk melakukan hal ini dengan Python ( getattr, setattr, __getattribute__, dll ..., tapi yang sangat ringkas dan bersih adalah:

def set_email(self, value):
    if '@' not in value:
        raise Exception("This doesn't look like an email address.")
    self._email = value

def get_email(self):
    return self._email

email = property(get_email, set_email)

Berikut ini adalah artikel singkat yang memperkenalkan topik getter dan setter dengan Python.


1
@BasicWolf - Saya pikir secara implisit jelas saya berada di sisi properti pagar! :) Tapi saya menambahkan para untuk jawaban saya untuk mengklarifikasi itu.
mac

9
PETUNJUK: Kata "selalu" adalah isyarat bahwa penulis berusaha meyakinkan Anda dengan pernyataan, bukan argumen. Begitu juga dengan kehadiran huruf tebal. (Maksud saya, jika Anda melihat CAPS sebagai gantinya, maka - whoa - itu pasti benar.) Lihat, fitur "properti" kebetulan berbeda dari Jawa (musuh de facto Python untuk beberapa alasan), dan oleh karena itu komunitas grup Python berpikir mendeklarasikannya menjadi lebih baik. Pada kenyataannya, properti melanggar aturan "Eksplisit lebih baik daripada implisit", tetapi tidak ada yang mau mengakuinya. Itu membuatnya menjadi bahasa, jadi sekarang dinyatakan "Pythonic" melalui argumen tautologis.
Stuart Berg

3
Tidak ada perasaan sakit. :-P Saya hanya mencoba menunjukkan bahwa konvensi "Pythonic" tidak konsisten dalam kasus ini: "Eksplisit lebih baik daripada implisit" bertentangan langsung dengan penggunaan a property. ( Kelihatannya seperti tugas sederhana, tetapi disebut fungsi.) Oleh karena itu, "Pythonic" pada dasarnya adalah istilah yang tidak berarti, kecuali oleh definisi tautologis: "Konvensi Pythonic adalah hal-hal yang kita definisikan sebagai Pythonic."
Stuart Berg

1
Sekarang, gagasan memiliki seperangkat konvensi yang mengikuti tema sangat bagus . Jika seperangkat konvensi semacam itu memang ada, maka Anda dapat menggunakannya sebagai seperangkat aksioma untuk memandu pemikiran Anda, bukan hanya daftar panjang trik untuk menghafal, yang secara signifikan kurang bermanfaat. Aksioma dapat digunakan untuk ekstrapolasi , dan membantu Anda mendekati masalah yang belum pernah dilihat orang. Ini memalukan bahwa propertyfitur mengancam untuk membuat ide aksioma Pythonic hampir tidak berharga. Jadi yang tersisa hanyalah daftar periksa.
Stuart Berg

1
Saya tidak setuju. Saya lebih suka properti di sebagian besar situasi, tetapi ketika Anda ingin menekankan bahwa pengaturan sesuatu memiliki efek samping selain memodifikasi selfobjek , setter eksplisit dapat membantu. Misalnya, user.email = "..."tidak terlihat seperti itu dapat menimbulkan pengecualian karena sepertinya hanya mengatur atribut, sedangkan user.set_email("...")memperjelas bahwa mungkin ada efek samping seperti pengecualian.
bluenote10

65

[ 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 defaultXdan defaultYmenggunakan __init__metode objek. Alasannya adalah untuk kasus kami, saya ingin mengasumsikan bahwa someDefaultComputationmetode 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 xdan ynilai - 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 setattrdalam 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 valueatau menjalankannya methodjika 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 methodsini adalah metode kelas, valueadalah nilai dari UberProperty, dan saya telah menambahkan isSetkarena Nonemungkin 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 uberPropertydan 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 UberPropertyhanya menyimpan methodreferensi:

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 UberPropertyyang tentu saja tidak bisa dipanggil, jadi ini tidak banyak berguna.

Yang kita butuhkan adalah beberapa cara untuk secara dinamis mengikat UberPropertyinstance 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 UberPropertycontoh, jadi hal yang jelas untuk dikembalikan adalah BoundUberProperty. Di sinilah kita akan benar-benar mempertahankan status xatribut.

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

@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, setValuedan clearValuedi 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.xkembali 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.


53
Ya, ini 'tldr'. Bisakah Anda meringkas apa yang Anda coba lakukan di sini?
poolie

9
@Adam return self.x or self.defaultX()ini adalah kode berbahaya. Apa yang terjadi kapan self.x == 0?
Kelly Thomas

FYI, Anda bisa membuatnya sehingga Anda bisa membuat parameter pengambil, semacam. Ini akan melibatkan membuat variabel menjadi kelas khusus, yang telah Anda timpa __getitem__metode. Akan aneh, karena Anda akan memiliki python yang sama sekali tidak standar.
akan

2
@KellyThomas Hanya berusaha membuat contoh sederhana. Untuk melakukannya dengan benar, Anda harus membuat dan menghapus entri x dict sama sekali, karena bahkan nilai None mungkin telah ditetapkan secara khusus. Tapi ya, Anda memang benar ini adalah sesuatu yang perlu Anda pertimbangkan dalam kasus penggunaan produksi.
Adam Donahue

Getter mirip Java memungkinkan Anda melakukan perhitungan yang persis sama, bukan?
qed

26

Saya pikir keduanya memiliki tempat mereka. Salah satu masalah dengan menggunakan @propertyadalah sulit untuk memperluas perilaku getter atau setter di subclass menggunakan mekanisme kelas standar. Masalahnya adalah bahwa fungsi pengambil / penyetel sebenarnya tersembunyi di properti.

Anda benar-benar bisa mendapatkan fungsi, misalnya dengan

class C(object):
    _p = 1
    @property
    def p(self):
        return self._p
    @p.setter
    def p(self, val):
        self._p = val

Anda dapat mengakses fungsi pengambil dan penyetel seperti C.p.fgetdan C.p.fset, tetapi Anda tidak dapat dengan mudah menggunakan fasilitas pewarisan metode normal (misalnya super) untuk memperluasnya. Setelah menggali seluk-beluk super, Anda memang bisa menggunakan super dengan cara ini:

# Using super():
class D(C):
    # Cannot use super(D,D) here to define the property
    # since D is not yet defined in this scope.
    @property
    def p(self):
        return super(D,D).p.fget(self)

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for D'
        super(D,D).p.fset(self, val)

# Using a direct reference to C
class E(C):
    p = C.p

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for E'
        C.p.fset(self, val)

Namun, menggunakan super () cukup kikuk, karena properti harus didefinisikan ulang, dan Anda harus menggunakan mekanisme super (cls, cls) yang sedikit kontra-intuitif untuk mendapatkan salinan p.


20

Menggunakan properti bagi saya lebih intuitif dan lebih cocok dengan sebagian besar kode.

Perbandingan

o.x = 5
ox = o.x

vs.

o.setX(5)
ox = o.getX()

bagi saya cukup jelas yang lebih mudah dibaca. Properti juga memungkinkan untuk variabel pribadi lebih mudah.


12

Saya lebih suka tidak menggunakan keduanya dalam banyak kasus. Masalah dengan properti adalah mereka membuat kelas kurang transparan. Terutama, ini adalah masalah jika Anda mengajukan pengecualian dari setter. Misalnya, jika Anda memiliki properti Account.email:

class Account(object):
    @property
    def email(self):
        return self._email

    @email.setter
    def email(self, value):
        if '@' not in value:
            raise ValueError('Invalid email address.')
        self._email = value

maka pengguna kelas tidak berharap bahwa menetapkan nilai ke properti dapat menyebabkan pengecualian:

a = Account()
a.email = 'badaddress'
--> ValueError: Invalid email address.

Sebagai hasilnya, pengecualian mungkin tidak tertangani, dan baik menyebar terlalu tinggi dalam rantai panggilan untuk ditangani dengan benar, atau mengakibatkan traceback yang sangat tidak membantu yang disajikan kepada pengguna program (yang sayangnya terlalu umum di dunia python dan java ).

Saya juga akan menghindari penggunaan getter dan setter:

  • karena mendefinisikan mereka untuk semua properti di muka sangat memakan waktu,
  • membuat jumlah kode tidak perlu lebih lama, yang membuat pemahaman dan pemeliharaan kode lebih sulit,
  • jika Anda mendefinisikannya hanya untuk properti seperlunya, antarmuka kelas akan berubah, dan melukai semua pengguna kelas

Alih-alih properti dan getter / setter saya lebih suka melakukan logika kompleks di tempat yang didefinisikan dengan baik seperti dalam metode validasi:

class Account(object):
    ...
    def validate(self):
        if '@' not in self.email:
            raise ValueError('Invalid email address.')

atau metode Account.save serupa.

Perhatikan bahwa saya tidak mencoba mengatakan bahwa tidak ada kasus ketika properti berguna, hanya saja Anda mungkin lebih baik jika Anda dapat membuat kelas Anda sederhana dan transparan sehingga Anda tidak membutuhkannya.


3
@ user2239734 Saya pikir Anda salah paham konsep properti. Meskipun Anda dapat memvalidasi nilai saat mengatur properti, tidak perlu melakukannya. Anda bisa memiliki properti dan validate()metode di kelas. Properti hanya digunakan ketika Anda memiliki logika kompleks di belakang obj.x = ytugas sederhana , dan terserah apa logika itu.
Zaur Nasibov

12

Saya merasa seperti properti adalah tentang membiarkan Anda mendapatkan overhead menulis getter dan setter hanya ketika Anda benar-benar membutuhkannya.

Budaya Java Programming sangat menyarankan untuk tidak pernah memberikan akses ke properti, dan sebaliknya, melalui getter dan setter, dan hanya yang benar-benar dibutuhkan. Agak verbose untuk selalu menulis potongan kode yang jelas ini, dan perhatikan bahwa 70% dari waktu mereka tidak pernah digantikan oleh logika non-sepele.

Dengan Python, orang-orang benar-benar peduli pada overhead semacam itu, sehingga Anda bisa merangkul latihan berikut:

  • Jangan menggunakan getter dan setter pada awalnya, saat jika tidak diperlukan
  • Gunakan @propertyuntuk mengimplementasikannya tanpa mengubah sintaks dari sisa kode Anda.

1
"dan perhatikan bahwa 70% dari waktu mereka tidak pernah digantikan oleh logika yang tidak sepele." - itu angka yang agak spesifik, apakah itu berasal dari suatu tempat, atau apakah Anda bermaksud itu sebagai sebuah "gelombang besar" agak hal (saya tidak bercanda, jika ada penelitian yang mengkuantifikasi angka itu, saya akan benar-benar tertarik membacanya)
Adam Parkin

1
Oh tidak maaf Kedengarannya seperti saya memiliki beberapa studi untuk membuat cadangan nomor ini, tetapi saya hanya bermaksud sebagai "sebagian besar waktu".
fulmicoton

7
Bukan karena orang-orang peduli dengan overhead, itu adalah dengan Python Anda dapat mengubah dari akses langsung ke metode accessor tanpa mengubah kode klien, jadi Anda tidak akan rugi dengan langsung memaparkan properti pada awalnya.
Neil G

10

Saya terkejut bahwa tidak ada yang menyebutkan bahwa properti merupakan metode terikat dari kelas deskriptor, Adam Donohue dan NeilenMarais mendapatkan ide ini tepat di pos mereka - bahwa getter dan setter adalah fungsi dan dapat digunakan untuk:

  • mengesahkan
  • mengubah data
  • tipe bebek (tipe paksaan ke tipe lain)

Ini menyajikan cara cerdas untuk menyembunyikan detail implementasi dan pengodean kode seperti ekspresi reguler, ketik gips, coba .. kecuali blok, pernyataan, atau nilai yang dihitung.

Secara umum melakukan CRUD pada suatu objek mungkin sering cukup biasa tetapi mempertimbangkan contoh data yang akan bertahan ke database relasional. ORM dapat menyembunyikan detail implementasi dari vernakular SQL tertentu dalam metode yang cenderung fget, fset, fdel didefinisikan dalam kelas properti yang akan mengelola mengerikan jika .. elif .. tangga lain yang sangat jelek dalam kode OO - mengekspos yang sederhana dan anggun self.variable = somethingdan singkirkan detail untuk pengembang menggunakan ORM.

Jika seseorang berpikir tentang properti hanya sebagai sisa suram dari bahasa Bondage dan Disiplin (yaitu Jawa) mereka kehilangan titik deskriptor.


6

Dalam proyek kompleks saya lebih suka menggunakan properti read-only (atau getter) dengan fungsi setter eksplisit:

class MyClass(object):
...        
@property
def my_attr(self):
    ...

def set_my_attr(self, value):
    ...

Dalam proyek yang panjang, debug dan refactoring membutuhkan lebih banyak waktu daripada menulis kode itu sendiri. Ada beberapa kelemahan untuk menggunakan @property.setteryang membuat debugging lebih sulit:

1) python memungkinkan pembuatan atribut baru untuk objek yang ada. Ini membuat salah cetak berikut sangat sulit dilacak:

my_object.my_atttr = 4.

Jika objek Anda adalah algoritma yang rumit, maka Anda akan menghabiskan cukup banyak waktu untuk mencari tahu mengapa itu tidak bertemu (perhatikan 't' tambahan pada baris di atas)

2) setter kadang-kadang mungkin berevolusi menjadi metode yang rumit dan lambat (misalnya memukul basis data). Akan sangat sulit bagi pengembang lain untuk mengetahui mengapa fungsi berikut ini sangat lambat. Dia mungkin menghabiskan banyak waktu pada do_something()metode pembuatan profil , sementara my_object.my_attr = 4.sebenarnya penyebab perlambatan:

def slow_function(my_object):
    my_object.my_attr = 4.
    my_object.do_something()

5

Baik @propertygetter tradisional dan setter memiliki keuntungan. Itu tergantung pada kasus penggunaan Anda.

Keuntungan dari @property

  • Anda tidak perlu mengubah antarmuka saat mengubah implementasi akses data. Ketika proyek Anda kecil, Anda mungkin ingin menggunakan akses atribut langsung untuk mengakses anggota kelas. Sebagai contoh, katakanlah Anda memiliki objek footipe Foo, yang memiliki anggota num. Maka Anda bisa mendapatkan anggota ini dengan num = foo.num. Ketika proyek Anda tumbuh, Anda mungkin merasa perlu ada beberapa pemeriksaan atau debug pada akses atribut sederhana. Maka Anda dapat melakukannya dengan@property di dalam kelas. Antarmuka akses data tetap sama sehingga tidak perlu mengubah kode klien.

    Dikutip dari PEP-8 :

    Untuk atribut data publik sederhana, yang terbaik adalah 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.

  • Menggunakan @propertyuntuk akses data di Python dianggap sebagai Pythonic :

    • Ini dapat memperkuat identifikasi diri Anda sebagai programmer Python (bukan Java).

    • Ini dapat membantu wawancara pekerjaan Anda jika pewawancara Anda menganggap getter dan setter gaya Jawa adalah anti-pola .

Keuntungan dari getter dan setter tradisional

  • Getter dan setter tradisional memungkinkan akses data yang lebih rumit daripada akses atribut sederhana. Misalnya, ketika Anda menetapkan anggota kelas, kadang-kadang Anda memerlukan bendera yang menunjukkan di mana Anda ingin memaksakan operasi ini bahkan jika ada sesuatu yang tidak terlihat sempurna. Meskipun tidak jelas bagaimana cara menambah akses anggota langsung foo.num = num, Anda dapat dengan mudah menambah setter tradisional Anda dengan forceparameter tambahan :

    def Foo:
        def set_num(self, num, force=False):
            ...
  • Getter dan setter tradisional menjelaskan bahwa akses anggota kelas adalah melalui metode. Ini berarti:

    • Apa yang Anda dapatkan sebagai hasilnya mungkin tidak sama dengan apa yang disimpan di dalam kelas itu.

    • Bahkan jika akses terlihat seperti akses atribut sederhana, kinerjanya dapat sangat bervariasi dari itu.

    Kecuali jika pengguna kelas Anda mengharapkan @propertypersembunyian di balik setiap pernyataan akses atribut, membuat hal-hal seperti itu eksplisit dapat membantu meminimalkan kejutan pengguna kelas Anda.

  • Seperti yang disebutkan oleh @NeilenMarais dan dalam posting ini , memperluas getter tradisional dan setter dalam subclass lebih mudah daripada memperluas properti.

  • Getter dan setter tradisional telah banyak digunakan sejak lama dalam berbagai bahasa. Jika Anda memiliki orang-orang dari latar belakang yang berbeda di tim Anda, mereka terlihat lebih akrab daripada @property. Juga, seiring pertumbuhan proyek Anda, jika Anda mungkin perlu bermigrasi dari Python ke bahasa lain yang tidak memiliki @property, menggunakan getter dan setter tradisional akan membuat migrasi lebih lancar.

Peringatan

  • Baik @propertygetter dan setter tradisional tidak membuat anggota kelas pribadi, bahkan jika Anda menggunakan garis bawah dua kali sebelum namanya:

    class Foo:
        def __init__(self):
            self.__num = 0
    
        @property
        def num(self):
            return self.__num
    
        @num.setter
        def num(self, num):
            self.__num = num
    
        def get_num(self):
            return self.__num
    
        def set_num(self, num):
            self.__num = num
    
    foo = Foo()
    print(foo.num)          # output: 0
    print(foo.get_num())    # output: 0
    print(foo._Foo__num)    # output: 0

2

Berikut adalah kutipan dari "Python Efektif: 90 Cara Tertentu untuk Menulis Python Lebih Baik" (Buku yang luar biasa. Saya sangat merekomendasikannya).

Hal-hal untuk diingat

✦ Tentukan antarmuka kelas baru menggunakan atribut publik sederhana dan hindari mendefinisikan metode penyetel dan pengambil.

✦ Gunakan @ properti untuk menentukan perilaku khusus ketika atribut diakses pada objek Anda, jika perlu.

✦ Ikuti aturan yang paling tidak mengejutkan dan hindari efek samping aneh dalam metode properti Anda.

✦ Pastikan metode @ properti cepat; untuk pekerjaan yang lambat atau rumit — terutama yang melibatkan I / O atau menyebabkan efek samping — gunakan metode normal sebagai gantinya.

Salah satu penggunaan lanjutan dari properti @property adalah mentransisikan apa yang dulunya atribut numerik sederhana menjadi perhitungan sambil jalan. Ini sangat membantu karena memungkinkan Anda memigrasikan semua penggunaan kelas yang ada untuk memiliki perilaku baru tanpa mengharuskan situs panggilan untuk ditulis ulang (yang sangat penting jika ada kode panggilan yang tidak Anda kontrol). @ properti juga menyediakan penghentian penting untuk meningkatkan antarmuka dari waktu ke waktu.

Saya terutama menyukai @ properti karena ini memungkinkan Anda membuat kemajuan tambahan menuju model data yang lebih baik dari waktu ke waktu.
@property adalah alat untuk membantu Anda mengatasi masalah yang akan Anda temui dalam kode dunia nyata. Jangan terlalu sering menggunakannya. Ketika Anda menemukan diri Anda berulang kali memperluas metode @ properti, mungkin sudah waktunya untuk memperbaiki kelas Anda alih-alih membuka lebih lanjut desain buruk kode Anda.

✦ Gunakan @property untuk memberikan instance instance atribut fungsionalitas baru.

✦ Buat kemajuan bertahap menuju model data yang lebih baik dengan menggunakan @ properti.

✦ Pertimbangkan refactoring kelas dan semua situs panggilan ketika Anda merasa terlalu banyak menggunakan @property.

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.