Python-functies van hogere orde. De wereld van Python: functionaliteit in kleine manieren - Geavanceerde Python - Hexlet. Functioneert als objecten

2010-11-17 09:47

Met de kaart-, zip- en lambda-functies (die overigens ‘hogere-orde-functies’ of ‘eersteklas-functies’ worden genoemd) kun je heel eenvoudig verschillende gegevensmanipulaties uitvoeren, waarvoor je wat meer code moet schrijven in een ‘gewone’ versie. procedurele stijl. Alles wat hieronder wordt geschreven, heeft betrekking op de zogenaamde functionele programmering, kijk voor details.

Functiekaart, zip en lambda in voorbeelden.

Een eenvoudige taak is een lijst a = en een lijst b = van dezelfde lengte en je moet ze in paren samenvoegen. Het is net zo eenvoudig als het pellen van peren - met behulp van de functie ritssluiting :

a = [ 1 , 2 ] b = [ 3 , 4 ] print zip (a , b ) [(1 , 3 ), (2 , 4 )]

of in drietallen:

a = [ 1 , 2 ] b = [ 3 , 4 ] c = [ 5 , 6 ] print zip (a , b , c ) [(1 , 3 , 5 ), (2 , 4 , 6 )]

of meer in het algemeen

lijst = [a, b, c] print zip (* lijst) [(1, 3, 5), (2, 4, 6)]

Het sterretje * vóór lijst lijkt aan te geven dat er een lijst met argumenten wordt doorgegeven, d.w.z. Handelen is gelijk aan het doorgeven van a, b, c, d.w.z. Het is zelfs mogelijk ritssluiting afdrukken(*) het resultaat zal niet veranderen.

def f (x): return x * x nums = [ 1 , 2 , 3 ] voor num in nums: print f (num )

Een meer ervaren noob die het begrip van lijsten heeft bestudeerd:

def f (x): return x * x print [ f (num) voor aantal in nums ]

De programmeur zal het gemakkelijker maken:

def f (x): return x * x print kaart (f, nums)

En de echte medskills-hacker zal het als volgt doen (op voorwaarde natuurlijk dat de functie als lambda geschreven kan worden; de functie zal niet altijd eenvoudig genoeg zijn om als lambda geschreven te worden):

kaart afdrukken (lambda x: x * x, nums)

De laatste invoer is een voorbeeld van de meest competente aanpak. Het is een feit dat wanneer iemand code schrijft zoals poëzie, in een vlaag van inspiratie (die met andere woorden ‘in een wilde waanzin’ kan worden genoemd), de snelheid van schrijven buitengewoon belangrijk is (dit is waar de wortels van de eerbiedige liefde liggen). van veel ontwikkelaars voor eenvoudige teksteditors vim, emacs, sublimetext grow) , en de kracht van Python is precies de grootte van de gegenereerde code - het is erg compact. Het schrijven van één regel is uiteraard sneller dan zeven, en korte code is gemakkelijker te lezen, maar het schrijven van dergelijke code vereist een bepaalde vaardigheid. De andere kant van de medaille is dat in deze ‘wilde waanzin’ soms hele reeksen van tamelijk complexe acties op één regel worden geschreven, zozeer zelfs dat het erg moeilijk is om te begrijpen wat daar gebeurt en wat er uiteindelijk gebeurt.

Uit het voorbeeld wordt dat duidelijk kaart past een functie toe op een lijst en retourneert het resultaat, opnieuw in de vorm van een lijst. U kunt meerdere lijsten doorgeven, waarna de functie (die de eerste parameter is) verschillende argumenten moet hebben (afhankelijk van het aantal lijsten dat wordt doorgegeven aan kaart).

def f (x, y): return x * y a = [1, 3, 4] b = [3, 4, 5] print kaart (f, a, b) [3, 12, 20]

Cool, toch?

Als de lijsten echter verschillende lengtes hebben, d.w.z. De één is korter dan de ander, dan wordt deze met Geen waarden opgevuld tot de gewenste lengte. Indien verwijderd uit de lijst B de laatste waarde - het voorbeeld werkt niet, omdat In functie f wordt geprobeerd een getal met Geen te vermenigvuldigen, en Python staat dit niet toe, wat het overigens gunstig onderscheidt van php, dat in een vergelijkbare situatie zou blijven werken. Als de functie f groot genoeg is, zou het daarom een ​​goed idee zijn om de doorgegeven waarden te controleren. Bijvoorbeeld;

Als de functie wordt vervangen door Geen, dan kaart werkt ongeveer hetzelfde als ritssluiting, maar als de verzonden lijsten verschillende lengtes hebben, zal het resultaat Geen zijn - wat overigens in sommige gevallen zeer toepasselijk is.

a = [ 1 , 3 , 4 ] b = [ 3 , 4 ] print kaart (Geen , a , b ) [(1 , 3 ), (3 , 4 ), (4 , Geen )]

Nu ongeveer lambda-functies in Python. Ze worden gebruikt wanneer u een functie moet definiëren zonder def func_name(): ..., omdat de functie vaak (zoals in voorgaande voorbeelden) zo klein is dat het geen zin heeft om deze afzonderlijk te definiëren (extra regels code belemmeren de leesbaarheid ). Daarom kan de functie “ter plaatse” worden gedefinieerd f = lambdax: x*x alsof hij ons vertelt - accepteert x, retourneert x*x

Met standaard Python-tools kun je dus behoorlijk complexe acties op één regel opschrijven. Bijvoorbeeld de functie:

def f (x , y ): if (y == Geen ): y = 1 retour x * y

kan worden weergegeven als:

lambda x , y : x * (y als y niet Geen anders 1 is)

Nu zou het leuk zijn om lijsten door te geven, gesorteerd op lengte - len(a) > (b) - net zo eenvoudig als het pellen van peren - laten we de functie gebruiken gesorteerd :

gesorteerd ([ a , b ], sleutel = lambda x : len ( x ), omgekeerd = True )

functie gesorteerd neemt een lijst met waarden ( = [,]) en sorteert op sleutel– die wordt gegeven door de functie len(x) - die de lengte van de lijst retourneert, we sorteren deze in aflopende volgorde (reverse=True)

Uiteindelijk wordt de hele operatie als volgt geschreven:

kaart (lambda x , y : x * (y als y niet Geen anders 1 is), * gesorteerd ([ a , b ], sleutel = lambda x : len (x ), omgekeerd = True ))

Lijsten a en b kunnen verschillende lengtes hebben en in willekeurige volgorde worden verzonden. Lambda-expressies zijn handig voor het definiëren van niet erg complexe functies die vervolgens worden doorgegeven aan andere functies.

3.2.3 Woordenboekbegrippen

Stel dat we een woordenboek hebben waarvan de sleutels tekens zijn en waarvan de waarden overeenkomen met het aantal keren dat dat teken in een bepaalde tekst voorkomt. Het woordenboek maakt momenteel onderscheid tussen hoofdletters en kleine letters.

