Na Codewars.com napotkałem następujące zadanie:
Utwórz funkcję,
add
która dodaje liczby do siebie, gdy są wywoływane po kolei. Więcadd(1)
powinien wrócić1
,add(1)(2)
powinien wrócić1+2
, ...
Chociaż znam podstawy Pythona, nigdy nie spotkałem funkcji, którą można wywołać w takiej kolejności, tj. Funkcji, f(x)
którą można nazwać as f(x)(y)(z)...
. Jak dotąd nie jestem nawet pewien, jak zinterpretować ten zapis.
Jako matematyk podejrzewałbym, że f(x)(y)
jest to funkcja, która przypisuje każdej x
funkcji, g_{x}
a następnie zwraca g_{x}(y)
i podobnie dla f(x)(y)(z)
.
Gdyby ta interpretacja była poprawna, Python pozwoliłby mi dynamicznie tworzyć funkcje, które wydają mi się bardzo interesujące. Szukałem w sieci przez ostatnią godzinę, ale nie mogłem znaleźć tropu we właściwym kierunku. Ponieważ nie wiem, jak nazywa się ta koncepcja programowania, może to nie być zaskakujące.
Jak nazywasz tę koncepcję i gdzie mogę przeczytać o niej więcej?
źródło
functools.partial()
| WP: ZamknięciaOdpowiedzi:
Nie wiem, czy jest to łańcuch funkcji tak samo, jak wywoływalny łańcuch, ale ponieważ funkcje są wywoływane, myślę, że nie wyrządzono żadnej szkody. Tak czy inaczej, mogę pomyśleć o zrobieniu tego na dwa sposoby:
Podklasy
int
i definiowanie__call__
:Pierwszym sposobem byłaby niestandardowa
int
podklasa, która określa,__call__
która zwraca nowe wystąpienie samej siebie ze zaktualizowaną wartością:class CustomInt(int): def __call__(self, v): return CustomInt(self + v)
Funkcja
add
może być teraz zdefiniowany zwrócićCustomInt
instancji, jak wywoływalnym która zwraca zaktualizowaną wartość sama w sobie, mogą być wywoływane z rzędu:>>> def add(v): ... return CustomInt(v) >>> add(1) 1 >>> add(1)(2) 3 >>> add(1)(2)(3)(44) # and so on.. 50
Ponadto, jako
int
podklasa, zwrócona wartość utrzymuje się__repr__
i__str__
zachowanieint
s. Jednak w przypadku bardziej złożonych operacji należy odpowiednio zdefiniować inne dundery .Jak zauważył @Caridorc w komentarzu,
add
można go również zapisać jako:Zmiana nazwy klasy na
add
zamiastCustomInt
działa również podobnie.Zdefiniuj zamknięcie, wymaga dodatkowego wezwania do uzyskania wartości:
Jedyny inny sposób, jaki przychodzi mi do głowy, dotyczy funkcji zagnieżdżonej, która wymaga dodatkowego wywołania pustego argumentu w celu zwrócenia wyniku. Ja nie używając
nonlocal
i opt do mocowania atrybutów do obiektów funkcyjnych, aby go przenośny między pyton: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
To w sposób ciągły zwraca siebie (
_inner_adder
), które, jeślival
podano a, zwiększa je (_inner_adder += val
), a jeśli nie, zwraca wartość taką, jaka jest. Jak wspomniałem, wymaga dodatkowego()
wywołania w celu zwrócenia zwiększonej wartości:>>> add(1)(2)() 3 >>> add(1)(2)(3)() # and so on.. 6
źródło
add = CostumInt
powinno działać i być prostsze.(2*add(1)(2))(3)
nie działa,TypeError
ponieważint
nie można wywołać. ZasadniczoCustomInt
jest konwertowany na zwykły,int
gdy jest używany w dowolnym kontekście, z wyjątkiem wywoływania. Aby uzyskać bardziej solidne rozwiązanie, musisz w zasadzie ponownie zaimplementować wszystkie__*__
metody, w tym__r*__
wersje ...CustomInt
w ogóle, aleadd
podczas definiowania.Możesz mnie nienawidzić, ale tutaj jest jedna linijka :)
add = lambda v: type("", (int,), {"__call__": lambda self, v: self.__class__(self + v)})(v)
Edycja: OK, jak to działa? Kod jest identyczny z odpowiedzią @Jim, ale wszystko dzieje się w jednej linii.
type
mogą być wykorzystane do skonstruowania nowych typów:type(name, bases, dict) -> a new type
. Ponieważname
podajemy pusty ciąg, ponieważ nazwa nie jest w tym przypadku potrzebna. Dlabases
(krotka) podajemy znak(int,)
, który jest identyczny z dziedziczeniemint
.dict
to atrybuty klas, do których dołączamy__call__
lambdę.self.__class__(self + v)
jest identyczny zreturn CustomInt(self + v)
źródło
class add(int):__call__ = lambda self, v: add(self+v)
Jeśli chcesz zdefiniować funkcję, która ma być wywoływana wiele razy, najpierw musisz za każdym razem zwrócić wywoływalny obiekt (na przykład funkcję), w przeciwnym razie musisz utworzyć własny obiekt przez zdefiniowanie
__call__
atrybutu, aby był wywoływalny.Następnym punktem jest to, że musisz zachować wszystkie argumenty, co w tym przypadku oznacza, że możesz chcieć użyć Coroutines lub funkcji rekurencyjnej. Należy jednak pamiętać, że programy korekcyjne są znacznie bardziej zoptymalizowane / elastyczne niż funkcje rekurencyjne , szczególnie w przypadku takich zadań.
Oto przykładowa funkcja używająca Coroutines, która zachowuje swój najnowszy stan. Zauważ, że nie można go wywołać wiele razy, ponieważ zwracana wartość jest wartością,
integer
której nie można wywołać, ale możesz pomyśleć o przekształceniu tego w oczekiwany obiekt ;-).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
źródło
Pythonowym sposobem na to byłoby użycie dynamicznych argumentów:
def add(*args): return sum(args)
To nie jest odpowiedź, której szukasz i możesz o tym wiedzieć, ale pomyślałem, że i tak jej udzielę, ponieważ gdyby ktoś zastanawiał się nad zrobieniem tego nie z ciekawości, ale z pracy. Prawdopodobnie powinni mieć „właściwą rzecz do zrobienia”.
źródło
add = sum
gdybyś jechał tą trasąPo prostu:
class add(int): def __call__(self, n): return add(self + n)
źródło
Jeśli chcesz zaakceptować dodatkowy wynik
()
w celu pobrania wyniku, możesz użyćfunctools.partial
:from functools import partial def add(*args, result=0): return partial(add, result=sum(args)+result) if args else result
Na przykład:
>>> 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
Umożliwia to również określenie wielu numerów jednocześnie:
>>> add(1, 2, 3)(4, 5)(6)() 21
Jeśli chcesz ograniczyć to do jednego numeru, możesz wykonać następujące czynności:
def add(x=None, *, result=0): return partial(add, result=x+result) if x is not None else result
Jeśli chcesz
add(x)(y)(z)
łatwo zwrócić wynik i być dalej wywoływanym, najlepszym rozwiązaniemint
jest klasyfikowanie podklasy .źródło