Functools -Kekuatan Fungsi Tingkat Tinggi dengan Python

Node Sumber: 1865357

Artikel ini diterbitkan sebagai bagian dari Blogathon Ilmu Data

Pengantar

Pustaka Standar Python memiliki banyak modul hebat untuk membantu menjaga kode Anda lebih bersih dan sederhana dan functools pasti salah satunya

caching

Mari kita mulai dengan beberapa fungsi modul functools yang paling sederhana namun kuat. Mari kita mulai dengan fungsi caching (serta dekorator) โ€“ lru_cache,cache dan cached_property. Yang pertama dari mereka โ€“ lru_cache menyediakan cache dari hasil terakhir dari eksekusi fungsi, atau dengan kata lain, mengingat hasil pekerjaannya:

from functools import lru_cache
import requests @lru_cache(maxsize=32)
def get_with_cache(url): try: r = requests.get(url) return r.text except: return "Not Found" for url in ["https://google.com/", "https://reddit.com/", "https://google.com/", "https://google.com/"]: get_with_cache(url) print(get_with_cache.cache_info())
# CacheInfo(hits=2, misses=4, maxsize=32, currsize=4)
print(get_with_cache.cache_parameters())
# {'maxsize': 32, 'typed': False}

Dalam contoh ini, kami membuat permintaan GET dan meng-cache hasilnya (hingga 32 hasil) menggunakan dekorator @lru_cache. Untuk melihat apakah caching benar-benar berfungsi, Anda dapat memeriksa fungsi informasi cache menggunakan metode cache_infoyang menunjukkan jumlah hit dan hit cache. Dekorator juga menyediakan metode clear_cachedan cache_parametersuntuk pembatalan masing-masing hasil cache dan parameter pengujian.

Jika Anda memerlukan caching yang lebih terperinci, Anda bisa menyertakan argumen opsional typed=true, yang memungkinkan Anda menyimpan berbagai jenis argumen secara terpisah.

Dekorator lain untuk caching di functools adalah fungsi yang disebut sederhana cache. Ini adalah pembungkus sederhana lru_cacheyang menghilangkan argumen max_size, menguranginya, dan tidak menghapus nilai lama.

Dekorator lain yang dapat Anda gunakan untuk caching adalah cached_property. Seperti namanya, ini digunakan untuk menyimpan hasil atribut kelas. Mekanik ini sangat berguna jika Anda memiliki properti yang mahal untuk dihitung, tetapi tetap sama.

from functools import cached_property class Page: @cached_property def render(self, value): # Do something with supplied value... # Long computation that renders HTML page... return html

Contoh sederhana ini menunjukkan. Seperti yang dapat digunakan, properti yang di-cache, misalnya, untuk meng-cache halaman HTML yang dirender yang perlu ditampilkan kepada pengguna berulang kali. Hal yang sama dapat dilakukan untuk kueri basis data tertentu atau perhitungan matematika yang panjang.

Keindahan lain cached_propertyapakah itu hanya berjalan pada pencarian, sehingga memungkinkan kita untuk mengubah nilai atribut. Setelah mengubah atribut, nilai yang di-cache sebelumnya tidak akan berubah, sebagai gantinya, nilai baru akan dihitung dan di-cache. Anda juga dapat menghapus cache, dan yang perlu Anda lakukan hanyalah menghapus atributnya.

Saya ingin mengakhiri bagian ini dengan peringatan tentang semua dekorator di atas โ€“ jangan menggunakannya jika fungsi Anda memiliki beberapa efek samping atau jika itu membuat objek yang dapat diubah setiap kali dipanggil karena ini jelas bukan fungsi yang ingin Anda simpan dalam cache .

Perbandingan dan pengurutan

Anda mungkin sudah tahu bahwa Anda dapat mengimplementasikan operator pembanding di Python seperti <, >=or ==, dengan lt, gtor eq. Namun, bisa sangat frustasi untuk menyadari masing-masing eq, lt, le, gtor ge. Untungnya, functools ada dekoratornya @total_orderingyang dapat membantu kami dalam hal ini, karena yang perlu kami terapkan hanyalah eqsalah satu metode yang tersisa, dan dekorator lainnya akan dihasilkan secara otomatis.

from functools import total_ordering @total_ordering
class Number: def __init__(self, value): self.value = value def __lt__(self, other): return self.value Number(3))
# True
print(Number(1) = Number(15))
# True
print(Number(10) <= Number(2))
# False