We hebben een woordenboek nodig waarin het voorkomen van hoofdletters en kleine letters wordt gecombineerd:

dct = ( "a" : 10 , "b" : 34 , "A" : 7 , "Z" : 3 )

frequentie = ( k. lager () : dct. krijg ( k. lager () , 0 ) + dct. krijg ( k. hoger () , 0 )

voor k in dct. sleutels())

afdrukfrequentie #("a": 17, "z": 3, "b": 34)

Python ondersteunt het creëren van anonieme functies (d.w.z. functies die niet aan een naam gebonden zijn) tijdens runtime, met behulp van een constructie genaamd “lambda”. Dit is niet precies hetzelfde als lambda in functionele programmeertalen, maar het is een zeer krachtig concept dat goed geïntegreerd is in Python en vaak gebruikt wordt in combinatie met typische functionele concepten zoals filter() , map() en reduce() .

Met behulp van de lambda kunnen anonieme functies in de vorm van een uitdrukking worden gemaakt
stelling:

args is een door komma's gescheiden lijst met argumenten, en expression is een expressie die deze argumenten omvat. Dit stukje code laat het verschil zien tussen een normale functiedefinitie en een lambda-functie:

def-functie (x):

terug x * x

printfunctie(2) #4

#-----------------------#

functie = lambda x : x * x

printfunctie(2) #4

Zoals je kunt zien, doen beide function() precies hetzelfde en kunnen ze op dezelfde manier worden gebruikt. Merk op dat de lambda-definitie geen “return”-instructie bevat; deze bevat altijd een expressie die wordt geretourneerd. Merk ook op dat u een lambda-definitie overal kunt plaatsen waar een functie wordt verwacht, en dat u deze helemaal niet aan een variabele hoeft toe te wijzen.

De volgende codefragmenten demonstreren het gebruik van lambda-functies.

def verhoging (n):

retour lambda x : x + n

afdrukstapgrootte(2) # op 0x022B9530>

afdrukstapgrootte (2) (20) #22

De bovenstaande code definieert een functie-increment die direct een anonieme functie creëert en deze retourneert. De geretourneerde functie verhoogt het argument met de waarde die is opgegeven toen deze werd gemaakt.

U kunt nu meerdere verschillende verhogingsfuncties maken en deze aan variabelen toewijzen, en ze vervolgens onafhankelijk van elkaar gebruiken. Zoals uit de laatste verklaring blijkt, hoeft u de functie niet eens ergens aan toe te wijzen; u kunt hem gewoon meteen gebruiken en vergeten wanneer u hem niet meer nodig heeft.

Q3. Waar is lambda goed voor?
Ant.
Het antwoord is:

  • We hebben geen lambda nodig, we zouden prima zonder kunnen. Maar…
  • er zijn bepaalde situaties waarin het handig is: het maakt het schrijven van code een beetje eenvoudiger en de geschreven code een beetje schoner.

Q4. Wat voor situaties?

Welnu, situaties waarin we een eenvoudige eenmalige functie nodig hebben: een functie die maar één keer wordt gebruikt.

Normaal gesproken worden functies gemaakt voor een van de volgende twee doeleinden: (a) om duplicatie van code te verminderen, of (b) om code te modulariseren.

  • Als uw toepassing op verschillende plaatsen dubbele stukjes code bevat, kunt u één kopie van die code in een functie plaatsen, de functie een naam geven en deze vervolgens - met behulp van die functienaam - vanaf verschillende plaatsen in uw code aanroepen.
  • Als je een stuk code hebt dat één goed gedefinieerde bewerking uitvoert - maar erg lang en lastig is en de anders leesbare stroom van je programma onderbreekt - dan kun je die lange, pittige code eruit halen en helemaal zelf in een functie plaatsen.

Maar stel dat u een functie moet maken die slechts één keer zal worden gebruikt - aangeroepen vanuit slechts één plaats in uw aanvraag. Allereerst hoeft u de functie geen naam te geven. Het kan ‘anoniem’ zijn. En u kunt het gewoon definiëren op de plek waar u het wilt gebruiken. Dat is waar lambda nuttig is.

Meestal wordt lambda gebruikt in de context van een andere bewerking, zoals sorteren of gegevensreductie:

namen = [ "David Beazley" , "Brian Jones" , "Raymond Hettinger" , "Ned Batchelder" ]

print gesorteerd (namen, sleutel = lambda-naam: naam. split () [-1]. lager ())

# ["Ned Batchelder", "David Beazley", "Raymond Hettinger", "Brian Jones"]

Hoewel u met lambda een eenvoudige functie kunt definiëren, is het gebruik ervan zeer beperkt. In
Er kan met name slechts één expressie worden opgegeven, waarvan het resultaat de return is
waarde. Dit betekent dat er geen andere taalfuncties, waaronder meerdere instructies, conditionals, iteratie en afhandeling van uitzonderingen, kunnen worden opgenomen.
Je kunt met plezier veel Python-code schrijven zonder ooit lambda te gebruiken. Echter,
je zult het af en toe tegenkomen in programma's waarin iemand heel veel klein schrijft
functies die verschillende uitdrukkingen evalueren, of in programma's waarvoor gebruikers moeten zorgen
terugbelfuncties.

Je hebt een anonieme functie gedefinieerd met behulp van lambda, maar je moet ook de
waarden van bepaalde variabelen op het moment van definitie.

>>>x=10

>>> a = lambda y : x + y

>>>x=20

>>> b = lambda y : x + y

Stel jezelf nu een vraag. Wat zijn de waarden van a(10) en b(10)? Als je denkt dat de
resultaten kunnen 20 en 30 zijn, dan heb je het mis:

Het probleem hier is dat de waarde van x die in de lambda-expressie wordt gebruikt, een vrije variabele is
dat wordt gebonden tijdens runtime, niet tijdens de definitie. Dus de waarde van x in de lambda
expressies is wat de waarde van de x-variabele ook is op het moment van uitvoering.
Bijvoorbeeld:

Als u wilt dat een anonieme functie een waarde vastlegt op het punt van definitie en
bewaar deze en neem de waarde op als standaardwaarde, zoals deze:

Het probleem dat hier wordt aangepakt, is iets dat vaak in de code voorkomt
probeert net iets te slim te zijn met het gebruik van lambda-functies. Bijvoorbeeld,
het maken van een lijst met lambda-expressies met behulp van een lijstbegrip of in een soort lus en verwachten dat de lambda-functies de iteratievariabele onthouden op het moment van definitie. Bijvoorbeeld:

>>> funcs = [ lambda x : x + n voor n binnen bereik (5 ) ]

  • Vertaling

