Hoe doet functools gedeeltelijk wat het doet?

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 incrhet zal worden doorgegeven als yaan lambdawat sum(1, y)dwz 1 + y.

Dat begrijp ik. Maar ik begreep deze incr2(4)niet.

Hoe wordt de 4doorgegeven als xin gedeeltelijke functie? Voor mij zou 4de sum2moeten vervangen. Wat is de relatie tussen xen 4?


Antwoord 1, autoriteit 100%

Ruwweg, partialdoet 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 contextObject 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 partialde 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.partialdeze 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_distals een functie met een enkeleparameter:

from functools import partial
p_euclid_dist = partial(euclid_dist, target)

p_euclid_distNu 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()

Poolheeft 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, partialgeeft 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 partialop re.searchkunt gebruiken om code leesbaarder te maken. De handtekening van de re.search-methode is:

search(pattern, string, flags=0) 

Door het toepassen van partialkunnen we meerdere versies van de reguliere expressie maken searchom 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_apartEN is_grouped_togetherzijn twee nieuwe functies die zijn afgeleid van re.searchmet de patternargument (Aangezien patternis het eerste argument in de re.searchMETHODES 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_asyncmethode van multiprocessingmodule. U kunt slechts één argument voor de werknemersfunctie doorstaan, zodat ik partialmoest gebruiken om mijn werkfunctie eruit te laten zien als een vulbaar met slechts één ingangsargument, maar in werkelijkheid had mijn werknemersfunctie meerdere ingangsargumenten.

Other episodes