Ini adalah kumpulan kedua belas tips Python dan pemrograman dari feed @pythonetc saya.
←
Koleksi sebelumnyaAnda tidak dapat mengubah variabel penutupan dengan penugasan sederhana. Python menganggap penugasan sebagai definisi di dalam fungsi tubuh dan tidak melakukan penutupan sama sekali.
Bekerja dengan baik, menampilkan
2
:
def make_closure(x): def closure(): print(x) return closure make_closure(2)()
Dan kode ini melempar
UnboundLocalError: local variable 'x' referenced before assignment
:
def make_closure(x): def closure(): print(x) x *= 2 print(x) return closure make_closure(2)()
Untuk membuat kode berfungsi, gunakan
nonlocal
. Ini secara eksplisit memberi tahu juru bahasa untuk tidak menganggap penugasan sebagai definisi:
def make_closure(x): def closure(): nonlocal x print(x) x *= 2 print(x) return closure make_closure(2)()
Terkadang saat iterasi Anda perlu mencari tahu elemen mana yang sedang diproses, pertama atau terakhir. Ini dapat dengan mudah ditentukan menggunakan flag eksplisit:
def sparse_list(iterable, num_of_zeros=1): result = [] zeros = [0 for _ in range(num_of_zeros)] first = True for x in iterable: if not first: result += zeros result.append(x) first = False return result assert sparse_list([1, 2, 3], 2) == [ 1, 0, 0, 2, 0, 0, 3, ]
Tentu saja, Anda bisa menangani elemen pertama di luar loop. Ini terlihat lebih bersih, tetapi mengarah pada duplikasi kode parsial. Selain itu, tidak akan mudah untuk melakukan ini ketika bekerja dengan
iterable
abstrak:
def sparse_list(iterable, num_of_zeros=1): result = [] zeros = [0 for _ in range(num_of_zeros)] iterator = iter(iterable) try: result.append(next(iterator)) except StopIteration: return [] for x in iterator: result += zeros result.append(x) return result
Anda juga dapat menggunakan
enumerate
dan melakukan pemeriksaan
i == 0
(hanya berfungsi untuk menentukan elemen pertama, bukan yang terakhir), namun, solusi terbaik adalah generator yang mengembalikan flag
first
dan
last
bersama dengan elemen
iterable
:
def first_last_iter(iterable): iterator = iter(iterable) first = True last = False while not last: if first: try: current = next(iterator) except StopIteration: return else: current = next_one try: next_one = next(iterator) except StopIteration: last = True yield (first, last, current) first = False
Sekarang fungsi aslinya mungkin terlihat seperti ini:
def sparse_list(iterable, num_of_zeros=1): result = [] zeros = [0 for _ in range(num_of_zeros)] for first, last, x in first_last_iter(iterable): if not first: result += zeros result.append(x) return result
Jika Anda perlu mengukur waktu yang telah berlalu antara dua peristiwa, maka gunakan
time.monotonic()
alih-alih
time.time()
.
time.monotonic()
tidak pernah berubah ke arah yang lebih kecil, bahkan ketika memperbarui jam sistem:
from contextlib import contextmanager import time @contextmanager def timeit(): start = time.monotonic() yield print(time.monotonic() - start) def main(): with timeit(): time.sleep(2) main()
Manajer konteks bersarang biasanya tidak tahu bahwa mereka bersarang. Anda dapat memberi tahu mereka tentang hal ini dengan membuat manajer internal menggunakan manajer eksternal:
from contextlib import AbstractContextManager import time class TimeItContextManager(AbstractContextManager): def __init__(self, name, parent=None): super().__init__() self._name = name self._parent = parent self._start = None self._substracted = 0 def __enter__(self): self._start = time.monotonic() return self def __exit__(self, exc_type, exc_value, traceback): delta = time.monotonic() - self._start if self._parent is not None: self._parent.substract(delta) print(self._name, 'total', delta) print(self._name, 'outer', delta - self._substracted) return False def child(self, name): return type(self)(name, parent=self) def substract(self, n): self._substracted += n timeit = TimeItContextManager def main(): with timeit('large') as large_t: with large_t.child('medium') as medium_t: with medium_t.child('small-1'): time.sleep(1) with medium_t.child('small-2'): time.sleep(1) time.sleep(1) time.sleep(1) main()

Saat Anda perlu menyampaikan informasi tentang rangkaian panggilan, hal pertama yang terlintas dalam pikiran adalah meneruskan data dalam bentuk argumen fungsi.
Dalam beberapa kasus, mungkin jauh lebih nyaman untuk memodifikasi semua fungsi dalam rantai untuk mentransfer data baru. Sebagai gantinya, Anda bisa menentukan konteks yang akan digunakan oleh semua fungsi dalam rantai. Bagaimana cara melakukannya?
Solusi termudah adalah dengan menggunakan variabel global. Dalam Python, Anda juga dapat menggunakan modul dan kelas sebagai penjaga konteks karena, sebenarnya, mereka juga variabel global. Anda mungkin sudah melakukan ini secara teratur, misalnya, untuk penjurnalan.
Jika aplikasi Anda multithreaded, maka variabel global biasa tidak akan berfungsi untuk Anda, karena mereka tidak aman untuk thread. Pada setiap titik waktu, Anda dapat memiliki beberapa rantai panggilan, dan masing-masing membutuhkan konteksnya sendiri. Modul
threading
akan membantu Anda, ia menyediakan objek
threading.local()
, yang aman untuk thread. Anda dapat menyimpan data di dalamnya dengan akses sederhana ke atribut:
threading.local().symbol = '@'
.
Namun, kedua pendekatan yang dijelaskan tidak aman-konkurensi, yaitu, mereka tidak cocok untuk rantai panggilan Coroutine, di mana sistem tidak hanya memanggil fungsi, tetapi juga mengharapkan mereka untuk dieksekusi. Ketika coroutine dieksekusi
await
, suatu aliran peristiwa dapat memicu coroutine lain dari rantai yang berbeda. Ini tidak akan berfungsi:
import asyncio import sys global_symbol = '.' async def indication(timeout): while True: print(global_symbol, end='') sys.stdout.flush() await asyncio.sleep(timeout) async def sleep(t, indication_t, symbol='.'): loop = asyncio.get_event_loop() global global_symbol global_symbol = symbol task = loop.create_task( indication(indication_t) ) await asyncio.sleep(t) task.cancel() loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather( sleep(1, 0.1, '0'), sleep(1, 0.1, 'a'), sleep(1, 0.1, 'b'), sleep(1, 0.1, 'c'), ))
Anda dapat memperbaikinya dengan memaksa siklus untuk mengatur dan mengembalikan konteks setiap kali Anda beralih di antara coroutine. Anda bisa menerapkan perilaku ini menggunakan modul
contextvars
, yang telah tersedia sejak Python 3.7.
import asyncio import sys import contextvars global_symbol = contextvars.ContextVar('symbol') async def indication(timeout): while True: print(global_symbol.get(), end='') sys.stdout.flush() await asyncio.sleep(timeout) async def sleep(t, indication_t, symbol='.'): loop = asyncio.get_event_loop() global_symbol.set(symbol) task = loop.create_task(indication(indication_t)) await asyncio.sleep(t) task.cancel() loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.gather( sleep(1, 0.1, '0'), sleep(1, 0.1, 'a'), sleep(1, 0.1, 'b'), sleep(1, 0.1, 'c'), ))