Als het over functioneel programmeren gaat, gooien mensen vaak een aantal ‘functionele’ kenmerken weg. Onveranderlijke gegevens, eersteklas functies en optimalisatie van staartrecursie. Dit zijn taaleigenschappen waarmee u functionele programma's kunt schrijven. Ze noemen mapping, currying en het gebruik van functies van hogere orde. Dit zijn programmeertechnieken die worden gebruikt om functionele code te schrijven. Ze noemen parallellisatie, luie evaluatie en determinisme. Dit zijn de voordelen van functionele programma's.

Scoor het. Functionele code onderscheidt zich door één eigenschap: de afwezigheid bijwerkingen. Het is niet afhankelijk van gegevens buiten de huidige functie en verandert geen gegevens buiten de functie. Hieruit kunnen alle andere “eigenschappen” worden afgeleid.

Niet-functionele functie:

A = 0 def toename1(): globaal a a += 1

Functionele functie:

Def toename2(a): retourneert a + 1

In plaats van door een lijst te bladeren, gebruikt u kaart en verkleint u deze

Kaart

Accepteert een functie en een dataset. Creëert een nieuwe verzameling, voert de functie uit op elke gegevenspositie en voegt de geretourneerde waarde toe aan de nieuwe verzameling. Retourneert een nieuwe collectie.

Een eenvoudige kaart die een lijst met namen bevat en een lijst met lengtes retourneert:

Name_lengths = kaart(len, ["Masha", "Petya", "Vasya"]) print naam_lengtes # =>

Deze kaart vierkant elk element:

Vierkantjes = kaart(lambda x: x * x, ) print vierkantjes # =>

Het accepteert geen benoemde functie, maar neemt een anonieme functie die is gedefinieerd via lambda. Lambda-parameters worden links van de dikke darm gedefinieerd. Het hoofdgedeelte van de functie bevindt zich aan de rechterkant. Het resultaat wordt impliciet geretourneerd.

De niet-functionele code in het volgende voorbeeld neemt een lijst met namen en vervangt deze door willekeurige bijnamen.

Importeer willekeurige namen = ["Masha", "Petya", "Vasya"] code_names = ["Shpuntik", "Vintik", "Funtik"] voor i binnen bereik(len(namen)): namen[i] = willekeurig. choice(code_names) print namen # => ["Shpuntik", "Vintik", "Shpuntik"]

Het algoritme kan dezelfde bijnamen aan verschillende geheim agenten toekennen. Laten we hopen dat dit geen problemen oplevert tijdens de geheime missie.

Laten we dit herschrijven met behulp van de kaart:

Importeer willekeurige namen = ["Masha", "Petya", "Vasya"] geheime_namen = map(lambda x: random.choice(["Shpuntik", "Vintik", "Funtik"]), namen)

Oefening 1. Probeer de volgende code te herschrijven met map. Er is een lijst met echte namen nodig en deze worden vervangen door bijnamen met behulp van een betrouwbaardere methode.

Names = ["Masha", "Petya", "Vasya"] voor i binnen bereik(len(namen)): namen[i] = hash(namen[i]) print namen # =>

Mijn oplossing:

namen = ["Masha", "Petya", "Vasya"] geheime_namen = kaart(hash, namen)

Verminderen

Reduce heeft een functie en een reeks items nodig. Retourneert de waarde die wordt verkregen door alle items te combineren.

Een voorbeeld van een eenvoudige reductie. Retourneert de som van alle items in een set:

Som = reduce(lambda a, x: a + x, ) print som # => 10

X is het huidige item en is de batterij. Dit is de waarde die wordt geretourneerd door het uitvoeren van de lambda op het vorige item. reduce() doorloopt alle waarden en voert voor elke waarde een lambda uit op de huidige waarden a en x, en retourneert het resultaat in a voor de volgende iteratie.

Wat is de waarde van a in de eerste iteratie? Het is gelijk aan het eerste element van de verzameling, en reduce() begint te werken vanaf het tweede element. Dat wil zeggen dat de eerste x gelijk is aan het tweede item van de set.

In het volgende voorbeeld wordt geteld hoe vaak het woord 'kapitein' voorkomt in een lijst met tekenreeksen:

Sentences = ["Kapitein Jack Sparrow", "zeekapitein", "uw boot is klaar, kapitein"] cap_count = 0 voor zin in zinnen: cap_count += zin.count("kapitein") print cap_count # => 3

Dezelfde code met reduce:

Sentences = ["kapitein Jack Sparrow", "zeekapitein", "uw boot is klaar, kapitein"] cap_count = reduce(lambda a, x: a + x.count("kapitein"), zinnen, 0)

Waar komt de beginwaarde a hier vandaan? Het kan niet worden berekend op basis van het aantal herhalingen in de eerste regel. Daarom wordt het gegeven als het derde argument voor de functie reduce().

Waarom zijn in kaart brengen en verminderen beter?

Ten eerste passen ze meestal op één lijn.

Ten tweede bevinden de belangrijke delen van de iteratie (de verzameling, de bewerking en de geretourneerde waarde) zich altijd op dezelfde plaats, in kaart gebracht en verminderd.

Ten derde kan code in een lus de waarde van eerder gedefinieerde variabelen wijzigen, of de code erna beïnvloeden. Volgens afspraak zijn in kaart brengen en verkleinen functioneel.

Ten vierde zijn in kaart brengen en verkleinen elementaire handelingen. In plaats van lussen regel voor regel te lezen, is het voor de lezer gemakkelijker om de kaart waar te nemen en ingebouwde complexe algoritmen te verminderen.

Ten vijfde hebben ze veel vrienden die nuttig, enigszins aangepast gedrag van deze functies mogelijk maken. Bijvoorbeeld filteren, alles, alles en zoeken.

Oefening 2: Herschrijf de volgende code met behulp van map, reduce en filter. Filter accepteert een functie en een verzameling. Retourneert een verzameling van de dingen waarvoor de functie True retourneert.

Mensen = [("naam": "Masha", "hoogte": 160), ("hoogte": "Sasha", "hoogte": 80), ("naam": "Pasha")] height_total = 0 height_count = 0 voor persoon in mensen: als "hoogte" persoonlijk: hoogte_totaal += persoon["hoogte"] hoogte_telling += 1 als lengte_telling > 0: gemiddelde_hoogte = hoogte_totaal / hoogte_telling print gemiddelde_hoogte # => 120

Mijn oplossing:

people = [("naam": "Masha", "hoogte": 160), ("hoogte": "Sasha", "hoogte": 80), ("naam": "Pasha")] hoogten = kaart(lambda x: x["hoogte"], filter(lambda x: "hoogte" in x, mensen)) als len(hoogten) > 0: van operatorimport voeg gemiddelde_hoogte toe = reduce(toevoegen, hoogten) / len(hoogten)

Schrijf declaratief, niet dwingend.

Het volgende programma bootst een race met drie auto's na. Op elk moment beweegt de auto vooruit of niet. Het programma geeft telkens de door de auto's afgelegde afstand weer. Na vijf tijdsintervallen eindigt de race.

Uitvoervoorbeelden:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Programmatekst:

Van willekeurige import willekeurige tijd = 5 auto_posities = terwijl tijd: # tijd verkorten -= 1 print "" voor i binnen bereik (len (auto_posities)): # verplaats auto als willekeurig () > 0,3: auto_posities [i] += 1 # teken autoprint "-" * car_positions[i]

De code is absoluut noodzakelijk. De functionele versie zou declaratief zijn: het zou beschrijven wat er gedaan moet worden, niet hoe het gedaan moet worden.

Gebruik van de functies

Declarativiteit kan worden bereikt door code in functies in te voegen:

Van willekeurige import willekeurige def move_cars(): for i, _ in enumerate(car_positions): if random() > 0.3: car_positions[i] += 1 def draw_car(car_position): print "-" * car_position def run_step_of_race(): globale tijd tijd -= 1 move_cars() def draw(): print "" voor car_position in car_positions: draw_car(car_position) time = 5 car_positions = while time: run_step_of_race() draw()

Om het programma te begrijpen, kijkt de lezer naar de hoofdlus. “Als er nog tijd over is, doorlopen we één stap van de race en laten we het resultaat zien. Laten we nog eens kijken hoe laat het is." Als de lezer wil begrijpen hoe de racestap werkt, kan hij de code afzonderlijk lezen.

Geen commentaar nodig, de code legt zichzelf uit.

Door code in functies op te splitsen, wordt de code beter leesbaar. Deze techniek gebruikt functies, maar alleen als subroutines. Ze verpakken de code, maar maken deze niet functioneel. Functies beïnvloeden de code om hen heen en veranderen globale variabelen in plaats van waarden terug te geven. Als de lezer een variabele tegenkomt, zal hij moeten uitzoeken waar deze vandaan komt.

Hier functionele versie dit programma:

Van willekeurige import willekeurige def move_cars(car_positions): return map(lambda x: x + 1 if random() > 0.3 else x, car_positions) def output_car(car_position): return "-" * car_position def run_step_of_race(state): return ( "time": state["time"] - 1, "car_positions": move_cars(state["car_positions"])) def draw(state): print "" print "\n".join(map(output_car, state[ "car_positions"])) def race(state): draw(state) if state["time"]: race(run_step_of_race(state)) race(("time": 5, "car_positions": ))

De code is nu opgesplitst in functionele functies. Er zijn drie tekenen hiervan. De eerste is dat er geen gedeelde variabelen zijn. tijd en car_positions worden rechtstreeks doorgegeven aan race(). Ten tweede nemen functies parameters aan. Ten derde veranderen variabelen niet binnen functies; alle waarden worden geretourneerd. Elke keer dat run_step_of_race() de volgende stap uitvoert, wordt deze teruggestuurd naar de volgende.

Hier zijn twee functies zero() en one():

Def nul(s): if s == "0": retourneert s def one(s): if s == "1": retourneert s

Zero() neemt de tekenreeks s. Als het eerste teken 0 is, wordt de rest van de tekenreeks geretourneerd. Zo niet, dan Geen. one() doet hetzelfde als het eerste teken 1 is.

Laten we ons de functie rule_sequence() voorstellen. Er zijn een string en een lijst met regelfuncties voor nodig, bestaande uit de functies nul en één. Het roept de eerste regel aan en geeft deze een string door. Als Geen wordt geretourneerd, wordt de geretourneerde waarde gebruikt en wordt de volgende regel aangeroepen. En zo verder. Als Geen wordt geretourneerd, stopt rule_sequence() en retourneert Geen. Anders – de betekenis van de laatste regel.

Voorbeelden van invoer- en uitvoergegevens:

Print rule_sequence("0101", ) # => 1 print rule_sequence("0101", ) # => Geen

Imperatieve versie van rule_sequence():

Def rule_sequence(s, regels): voor regel in regels: s = regel(s) if s == Geen: break return s

Oefening 3. Deze code maakt gebruik van een lus. Herschrijf het declaratief met behulp van recursie.

Mijn oplossing:

def rule_sequence(s, regels): if s == Geen of geen regels: return s else: return rule_sequence(rules(s), regels)

Gebruik pijpleidingen

Laten we nu een ander type lus herschrijven met behulp van een techniek die een pijplijn wordt genoemd.

De volgende lus wijzigt de woordenboeken met de naam, het onjuiste land van herkomst en de status van sommige groepen.

Bands = [("name": "sunset rubdown", "country": "UK", "active": False), ("name": "women", "country": "Duitsland", "active": False ), ("name": "a silver mt. zion", "country": "Spanje", "active": True)] def format_bands(bands): voor band in bands: band["country"] = "Canada " band["naam"] = band["naam"].replace(".", "") band["naam"] = band["naam"].title() format_bands(banden) print bands # => [("name": "Sunset Rubdown", "active": False, "country": "Canada"), # ("name": "Vrouwen", "active": False, "country": "Canada" ) , # ("naam": "A Silver Mt Zion", "actief": waar, "land": "Canada")]

De functienaam "formaat" is te algemeen. Over het algemeen veroorzaakt de code enige bezorgdheid. Er gebeuren drie verschillende dingen in één cyclus. De waarde van de sleutel "land" wordt gewijzigd in "Canada". De punten worden verwijderd en de eerste letter van de naam wordt gewijzigd in een hoofdletter. Het is moeilijk te begrijpen wat de code moet doen, en moeilijk te zeggen of de code dat doet. Het is moeilijk te gebruiken, testen en parallelliseren.

Vergelijken:

Pipeline_each(banden, ) afdrukken

Het is eenvoudig. Helperfuncties zien er functioneel uit omdat ze aan elkaar zijn gekoppeld. De output van de vorige is de input van de volgende. Ze zijn eenvoudig te testen, hergebruiken, valideren en parallelliseren.

Pipeline_each() doorloopt de groepen één voor één en geeft ze door aan conversiefuncties zoals set_canada_as_country(). Nadat de functie op alle groepen is toegepast, maakt pipeline_each() er een lijst van en geeft deze door aan de volgende.

Laten we eens kijken naar de transformatiefuncties.

Def assoc(_d, key, value): from copy import deepcopy d = deepcopy(_d) d = waarde return d def set_canada_as_country(band): return assoc(band, "country", "Canada") def strip_punctuation_from_name(band): return assoc(band, "naam", band["naam"].replace(".", "")) def hoofdletter_namen(band): return assoc(band, "naam", band["naam"].title( ))

Elk koppelt een groepssleutel aan een nieuwe waarde. Dit is moeilijk te doen zonder de originele gegevens te wijzigen, dus lossen we dit op met assoc(). Het gebruikt deepcopy() om een ​​kopie van het doorgegeven woordenboek te maken. Elke functie transformeert een kopie en retourneert die kopie.

