Buku โ€œPython. Praktik dan Alat Terbaik "

gambarHalo, Penduduk! Python adalah bahasa pemrograman dinamis yang digunakan di berbagai bidang subjek. Meskipun mudah untuk menulis kode dengan Python, jauh lebih sulit untuk membuat kode dapat dibaca, digunakan kembali, dan mudah dipelihara. Edisi ketiga dari Python. Praktik dan Alat Terbaik โ€akan memberi Anda alat untuk memecahkan masalah pengembangan dan pemeliharaan perangkat lunak secara efektif. Penulis memulai dengan berbicara tentang fitur baru di Python 3.7 dan aspek lanjutan dari sintaks Python. Mereka melanjutkan dengan nasihat tentang penerapan paradigma populer, termasuk pemrograman berorientasi objek, fungsional, dan berbasis peristiwa. Penulis juga berbicara tentang praktik penamaan terbaik, tentang bagaimana Anda dapat mengotomatiskan penerapan program di server jarak jauh. Anda akan belajar,cara membuat ekstensi Python yang berguna di C, C ++, Cython dan CFFI.



Untuk siapa buku ini
Python, . , Python. , , , Python.



, . , Python. , , . Python 3.7 , Python 2.7 .



- -, , : .



Pola akses untuk atribut yang diperluas



Saat mempelajari Python, banyak programmer C ++ dan Java yang terkejut dengan kurangnya kata kunci privat. Konsep yang paling dekat dengannya adalah nama mangling. Setiap kali atribut diawali dengan __, itu secara dinamis diganti oleh interpreter:



class MyClass:
__secret_value = 1
      
      





Mengakses atribut __secret_value dengan nama aslinya akan memunculkan pengecualian AttributeError:



>>> instance_of = MyClass()
>>> instance_of.__secret_value
Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute '__secret_value'
>>> dir(MyClass)
['_MyClass__secret_value', '__class__', '__delattr__', '__dict__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> instance_of._MyClass__secret_value
1
      
      





Ini dilakukan secara khusus untuk menghindari konflik nama berdasarkan pewarisan, karena atribut diganti namanya dengan nama kelas sebagai awalan. Ini bukan analogi privat, karena atribut dapat diakses melalui nama gabungan. Properti ini dapat digunakan untuk melindungi akses ke beberapa atribut, tetapi dalam praktiknya __ tidak pernah digunakan. Jika atributnya bukan untuk publik, maka biasanya menggunakan awalan _. Itu tidak memanggil algoritme dekorasi nama, tetapi mendokumentasikan atribut sebagai elemen privat kelas dan merupakan gaya yang dominan.



Python memiliki mekanisme lain untuk memisahkan publik dari bagian privat sebuah kelas. Deskriptor dan properti menyediakan cara untuk membereskan pemisahan ini.



Deskriptor



Deskriptor memungkinkan Anda menyesuaikan tindakan yang terjadi saat Anda mereferensikan atribut pada objek.



Deskriptor berada di jantung akses atribut kompleks di Python. Mereka digunakan untuk mengimplementasikan properti, metode, metode kelas, metode statis, dan supertipe. Ini adalah kelas yang menentukan bagaimana atribut kelas lain akan diakses. Dengan kata lain, suatu kelas dapat mendelegasikan kontrol suatu atribut ke kelas lain.



Kelas deskriptor didasarkan pada tiga metode khusus yang membentuk protokol deskriptor:



__set __ (self, obj, nilai) - Dipanggil setiap kali atribut disetel. Dalam contoh berikut, kami akan menyebutnya sebagai "penyetel";



__get __ (self, obj, owner = None) - dipanggil setiap kali atribut dibaca (selanjutnya disebut pengambil);



__delete __ (self, object) - Dipanggil saat del dipanggil oleh atribut.



Deskriptor yang mengimplementasikan __get__ dan __set__ disebut deskriptor data. Jika hanya mengimplementasikan __get__, ini disebut deskriptor tanpa data.



Metode protokol ini sebenarnya dipanggil dengan metode __getattribute __ () (jangan disamakan dengan __getattr __ (), yang memiliki tujuan berbeda) setiap kali atribut dicari. Setiap kali pencarian seperti itu dilakukan menggunakan titik atau panggilan fungsi langsung, metode __getattribute __ () secara implisit dipanggil, yang mencari atribut dalam urutan berikut.



  1. Memeriksa apakah atribut adalah deskriptor data pada objek kelas instance.
  2. Jika tidak, akan terlihat apakah atribut tersebut ditemukan di __dict__ objek instance.
  3. Terakhir, periksa apakah atribut adalah pegangan tanpa data pada objek kelas instance.


Dengan kata lain, deskriptor data diutamakan daripada __dict__, yang pada gilirannya didahulukan daripada deskriptor non-data.



Untuk kejelasan, berikut adalah contoh dari dokumentasi resmi Python yang menunjukkan bagaimana deskriptor bekerja dalam kode sebenarnya:



class RevealAccess(object):
   """ ,     
           
   """
   def __init__(self, initval=None, name='var'):
      self.val = initval
      self.name = name
   def __get__(self, obj, objtype):
      print('Retrieving', self.name)
      return self.val
   def __set__(self, obj, val):
      print('Updating', self.name)
      self.val = val
class MyClass(object):
   x = RevealAccess(10, 'var "x"')
   y = 5
      
      





Berikut contoh penggunaannya secara interaktif:



>>> m = MyClass()
>>> m.x
Retrieving var "x"
10
>>> m.x = 20
Updating var "x"
>>> m.x
Retrieving var "x"
20
>>> m.y
5
      
      





Contoh tersebut dengan jelas menunjukkan bahwa jika kelas memiliki deskriptor data untuk atribut itu, maka __get __ () dipanggil untuk mengembalikan nilai setiap kali atribut instance diambil, dan __set __ () dipanggil setiap kali atribut tersebut diberi nilai. Penggunaan metode __del__ tidak ditampilkan dalam contoh sebelumnya, tetapi harus jelas: ini dipanggil setiap kali atribut instance dihapus menggunakan pernyataan del instance.attribute atau delattr (instance, 'attribute').



Perbedaan antara data dan deskriptor non-data penting karena alasan yang kami sebutkan di awal sub-bagian ini. Python menggunakan protokol deskriptor untuk mengikat fungsi kelas ke instance melalui metode. Standar ini juga berlaku untuk dekorator metode kelas dan metode statis. Ini karena objek fungsional pada dasarnya juga merupakan deskriptor dataless:



>>> def function(): pass
>>> hasattr(function, '__get__')
True
>>> hasattr(function, '__set__')
False
      
      





Hal yang sama berlaku untuk fungsi yang dibuat menggunakan ekspresi lambda:



>>> hasattr(lambda: None, '__get__')
True
>>> hasattr(lambda: None, '__set__')
False
      
      





Jadi, kecuali __dict__ lebih diutamakan daripada deskriptor dataless, kami tidak akan dapat secara dinamis mengganti metode tertentu dari instance yang sudah dibuat instance-nya pada waktu proses. Untungnya, berkat cara kerja deskriptor dengan Python, ini dimungkinkan; Oleh karena itu, developer dapat memilih instance mana yang berfungsi tanpa menggunakan subclass.



Contoh kehidupan nyata: evaluasi atribut secara malas. Salah satu contoh penggunaan deskriptor adalah untuk menunda inisialisasi atribut kelas ketika diakses dari sebuah instance. Ini dapat berguna jika inisialisasi atribut tersebut bergantung pada konteks aplikasi global. Kasus lainnya adalah ketika inisialisasi tersebut terlalu mahal, dan tidak diketahui apakah atribut akan digunakan sama sekali setelah mengimpor kelas. Deskriptor semacam itu dapat diimplementasikan sebagai berikut:



class InitOnAccess:
   def __init__(self, klass, *args, **kwargs):
      self.klass = klass
      self.args = args
      self.kwargs = kwargs
      self._initialized = None
   def __get__(self, instance, owner):
      if self._initialized is None:
         print('initialized!')
         self._initialized = self.klass(*self.args, **self.kwargs)
      else:
         print('cached!')
      return self._initialized
      
      





Di bawah ini adalah contoh penggunaan:



>>> class MyClass:
... lazily_initialized = InitOnAccess(list, "argument")
...
>>> m = MyClass()
>>> m.lazily_initialized
initialized!
['a', 'r', 'g', 'u', 'm', 'e', 'n', 't']
>>> m.lazily_initialized
cached!
['a', 'r', 'g', 'u', 'm', 'e', 'n', 't']
      
      





Pustaka resmi PyPI OpenGL Python yang disebut PyOpenGL menggunakan teknik seperti ini untuk mengimplementasikan objek lazy_property yang merupakan dekorator dan deskriptor data:



class lazy_property(object):
   def __init__(self, function):
      self.fget = function
   def __get__(self, obj, cls):
      value = self.fget(obj)
      setattr(obj, self.fget.__name__, value)
      return value
      
      





Implementasi ini mirip dengan menggunakan properti dekorator (kita akan membicarakannya nanti), tetapi fungsi yang dibungkus dekorator dijalankan hanya sekali, dan kemudian atribut kelas diganti dengan nilai yang dikembalikan oleh properti fungsi ini. Metode ini sering kali berguna saat Anda perlu memenuhi dua persyaratan secara bersamaan:



  • sebuah instance objek harus disimpan sebagai atribut kelas, yang dibagikan di antara instance-nya (untuk menghemat sumber daya);
  • objek ini tidak dapat diinisialisasi pada saat impor, karena proses pembuatannya bergantung pada beberapa keadaan global aplikasi / konteks.


Dalam kasus aplikasi yang ditulis menggunakan OpenGL, Anda akan sering menghadapi situasi ini. Misalnya, membuat shader di OpenGL mahal karena memerlukan kode kompilasi yang ditulis dalam OpenGL Shading Language (GLSL). Masuk akal untuk membuatnya hanya sekali dan pada saat yang sama menyimpan deskripsi mereka di dekat kelas yang membutuhkannya. Di sisi lain, kompilasi shader tidak dapat dilakukan tanpa menginisialisasi konteks OpenGL, sehingga sulit untuk menentukan dan mengumpulkannya di namespace modul global pada saat pengimporan.



Contoh berikut menunjukkan kemungkinan penggunaan versi modifikasi dekorator lazy_property PyOpenGL (di sini lazy_class_attribute) dalam beberapa aplikasi OpenGL abstrak. Perubahan pada dekorator lazy_property asli diperlukan agar atribut dapat dibagikan ke berbagai instance kelas:



import OpenGL.GL as gl
from OpenGL.GL import shaders
class lazy_class_attribute(object):
   def __init__(self, function):
      self.fget = function
   def __get__(self, obj, cls):
      value = self.fget(obj or cls)
      # :   - 
      #    
      setattr(cls, self.fget.__name__, value)
      return value
class ObjectUsingShaderProgram(object):
   #   -
    VERTEX_CODE = """
      #version 330 core
      layout(location = 0) in vec4 vertexPosition;
      void main(){
         gl_Position = vertexPosition;
      }
"""
#  ,    
FRAGMENT_CODE = """
   #version 330 core
   out lowp vec4 out_color;
   void main(){
      out_color = vec4(1, 1, 1, 1);
   }
"""
@lazy_class_attribute
def shader_program(self):
   print("compiling!")
   return shaders.compileProgram(
      shaders.compileShader(
         self.VERTEX_CODE, gl.GL_VERTEX_SHADER
      ),
      shaders.compileShader(
         self.FRAGMENT_CODE, gl.GL_FRAGMENT_SHADER
      )
   )
      
      





Seperti semua fitur sintaksis Python tingkat lanjut, fitur ini juga harus digunakan dengan hati-hati dan didokumentasikan dengan baik dalam kode. Untuk pengembang yang tidak berpengalaman, perubahan perilaku kelas dapat menjadi kejutan karena deskriptor mempengaruhi perilaku kelas. Oleh karena itu, sangat penting untuk memastikan bahwa semua anggota tim Anda terbiasa dengan deskriptor dan memahami konsep ini jika itu memainkan peran penting dalam basis kode proyek.



Properti



Properti menyediakan jenis deskriptor built-in yang mengetahui cara mengaitkan atribut dengan sekumpulan metode. Properti mengambil empat argumen opsional: fget, fset, fdel, dan doc. Yang terakhir dapat disediakan untuk mendefinisikan docstring yang terkait dengan atribut seolah-olah itu adalah sebuah metode. Di bawah ini adalah contoh kelas Persegi Panjang yang dapat dimanipulasi baik dengan mengakses langsung atribut yang memiliki dua titik sudut, atau dengan menggunakan properti lebar dan tinggi:



class Rectangle:
   def __init__(self, x1, y1, x2, y2):
      self.x1, self.y1 = x1, y1
      self.x2, self.y2 = x2, y2
   def _width_get(self):
      return self.x2 - self.x1
      def _width_set(self, value):
      self.x2 = self.x1 + value
   def _height_get(self):
      return self.y2 - self.y1
   def _height_set(self, value):
      self.y2 = self.y1 + value
   width = property(
       _width_get, _width_set,
       doc="rectangle width measured from left"
   )
   height = property(
       _height_get, _height_set,
       doc="rectangle height measured from top"
   )
   def __repr__(self):
      return "{}({}, {}, {}, {})".format(
         self.__class__.__name__,
         self.x1, self.y1, self.x2, self.y2
     )

      
      





Cuplikan kode berikut memberikan contoh properti seperti itu yang ditentukan dalam sesi interaktif:



>>> rectangle.width, rectangle.height
(15, 24)
>>> rectangle.width = 100
>>> rectangle
Rectangle(10, 10, 110, 34)
>>> rectangle.height = 100
>>> rectangle
Rectangle(10, 10, 110, 110)
>>> help(Rectangle)
Help on class Rectangle in module chapter3:
class Rectangle(builtins.object)
| Methods defined here:
|
| __init__(self, x1, y1, x2, y2)
| Initialize self. See help(type(self)) for accurate signature.
|
| __repr__(self)
| Return repr(self).
|
| --------------------------------------------------------
| Data descriptors defined here:
| (...)
|
| height
| rectangle height measured from top
|
| width
| rectangle width measured from left
      
      





Properti ini membuat deskriptor lebih mudah untuk ditulis, tetapi harus ditangani dengan hati-hati saat menggunakan pewarisan kelas. Atribut dibuat secara dinamis menggunakan metode kelas saat ini dan tidak akan menerapkan metode yang diganti dalam kelas turunan.



Kode dalam contoh berikut tidak akan dapat menimpa implementasi metode fget dari properti lebar kelas induk (Persegi Panjang):



>>> class MetricRectangle(Rectangle):
... def _width_get(self):
... return "{} meters".format(self.x2 - self.x1)
...
>>> Rectangle(0, 0, 100, 100).width
100
      
      





Untuk mengatasi masalah ini, seluruh properti harus ditimpa di kelas turunan:



>>> class MetricRectangle(Rectangle):
... def _width_get(self):
... return "{} meters".format(self.x2 - self.x1)
... width = property(_width_get, Rectangle.width.fset)
...
>>> MetricRectangle(0, 0, 100, 100).width
'100 meters'
      
      





Sayangnya, kode tersebut memiliki beberapa masalah pemeliharaan. Kebingungan bisa muncul jika pengembang memutuskan untuk mengubah kelas induk tetapi lupa untuk memperbarui panggilan properti. Inilah mengapa tidak disarankan untuk hanya mengganti bagian dari perilaku properti. Alih-alih mengandalkan implementasi kelas induk, sebaiknya tulis ulang semua metode properti di kelas turunan jika Anda ingin mengubah cara kerjanya. Biasanya tidak ada opsi lain, karena mengubah properti perilaku penyetel memerlukan perubahan dalam perilaku pengambil.



Cara terbaik untuk membuat properti adalah dengan menggunakan properti sebagai dekorator. Ini akan mengurangi jumlah tanda tangan metode di dalam kelas dan membuat kode lebih mudah dibaca dan dipelihara:



class Rectangle:
   def __init__(self, x1, y1, x2, y2):
      self.x1, self.y1 = x1, y1
      self.x2, self.y2 = x2, y2
   @property
   def width(self):
      """    """
      return self.x2 - self.x1
   @width.setter
   def width(self, value):
      self.x2 = self.x1 + value
   @property
   def height(self):
      """   """
      return self.y2 - self.y1
   @height.setter
   def height(self, value):
      self.y2 = self.y1 + value
      
      





Slot



Fitur menarik yang jarang digunakan developer adalah slot. Mereka memungkinkan Anda menyetel daftar statis atribut untuk kelas menggunakan atribut __slots__ dan melewati pembuatan kamus __dict__ di setiap instance kelas. Mereka dibuat untuk menghemat ruang memori untuk kelas dengan sedikit atribut, karena __dict__ tidak dibuat di setiap instance.



Mereka juga dapat membantu dalam membuat kelas yang tanda tangannya perlu dibekukan. Misalnya, jika Anda perlu membatasi properti dinamis suatu bahasa untuk kelas tertentu, maka slot dapat membantu:



>>> class Frozen:
... __slots__ = ['ice', 'cream']
...
>>> '__dict__' in dir(Frozen)
False
>>> 'ice' in dir(Frozen)
True
>>> frozen = Frozen()
>>> frozen.ice = True
>>> frozen.cream = None
>>> frozen.icy = True
Traceback (most recent call last): File "<input>", line 1, in <module>
AttributeError: 'Frozen' object has no attribute 'icy'
      
      





Fitur ini harus digunakan dengan hati-hati. Jika kumpulan atribut yang tersedia terbatas pada slot, akan jauh lebih sulit untuk menambahkan sesuatu ke objek secara dinamis. Beberapa trik terkenal, seperti menambal monyet, tidak akan berfungsi dengan instance kelas yang memiliki slot tertentu. Untungnya, atribut baru dapat ditambahkan ke kelas turunan jika tidak memiliki slot yang ditentukan sendiri:



>>> class Unfrozen(Frozen):
... pass
...
>>> unfrozen = Unfrozen()
>>> unfrozen.icy = False
>>> unfrozen.icy
False
      
      





Tentang Penulis



Michal Jaworski adalah programmer Python dengan pengalaman sepuluh tahun. Dia telah memegang berbagai posisi di berbagai perusahaan: dari developer full-stack biasa, kemudian arsitek perangkat lunak dan, terakhir, hingga wakil presiden pengembangan di perusahaan startup yang dinamis. Michal saat ini adalah Senior Backend Engineer di Showpad. Memiliki pengalaman luas dalam pengembangan layanan terdistribusi berkinerja tinggi. Selain itu, dia adalah kontributor aktif untuk banyak proyek Python open source.

Tarek Ziade adalah pengembang Python. Tinggal di pedesaan dekat Dijon, Prancis. Bekerja di Mozilla, di tim layanan. Tarek mendirikan kelompok pengguna Python Prancis (disebut Afpy) dan telah menulis beberapa buku tentang Python dalam bahasa Prancis dan Inggris. Di waktu luangnya dari meretas dan berpesta, ia terlibat dalam hobi favoritnya: jogging atau bermain terompet.



Anda dapat mengunjungi blog pribadinya (Fetchez le Python) dan mengikutinya di Twitter (tarek_ziade).



Tentang editor ilmiah



Cody Jackson adalah Ph.D., pendiri Socius Consulting, sebuah firma konsultan IT dan manajemen bisnis yang berbasis di San Antonio, dan salah satu pendiri Top Men Technologies. Saat ini dia bekerja untuk CACI International sebagai Lead Engineer untuk ICS / SCADA Modeling. Di industri IT sejak 1994, sejak waktunya di Angkatan Laut sebagai ahli kimia nuklir dan insinyur radio. Sebelum CACI, dia bekerja di universitas di ECPI sebagai Asisten Profesor Sistem Informasi Komputer. Saya belajar pemrograman Python sendiri, menulis buku Belajar Membuat Program Menggunakan Python dan Resep Rahasia Ninja Python.



Rincian lebih lanjut tentang buku ini dapat ditemukan di situs web penerbit

" Daftar Isi

" Kutipan



Untuk Habitants diskon 25% untuk kupon - Python



Setelah pembayaran untuk versi kertas dari buku tersebut, sebuah e-book dikirim ke email.



All Articles