Ini adalah pilihan baru tips dan trik tentang Python dan pemrograman dari saluran-Telegram saya @pythonetc.
â
Publikasi sebelumnyaAnda tidak dapat bermutasi variabel penutupan hanya dengan menetapkan mereka. Python memperlakukan penugasan sebagai definisi di dalam fungsi tubuh dan tidak membuat penutupan sama sekali.
Berfungsi dengan baik, mencetak
2
:
def make_closure(x): def closure(): print(x) return closure make_closure(2)
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 membuatnya berfungsi, Anda harus menggunakan
nonlocal
. Ini secara eksplisit memberi tahu juru bahasa untuk tidak memperlakukan penugasan sebagai sebuah definisi:
def make_closure(x): def closure(): nonlocal x print(x) x *= 2 print(x) return closure make_closure(2)()
Kadang-kadang selama iterasi Anda mungkin ingin tahu apakah itu elemen pertama atau terakhir dari iterasi. Cara sederhana untuk menangani ini adalah dengan menggunakan bendera 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, ]
Anda juga dapat memproses elemen pertama di luar loop, yang mungkin tampak lebih jelas tetapi mengarah pada duplikasi kode sampai batas tertentu. Ini juga bukan hal yang mudah dilakukan saat bekerja dengan iterables 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 bisa menggunakan
enumerate
dan memeriksa
i == 0
(hanya berfungsi untuk mendeteksi elemen pertama, bukan yang terakhir), tetapi solusi utamanya mungkin generator yang mengembalikan bendera
first
dan
last
bersama dengan elemen dari sebuah 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
Fungsi awal sekarang 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 ingin mengukur waktu antara dua peristiwa, Anda harus menggunakan
time.monotonic()
alih-alih
time.time()
.
time.monotonic()
tidak pernah mundur meskipun jam sistem diperbarui:
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 membuat mereka tahu dengan menelurkan manajer konteks internal oleh yang luar:
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()

Jika Anda ingin melewatkan beberapa informasi di rantai panggilan, Anda biasanya menggunakan cara yang paling mudah: Anda meneruskannya sebagai argumen fungsi.
Namun, dalam beberapa kasus, mungkin sangat tidak nyaman untuk memodifikasi semua fungsi dalam rantai untuk menyebarkan beberapa data baru. Sebagai gantinya, Anda mungkin ingin mengatur semacam konteks untuk digunakan oleh semua fungsi di rantai. Bagaimana konteks ini dilakukan secara teknis?
Solusi paling sederhana adalah variabel global. Dalam Python, Anda juga dapat menggunakan modul dan kelas sebagai pemegang konteks karena mereka juga variabel global. Anda mungkin melakukannya setiap hari untuk hal-hal seperti penebang.
Jika aplikasi Anda multi-utas, variabel global kosong tidak akan berfungsi untuk Anda karena tidak aman-utas. Anda mungkin memiliki lebih dari satu rantai panggilan berjalan pada saat yang sama, dan masing-masing membutuhkan konteksnya sendiri. Modul
threading
Anda, ia menyediakan objek
threading.local()
yang aman untuk thread. Simpan di sana data apa pun hanya dengan mengakses atribut:
threading.local().symbol = '@'
.
Namun, kedua pendekatan itu konkurensi-tidak aman yang berarti mereka tidak akan bekerja untuk coroutine call-chain di mana fungsi tidak hanya dipanggil tetapi dapat ditunggu juga. Setelah coroutine
await
, loop acara dapat menjalankan coroutine yang sama sekali berbeda dari rantai yang sama sekali berbeda. Itu tidak akan berhasil:
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 mengatur loop dan mengembalikan konteks setiap kali beralih di antara coroutine. Anda dapat melakukannya dengan modul
contextvars
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'), ))