Alles lijkt in orde te zijn. De originele gegevens zijn beschermd tegen wijzigingen. Maar er zijn twee mogelijke plaatsen in de code voor gegevenswijzigingen. In strip_punctuation_from_name() wordt een naam zonder punten gemaakt door het aanroepen van Replace() met de originele naam. capitalize_names() creëert een naam met de eerste hoofdletter gebaseerd op title() en originele naam. Als vervangen en tijd niet functioneel zijn, dan zijn strip_punctuation_from_name() en capitalize_names() niet functioneel.

Gelukkig zijn ze functioneel. In Python zijn strings onveranderlijk. Deze functies werken met kopieën van tekenreeksen. Oef, godzijdank.

Dit contrast tussen tekenreeksen en woordenboeken (hun veranderlijke aard) in Python demonstreert de voordelen van talen als Clojure. Daar hoeft de programmeur niet na te denken of hij de gegevens gaat wijzigen. Het zal niet veranderen.

Oefening 4. Probeer een pipeline_each-functie te maken. Denk na over de volgorde van de handelingen. Groepen bevinden zich in een array en worden één voor één doorgegeven aan de eerste conversiefunctie. Vervolgens wordt de resulterende array stuk voor stuk doorgegeven aan de tweede functie, enzovoort.

Mijn oplossing:

def pipeline_each(data, fns): return reduce(lambda a, x: map(x, a), fns, data)

Alle drie de transformatiefuncties resulteren in het wijzigen van een specifiek veld voor de groep. call() kan worden gebruikt om hiervoor een abstractie te maken. Het accepteert een functie en de sleutel waarop deze zal worden toegepast.

Set_canada_as_country = call(lambda x: "Canada", "land") strip_punctuation_from_name = call(lambda x: x.replace(".", ""), "naam") capitalize_names = call(str.title, "naam") print pipeline_each(banden, )

Of, wat de leesbaarheid opoffert:

Pipeline_each(banden, ) afdrukken

Code voor oproep():

Def assoc(_d, sleutel, waarde): van kopie import deepcopy d = deepcopy(_d) d = waarde retour d def call(fn, sleutel): def apply_fn(record): return assoc(record, sleutel, fn(record. get(sleutel))) retourneert apply_fn

Wat is hier aan de hand?

Een. call is een functie van hogere orde, omdat neemt een andere functie als argument en retourneert de functie.

Twee. apply_fn() is vergelijkbaar met de conversiefuncties. Haalt het item (groep) op. Zoekt de waarderecord op. Bellen fn. Wijst het resultaat toe aan een kopie van de record en retourneert deze.

Drie. bellen zelf doet niets. apply_fn() doet al het werk. In het voorbeeld pipeline_each() stelt één exemplaar van apply_fn() "country" in op "Canada". De andere geeft de eerste letter een hoofdletter.

Vier. Bij het uitvoeren van een exemplaar van apply_fn() zijn de fn- en key-functies binnen het bereik niet beschikbaar. Dit zijn geen argumenten voor apply_fn() of lokale variabelen. Maar er zal toegang tot hen zijn. Wanneer een functie wordt gedefinieerd, behoudt deze verwijzingen naar de variabelen die worden gesloten: de variabelen die buiten de functie zijn gedefinieerd en intern worden gebruikt. Wanneer een functie wordt uitgevoerd, worden variabelen gezocht tussen lokale variabelen, vervolgens tussen argumenten en vervolgens tussen verwijzingen naar afsluitingen. Daar vindt u fn en sleutel.

Vijf. Er wordt geen melding gemaakt van groepen die in gesprek zijn. Dit komt omdat oproep kan worden gebruikt om pijplijnen te maken, ongeacht hun inhoud. Vooral functioneel programmeren bouwt een bibliotheek op algemene functies, geschikt voor composities en hergebruik.

Goed gedaan. Afsluitingen, functies van hogere orde en reikwijdte - alles in een paar paragrafen. Je kunt ook thee en koekjes krijgen.

Er blijft nog één verwerking van groepsgegevens over. Verwijder alles behalve de naam en het land. extract_name_and_country() functie:

Def extract_name_and_country(band): geplukte_band = () geplukte_band["naam"] = band["naam"] geplukte_band["land"] = band["land"] return geplukte_band print pipeline_each(banden, ) # => [(" name": "Sunset Rubdown", "country": "Canada"), # ("name": "Dames", "country": "Canada"), # ("name": "A Silver Mt Zion", " land": "Canada")]

Extract_name_and_country() zou geschreven kunnen worden in een generieke vorm genaamd pluck(). Het zou als volgt worden gebruikt:

Pipeline_each(banden, )]) afdrukken

Oefening 5. pluck accepteert een lijst met sleutels die uit records moeten worden gehaald. Probeer het te schrijven. Dit zal een functie van hogere orde zijn.

Het is niet voor niets dat de Python-taal tegelijkertijd populair is onder Google-programmeurs en Hacker-editors :). Met deze werkelijk krachtige taal kun je code schrijven volgens verschillende paradigma's, en vandaag zullen we proberen erachter te komen wat het verschil tussen beide is en welke het beste is om te volgen.

Welke paradigma's?! Laten we coderen!

Als u iets moet schrijven, is het laatste waar u zich waarschijnlijk zorgen over maakt, welk programmeerparadigma u moet kiezen. In plaats daarvan kiest u de meest geschikte taal, of begint u onmiddellijk met coderen in uw favoriete, geprefereerde en door de jaren heen bewezen taal. Het is waar, laat ideologen nadenken over ideologie, het is onze taak om te programmeren :). En toch volg je bij het programmeren noodzakelijkerwijs een soort paradigma. Laten we eens kijken naar een voorbeeld. Laten we proberen iets eenvoudigs te schrijven... laten we bijvoorbeeld de oppervlakte van een cirkel berekenen.

Je kunt het als volgt schrijven:

Oppervlakte van een cirkel (optie één)

dubbele oppervlakte_van_cirkel(dubbele r) (
retourneer M_PI*pow(r,2);
}
int hoofd() (
dubbele r = 5;
uit<< "Площадь: "<< area_of_circle(r)<< endl;
}

Of je kunt dit doen:

Oppervlakte van een cirkel (optie twee)

klasse Cirkel(
dubbele r;
publiek:
Cirkel(dubbele r) ( this->r = r; )
double area() ( return M_PI*pow(this->r,2); )
void print_area() (
uit<< "Площадь: "<< this->gebied()<< endl;
}
};
int main() ((nieuwe Circle(5))->print_area();)

Het kan ook anders... maar hoe hard je het ook probeert, de code zal imperatief zijn (zoals in het eerste geval) of objectgeoriënteerd (zoals in het tweede geval).
Dit komt niet door een gebrek aan verbeeldingskracht, maar simpelweg omdat C++ is toegesneden op deze paradigma's.

En het beste (of slechtste, afhankelijk van de rechtheid van je handen) dat je ermee kunt doen, is door verschillende paradigma's te combineren.

Paradigma's