Dengan cara ini, kita dapat mengimplementasikan semua operasi perbandingan yang diperluas, meskipun hanya memiliki eq dan dengan tangan lt. Manfaat yang paling jelas adalah kenyamanannya, yaitu Anda tidak perlu menulis semua metode ajaib tambahan ini, tetapi mungkin lebih penting untuk mengurangi jumlah kode dan keterbacaannya yang lebih baik.

Overload

Kita semua mungkin telah diajari bahwa tidak ada kelebihan dalam Python, tetapi sebenarnya ada cara mudah untuk mengimplementasikannya menggunakan dua fungsi dari functools, yaitu pengiriman tunggal dan/atau metode pengiriman tunggal. Fungsi-fungsi ini membantu kami menerapkan apa yang kami sebut sebagai algoritme pengiriman berganda yang memungkinkan bahasa pemrograman yang diketik secara dinamis seperti Python untuk membedakan antara jenis saat runtime.

Sebagian

Kita semua bekerja dengan berbagai pustaka atau kerangka kerja eksternal, banyak di antaranya menyediakan fungsi dan antarmuka yang mengharuskan kita meneruskan panggilan balik, seperti untuk operasi asinkron atau mendengarkan acara. Ini bukanlah hal baru, tetapi bagaimana jika kita juga perlu menyampaikan beberapa argumen bersama dengan callback. Di sinilah functools berguna.partial. Dapat digunakan partialuntuk membekukan beberapa (atau semua) argumen ke suatu fungsi dengan membuat objek baru dengan tanda tangan fungsi yang disederhanakan. Bingung? Mari kita lihat beberapa contoh praktis:

def output_result(result, log=None): if log is not None: log.debug(f"Result is: {result}") def concat(a, b): return a + b import logging
from multiprocessing import Pool
from functools import partial logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("default") p = Pool()
p.apply_async(concat, ("Hello ", "World"), callback=partial(output_result, log=logger))
p.close()
p.join()

Kode di atas menunjukkan bagaimana Anda dapat menggunakannya partialuntuk melewatkan fungsi ( output_result) bersama dengan argumen ( log=logger) sebagai panggilan balik. Dalam hal ini, kami akan menggunakan multiprocessing.apply_async, yang menghitung hasil fungsi secara asinkron ( concat) dan mengembalikan hasil callback. Namun, apply_asynchasilnya akan selalu diteruskan sebagai argumen pertama, dan jika kita ingin memasukkan argumen tambahan, seperti halnya dengan log=logger, kita perlu menggunakan parsial.

Kami telah mempertimbangkan kasus penggunaan yang cukup canggih, dan contoh yang lebih sederhana adalah pembuatan fungsi biasa yang menulis di stderr alih-alih stdout:

import sys
from functools import partial print_stderr = partial(print, file=sys.stderr)
print_stderr("This goes to standard error output")

Dengan trik sederhana ini, kami membuat fungsi callable baru yang akan selalu lolos file=sys.stderrsebagai argumen bernama untuk keluaran, yang memungkinkan kita menyederhanakan kode kita dan tidak harus menentukan nilai argumen bernama setiap saat.

Dan satu contoh bagus terakhir. Kita bisa menggunakan partialdalam hubungannya dengan fungsi yang sedikit diketahui iteruntuk membuat iterator dengan meneruskan objek yang dapat dipanggil dan sentinelin iter, yang dapat diterapkan seperti ini:

from functools import partial RECORD_SIZE = 64 # Read binary file...
with open("file.data", "rb") as file: records = iter(partial(file.read, RECORD_SIZE), b'') for r in records: # Do something with the record...

