Haruskah saya menggunakan kelas atau kamus?

101

Saya memiliki kelas yang hanya berisi bidang dan tidak ada metode, seperti ini:

class Request(object):

    def __init__(self, environ):
        self.environ = environ
        self.request_method = environ.get('REQUEST_METHOD', None)
        self.url_scheme = environ.get('wsgi.url_scheme', None)
        self.request_uri = wsgiref.util.request_uri(environ)
        self.path = environ.get('PATH_INFO', None)
        # ...

Ini dapat dengan mudah diterjemahkan menjadi dikt. Kelas lebih fleksibel untuk penambahan di masa mendatang dan dapat digunakan dengan cepat __slots__. Jadi, apakah ada manfaatnya menggunakan dict? Akankah sebuah dict lebih cepat dari sebuah kelas? Dan lebih cepat dari kelas dengan slot?

deamon
sumber
2
saya selalu menggunakan kamus untuk menyimpan data, dan ini terlihat seperti kasus penggunaan bagi saya. dalam beberapa kasus, menurunkan kelas dari dictbisa masuk akal, hingga. keuntungan rapi: ketika Anda men-debug, katakan saja print(request)dan Anda akan segera melihat semua informasi status. dengan pendekatan yang lebih klasik Anda harus menulis __str__metode kustom Anda , yang menyebalkan jika Anda harus melakukannya sepanjang waktu.
mengalir
Mereka dapat dipertukarkan stackoverflow.com/questions/1305532/…
Oleksiy
Jika kelas benar-benar masuk akal dan jelas bagi orang lain, mengapa tidak? Selanjutnya, jika Anda mendefinisikan banyak kelas dengan antarmuka yang sama misalnya, mengapa tidak? Tetapi Python tidak mendukung konsep berorientasi objek yang perkasa seperti C ++.
MasterControlProgram
3
@Ralf OOP apa yang tidak didukung python?
qwr

Jawaban:

32

Mengapa Anda menjadikan ini kamus? Apa keuntungannya? Apa yang terjadi jika nanti Anda ingin menambahkan beberapa kode? Kemana __init__kode Anda akan pergi?

Kelas untuk menggabungkan data terkait (dan biasanya kode).

Kamus digunakan untuk menyimpan hubungan nilai-kunci, di mana biasanya semua kunci memiliki tipe yang sama, dan semua nilai juga merupakan satu jenis. Terkadang mereka dapat berguna untuk menggabungkan data ketika nama kunci / atribut tidak semuanya diketahui sebelumnya, tetapi seringkali ini merupakan tanda bahwa ada sesuatu yang salah dengan desain Anda.

Pertahankan ini sebagai kelas.

adw
sumber
Saya akan membuat semacam metode pabrik yang membuat dikt alih-alih metode kelas __init__. Tapi Anda benar: Saya akan memisahkan hal-hal yang menjadi milik bersama.
deamon
88
sangat setuju dengan Anda: kamus, set, daftar, dan tuple semuanya ada untuk menggabungkan data terkait. sama sekali tidak ada anggapan bahwa nilai-nilai kamus harus atau harus dari tipe data yang sama, justru sebaliknya. dalam banyak daftar dan set, nilai akan memiliki tipe yang sama, tetapi itu terutama karena itulah yang ingin kita hitung bersama. Saya benar-benar berpikir bahwa meluasnya penggunaan kelas untuk menyimpan data adalah penyalahgunaan oop; ketika Anda memikirkan tentang masalah serialisasi, Anda dapat dengan mudah melihat alasannya.
mengalir
4
Ini pemrograman berorientasi OBYEK dan tidak berorientasi kelas karena suatu alasan: kita berurusan dengan objek. Suatu objek dicirikan oleh 2 (3) properti: 1. status (anggota) 2. perilaku (metode) dan 3. contoh dapat dijelaskan dalam beberapa kata. Oleh karena itu, kelas untuk menggabungkan status dan perilaku.
friendzis
14
Saya telah menandai ini karena Anda harus selalu menggunakan struktur data yang lebih sederhana jika memungkinkan. Dalam hal ini, kamus sudah cukup untuk tujuan yang dimaksudkan. Pertanyaannya where would your __init__ code go?sangat memprihatinkan. Itu bisa meyakinkan pengembang yang kurang berpengalaman bahwa hanya kelas yang akan digunakan karena metode init tidak digunakan dalam kamus. Konyol.
Lloyd Moore
1
@Ralf A kelas Foo hanyalah sebuah tipe, seperti int dan string. Apakah Anda menyimpan nilai dalam tipe integer atau variabel foo tipe integer? Perbedaan semantik yang halus, tetapi penting. Dalam bahasa seperti C, perbedaan ini tidak terlalu relevan di luar lingkungan akademis. Meskipun sebagian besar bahasa OO memiliki dukungan variabel kelas, yang membuat perbedaan antara kelas dan objek / instance menjadi sangat relevan - apakah Anda menyimpan data di kelas (digunakan bersama di semua instance) atau di objek tertentu? Jangan digigit
friendzis
44