Zoals je misschien al geraden hebt, kun je in dezelfde taal schrijven volgens verschillende paradigma's, soms zelfs meerdere tegelijk. Laten we eens kijken naar hun belangrijkste vertegenwoordigers, want zonder deze kennis zul je jezelf nooit als een professionele codeur kunnen beschouwen en zul je hoogstwaarschijnlijk het werken in een team moeten vergeten.

Imperatieve programmering

“Eerst doen we dit, dan dit, dan dit”

Talen: Bijna allemaal

Een paradigma dat absoluut begrijpelijk is voor elke programmeur: “Een persoon geeft een reeks instructies aan een machine.”
Iedereen begint programmeren te leren/begrijpen vanuit het imperatieve paradigma.

Functioneel programmeren

“We berekenen de uitdrukking en gebruiken het resultaat voor iets anders.”

Talen: Haskell, Erlang, F#

Een paradigma dat absoluut onbegrijpelijk is voor een beginnende programmeur. We beschrijven niet een opeenvolging van toestanden (zoals in het imperatieve paradigma), maar een opeenvolging van acties.

Objectgeoriënteerd programmeren

“We wisselen berichten uit tussen objecten en simuleren interacties in de echte wereld.”

Talen: Bijna allemaal

Met zijn komst is het objectgeoriënteerde paradigma stevig in ons leven terechtgekomen.
Bijna alle moderne bedrijfsprocessen zijn gebouwd op OOP.

Logische programmering

“Wij beantwoorden de vraag door een oplossing te vinden.”

Talen: Proloog

Logisch programmeren is nogal specifiek, maar tegelijkertijd interessant en intuïtief.
Een eenvoudig voorbeeld zal volstaan:

(stel de regels)
heks(X)<= burns(X) and female(X).
brandwonden(X)<= wooden(X).
houten(X)<= floats(X).
drijft(X)<= sameweight(duck, X).
(waarnemingen instellen)
vrouw (meisje).
hetzelfde gewicht (eend, meisje).
(stel een vraag)
? heks (meisje).

Hoewel elke programmeur per definitie bekend is met imperatief en objectgeoriënteerd programmeren, komen we functioneel programmeren in zijn pure vorm zelden tegen.

Functioneel programmeren staat in contrast met imperatief programmeren.

Imperatief programmeren omvat een reeks wijzigingen in de status van een programma, en variabelen worden gebruikt om deze status op te slaan.

Functioneel programmeren daarentegen omvat een reeks acties op gegevens. Dit lijkt op wiskunde: we schrijven de formule f(x) lange tijd op het bord, vervangen dan x en krijgen het resultaat.

En het hele punt van functioneel programmeren is dat de formule hier een hulpmiddel is dat we op X toepassen.

Python met twee gezichten

Er is geen betere theorie dan de praktijk, dus laten we alvast iets schrijven. Beter nog, schrijf het in Python :).
Laten we de som van de kwadraten van de elementen van de “data” -array imperatief en functioneel berekenen:

Imperatief Python

gegevens = [...]
som = 0
voor element in a:
som += element ** 2
som afdrukken

Functionele Python

gegevens = [...]
sq = lambda x: x**2
som = lambda x,y: x+y
print reduce(som, kaart(sq, data))

Beide voorbeelden zijn in Python, hoewel ik het niet in de lijst met functionele talen heb opgenomen. Dit is geen toeval, aangezien een volledig functionele taal een tamelijk specifiek en zelden gebruikt iets is. De eerste functionele taal was Lisp, maar zelfs deze was niet volledig functioneel (verbijsterend, nietwaar?). Volledig functionele talen worden voor allerlei wetenschappelijke toepassingen gebruikt en worden nog niet veel gebruikt.

Maar als de ‘functionaliteiten’ zelf niet wijdverbreid zijn geworden, zijn bepaalde ideeën daarvan gemigreerd naar scriptingtalen (en niet alleen naar) programmeertalen.
Het bleek dat het absoluut niet nodig is om volledig functionele code te schrijven; het volstaat om de imperatieve code te versieren met functionele elementen.

Python in actie

Het blijkt dat de concepten van FP meer dan elegant in Python zijn geïmplementeerd. Laten we ze eens nader bekijken.

?-rekening

Lambda-calculus is een wiskundig concept dat impliceert dat functies als argumenten kunnen worden gebruikt en andere functies kunnen retourneren.
Dergelijke functies worden functies van hogere ordes genoemd. ?-calculus is gebaseerd op twee bewerkingen: toepassing en abstractie.
In de vorige aanbieding heb ik al een voorbeeld van een toepassing gegeven. De map- en reduce-functies zijn dezelfde functies van hogere orde die de functie 'toepassen' of toepassen die als argument wordt doorgegeven aan elk element van de lijst (voor map) of elk opeenvolgend paar lijstelementen (voor reduce).

Wat abstractie betreft, is het andersom: functies creëren nieuwe functies op basis van hun argumenten.

Lambda-abstractie

def toevoegen(n):
retour lambda x: x + n

voegt =

Hier hebben we een lijst met functies gemaakt, die elk een bepaald getal aan het argument toevoegen.
Dit kleine voorbeeld bevat ook nog een paar interessantere definities van functioneel programmeren: afsluiting en currying.

Een afsluiting is de definitie van een functie die afhangt van de interne toestand van een andere functie. In ons voorbeeld is dit lambda x. Met deze techniek doen we iets dat lijkt op het gebruik van globale variabelen, alleen op lokaal niveau.

Carrying is de transformatie van een functie die een paar argumenten meeneemt naar een functie die zijn argumenten één voor één meeneemt. Dit is wat we in het voorbeeld deden, maar we kregen uiteindelijk een reeks van dergelijke functies.

Op deze manier kunnen we code schrijven die niet alleen met variabelen werkt, maar ook met functies, waardoor we nog een paar “vrijheidsgraden” krijgen.

Pure functies en een luie compiler

Imperatieve functies kunnen externe (globale) variabelen wijzigen, wat betekent dat een functie verschillende waarden kan retourneren voor dezelfde argumentwaarden in verschillende stadia van de programma-uitvoering.

Deze uitspraak is helemaal niet geschikt voor het functionele paradigma. Hier worden functies als wiskundig beschouwd, alleen afhankelijk van argumenten en andere functies. Daarom worden ze ook wel ‘pure functies’ genoemd.

Zoals we al hebben ontdekt, kun je in het functionele paradigma functies beheren zoals je wilt. Maar we krijgen het meeste voordeel als we ‘pure functies’ schrijven. Een zuivere functie is een functie zonder bijwerkingen, wat betekent dat deze niet afhankelijk is van zijn omgeving en zijn toestand niet verandert.