Biasanya, saat membaca file, kita ingin mengulang baris, tetapi dalam kasus data biner, kita mungkin perlu mengulang catatan dengan ukuran tetap. Anda dapat melakukan ini dengan membuat objek yang dapat dipanggil menggunakan partialyang membaca potongan data yang ditentukan dan meneruskannya iteruntuk membuat iterator. Iterator ini kemudian memanggil fungsi baca hingga mencapai akhir file, selalu hanya mengambil ukuran potongan yang ditentukan ( RECORD_SIZE). Akhirnya, ketika akhir file tercapai, nilainya sentinel(b ") dikembalikan dan iterasi berhenti.

dekorator

Kita sudah membicarakan beberapa dekorator di bagian sebelumnya, tetapi bukan dekorator untuk membuat lebih banyak dekorator. Salah satu dekorator tersebut adalah functools.wraps. Untuk memahami mengapa Anda membutuhkannya, mari kita lihat sebuah contoh:

def decorator(func): def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") greet("Martin")
# Before Calling greet
# Hello, Martin!
# After Calling greet

Contoh ini menunjukkan bagaimana dekorator sederhana dapat diimplementasikan. Kami membungkus fungsi yang melakukan tugas tertentu ( actual_func) dengan dekorator eksternal, dan menjadi dekorator itu sendiri, yang kemudian dapat diterapkan ke fungsi lain, misalnya, seperti halnya dengan greet. Saat Anda memanggil fungsi, greet Anda akan melihat bahwa itu mencetak pesan dari keduanya actual_funcdan dengan sendirinya. Terlihat baik-baik saja, bukan? Tetapi apa yang terjadi jika kita melakukan ini:

print(greet.__name__)
# actual_func
print(greet.__doc__)
# Inner function within decorator, which does the actual work

Saat kami memanggil nama dan dokumentasi dari fungsi yang didekorasi, kami menyadari bahwa mereka telah diganti dengan nilai dari fungsi dekorator. Ini buruk karena kami tidak dapat menulis ulang semua nama fungsi dan dokumentasi kami saat kami menggunakan beberapa dekorator. Bagaimana masalah ini bisa diselesaikan? Tentunya dengan functools.wraps:

from functools import wraps def decorator(func): @wraps(func) def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") print(greet.__name__)
# greet
print(greet.__doc__)
# Says hello to somebody

Satu-satunya tujuan fungsi wrapsadalah menyalin nama, dokumentasi, daftar argumen, dll., untuk mencegah penimpaan. Mengingat bahwa wrapsitu juga merupakan dekorator, Anda cukup menambahkannya ke fungsi_aktual kami, dan masalahnya selesai!

Menurunkan

Last but not least dalam modul functools adalah pengurangan ini. Mungkin dari bahasa lain, Anda mungkin mengenalnya sebagai fold(Haskell). Fungsi ini mengambil iterable dan melipat (menambah) semua nilainya menjadi satu. Ada banyak aplikasi untuk ini, misalnya:

from functools import reduce
import operator def product(iterable): return reduce(operator.mul, iterable, 1) def factorial(n): return reduce(operator.mul, range(1, n)) def sum(numbers): # Use `sum` function from standard library instead return reduce(operator.add, numbers, 1) def reverse(iterable): return reduce(lambda x, y: y+x, iterable) print(product([1, 2, 3]))
# 6
print(factorial(5))
# 24
print(sum([2, 6, 8, 3]))
# 20
print(reverse("hello"))
# olleh

Seperti yang Anda lihat dari kode, reduce dapat menyederhanakan atau memadatkan kode menjadi satu baris, yang jika tidak akan menjadi lebih panjang. Karena itu, biasanya merupakan ide yang buruk untuk menyalahgunakan fungsi ini hanya demi menyusutkan kode, menjadikannya "lebih pintar", karena dengan cepat menjadi menakutkan dan tidak dapat dibaca. Untuk alasan ini, menurut saya, harus digunakan dengan hemat.

Dan jika Anda ingat bahwa itu reduce sering mempersingkat semuanya menjadi satu baris, itu dapat digabungkan dengan sempurna partial:

product = partial(reduce, operator.mul) print(product([1, 2, 3]))
# 6

Dan terakhir, jika Anda membutuhkan lebih dari sekadar hasil akhir "runtuh", maka Anda dapat menggunakannya accumulateโ€“ dari modul hebat lainnya itertools. Untuk menghitung maksimum, dapat digunakan sebagai berikut:

from itertools import accumulate data = [3, 4, 1, 3, 5, 6, 9, 0, 1] print(list(accumulate(data, max)))
# [3, 4, 4, 4, 5, 6, 9, 9, 9]

Kesimpulan

Seperti yang Anda lihat functools, ada banyak fungsi dan dekorator berguna yang dapat membuat hidup Anda lebih mudah, tetapi ini hanyalah puncak gunung es. Seperti yang saya katakan di awal, ada banyak fungsi di pustaka standar Python yang membantu Anda menulis kode yang lebih baik, jadi selain fungsi yang kami bahas di sini, Anda juga dapat memperhatikan modul lain, seperti operatoror itertool. Untuk pertanyaan apa pun, Anda dapat menekan kotak komentar saya. Saya akan mencoba yang terbaik untuk menyelesaikan input Anda dan berharap dapat memberi Anda hasil yang diinginkan. Anda juga dapat menghubungi saya di LinkedIn:-

https://www.linkedin.com/in/shivani-sharma-aba6141b6/

Media yang ditampilkan dalam artikel ini tidak dimiliki oleh Analytics Vidhya dan digunakan atas kebijaksanaan Penulis.

Sumber: https://www.analyticsvidhya.com/blog/2021/08/functools-the-power-of-higher-order-functions-in-python/

Stempel Waktu:

Lebih dari Analisis Vidhya