The tuple dari orang yang sehat

Bernama tuple
Artikel ini adalah tentang salah satu penemuan terbaik Python: namedtuple. Kami akan mempertimbangkan fitur-fiturnya yang menyenangkan, dari yang terkenal hingga yang tidak jelas. Tingkat perendaman dalam topik akan meningkat secara bertahap, jadi saya harap semua orang akan menemukan sesuatu yang menarik untuk diri mereka sendiri. Ayo pergi!


Pendahuluan


Tentunya Anda dihadapkan pada situasi di mana Anda perlu mentransfer beberapa properti objek dalam keadaan utuh. Misalnya, informasi tentang hewan peliharaan: jenis, nama panggilan, dan usia.


Seringkali terlalu malas untuk membuat kelas terpisah untuk hal ini, dan tuple digunakan:


("pigeon", "", 3) ("fox", "", 7) ("parrot", "", 1) 

Untuk kejelasan yang lebih besar, tuple - collections.namedtuple bernama cocok:


 from collections import namedtuple Pet = namedtuple("Pet", "type name age") frank = Pet(type="pigeon", name="", age=3) >>> frank.age 3 

Semua orang tahu ini ツ Dan inilah beberapa fitur yang kurang dikenal:


Kolom perubahan cepat


Bagaimana jika salah satu properti perlu diubah? Frank sudah tua, dan iring-iringan mobil tidak bisa diubah. Agar tidak membuat ulang seluruhnya, kami datang dengan metode _replace() :


 >>> frank._replace(age=4) Pet(type='pigeon', name='', age=4) 

Dan jika Anda ingin membuat seluruh struktur bisa berubah - _asdict() :


 >>> frank._asdict() OrderedDict([('type', 'pigeon'), ('name', ''), ('age', 3)]) 

Perubahan judul otomatis


Misalkan Anda mengimpor data dari CSV dan mengubah setiap baris menjadi tupel. Nama-nama bidang diambil dari tajuk file CSV. Tapi ada yang salah:


 # headers = ("name", "age", "with") >>> Pet = namedtuple("Pet", headers) ValueError: Type names and field names cannot be a keyword: 'with' # headers = ("name", "age", "name") >>> Pet = namedtuple("Pet", headers) ValueError: Encountered duplicate field name: 'name' 

Solusinya adalah rename=True Argumen rename=True dalam konstruktor:


 # headers = ("name", "age", "with", "color", "name", "food") Pet = namedtuple("Pet", headers, rename=True) >>> Pet._fields ('name', 'age', '_2', 'color', '_4', 'food') 

Nama "tidak berhasil" diganti namanya sesuai dengan nomor seri.


Nilai default


Jika sebuah tupel memiliki banyak bidang opsional, Anda masih harus mencantumkannya setiap kali Anda membuat objek:


 Pet = namedtuple("Pet", "type name alt_name") >>> Pet("pigeon", "") TypeError: __new__() missing 1 required positional argument: 'alt_name' >>> Pet("pigeon", "", None) Pet(type='pigeon', name='', alt_name=None) 

Untuk menghindari ini, tentukan defaults di konstruktor:


 Pet = namedtuple("Pet", "type name alt_name", defaults=("",)) >>> Pet("pigeon", "") Pet(type='pigeon', name='', alt_name='') 

defaults memberikan nilai default dari tail. Bekerja dalam python 3.7+


Untuk versi yang lebih lama, Anda dapat dengan lebih kikuk mencapai hasil yang sama melalui prototipe:


 Pet = namedtuple("Pet", "type name alt_name") default_pet = Pet(None, None, "") >>> default_pet._replace(type="pigeon", name="") Pet(type='pigeon', name='', alt_name='') >>> default_pet._replace(type="fox", name="") Pet(type='fox', name='', alt_name='') 

Tetapi dengan defaults , tentu saja, jauh lebih baik.


Cahaya luar biasa


Salah satu manfaat dari tuple bernama adalah ringan. Pasukan seratus ribu merpati hanya membutuhkan waktu 10 megabita:


 from collections import namedtuple import objsize # 3rd party Pet = namedtuple("Pet", "type name age") frank = Pet(type="pigeon", name="", age=None) pigeons = [frank._replace(age=idx) for idx in range(100000)] >>> round(objsize.get_deep_size(pigeons)/(1024**2), 2) 10.3 

Sebagai perbandingan, jika Anda menjadikan Pet sebagai kelas biasa, daftar yang serupa sudah akan menempati 19 megabita.