Het gebruik van pure functies biedt ons een aantal voordelen:

  • Ten eerste, als functies niet afhankelijk zijn van omgevingsvariabelen, verminderen we het aantal fouten dat gepaard gaat met ongewenste waarden van dezelfde variabelen. Naast het aantal fouten verminderen we ook de tijd die nodig is om fouten in het programma te debuggen, en het is veel gemakkelijker om dergelijke functies te debuggen.
  • Ten tweede, als de functies onafhankelijk zijn, heeft de compiler ruimte om rond te dwalen. Als een functie alleen afhankelijk is van argumenten, kan deze slechts één keer worden geëvalueerd. De volgende keer kunt u de in de cache opgeslagen waarde gebruiken. Als de functies niet van elkaar afhankelijk zijn, kunnen ze bovendien worden verwisseld en zelfs automatisch worden geparallelliseerd.

Om de prestaties te verbeteren, maakt FP ook gebruik van luie evaluatie. Een treffend voorbeeld:

afdruklengte()

In theorie zouden we aan de uitgang een deling door nul moeten krijgen. Maar een luie Python-compiler zal simpelweg niet de waarden van elk element van de lijst berekenen, omdat hem daar niet om is gevraagd. Lijstlengte nodig - alstublieft!
Dezelfde principes zijn van toepassing op andere taalconstructies.

Hierdoor krijgt niet alleen de programmeur, maar ook de compiler verschillende “vrijheidsgraden”.

Maak een lijst van expressies en voorwaardelijke instructies

Om ervoor te zorgen dat het leven (en programmeren) je niet als honing lijkt, bedachten Python-ontwikkelaars een speciale ‘zoete’ syntaxis, die de burgerij ‘syntactische suiker’ noemt.
Hiermee kun je voorwaardelijke uitspraken en lussen verwijderen... nou ja, als je ze niet wegdoet, breng ze dan zeker tot een minimum terug.

In principe zag je het al in het vorige voorbeeld - het voegt = toe. Hier maken en initialiseren we onmiddellijk de lijst met functiewaarden. Handig, toch?
Er bestaat ook zoiets als de operatoren en en of, waarmee je omslachtige constructies zoals if-elif-else kunt vermijden.

Met behulp van de Python-toolkit kun je dus een omslachtig imperatief stukje code omzetten in een prachtig functioneel stukje code.

Imperatieve code

L=
voor x in xbereik(10):
als x % 2 == 0:
als x**2>=50:
L.toevoegen(x)
anders:
L.toevoegen(-x)
afdrukken L

Functiecode

afdrukken

Resultaten

Zoals je al begrijpt, is het niet nodig om het functionele paradigma volledig te volgen; het is voldoende om het vakkundig te gebruiken in combinatie met het dwingende paradigma om je leven te vereenvoudigen. Ik bleef echter praten over het imperatieve paradigma... en zei niets over OOP en FP.

Welnu, OOP is in feite een aanvulling op het imperatieve paradigma, en als je van IP naar OOP bent overgestapt, zou de volgende stap het toepassen van FP in OOP moeten zijn. Tot slot wil ik een paar woorden zeggen over het abstractieniveau. Dus hoe hoger het is, hoe beter, en het is de combinatie van OOP en FP die ons dit niveau geeft.

CD

Op de schijf heb ik nieuwe Python-distributies voor Windows-gebruikers gezet. Linux-mensen hebben geen hulp nodig :).

WWW

Enkele goede bronnen voor degenen die meer willen weten:

INFO

Als u niet van Python houdt, hoeft u zich geen zorgen te maken: u kunt functionele programmeerideeën met succes toepassen in andere talen op hoog niveau.

Er zijn verschillende paradigma's in programmeren, bijvoorbeeld OOP, functioneel, imperatief, logisch en veel daarvan. We zullen het hebben over functioneel programmeren.

De vereisten voor volwaardig functioneel programmeren in Python zijn: functies van hogere orde, ontwikkelde hulpmiddelen voor lijstverwerking, recursie en de mogelijkheid om luie berekeningen te organiseren.

Vandaag zullen we kennis maken met eenvoudige elementen, en complexe ontwerpen zullen in andere lessen voorkomen.

Theorie in theorie

Net als bij OOP en functioneel programmeren proberen we definities te vermijden. Toch is het moeilijk om een ​​duidelijke definitie te geven, dus een duidelijke definitie zal hier niet te vinden zijn. Echter! Laten we de wensen voor een functionele taal onder de aandacht brengen:

  • Functies van hogere orde
  • Zuivere functies
  • Onveranderlijke gegevens

Dit is geen volledige lijst, maar zelfs dit is genoeg om het “mooi” te maken. Als de lezer meer wil, is hier een uitgebreide lijst:

  • Functies van hogere orde
  • Zuivere functies
  • Onveranderlijke gegevens
  • Sluitingen
  • Luiheid
  • Staart recursie
  • Algebraïsche gegevenstypen
  • Patroonaanpassing

Laten we al deze punten geleidelijk overwegen en bekijken hoe we ze in Python kunnen gebruiken.

En vandaag, in het kort, wat staat er op de eerste lijst.

Zuivere functies

Pure functies veroorzaken geen waarneembare bijwerkingen, maar leveren alleen een resultaat op. Ze veranderen de globale variabelen niet, zenden of printen niets, raken geen objecten aan, enzovoort. Ze accepteren gegevens, berekenen iets, houden alleen rekening met de argumenten en retourneren nieuwe gegevens.

  • Gemakkelijker om code te lezen en te begrijpen
  • Gemakkelijker te testen (geen noodzaak om “voorwaarden” te creëren)
  • Betrouwbaarder omdat ze niet afhankelijk zijn van het ‘weer’ en de toestand van de omgeving, maar alleen van de argumenten
  • Kan parallel worden uitgevoerd en de resultaten kunnen in de cache worden opgeslagen

Onveranderlijke gegevens

Onveranderlijke datastructuren zijn verzamelingen die niet kunnen worden gewijzigd. Bijna net als cijfers. Het nummer bestaat gewoon, het kan niet worden gewijzigd. Op dezelfde manier is een onveranderlijke array zoals deze is gemaakt en zal dat altijd zo blijven. Als u een element moet toevoegen, moet u een nieuwe array maken.

Voordelen van onveranderlijke structuren:

  • Deel veilig referenties tussen threads
  • Makkelijk te testen
  • Gemakkelijk te volgen levenscyclus (komt overeen met de gegevensstroom)

Functies van hogere orde

Een functie die een andere functie als argument neemt en/of een andere functie retourneert, wordt aangeroepen hogere orde functie:

Def f(x): retour x + 3 def g(functie, x): retourfunctie(x) * functie(x) print(g(f, 7))

Nadat we de theorie hebben overwogen, gaan we verder met de praktijk, van eenvoudig tot complex.

Lijstinsluitsels of lijstgenerator

Laten we eens kijken naar één taalontwerp dat het aantal regels code zal helpen verminderen. Het is niet ongebruikelijk om met behulp van deze constructie het niveau van een Python-programmeur te bepalen.

Voorbeeldcode:

Voor x in xbereik(5, 10): als x % 2 == 0: x =* 2 anders: x += 1