Gunakan kamus kecuali Anda membutuhkan mekanisme ekstra dari sebuah kelas. Anda juga bisa menggunakan namedtupleuntuk pendekatan hybrid:

>>> from collections import namedtuple
>>> request = namedtuple("Request", "environ request_method url_scheme")
>>> request
<class '__main__.Request'>
>>> request.environ = "foo"
>>> request.environ
'foo'

Perbedaan kinerja di sini akan minimal, meskipun saya akan terkejut jika kamus tidak lebih cepat.

Katriel
sumber
15
"Perbedaan kinerja di sini akan minimal , meskipun saya akan terkejut jika kamus tidak lebih cepat secara signifikan ." Ini tidak menghitung. :)
mipadi
1
@mipadi: ini benar. Sekarang diperbaiki: p
Katriel
Dict adalah 1,5 kali lebih cepat dari namtuple dan dua kali lebih cepat dari kelas tanpa slot. Periksa posting saya tentang jawaban ini.
alexpinho98
@ alexpinho98: Saya telah berusaha keras untuk menemukan 'kiriman' yang Anda maksud, tetapi saya tidak dapat menemukannya! bisakah Anda memberikan URL. Terima kasih!
Dan Oblinger
@DanOblinger Saya berasumsi bahwa yang dia maksud adalah jawabannya di bawah.
Adam Lewis
37

Kelas dengan python adalah dikt di bawahnya. Anda memang mendapatkan beberapa overhead dengan perilaku kelas, tetapi Anda tidak akan dapat menyadarinya tanpa profiler. Dalam hal ini, saya yakin Anda mendapat manfaat dari kelas karena:

  • Semua logika Anda hidup dalam satu fungsi
  • Mudah untuk diperbarui dan tetap dikemas
  • Jika nanti Anda mengubah apa pun, Anda dapat dengan mudah menjaga antarmuka tetap sama
Bruce Armstrong
sumber
Semua logika Anda tidak hidup dalam satu fungsi. Kelas adalah tupel dari keadaan bersama dan biasanya satu atau lebih metode. Jika Anda mengubah kelas, Anda tidak diberi jaminan apa pun tentang antarmukanya.
Lloyd Moore
25

Saya pikir penggunaan masing-masing terlalu subyektif bagi saya untuk memahaminya, jadi saya akan tetap berpegang pada angka.

Saya membandingkan waktu yang dibutuhkan untuk membuat dan mengubah variabel dalam sebuah dict, kelas gaya_baru dan kelas gaya_baru dengan slot.

Inilah kode yang saya gunakan untuk mengujinya (agak berantakan tetapi berhasil.)

import timeit

class Foo(object):

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

def create_dict():

    foo_dict = {}
    foo_dict['foo1'] = 'test'
    foo_dict['foo2'] = 'test'
    foo_dict['foo3'] = 'test'

    return foo_dict

class Bar(object):
    __slots__ = ['foo1', 'foo2', 'foo3']

    def __init__(self):

        self.foo1 = 'test'
        self.foo2 = 'test'
        self.foo3 = 'test'

tmit = timeit.timeit

print 'Creating...\n'
print 'Dict: ' + str(tmit('create_dict()', 'from __main__ import create_dict'))
print 'Class: ' + str(tmit('Foo()', 'from __main__ import Foo'))
print 'Class with slots: ' + str(tmit('Bar()', 'from __main__ import Bar'))

print '\nChanging a variable...\n'

print 'Dict: ' + str((tmit('create_dict()[\'foo3\'] = "Changed"', 'from __main__ import create_dict') - tmit('create_dict()', 'from __main__ import create_dict')))
print 'Class: ' + str((tmit('Foo().foo3 = "Changed"', 'from __main__ import Foo') - tmit('Foo()', 'from __main__ import Foo')))
print 'Class with slots: ' + str((tmit('Bar().foo3 = "Changed"', 'from __main__ import Bar') - tmit('Bar()', 'from __main__ import Bar')))

Dan inilah hasilnya ...

Membuat ...

Dict: 0.817466186345
Class: 1.60829183597
Class_with_slots: 1.28776730003

Mengubah variabel ...

Dict: 0.0735140918748
Class: 0.111714198313
Class_with_slots: 0.10618612142

