@Pythonetc kompilasi, Juli 2019


Ini adalah kumpulan kedua belas tips Python dan pemrograman dari feed @pythonetc saya.

Koleksi sebelumnya


Anda 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'), )) 

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


All Articles