Di Codewars.com saya mengalami tugas berikut:
Buat fungsi
add
yang menjumlahkan angka saat dipanggil secara berurutan. Jadiadd(1)
harus kembali1
,add(1)(2)
harus kembali1+2
, ...
Meskipun saya terbiasa dengan dasar-dasar Python, saya belum pernah menemukan fungsi yang dapat dipanggil secara berurutan, yaitu fungsi f(x)
yang dapat disebut sebagai f(x)(y)(z)...
. Sejauh ini, saya bahkan tidak yakin bagaimana menafsirkan notasi ini.
Sebagai ahli matematika, saya menduga itu f(x)(y)
adalah fungsi yang menetapkan ke setiap x
fungsi g_{x}
dan kemudian mengembalikan g_{x}(y)
dan juga untuk f(x)(y)(z)
.
Jika interpretasi ini benar, Python akan memungkinkan saya untuk membuat fungsi secara dinamis yang tampaknya sangat menarik bagi saya. Saya telah menelusuri web selama satu jam terakhir, tetapi tidak dapat menemukan petunjuk ke arah yang benar. Karena saya tidak tahu bagaimana konsep pemrograman ini disebut, bagaimanapun, ini mungkin tidak terlalu mengejutkan.
Bagaimana Anda menyebut konsep ini dan di mana saya dapat membaca lebih banyak tentangnya?
sumber
functools.partial()
| WP: PenutupanJawaban:
Saya tidak tahu apakah ini function chaining sebanyak callable chaining, tapi, karena function adalah callable, saya rasa tidak ada kerusakan yang terjadi. Bagaimanapun, ada dua cara yang dapat saya pikirkan untuk melakukan ini:
Sub-klasifikasi
int
dan definisi__call__
:Cara pertama adalah dengan
int
subkelas khusus yang menentukan__call__
mana yang mengembalikan instance baru dengan nilai yang diperbarui:class CustomInt(int): def __call__(self, v): return CustomInt(self + v)
Fungsi
add
sekarang dapat didefinisikan untuk mengembalikan sebuahCustomInt
instance, yang, sebagai callable yang mengembalikan nilai yang diperbarui, dapat dipanggil secara berurutan:>>> def add(v): ... return CustomInt(v) >>> add(1) 1 >>> add(1)(2) 3 >>> add(1)(2)(3)(44) # and so on.. 50
Selain itu, sebagai
int
subclass, nilai yang dikembalikan mempertahankan__repr__
dan__str__
perilakuint
s. Untuk operasi yang lebih kompleks, Anda harus mendefinisikan dunder lain dengan tepat .Seperti yang dicatat @Caridorc dalam komentar,
add
bisa juga ditulis sebagai:Mengganti nama kelas menjadi
add
bukannyaCustomInt
juga berfungsi serupa.Tentukan closure, membutuhkan panggilan ekstra untuk menghasilkan nilai:
Satu-satunya cara lain yang dapat saya pikirkan melibatkan fungsi bersarang yang membutuhkan panggilan argumen kosong ekstra untuk mengembalikan hasilnya. Saya tidak menggunakan
nonlocal
dan memilih untuk melampirkan atribut ke objek fungsi agar portabel di antara Pythons:def add(v): def _inner_adder(val=None): """ if val is None we return _inner_adder.v else we increment and return ourselves """ if val is None: return _inner_adder.v _inner_adder.v += val return _inner_adder _inner_adder.v = v # save value return _inner_adder
Ini terus menerus mengembalikan dirinya sendiri (
_inner_adder
) yang, jika aval
diberikan, menambahnya (_inner_adder += val
) dan jika tidak, mengembalikan nilai apa adanya. Seperti yang saya sebutkan, ini membutuhkan()
panggilan ekstra untuk mengembalikan nilai yang bertambah:>>> add(1)(2)() 3 >>> add(1)(2)(3)() # and so on.. 6
sumber
add = CostumInt
harus bekerja juga dan lebih sederhana.(2*add(1)(2))(3)
gagal denganTypeError
karenaint
tidak dapat dipanggil. Pada dasarnyaCustomInt
diubah menjadi polosint
saat digunakan dalam konteks apa pun kecuali saat memanggil. Untuk solusi yang lebih kuat pada dasarnya Anda harus menerapkan ulang semua__*__
metode termasuk__r*__
versi ...CustomInt
sama sekali kecualiadd
saat mendefinisikannya.Anda bisa membenci saya, tapi ini satu kalimat :)
add = lambda v: type("", (int,), {"__call__": lambda self, v: self.__class__(self + v)})(v)
Edit: Ok, bagaimana ini bekerja? Kode tersebut identik dengan jawaban @Jim, tetapi semuanya terjadi dalam satu baris.
type
dapat digunakan untuk membangun jenis baru:type(name, bases, dict) -> a new type
. Karenaname
kami menyediakan string kosong, karena nama tidak terlalu dibutuhkan dalam kasus ini. Untukbases
(tuple) kami menyediakan(int,)
, yang identik dengan mewarisiint
.dict
adalah atribut kelas, tempat kami melampirkan__call__
lambda.self.__class__(self + v)
identik denganreturn CustomInt(self + v)
sumber
class add(int):__call__ = lambda self, v: add(self+v)
Jika Anda ingin mendefinisikan suatu fungsi untuk dipanggil beberapa kali, pertama-tama Anda perlu mengembalikan objek yang dapat dipanggil setiap kali (misalnya fungsi) jika tidak Anda harus membuat objek Anda sendiri dengan mendefinisikan
__call__
atribut, agar dapat dipanggil.Poin berikutnya adalah Anda perlu mempertahankan semua argumen, yang dalam hal ini berarti Anda mungkin ingin menggunakan Coroutine atau fungsi rekursif. Tetapi perhatikan bahwa Coroutine jauh lebih dioptimalkan / fleksibel daripada fungsi rekursif , khususnya untuk tugas-tugas semacam itu.
Berikut adalah contoh fungsi yang menggunakan Coroutines, yang mempertahankan status terbaru itu sendiri. Perhatikan bahwa itu tidak dapat dipanggil berkali-kali karena nilai yang dikembalikan adalah
integer
yang tidak dapat dipanggil, tetapi Anda mungkin berpikir untuk mengubahnya menjadi objek yang Anda harapkan ;-).def add(): current = yield while True: value = yield current current = value + current it = add() next(it) print(it.send(10)) print(it.send(2)) print(it.send(4)) 10 12 16
sumber
Cara pythonic untuk melakukan ini adalah dengan menggunakan argumen dinamis:
def add(*args): return sum(args)
Ini bukan jawaban yang Anda cari, dan Anda mungkin tahu ini, tetapi saya pikir saya akan tetap memberikannya karena jika seseorang bertanya-tanya tentang melakukan ini bukan karena penasaran tetapi untuk pekerjaan. Mereka mungkin harus memiliki jawaban "yang benar untuk dilakukan".
sumber
add = sum
jika pergi ke rute ituJika Anda bersedia menerima tambahan
()
untuk mendapatkan hasil, Anda dapat menggunakanfunctools.partial
:from functools import partial def add(*args, result=0): return partial(add, result=sum(args)+result) if args else result
Sebagai contoh:
>>> add(1) functools.partial(<function add at 0x7ffbcf3ff430>, result=1) >>> add(1)(2) functools.partial(<function add at 0x7ffbcf3ff430>, result=3) >>> add(1)(2)() 3
Ini juga memungkinkan menentukan beberapa nomor sekaligus:
>>> add(1, 2, 3)(4, 5)(6)() 21
Jika Anda ingin membatasinya ke satu nomor, Anda dapat melakukan hal berikut:
def add(x=None, *, result=0): return partial(add, result=x+result) if x is not None else result
Jika Anda ingin
add(x)(y)(z)
segera mengembalikan hasilnya dan dapat dipanggil lebih lanjut maka sub-klasifikasiint
adalah cara yang harus dilakukan.sumber
Secara sederhana:
class add(int): def __call__(self, n): return add(self + n)
sumber