Jadi, jika Anda hanya menyimpan variabel, Anda memerlukan kecepatan, dan tidak mengharuskan Anda melakukan banyak perhitungan, saya sarankan menggunakan dict (Anda selalu bisa membuat fungsi yang terlihat seperti metode). Tetapi, jika Anda benar-benar membutuhkan kelas, ingat - selalu gunakan __ slot __ .

catatan:

Saya menguji 'Kelas' dengan kelas gaya_baru dan gaya_baru. Ternyata kelas old_style lebih cepat dibuat tetapi lebih lambat untuk dimodifikasi (tidak terlalu signifikan tetapi signifikan jika Anda membuat banyak kelas dalam putaran yang ketat (tip: Anda salah melakukannya)).

Juga waktu untuk membuat dan mengubah variabel mungkin berbeda di komputer Anda karena milik saya sudah tua dan lambat. Pastikan Anda mengujinya sendiri untuk melihat hasil 'nyata'.

Edit:

Saya kemudian menguji nametuple: saya tidak dapat memodifikasinya tetapi untuk membuat 10.000 sampel (atau sesuatu seperti itu) butuh 1,4 detik sehingga kamus memang yang tercepat.

Jika saya mengubah fungsi dict untuk memasukkan kunci dan nilai dan mengembalikan dict alih-alih variabel yang berisi dict ketika saya membuatnya, itu memberi saya 0,65 daripada 0,8 detik.

class Foo(dict):
    pass

Membuat seperti kelas dengan slot dan mengubah variabel paling lambat (0,17 detik) jadi jangan gunakan kelas ini . gunakan dict (kecepatan) atau untuk kelas yang diturunkan dari objek ('syntax candy')

alexpinho98
sumber
Saya ingin melihat angka-angka untuk subkelas dict(tanpa metode yang diganti, saya kira?). Apakah itu bekerja dengan cara yang sama seperti kelas gaya baru yang ditulis dari awal?
Benjamin Hodgson
12

Saya setuju dengan @adw. Saya tidak akan pernah mewakili sebuah "objek" (dalam arti OO) dengan kamus. Kamus menggabungkan pasangan nama / nilai. Kelas mewakili objek. Saya telah melihat kode di mana objek diwakili dengan kamus dan tidak jelas apa bentuk sebenarnya dari benda itu. Apa yang terjadi jika nama / nilai tertentu tidak ada? Apa yang membatasi klien untuk memasukkan apa pun ke dalam. Atau mencoba mengeluarkan apa pun. Bentuk benda harus selalu didefinisikan dengan jelas.

Saat menggunakan Python, penting untuk membangun dengan disiplin karena bahasanya memungkinkan banyak cara bagi penulis untuk menembak dirinya sendiri.

jaydel
sumber
9
Jawaban di SO diurutkan berdasarkan suara, dan jawaban dengan jumlah suara yang sama diurutkan secara acak. Jadi tolong jelaskan siapa yang Anda maksud dengan "poster terakhir".
Mike DeSimone
4
Dan bagaimana Anda tahu bahwa sesuatu yang hanya memiliki bidang dan tidak memiliki fungsionalitas adalah "objek" dalam pengertian OO?
Muhammad Alkarouri
1
Tes lakmus saya adalah "apakah struktur data ini diperbaiki?". Jika ya, maka gunakan objek, jika tidak, dikt. Berangkat dari ini hanya akan membuat kebingungan.
weberc2
Anda harus menganalisis konteks masalah yang Anda selesaikan. Objek harus relatif jelas setelah Anda terbiasa dengan lanskap itu. Secara pribadi saya pikir mengembalikan kamus harus menjadi pilihan terakhir Anda kecuali jika apa yang Anda kembalikan HARUS menjadi pemetaan pasangan nama / nilai. Saya telah melihat terlalu banyak kode ceroboh di mana semua metode yang masuk dan keluar hanyalah kamus. Itu malas. Saya suka bahasa yang diketik secara dinamis, tetapi saya juga menyukai kode saya agar jelas dan logis. Memasukkan segala sesuatu ke dalam kamus bisa menjadi kenyamanan yang menyembunyikan makna.
jaydel
5

Saya akan merekomendasikan kelas, karena semua jenis informasi yang terlibat dengan permintaan. Jika seseorang menggunakan kamus, saya berharap data yang disimpan jauh lebih mirip di alam. Sebuah pedoman yang cenderung saya ikuti sendiri adalah bahwa jika saya ingin mengulang seluruh rangkaian pasangan kunci-> nilai dan melakukan sesuatu, saya menggunakan kamus. Jika tidak, data tampaknya memiliki struktur yang jauh lebih banyak daripada pemetaan nilai kunci-> dasar, yang berarti kelas kemungkinan akan menjadi alternatif yang lebih baik.