Een cyclus met een dergelijke aandoening is niet ongewoon. Laten we nu proberen deze 5 regels in één te veranderen:

>>>

Niet slecht, 5 regels of 1. Bovendien is de expressiviteit toegenomen en is dergelijke code gemakkelijker te begrijpen - er kan één opmerking worden toegevoegd voor het geval dat.

Over het algemeen is dit ontwerp als volgt:

Het is de moeite waard om te begrijpen dat als de code helemaal niet leesbaar is, het beter is om een ​​dergelijk ontwerp te verlaten.

Anonieme functies of lambda

We blijven de hoeveelheid code verminderen.

Def. berekening(x, y): retourneert x**2 + y**2

De functie is kort en er zijn minimaal 2 regels verloren gegaan. Is het mogelijk zulke kleine functies in te korten? Of formatteert u het misschien niet als functies? Je wilt immers niet altijd onnodige functies in een module creëren. En als de functie één regel in beslag neemt, dan nog meer. Daarom zijn er in programmeertalen anonieme functies die geen naam hebben.

Anonieme functies in Python worden geïmplementeerd met behulp van lambda-calculus en zien eruit als lambda-expressies:

>>> lambda x, y: x**2 + y**2 op 0x7fb6e34ce5f0>

Voor een programmeur zijn dit dezelfde functies en je kunt er ook mee werken.

Om meerdere keren toegang te krijgen tot anonieme functies, wijzen we ze toe aan een variabele en gebruiken we ze in ons voordeel.

>>> (lambda x, y: x**2 + y**2)(1, 4) 17 >>> >>> func = lambda x, y: x**2 + y**2 >>> func(1, 4) 17

Lambda-functies kunnen als argument fungeren. Zelfs voor andere lambda's:

Vermenigvuldiger = lambda n: lambda k: n*k

Lambda gebruiken

We hebben geleerd hoe we functies zonder naam kunnen maken, maar nu zullen we ontdekken waar we ze kunnen gebruiken. De standaardbibliotheek biedt verschillende functies die een functie als argument kunnen gebruiken: map(), filter(), reduce(), apply().

kaart()

De functie map() verwerkt een of meer reeksen met behulp van de gegeven functie.

>>> lijst1 = >>> lijst2 = [-1, 1, -5, 4, 6] >>> lijst(kaart(lambda x, y: x*y, lijst1, lijst2)) [-7, 2, -15, 40, 72]

We hebben al kennis gemaakt met de lijstgenerator, laten we deze gebruiken als de lengte van de lijst hetzelfde is):

>>> [-7, 2, -15, 40, 72]

Het valt dus op dat het gebruik van lijstinsluitsels korter is, maar lambda's flexibeler zijn. Laten we verder gaan.

filter()

Met de functie filter() kunt u de waarden van een reeks filteren. De resulterende lijst bevat alleen die waarden waarvoor de functiewaarde voor het element waar is:

>>> getallen = >>> lijst(filter(lambda x: x< 5, numbers)) # В результат попадают только те элементы x, для которых x < 5 истинно

Hetzelfde geldt voor lijstexpressies:

>>> cijfers = >>>

verminderen()

Om ketenberekeningen in een lijst te ordenen, kunt u de functie reduce() gebruiken. Het product van de elementen van een lijst kan bijvoorbeeld als volgt worden berekend (Python 2):

>>> getallen = >>> reduce(lambda res, x: res*x, getallen, 1) 720

Berekeningen vinden plaats in de volgende volgorde:

((((1*2)*3)*4)*5)*6

De oproepketen wordt gekoppeld via een tussenresultaat (res). Als de lijst leeg is, wordt gewoon de derde parameter gebruikt (bij een product van nul factoren is dit 1):

>>> reduceren(lambda res, x: res*x, , 1) 1

Natuurlijk tussen resultaat niet noodzakelijkerwijs een getal. Dit kan elk ander gegevenstype zijn, inclusief een lijst. Het volgende voorbeeld toont het omgekeerde van een lijst:

>>> reduceren(lambda res, x: [x]+res, , )

Python heeft ingebouwde functies voor de meest voorkomende bewerkingen:

>>> getallen = >>> som(getallen) 15 >>> lijst(omgekeerd(getallen))

Python 3 heeft geen ingebouwde reduce()-functie, maar deze is te vinden in de functools-module.

toepassen()

Een functie om een ​​andere functie toe te passen op positionele en benoemde argumenten, gegeven respectievelijk een lijst en een woordenboek (Python 2):

>>> def f(x, y, z, a=Geen, b=Geen): ... print x, y, z, a, b ... >>> apply(f, , ("a": 4, "b": 5)) 1 2 3 4 5

In Python 3 zou je een speciale syntaxis moeten gebruiken in plaats van de functie apply():

>>> def f(x, y, z, a=Geen, b=Geen): ... print(x, y, z, a, b) ... >>> f(*, **(" a": 4, "b": 5)) 1 2 3 4 5

Laten we de recensie afsluiten met deze ingebouwde functie. standaard bibliotheek en laten we verder gaan met de laatste functionele benadering voor vandaag.

Sluitingen

Functies die binnen andere functies zijn gedefinieerd, zijn sluitingen. Waarom is dit nodig? Laten we een voorbeeld bekijken om uit te leggen:

Code (fictief):

Def processing(element, type_filter, all_data_size): filters = Filter(all_data_size, type_filter).get_all() voor filt in filters: element = filt.filter(element) def main(): data = DataStorage().get_all_data() voor x in gegevens: verwerking(x, "alles", len(gegevens))

Wat je kunt opmerken in de code: in deze code zijn er variabelen die in wezen constant leven (dat wil zeggen hetzelfde), maar tegelijkertijd laden of initialiseren we meerdere keren. Als gevolg hiervan komen we tot het inzicht dat het initialiseren van variabelen het leeuwendeel van de tijd in beslag neemt in dit proces. Het komt voor dat zelfs het laden van variabelen in het bereik de prestaties vermindert. Om overheadgebruiksluitingen te verminderen.

De sluiting initialiseert variabelen één keer, die vervolgens zonder overhead kunnen worden gebruikt.

Laten we leren hoe u sluitingen kunt maken:

Def multiplier(n): "multiplier(n) retourneert een functie die vermenigvuldigt met n" def mul(k): return n*k return mul # hetzelfde effect kan worden bereikt met # multiplier = lambda n: lambda k: n* k mul2 = multiplier(2) # mul2 - functie die vermenigvuldigt met 2, bijvoorbeeld mul2(5) == 10

Conclusie

In de les hebben we de basisconcepten van FP besproken en ook een lijst met mechanismen samengesteld die in de volgende lessen zullen worden besproken. We hebben gesproken over manieren om de hoeveelheid code te verminderen, zoals het opnemen van lijsten (lijstgenerator), lamda-functies en het gebruik ervan, en tot slot waren er een paar woorden over sluitingen en waar ze voor dienen.