Ik kan er niet achter komen hoe het gedeeltelijke werkt in functools.
Ik heb de volgende code van hier:
>>> sum = lambda x, y : x + y
>>> sum(1, 2)
3
>>> incr = lambda y : sum(1, y)
>>> incr(2)
3
>>> def sum2(x, y):
return x + y
>>> incr2 = functools.partial(sum2, 1)
>>> incr2(4)
5
Nu in de rij
incr = lambda y : sum(1, y)
Ik begrijp dat welk argument ik ook doorgeef aan incr
het zal worden doorgegeven als y
aan lambda
wat sum(1, y)
dwz 1 + y
.
Dat begrijp ik. Maar ik begreep deze incr2(4)
niet.
Hoe wordt de 4
doorgegeven als x
in gedeeltelijke functie? Voor mij zou 4
de sum2
moeten vervangen. Wat is de relatie tussen x
en 4
?
Antwoord 1, autoriteit 100%
Ruwweg, partial
doet zoiets als dit (afgezien van ondersteuning voor trefwoordargs, enz.):
def partial(func, *part_args):
def wrapper(*extra_args):
args = list(part_args)
args.extend(extra_args)
return func(*args)
return wrapper
Dus, door partial(sum2, 4)
te bellen, u een nieuwe functie (een vulbaar, om nauwkeurig te zijn) die zich gedraagt als sum2
, maar heeft een positioneel argument minder. Dat ontbrekende argument is altijd gesubstitueerd met 4
, zodat partial(sum2, 4)(2) == sum2(4, 2)
Wat betreft waarom het nodig is, is er een verscheidenheid aan gevallen. Gewoon voor één, stel dat je ergens een functie moet passeren waar het naar verwachting 2 argumenten heeft:
class EventNotifier(object):
def __init__(self):
self._listeners = []
def add_listener(self, callback):
''' callback should accept two positional arguments, event and params '''
self._listeners.append(callback)
# ...
def notify(self, event, *params):
for f in self._listeners:
f(event, params)
Maar een functie die u al hebt nodig heeft toegang tot een derde context
Object om zijn taak te doen:
def log_event(context, event, params):
context.log_event("Something happened %s, %s", event, params)
Dus er zijn verschillende oplossingen:
Een aangepast object:
class Listener(object):
def __init__(self, context):
self._context = context
def __call__(self, event, params):
self._context.log_event("Something happened %s, %s", event, params)
notifier.add_listener(Listener(context))
Lambda:
log_listener = lambda event, params: log_event(context, event, params)
notifier.add_listener(log_listener)
Met gedeeltelijke partijen:
context = get_context() # whatever
notifier.add_listener(partial(log_event, context))
Van die drie is partial
de kortste en de snelste.
(Voor een meer complexe zaak wil je misschien een aangepast object).
Antwoord 2, autoriteit 41%
gedeeltenzijn ongelooflijk handig.
Bijvoorbeeld in een ‘pipelined’ reeks functieaanroepen (waarin de geretourneerde waarde van de ene functie het argument is dat aan de volgende wordt doorgegeven).
Soms vereist een functie in zo’n pijplijn een enkel argument, maar de functie onmiddellijk stroomopwaarts ervan retourneert twee waarden.
In dit scenario kunt u met functools.partial
deze functiepijplijn intact houden.
Hier is een specifiek, geïsoleerd voorbeeld: stel dat u sommige gegevens wilt sorteren op de afstand van elk gegevenspunt tot een doel:
# create some data
import random as RND
fnx = lambda: RND.randint(0, 10)
data = [ (fnx(), fnx()) for c in range(10) ]
target = (2, 4)
import math
def euclid_dist(v1, v2):
x1, y1 = v1
x2, y2 = v2
return math.sqrt((x2 - x1)**2 + (y2 - y1)**2)
Als u deze gegevens wilt sorteren op afstand tot het doel, wilt u natuurlijk het volgende doen:
data.sort(key=euclid_dist)
maar dat kan niet: de parameter keyvan de sorteer-methode accepteert alleen functies waarvoor een enkel-argument nodig is.
dus herschrijf euclid_dist
als een functie met een enkeleparameter:
from functools import partial
p_euclid_dist = partial(euclid_dist, target)
p_euclid_dist
Nu accepteert een enkel argument,
>>> p_euclid_dist((3, 3))
1.4142135623730951
Dus nu kunt u uw gegevens sorteren door de gedeeltelijke functie voor het sleutelargument van de Sorteermethode te doorgeven:
data.sort(key=p_euclid_dist)
# verify that it works:
for p in data:
print(round(p_euclid_dist(p), 3))
1.0
2.236
2.236
3.606
4.243
5.0
5.831
6.325
7.071
8.602
of bijvoorbeeld een van de argumenten van de functie verandert in een buitenste lus, maar wordt vastgelegd tijdens de iteratie in de binnenste lus. Door een gedeeltelijk te gebruiken, hoeft u niet de extra parameter in te dienen tijdens de iteratie van de binnenste lus, omdat de gewijzigde (gedeeltelijke) functie het niet vereist.
>>> from functools import partial
>>> def fnx(a, b, c):
return a + b + c
>>> fnx(3, 4, 5)
12
Maak een gedeeltelijke functie (met behulp van trefwoord arg)
>>> pfnx = partial(fnx, a=12)
>>> pfnx(b=4, c=5)
21
U kunt ook een gedeeltelijke functie maken met een positioneel argument
>>> pfnx = partial(fnx, 12)
>>> pfnx(4, 5)
21
Maar dit zal gooien (bijvoorbeeld het creëren van gedeeltelijke met trefwoord argument en vervolgens bellen met behulp van positionele argumenten)
>>> pfnx = partial(fnx, a=12)
>>> pfnx(4, 5)
Traceback (most recent call last):
File "<pyshell#80>", line 1, in <module>
pfnx(4, 5)
TypeError: fnx() got multiple values for keyword argument 'a'
een andere use-case: het schrijven van gedistribueerde code met behulp van de multiprocessing
-bibliotheek van python. Er wordt een pool van processen gemaakt met behulp van de methode Pool:
>>> import multiprocessing as MP
>>> # create a process pool:
>>> ppool = MP.Pool()
Pool
heeft een kaartmethode, maar er is maar één iterable voor nodig, dus als u een functie met een langere parameterlijst moet invoeren, definieert u de functie opnieuw als een gedeeltelijke, om dit op te lossen alles behalve één:
>>> ppool.map(pfnx, [4, 6, 7, 8])
Antwoord 3, autoriteit 16%
kort antwoord, partial
geeft standaardwaarden aan de parameters van een functie die anders geen standaardwaarden zouden hebben.
from functools import partial
def foo(a,b):
return a+b
bar = partial(foo, a=1) # equivalent to: foo(a=1, b)
bar(b=10)
#11 = 1+10
bar(a=101, b=10)
#111=101+10
Antwoord 4, autoriteit 13%
Partials kunnen worden gebruikt om nieuwe afgeleide functies te maken waaraan vooraf bepaalde invoerparameters zijn toegewezen
Raadpleeg deze echt goede blogpost hier
Een eenvoudig maar leuk beginnersvoorbeeld van de blog, beschrijft hoe je partial
op re.search
kunt gebruiken om code leesbaarder te maken. De handtekening van de re.search
-methode is:
search(pattern, string, flags=0)
Door het toepassen van partial
kunnen we meerdere versies van de reguliere expressie maken search
om aan onze vereisten te voldoen, dus bijvoorbeeld:
is_spaced_apart = partial(re.search, '[a-zA-Z]\s\=')
is_grouped_together = partial(re.search, '[a-zA-Z]\=')
Nu is_spaced_apart
EN is_grouped_together
zijn twee nieuwe functies die zijn afgeleid van re.search
met de pattern
argument (Aangezien pattern
is het eerste argument in de re.search
METHODES handtekening).
De handtekening van deze twee nieuwe functies (Callable) is:
is_spaced_apart(string, flags=0) # pattern '[a-zA-Z]\s\=' applied
is_grouped_together(string, flags=0) # pattern '[a-zA-Z]\=' applied
Hiermee kunt u deze gedeeltelijke functies op sommige tekst gebruiken:
for text in lines:
if is_grouped_together(text):
some_action(text)
elif is_spaced_apart(text):
some_other_action(text)
else:
some_default_action()
U kunt de link hierboven om te krijgen Een meer diepgaand inzicht in het onderwerp, omdat het dit specifieke voorbeeld en nog veel meer omvat ..
Antwoord 5, Autoriteit 4%
Naar mijn mening is het een manier om currying in Python te implementeren.
from functools import partial
def add(a,b):
return a + b
def add2number(x,y,z):
return x + y + z
if __name__ == "__main__":
add2 = partial(add,2)
print("result of add2 ",add2(1))
add3 = partial(partial(add2number,1),2)
print("result of add3",add3(1))
Het resultaat is 3 en 4.
Antwoord 6
Het is ook de moeite waard om te vermelden dat wanneer een gedeeltelijke functie een andere functie passeert waarbij we enkele parameters willen “hardcoderen”, dat de meest rechtse parameter zou moeten zijn
def func(a,b):
return a*b
prt = partial(func, b=7)
print(prt(4))
#return 28
maar als we hetzelfde doen, maar in plaats daarvan een parameter wijzigen
def func(a,b):
return a*b
prt = partial(func, a=7)
print(prt(4))
het zal een foutmelding geven,
“TypeError: func() heeft meerdere waarden voor argument ‘a'”
Antwoord 7
Dit antwoord is meer een voorbeeldcode. Alle bovenstaande antwoorden geven een goede uitleg over waarom men gedeeltelijk zou moeten gebruiken. Ik zal mijn observaties en use cases geven over gedeeltelijk.
from functools import partial
def adder(a,b,c):
print('a:{},b:{},c:{}'.format(a,b,c))
ans = a+b+c
print(ans)
partial_adder = partial(adder,1,2)
partial_adder(3) ## now partial_adder is a callable that can take only one argument
Uitvoer van de bovenstaande code moet zijn:
a:1,b:2,c:3
6
Merk op dat in het bovenstaande voorbeeld een nieuw vulbaar is geretourneerd dat parameter (C) zal nemen als het argument. Merk op dat het ook het laatste argument is voor de functie.
args = [1,2]
partial_adder = partial(adder,*args)
partial_adder(3)
Uitgang van de bovenstaande code is ook:
a:1,b:2,c:3
6
Merk op dat * werd gebruikt om de niet-trefwoordargumenten uit te pakken en het vulbare geretourneerd in termen van welk argument dat het kan nemen, is hetzelfde als hierboven.
Een andere observatie is:
Hieronder toont voorbeeld aan dat gedeeltelijke retourneert een callable die de
undeclared parameter (a) als een argument.
def adder(a,b=1,c=2,d=3,e=4):
print('a:{},b:{},c:{},d:{},e:{}'.format(a,b,c,d,e))
ans = a+b+c+d+e
print(ans)
partial_adder = partial(adder,b=10,c=2)
partial_adder(20)
Uitgang van de bovenstaande code moet zijn:
a:20,b:10,c:2,d:3,e:4
39
Evenzo,
kwargs = {'b':10,'c':2}
partial_adder = partial(adder,**kwargs)
partial_adder(20)
hierboven-codeafdrukken
a:20,b:10,c:2,d:3,e:4
39
Ik moest het gebruiken toen ik Pool.map_async
methode van multiprocessing
module. U kunt slechts één argument voor de werknemersfunctie doorstaan, zodat ik partial
moest gebruiken om mijn werkfunctie eruit te laten zien als een vulbaar met slechts één ingangsargument, maar in werkelijkheid had mijn werknemersfunctie meerdere ingangsargumenten.