Oleh karena itu, tetaplah dengan kelas.

Stigma
sumber
2
Saya sangat tidak setuju. Tidak ada alasan untuk membatasi penggunaan kamus untuk hal-hal yang berulang. Mereka untuk memelihara pemetaan.
Katriel
1
Tolong baca lebih dekat. Saya berkata mungkin ingin mengulang , bukan akan . Bagi saya, menggunakan kamus menyiratkan ada kesamaan fungsional yang besar antara kunci dan nilai. Misalnya nama dengan umur. Menggunakan kelas berarti berbagai 'kunci' memiliki nilai dengan arti yang sangat berbeda. Misalnya, ambil kelas PErson. Di sini, 'name' akan menjadi string, dan 'friends' bisa menjadi daftar, atau kamus, atau objek lain yang sesuai. Anda tidak akan mengulang semua atribut ini sebagai bagian dari penggunaan normal kelas ini.
Stigma
1
Saya pikir perbedaan antara kelas dan kamus dibuat tidak jelas dengan Python oleh fakta bahwa yang pertama diimplementasikan menggunakan yang terakhir ('slot' tidak bertahan). Saya tahu ini sedikit membingungkan saya ketika saya pertama kali belajar bahasa (bersama dengan fakta bahwa kelas adalah objek dan karenanya contoh dari beberapa metaclass misterius).
martineau
4

Jika semua yang Anda ingin kita wajib adalah sintaks permen seperti obj.bla = 5bukan obj['bla'] = 5, terutama jika Anda harus mengulangi bahwa banyak, Anda mungkin ingin menggunakan beberapa kelas wadah polos seperti di martineaus saran. Namun demikian, kode di sana cukup membengkak dan terlalu lambat. Anda bisa membuatnya sederhana seperti itu:

class AttrDict(dict):
    """ Syntax candy """
    __getattr__ = dict.__getitem__
    __setattr__ = dict.__setitem__
    __delattr__ = dict.__delitem__

Alasan lain untuk beralih ke namedtuples atau kelas dengan__slots__ mungkin penggunaan memori. Diktik membutuhkan lebih banyak memori daripada jenis daftar, jadi ini bisa menjadi poin untuk dipikirkan.

Bagaimanapun, dalam kasus khusus Anda, tampaknya tidak ada motivasi untuk beralih dari penerapan Anda saat ini. Anda tampaknya tidak mempertahankan jutaan objek ini, jadi tidak diperlukan tipe turunan daftar. Dan itu sebenarnya mengandung beberapa logika fungsional di dalam __init__, jadi Anda juga tidak boleh mendapatkannya AttrDict.

Michael
sumber
types.SimpleNamespace(tersedia sejak Python 3.3) adalah alternatif dari AttrDict kustom.
Cristian Ciupitu
4

Dimungkinkan untuk memiliki kue Anda dan memakannya juga. Dengan kata lain, Anda dapat membuat sesuatu yang menyediakan fungsionalitas instance kelas dan kamus. Lihat resep Dɪᴄᴛɪᴏɴᴀʀʏ ᴡɪᴛʜ ᴀᴛᴛʀɪʙᴜᴛᴇ-sᴛʏʟᴇ ᴀᴄᴄᴇss ActiveState dan komentar tentang cara melakukannya.

Jika Anda memutuskan untuk menggunakan kelas biasa daripada subkelas, saya telah menemukan resep Tʜᴇ sɪᴍᴘʟᴇ ʙᴜᴛ ʜᴀɴᴅʏ "ᴄᴏʟʟᴇᴄᴛᴏʀ ᴏғ ᴀ ʙᴜɴᴄʜ ᴏғ ɴᴀᴍᴇᴅ sᴛᴜғғ" ᴄʟᴀss (oleh Alex Martelli) sangat fleksibel dan berguna untuk hal-hal yang berkaitan dengan itu. seperti yang Anda lakukan (yaitu membuat agregator informasi yang relatif sederhana). Karena ini adalah kelas, Anda dapat dengan mudah memperluas fungsinya lebih jauh dengan menambahkan metode.

Terakhir, perlu dicatat bahwa nama anggota kelas harus merupakan pengenal resmi Python, tetapi kunci kamus tidak demikian — jadi kamus akan memberikan kebebasan yang lebih besar dalam hal itu karena kunci dapat berupa apa saja yang dapat di-hash (bahkan sesuatu yang bukan string).

Memperbarui

Sebuah kelas object(yang tidak memiliki __dict__) subkelas bernama SimpleNamespace(yang memang memilikinya) telah ditambahkan ke typesmodul Python 3.3, dan merupakan alternatif lain.

martineau.dll
sumber