Tentang Python
Python adalah bahasa pemrograman tingkat tinggi yang ditafsirkan, berorientasi objek, dengan semantik dinamis. Struktur data tingkat tinggi bawaan dikombinasikan dengan pengetikan dinamis dan pengikatan dinamis membuatnya sangat menarik untuk BRPS (pengembangan alat aplikasi yang cepat), serta untuk digunakan sebagai skrip dan bahasa penghubung untuk menghubungkan komponen atau layanan yang ada. Python mendukung modul dan paket, sehingga mendorong modularitas program dan penggunaan kembali kode.
Tentang artikel ini
Kesederhanaan dan kemudahan menguasai bahasa ini dapat membingungkan bagi pengembang (terutama mereka yang baru mulai mempelajari Python), sehingga Anda dapat kehilangan pandangan tentang beberapa seluk-beluk penting dan meremehkan kekuatan dari berbagai solusi yang mungkin menggunakan Python.
Dengan mengingat hal ini, artikel ini memperkenalkan "10 besar" kesalahan yang sulit ditemukan yang bahkan dapat dibuat oleh pengembang Python tingkat lanjut.
Kesalahan # 1: ekspresi penyalahgunaan sebagai nilai default untuk argumen fungsi
Python memungkinkan Anda untuk menunjukkan bahwa suatu fungsi dapat memiliki argumen opsional dengan menetapkan nilai default untuk mereka. Ini, tentu saja, adalah fitur bahasa yang sangat nyaman, tetapi dapat menyebabkan konsekuensi yang tidak menyenangkan jika jenis nilai ini bisa berubah. Sebagai contoh, pertimbangkan definisi fungsi berikut:
>>> def foo(bar=[]):
Kesalahan umum dalam kasus ini adalah berpikir bahwa nilai argumen opsional akan ditetapkan ke nilai default setiap kali fungsi dipanggil tanpa nilai untuk argumen ini. Dalam kode di atas, misalnya, kita dapat mengasumsikan bahwa dengan berulang kali memanggil fungsi foo () (yaitu, tanpa menentukan nilai untuk argumen bar), ia akan selalu mengembalikan "baz", karena diasumsikan bahwa setiap kali foo () dipanggil (tanpa menentukan bilah argumen), bilah diatur ke [] (yaitu, daftar kosong baru).
Tapi mari kita lihat apa yang akan terjadi:
>>> foo() ["baz"] >>> foo() ["baz", "baz"] >>> foo() ["baz", "baz", "baz"]
Hah? Mengapa fungsi terus menambahkan nilai default "baz" ke daftar yang ada setiap kali foo () dipanggil, alih-alih membuat daftar baru setiap kali?
Jawaban untuk pertanyaan ini akan menjadi pemahaman yang lebih dalam tentang apa yang terjadi dengan Python "di bawah tenda". Yaitu: nilai default untuk fungsi diinisialisasi hanya sekali, selama definisi fungsi. Dengan demikian, argumen bilah diinisialisasi secara default (mis., Daftar kosong) hanya ketika foo () didefinisikan untuk pertama kalinya, tetapi panggilan berikutnya ke foo () (mis., Tanpa menentukan argumen bilah) akan terus menggunakan daftar yang sama dengan yang sebelumnya dibuat untuk bilah argumen pada saat definisi pertama dari fungsi.
Untuk referensi, "solusi" umum untuk kesalahan ini adalah definisi berikut:
>>> def foo(bar=None): ... if bar is None:
Kesalahan # 2: penyalahgunaan variabel kelas
Perhatikan contoh berikut:
>>> class A(object): ... x = 1 ... >>> class B(A): ... pass ... >>> class C(A): ... pass ... >>> print Ax, Bx, Cx 1 1 1
Segalanya tampak teratur.
>>> Bx = 2 >>> print Ax, Bx, Cx 1 2 1
Ya, semuanya seperti yang diharapkan.
>>> Ax = 3 >>> print Ax, Bx, Cx 3 2 3
Apa-apaan ini ?! Kami baru saja mengganti Ax, mengapa Cx juga berubah?
Dalam Python, variabel kelas diperlakukan seperti kamus dan mengikuti apa yang sering disebut Method Resolution Order (MRO). Jadi, dalam kode di atas, karena atribut x tidak ditemukan di kelas C, itu akan ditemukan di kelas dasarnya (hanya A dalam contoh di atas, meskipun Python mendukung multiple inheritance). Dengan kata lain, C tidak memiliki properti sendiri x independen dari A. Dengan demikian, referensi ke Cx sebenarnya referensi ke Axe. Ini akan menyebabkan masalah jika kasus ini tidak ditangani dengan benar. Jadi ketika belajar Python, berikan perhatian khusus pada atribut kelas dan bekerja dengannya.
Kesalahan No. 3: Parameter salah untuk blok pengecualian
Misalkan Anda memiliki potongan kode berikut:
>>> try: ... l = ["a", "b"] ... int(l[2]) ... except ValueError, IndexError:
Masalahnya di sini adalah bahwa ekspresi pengecualian tidak menerima daftar pengecualian yang ditentukan dengan cara ini. Sebaliknya, dalam Python 2.x, ungkapan "kecuali Pengecualian, e" digunakan untuk mengikat pengecualian ke parameter opsional kedua yang diberikan kedua (dalam hal ini, e) untuk membuatnya tersedia untuk pemeriksaan lebih lanjut. Akibatnya, dalam kode di atas, pengecualian IndexError tidak ditangkap oleh pernyataan kecuali; melainkan, pengecualian berakhir dengan mengikat ke parameter bernama IndexError sebagai gantinya.
Cara yang benar untuk menangkap beberapa pengecualian dengan ekspresi pengecualian adalah dengan menentukan parameter pertama sebagai tuple yang berisi semua pengecualian yang ingin Anda tangkap. Juga, untuk kompatibilitas maksimum, gunakan kata kunci as, karena sintaks ini didukung dalam Python 2 dan Python 3:
>>> try: ... l = ["a", "b"] ... int(l[2]) ... except (ValueError, IndexError) as e: ... pass ... >>>
Kesalahan # 4: kesalahpahaman aturan lingkup Python
Cakupan dalam Python didasarkan pada apa yang disebut aturan LEGB, yang merupakan singkatan dari Lokal (nama yang ditetapkan dengan cara apa pun di dalam suatu fungsi (def atau lambda), dan tidak dideklarasikan global dalam fungsi ini), Melampirkan (nama dalam lingkup lokal dari semua fungsi yang termasuk secara statis) ( def atau lambda), dari internal ke eksternal), Global (nama yang ditetapkan di tingkat atas file modul, atau dengan menjalankan instruksi global di def di dalam file), Built-in (nama yang sebelumnya ditetapkan dalam modul nama built-in: open, range, Sintaksisor, ...). Tampaknya cukup sederhana, bukan? Sebenarnya ada beberapa seluk-beluk bagaimana ini bekerja di Python, yang membawa kita ke masalah pemrograman Python yang lebih kompleks secara umum di bawah ini. Perhatikan contoh berikut:
>>> x = 10 >>> def foo(): ... x += 1 ... print x ... >>> foo() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in foo UnboundLocalError: local variable 'x' referenced before assignment
Apa masalahnya?
Kesalahan di atas terjadi karena ketika Anda menetapkan variabel dalam cakupan, Python secara otomatis menganggapnya lokal untuk cakupan itu dan menyembunyikan variabel apa pun dengan nama yang sama di lingkup orangtua mana pun.
Dengan demikian, banyak yang terkejut ketika mereka menerima UnboundLocalError dalam kode yang sedang berjalan, ketika dimodifikasi dengan menambahkan operator penugasan di suatu tempat di badan fungsi.
Fitur ini sangat membingungkan bagi pengembang saat menggunakan daftar. Perhatikan contoh berikut:
>>> lst = [1, 2, 3] >>> def foo1(): ... lst.append(5)
Hah? Mengapa foo2 mogok saat foo1 berfungsi dengan baik?
Jawabannya sama seperti pada contoh sebelumnya, tetapi, menurut kepercayaan populer, situasi di sini lebih halus. foo1 tidak menerapkan operator penugasan ke pertama, sedangkan foo2 tidak. Mengingat bahwa lst + = [5] sebenarnya hanya sebuah singkatan untuk lst = lst + [5], kita melihat bahwa kita mencoba untuk menetapkan nilai lst (jadi Python menganggapnya dalam lingkup lokal). Namun, nilai yang ingin kami tetapkan didasarkan pada lst itu sendiri (sekali lagi, sekarang diasumsikan dalam lingkup lokal), yang belum ditentukan. Dan kami mendapatkan kesalahan.
Kesalahan # 5: mengubah daftar selama iterasi di atasnya
Masalah dalam potongan kode berikut harus cukup jelas:
>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> for i in range(len(numbers)): ... if odd(numbers[i]): ... del numbers[i]
Menghapus item dari daftar atau array selama iterasi di atasnya adalah masalah Python yang diketahui oleh setiap pengembang perangkat lunak yang berpengalaman. Tetapi, meskipun contoh di atas mungkin cukup jelas, bahkan pengembang berpengalaman dapat memulai menyapu ini dalam kode yang jauh lebih kompleks.
Untungnya, Python menyertakan sejumlah paradigma pemrograman elegan yang, jika digunakan dengan benar, dapat mengarah pada penyederhanaan dan optimalisasi kode yang signifikan. Konsekuensi tambahan yang menyenangkan dari hal ini adalah bahwa dalam kode yang lebih sederhana, kemungkinan jatuh ke dalam kesalahan secara tidak sengaja menghapus item daftar selama iterasi lebih kecil. Salah satu paradigma tersebut adalah generator daftar. Selain itu, memahami operasi generator daftar sangat membantu dalam menghindari masalah khusus ini, seperti yang ditunjukkan dalam penerapan alternatif kode di atas, yang berfungsi dengan baik:
>>> odd = lambda x : bool(x % 2) >>> numbers = [n for n in range(10)] >>> numbers[:] = [n for n in numbers if not odd(n)]
Kesalahan # 6: kesalahpahaman bagaimana Python mengikat variabel dalam penutupan
Perhatikan contoh berikut:
>>> def create_multipliers(): ... return [lambda x : i * x for i in range(5)] >>> for multiplier in create_multipliers(): ... print multiplier(2) ...
Anda dapat mengharapkan output berikut:
0 2 4 6 8
Tetapi sebenarnya Anda mendapatkan ini:
8 8 8 8 8
Kejutan!
Hal ini disebabkan oleh keterlambatan pengikatan dengan Python, yang berarti bahwa nilai-nilai variabel yang digunakan dalam penutupan dicari selama panggilan ke fungsi internal. Dengan demikian, dalam kode di atas, setiap kali salah satu fungsi yang dikembalikan dipanggil, nilai i dicari dalam lingkup sekitarnya selama panggilannya (dan pada saat itu siklus sudah selesai, jadi saya sudah ditugaskan hasil akhir - nilai 4) .
Solusi untuk masalah Python umum ini adalah:
>>> def create_multipliers(): ... return [lambda x, i=i : i * x for i in range(5)] ... >>> for multiplier in create_multipliers(): ... print multiplier(2) ... 0 2 4 6 8
Voila! Kami menggunakan argumen default di sini untuk menghasilkan fungsi anonim untuk mencapai perilaku yang diinginkan. Beberapa akan menyebut solusi ini elegan. Ada beberapa
kurus. Beberapa orang membenci hal-hal ini. Tetapi jika Anda seorang pengembang Python, penting untuk dipahami.
Kesalahan # 7: membuat dependensi modul siklik
Misalkan Anda memiliki dua file, a.py dan b.py, masing-masing mengimpor yang lain, sebagai berikut:
Di a.py:
import b def f(): return bx print f()
Di b.py:
import a x = 1 def g(): print af()
Pertama, coba impor a.py:
>>> import a 1
Itu bekerja dengan baik. Ini mungkin mengejutkan Anda. Bagaimanapun, modul saling mengimpor secara siklis dan ini mungkin akan menjadi masalah, bukan?
Jawabannya adalah bahwa hanya memiliki impor siklik modul tidak dengan sendirinya masalah dengan Python. Jika modul sudah diimpor, Python cukup pintar untuk tidak mencoba mengimpornya kembali. Namun, tergantung pada titik di mana setiap modul mencoba mengakses fungsi atau variabel yang ditentukan di yang lain, Anda mungkin benar-benar mengalami masalah.
Jadi, kembali ke contoh kita, ketika kita mengimpor a.py, tidak ada masalah mengimpor b.py, karena b.py tidak mengharuskan a.py didefinisikan selama impornya. Satu-satunya referensi dalam b.py ke a adalah panggilan ke af (). Tapi panggilan ini di g () dan tidak ada di a.py atau b.py tidak memanggil g (). Jadi semuanya bekerja dengan baik.
Tetapi apa yang terjadi jika kita mencoba mengimpor b.py (tanpa mengimpor a.py, yaitu):
>>> import b Traceback (most recent call last): File "<stdin>", line 1, in <module> File "b.py", line 1, in <module> import a File "a.py", line 6, in <module> print f() File "a.py", line 4, in f return bx AttributeError: 'module' object has no attribute 'x'
Oh, oh Ini tidak baik! Masalahnya di sini adalah bahwa selama proses impor b.py ia mencoba mengimpor a.py, yang pada gilirannya memanggil f (), yang mencoba mengakses bx Tetapi bx belum didefinisikan. Oleh karena itu pengecualian AttributeError.
Setidaknya satu solusi untuk masalah ini cukup sepele. Cukup modifikasi b.py untuk mengimpor a.py ke g ():
x = 1 def g(): import a
Sekarang ketika kita mengimpornya, semuanya baik-baik saja:
>>> import b >>> bg() 1
Kesalahan # 8: memotong nama dengan nama modul di pustaka standar Python
Salah satu daya tarik Python adalah banyak modulnya yang keluar dari kotak. Tetapi sebagai hasilnya, jika Anda tidak secara sadar mengikuti ini, Anda mungkin menemukan bahwa nama modul Anda mungkin dengan nama yang sama dengan modul di perpustakaan standar yang datang dengan Python (misalnya, dalam kode Anda mungkin ada modul dengan nama email.py, yang akan bertentangan dengan modul perpustakaan standar dengan nama yang sama).
Ini dapat menyebabkan masalah serius. Misalnya, jika ada modul yang mencoba mengimpor versi modul dari pustaka standar Python, dan Anda memiliki modul dengan nama yang sama dalam proyek, yang akan salah diimpor daripada modul dari pustaka standar.
Oleh karena itu, harus berhati-hati untuk tidak menggunakan nama yang sama seperti pada modul pustaka standar Python. Jauh lebih mudah untuk mengubah nama modul di proyek Anda daripada mengajukan permintaan untuk mengubah nama modul di perpustakaan standar dan mendapatkan persetujuan untuk itu.
Kesalahan # 9: Gagal memperhitungkan perbedaan antara Python 2 dan Python 3
Pertimbangkan file foo.py berikut:
import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def bad(): e = None try: bar(int(sys.argv[1])) except KeyError as e: print('key error') except ValueError as e: print('value error') print(e) bad()
Pada Python 2, ini akan berfungsi dengan baik:
$ python foo.py 1 key error 1 $ python foo.py 2 value error 2
Tapi sekarang mari kita lihat bagaimana cara kerjanya di Python 3:
$ python3 foo.py 1 key error Traceback (most recent call last): File "foo.py", line 19, in <module> bad() File "foo.py", line 17, in bad print(e) UnboundLocalError: local variable 'e' referenced before assignment
Apa yang terjadi di sini? "Masalahnya" adalah bahwa dalam Python 3, objek di blok pengecualian tidak tersedia di luarnya. (Alasan untuk ini adalah bahwa jika tidak objek di blok ini akan disimpan dalam memori sampai pengumpul sampah mulai dan menghapus referensi untuk mereka dari sana).
Salah satu cara untuk menghindari masalah ini adalah untuk menjaga referensi ke objek blok pengecualian di luar blok ini sehingga tetap tersedia. Berikut adalah versi contoh sebelumnya yang menggunakan teknik ini, sehingga mendapatkan kode yang cocok untuk Python 2 dan Python 3:
import sys def bar(i): if i == 1: raise KeyError(1) if i == 2: raise ValueError(2) def good(): exception = None try: bar(int(sys.argv[1])) except KeyError as e: exception = e print('key error') except ValueError as e: exception = e print('value error') print(exception) good()
Jalankan dengan Python 3:
$ python3 foo.py 1 key error 1 $ python3 foo.py 2 value error 2
Hore!
Kesalahan # 10: penggunaan metode __del__ secara tidak benar
Katakanlah Anda memiliki file mod.py seperti ini:
import foo class Bar(object): ... def __del__(self): foo.cleanup(self.myhandle)
Dan Anda mencoba melakukan ini dari another_mod.py lain:
import mod mybar = mod.Bar()
Dan dapatkan AttributeError yang mengerikan.
Mengapa Karena, seperti yang dilaporkan di
sini , ketika penerjemah dimatikan, variabel global modul semua memiliki nilai None. Akibatnya, dalam contoh di atas, ketika __del__ dipanggil, nama foo sudah disetel ke None.
Solusi untuk "tugas dengan tanda bintang" ini adalah dengan menggunakan atexit.register (). Dengan demikian, ketika program Anda menyelesaikan eksekusi (yaitu, ketika keluar secara normal), pegangan Anda dihapus sebelum penerjemah menyelesaikan pekerjaannya.
Dengan mengingat hal ini, perbaikan untuk kode mod.py di atas mungkin terlihat seperti ini:
import foo import atexit def cleanup(handle): foo.cleanup(handle) class Bar(object): def __init__(self): ... atexit.register(cleanup, self.myhandle)
Implementasi seperti itu menyediakan cara yang sederhana dan dapat diandalkan untuk memanggil pembersihan yang diperlukan setelah penghentian program normal. Jelas, keputusan tentang bagaimana berurusan dengan objek yang berhubungan dengan diri. Nama saya ditangani foo.cleanup, tapi saya pikir Anda mengerti idenya.
Kesimpulan
Python adalah bahasa yang kuat dan fleksibel dengan banyak mekanisme dan paradigma yang dapat secara signifikan meningkatkan kinerja. Namun, seperti halnya perangkat atau bahasa perangkat lunak apa pun, dengan pemahaman atau evaluasi terbatas atas kemampuannya, masalah yang tidak terduga dapat muncul selama pengembangan.
Pengantar nuansa Python tercakup dalam artikel ini akan membantu mengoptimalkan penggunaan bahasa Anda, sambil menghindari beberapa kesalahan umum.