Ini terjadi karena objek biasa dalam python membawa __dict__ __dict__ yang berat, yang berisi nama dan nilai semua atribut objek:


 class PetObj: def __init__(self, type, name, age): self.type = type self.name = name self.age = age frank_obj = PetObj(type="pigeon", name="", age=3) >>> frank_obj.__dict__ {'type': 'pigeon', 'name': '', 'age': 3} 

Objek namedTuple tidak memiliki kamus ini, dan karenanya memakan lebih sedikit memori:


 frank = Pet(type="pigeon", name="", age=3) >>> frank.__dict__ AttributeError: 'Pet' object has no attribute '__dict__' >>> objsize.get_deep_size(frank_obj) 335 >>> objsize.get_deep_size(frank) 239 

Tapi bagaimana tuple bernama itu menghilangkan __dict__ ? Baca terus ツ


Dunia batin yang kaya


Jika Anda telah bekerja dengan python untuk waktu yang lama, maka Anda mungkin tahu: objek ringan dapat dibuat melalui __slots__ __slots__ :


 class PetSlots: __slots__ = ("type", "name", "age") def __init__(self, type, name, age): self.type = type self.name = name self.age = age frank_slots = PetSlots(type="pigeon", name="", age=3) 

Objek "Slot" tidak memiliki kamus dengan atribut, sehingga hanya memakan sedikit memori. "Frank on the slot" seringan "Frank on the carcade", lihat:


 >>> objsize.get_deep_size(frank) 239 >>> objsize.get_deep_size(frank_slots) 231 

Jika Anda memutuskan bahwa namesuple juga menggunakan slot, ini tidak jauh dari kebenaran. Seperti yang Anda ingat, kelas tuple spesifik dideklarasikan secara dinamis:


 Pet = namedtuple("Pet", "type name age") 

Konstruktor namesuple menggunakan sihir gelap yang berbeda dan menghasilkan sesuatu seperti kelas ini (sangat menyederhanakan):


 class Pet(tuple): __slots__ = () type = property(operator.itemgetter(0)) name = property(operator.itemgetter(1)) age = property(operator.itemgetter(2)) def __new__(cls, type, name, age): return tuple.__new__(cls, (type, name, age)) 

Yaitu, hewan peliharaan kami adalah tuple biasa, di mana tiga metode properti telah disematkan dengan paku:


  • type mengembalikan elemen nol dari tuple
  • name - elemen pertama dari tuple
  • age - elemen kedua dari tuple

Dan __slots__ diperlukan hanya untuk membuat objek menjadi ringan. Akibatnya, Pet memakan sedikit ruang dan dapat digunakan sebagai tuple biasa:


 >>> frank.index("") 1 >>> type, _, _ = frank >>> type 'pigeon' 

Diciptakan secara licik, ya?


Tidak kalah dengan kelas data


Karena kita berbicara tentang pembuatan kode. Dalam python 3.7, kode uber-generator telah muncul, yang tidak memiliki yang sama - dataclasses.


Saat pertama kali melihat kelas data, Anda ingin beralih ke versi bahasa yang baru hanya untuk itu:


 from dataclasses import dataclass @dataclass class PetData: type: str name: str age: int 

Keajaiban sangat baik! Tapi ada nuansa - itu gemuk:


 frank_data = PetData(type="pigeon", name="", age=3) >>> objsize.get_deep_size(frank_data) 335 >>> objsize.get_deep_size(frank) 239 

Kelas data menghasilkan kelas python biasa, yang objeknya habis karena bobot __dict__ . Jadi, jika Anda membaca baris dari basis dan mengubahnya menjadi objek, kelas data bukan pilihan terbaik.


Tapi tunggu, Anda bisa membekukan kelas data seperti tuple. Mungkin itu akan menjadi lebih mudah?


 @dataclass(frozen=True) class PetFrozen: type: str name: str age: int frank_frozen = PetFrozen(type="pigeon", name="", age=3) >>> objsize.get_deep_size(frank_frozen) 335 

Sayang Bahkan beku, itu tetap menjadi benda berat biasa dengan kamus atribut. Jadi, jika Anda membutuhkan objek yang tidak berubah (yang juga dapat digunakan sebagai tupel biasa) - namupuple masih merupakan pilihan terbaik.


⌘ ⌘ ⌘


Saya sangat suka tuple bernama:


  • iterable jujur,
  • deklarasi tipe dinamis
  • Akses atribut yang dinamai
  • ringan dan tidak bisa diubah.

Dan pada saat yang sama diimplementasikan dalam 150 baris kode. Apa lagi yang dibutuhkan untuk kebahagiaan ツ


Jika Anda ingin tahu lebih banyak tentang pustaka Python standar, berlangganan saluran @ohmypy

Source: https://habr.com/ru/post/id438162/


All Articles