Effektiv Python: 4 Beste Praksis for Funksjonsargumenter

Funksjoner I Python har en rekke ekstra funksjoner som gjør programmererens liv enklere. Noen er lik evner i andre programmeringsspråk, men mange er unike For Python. Disse statister kan gjøre en funksjon formål mer opplagt. De kan eliminere støy og avklare intensjonen til innringere. De kan redusere subtile feil som er vanskelig å finne. I Dette utdraget Fra Effektiv Python: 59 Spesifikke Måter Å Skrive Bedre Python, Viser Brett Slatkin deg 4 beste praksis for funksjonsargumenter I Python.

Spar 35% på listeprisen* for den relaterte boken eller multi-format ebok (EPUB + MOBI + PDF ) med rabattkode ARTIKKEL.
* Se informit.com/terms

Punkt 18: Reduser Visuell Støy med Variable Posisjonsargumenter

Godta valgfrie posisjonsargumenter (ofte kalt star args i referanse til det konvensjonelle navnet på parameteren, *args) kan gjøre et funksjonskall tydeligere og fjerne visuell støy.

si for eksempel at du vil logge litt feilsøkingsinformasjon. Med et fast antall argumenter trenger du en funksjon som tar en melding og en liste over verdier.

def log(message, values): if not values: print(message) else: values_str = ', '.join(str(x) for x in values) print('%s: %s' % (message, values_str))log('My numbers are', )log('Hi there', )>>>My numbers are: 1, 2Hi there

Å måtte passere en tom liste når du ikke har noen verdier å logge er tungvint og støyende. Det ville være bedre å legge ut det andre argumentet helt. Du kan gjøre Dette I Python ved å prefiks det siste posisjonsparameternavnet med *. Den første parameteren for loggmeldingen kreves, mens et hvilket som helst antall etterfølgende posisjonsargumenter er valgfrie. Funksjonen kroppen trenger ikke å endre, bare innringere gjøre.

def log(message, *values): # The only difference if not values: print(message) else: values_str = ', '.join(str(x) for x in values) print('%s: %s' % (message, values_str))log('My numbers are', 1, 2)log('Hi there') # Much better>>>My numbers are: 1, 2Hi there

hvis du allerede har en liste og vil kalle en variabel argumentfunksjon som log, kan du gjøre dette ved å bruke * – operatoren. Dette instruerer Python å sende elementer fra sekvensen som posisjonsargumenter.

favorites = log('Favorite colors', *favorites)>>>Favorite colors: 7, 33, 99

det er to problemer med å godta et variabelt antall posisjonsargumenter.

det første problemet er at variabelargumentene alltid blir omgjort til en tuple før de sendes til funksjonen din. Dette betyr at hvis den som ringer til funksjonen din bruker * – operatøren på en generator, blir den iterert til den er oppbrukt. Den resulterende tuppel vil inkludere hver verdi fra generatoren, som kan forbruke mye minne og føre til at programmet krasjer.

def my_generator(): for i in range(10): yield idef my_func(*args): print(args)it = my_generator()my_func(*it)>>>(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

Funksjoner som godtar * args er best for situasjoner der du vet at antall innganger i argumentlisten vil være rimelig små. Den er ideell for funksjonssamtaler som passerer mange bokstaver eller variable navn sammen. Det er først og fremst for bekvemmeligheten av programmereren og lesbarheten av koden.Det andre problemet med * args er at du ikke kan legge til nye posisjonsargumenter til funksjonen din i fremtiden uten å migrere alle som ringer. Hvis du prøver å legge til et posisjonsargument foran argumentlisten, brytes eksisterende innringere subtilt hvis de ikke oppdateres.

def log(sequence, message, *values): if not values: print('%s: %s' % (sequence, message)) else: values_str = ', '.join(str(x) for x in values) print('%s: %s: %s' % (sequence, message, values_str))log(1, 'Favorites', 7, 33) # New usage is OKlog('Favorite numbers', 7, 33) # Old usage breaks>>>1: Favorites: 7, 33Favorite numbers: 7: 33

problemet her er at det andre anropet til logg brukte 7 som meldingsparameter fordi et sekvensargument ikke ble gitt. Bugs som dette er vanskelig å spore opp fordi koden fortsatt kjører uten å heve noen unntak. For å unngå denne muligheten helt, bør du bruke søkeord-bare argumenter når du vil utvide funksjoner som godtar * args (se Punkt 21: “Håndheve Klarhet Med Søkeord-Bare Argumenter”).

Ting å Huske

  • Funksjoner kan godta et variabelt antall posisjonsargumenter ved å bruke * args i def-setningen.
  • du kan bruke elementene fra en sekvens som posisjonsargumenter for en funksjon med * – operatoren.
  • Bruke * operatør med en generator kan føre til at programmet går tom for minne og krasj.Legge til nye posisjonsparametere til funksjoner som godtar * args kan introdusere vanskelige å finne feil .

Punkt 19: Gi Valgfri Oppførsel Med Søkeordargumenter

som de fleste andre programmeringsspråk, kaller en funksjon I Python tillater bestått argumenter etter posisjon.

def remainder(number, divisor): return number % divisorassert remainder(20, 7) == 6

alle posisjonsargumenter til Python-funksjoner kan også sendes med nøkkelord, der navnet på argumentet brukes i en oppgave i parentesene i et funksjonskall. Søkeordargumentene kan sendes i hvilken som helst rekkefølge så lenge alle de nødvendige posisjonsargumentene er angitt. Du kan blande og matche søkeord og posisjonsargumenter. Disse anropene er ekvivalente:

remainder(20, 7)remainder(20, divisor=7)remainder(number=20, divisor=7)remainder(divisor=7, number=20)

Posisjonsargumenter må angis før søkeordargumenter.

remainder(number=20, 7)>>>SyntaxError: non-keyword arg after keyword arg

hvert argument kan bare angis en gang.

remainder(20, number=7)>>>TypeError: remainder() got multiple values for argument 'number'

fleksibiliteten til søkeordargumenter gir tre betydelige fordeler.

den første fordelen er at søkeordargumenter gjør funksjonskallet klarere for nye lesere av koden. Med anropsresten (20, 7) er det ikke tydelig hvilket argument som er tallet og som er divisoren uten å se på implementeringen av restmetoden. I samtalen med søkeordargumenter gjør nummer=20 og divisor=7 det umiddelbart klart hvilken parameter som brukes til hvert formål.

den andre virkningen av søkeordargumenter er at de kan ha standardverdier angitt i funksjonsdefinisjonen. Dette gjør at en funksjon for å gi flere muligheter når du trenger dem, men lar deg godta standard virkemåte mesteparten av tiden. Dette kan eliminere repeterende kode og redusere støy.

si for eksempel at du vil beregne væskehastigheten som strømmer inn i en mva. Hvis momsen også er på en skala, kan du bruke forskjellen mellom to vektmålinger på to forskjellige tidspunkter for å bestemme strømningshastigheten.

def flow_rate(weight_diff, time_diff): return weight_diff / time_diffweight_diff = 0.5time_diff = 3flow = flow_rate(weight_diff, time_diff)print('%.3f kg per second' % flow)>>>0.167 kg per second

i det typiske tilfellet er det nyttig å kjenne strømningshastigheten i kilo per sekund. Andre ganger vil det være nyttig å bruke de siste sensormålingene til å tilnærme større tidsskalaer, som timer eller dager. Du kan angi denne virkemåten i samme funksjon ved å legge til et argument for tidsperiodeskaleringsfaktoren.

def flow_rate(weight_diff, time_diff, period): return (weight_diff / time_diff) * period

problemet er at nå må du angi periodeargumentet hver gang du ringer funksjonen, selv i det vanlige tilfellet av strømningshastighet per sekund (hvor perioden er 1).

flow_per_second = flow_rate(weight_diff, time_diff, 1)

For å gjøre dette mindre støyende, kan jeg gi periodeargumentet en standardverdi.

def flow_rate(weight_diff, time_diff, period=1): return (weight_diff / time_diff) * period

periodeargumentet er nå valgfritt.

flow_per_second = flow_rate(weight_diff, time_diff)flow_per_hour = flow_rate(weight_diff, time_diff, period=3600)

dette fungerer bra for enkle standardverdier—det blir vanskelig for komplekse standardverdier-se Punkt 20: “Bruk Ingen og Docstrings For Å Angi Dynamiske Standardargumenter”).

den tredje grunnen til å bruke søkeord argumenter er at de gir en effektiv måte å utvide en funksjon parametere mens resterende bakoverkompatibel med eksisterende innringere. Dette lar deg gi ekstra funksjonalitet uten å måtte migrere mye kode, noe som reduserer sjansen for å introdusere feil.

si for eksempel at du vil utvide flow_rate-funksjonen ovenfor for å beregne strømningshastigheter i vektenheter i tillegg til kilogram. Du kan gjøre dette ved å legge til en ny valgfri parameter som gir en konverteringsfrekvens til dine foretrukne måleenheter.

def flow_rate(weight_diff, time_diff, period=1, units_per_kg=1): return ((weight_diff * units_per_kg) / time_diff) * period

standardargumentverdien for units_per_kg er 1, noe som gjør at de returnerte vektenhetene forblir som kilo. Dette betyr at alle eksisterende innringere ikke vil se noen endring i atferd. Nye innringere til flow_rate kan angi det nye søkeordargumentet for å se den nye virkemåten.

pounds_per_hour = flow_rate(weight_diff, time_diff, period=3600, units_per_kg=2.2)

det eneste problemet med denne tilnærmingen er at valgfrie søkeordargumenter som periode og units_per_kg fortsatt kan angis som posisjonsargumenter.

pounds_per_hour = flow_rate(weight_diff, time_diff, 3600, 2.2)

Å Levere valgfrie argumenter posisjonelt Kan være forvirrende fordi det ikke er klart hva verdiene 3600 og 2.2 tilsvarer. Beste praksis er å alltid angi valgfrie argumenter ved hjelp av søkeordnavnene og aldri sende dem som posisjonsargumenter.

Ting å Huske

  • Funksjonsargumenter kan spesifiseres etter posisjon eller søkeord.
  • Nøkkelord gjør det klart hva hensikten med hvert argument er når det ville være forvirrende med bare posisjonsargumenter.
  • Søkeordargumenter med standardverdier gjør det enkelt å legge til ny atferd i en funksjon, spesielt når funksjonen har eksisterende innringere.
  • Valgfrie søkeord argumenter bør alltid være bestått av søkeord i stedet for ved plassering.

Element 20: Bruk None og Docstrings Til Å Angi Dynamiske Standardargumenter

Noen ganger må du bruke en ikke-statisk type som standardverdien for søkeordargumentet. Si for eksempel at du vil skrive ut loggingsmeldinger som er merket med tidspunktet for den loggede hendelsen. I standard tilfelle vil du at meldingen skal inkludere tidspunktet da funksjonen ble kalt. Du kan prøve følgende tilnærming, forutsatt at standardargumentene revurderes hver gang funksjonen kalles.

def log(message, when=datetime.now()): print('%s: %s' % (when, message))log('Hi there!')sleep(0.1)log('Hi again!')>>>2014-11-15 21:10:10.371432: Hi there!2014-11-15 21:10:10.371432: Hi again!

tidsstemplene er de samme fordi datetime.nå er bare utført en eneste gang: når funksjonen er definert. Standardargumentverdier evalueres bare en gang per modulbelastning, som vanligvis skjer når et program starter opp. Etter at modulen som inneholder denne koden er lastet, er datetime.nå standard argument vil aldri bli evaluert igjen.konvensjonen for å oppnå ønsket resultat i Python er å gi en standardverdi På Ingen og å dokumentere den faktiske oppførselen i docstring (se Punkt 49: “Skriv Docstrings For Hver Funksjon, Klasse og Modul”). Når koden ser en argumentverdi På Ingen, tildeler du standardverdien tilsvarende.

def log(message, when=None): """Log a message with a timestamp. Args: message: Message to print. when: datetime of when the message occurred. Defaults to the present time. """ when = datetime.now() if when is None else when print('%s: %s' % (when, message))

nå vil tidsstemplene være forskjellige.

log('Hi there!')sleep(0.1)log('Hi again!')>>>2014-11-15 21:10:10.472303: Hi there!2014-11-15 21:10:10.573395: Hi again!

Bruk Av None for standardargumentverdier er spesielt viktig når argumentene kan endres. Si for eksempel at du vil laste inn en verdi kodet SOM JSON-data. Hvis dekoding av dataene mislykkes, vil du at en tom ordliste skal returneres som standard. Du kan prøve denne tilnærmingen.

def decode(data, default={}): try: return json.loads(data) except ValueError: return default

problemet her er det samme som datetime.nå eksempel ovenfor. Ordlisten angitt for standard vil bli delt av alle samtaler for å dekode fordi standardargumentverdier bare evalueres en gang (ved modulbelastningstid). Dette kan forårsake ekstremt overraskende oppførsel.

foo = decode('bad data')foo = 5bar = decode('also bad')bar = 1print('Foo:', foo)print('Bar:', bar)>>>Foo: {'stuff': 5, 'meep': 1}Bar: {'stuff': 5, 'meep': 1}

du forventer to forskjellige ordbøker, hver med en nokkel og verdi. Men å endre en synes også å endre den andre. Den skyldige er at foo og bar er begge lik standardparameteren. De er det samme ordbokobjektet.

assert foo is bar

løsningen er å sette standardverdien for søkeordargumentet Til Ingen og deretter dokumentere oppførselen i funksjonens docstring.

def decode(data, default=None): """Load JSON data from a string. Args: data: JSON data to decode. default: Value to return if decoding fails. Defaults to an empty dictionary. """ if default is None: default = {} try: return json.loads(data) except ValueError: return default

nå kjører samme testkode som før det forventede resultatet.

foo = decode('bad data')foo = 5bar = decode('also bad')bar = 1print('Foo:', foo)print('Bar:', bar)>>>Foo: {'stuff': 5}Bar: {'meep': 1}

Ting å Huske

  • Standardargumenter evalueres bare en gang: Under funksjonsdefinisjon ved modulbelastningstid. Dette kan føre til merkelig oppførsel for dynamiske verdier(som {} eller).
  • Bruk Ingen som standardverdi for søkeordargumenter som har en dynamisk verdi. Dokumentere den faktiske standard virkemåten i funksjonens docstring.

Punkt 21: Håndheve Klarhet Med Søkeord-Bare Argumenter

Passerer argumenter etter søkeord er en kraftig funksjon I Python funksjoner (se Punkt 19: “Gi Valgfri Atferd Med Søkeord Argumenter”). Fleksibiliteten til søkeordargumenter gjør det mulig å skrive kode som vil være tydelig for brukstilfellene dine.

for eksempel, si at du vil dele ett tall med et annet, men vær veldig forsiktig med spesielle tilfeller. Noen ganger vil du ignorere Zerodivisionfeil unntak og returnere uendelig i stedet. Andre ganger vil du ignorere OverflowError unntak og returnere null i stedet.

def safe_division(number, divisor, ignore_overflow, ignore_zero_division): try: return number / divisor except OverflowError: if ignore_overflow: return 0 else: raise except ZeroDivisionError: if ignore_zero_division: return float('inf') else: raise

Bruk av denne funksjonen er enkel. Denne samtalen vil ignorere float overflow fra divisjon og vil returnere null.

result = safe_division(1, 10**500, True, False)print(result)>>>0.0

dette anropet vil ignorere feilen fra å dele med null og vil returnere uendelig.

result = safe_division(1, 0, False, True)print(result)>>>inf

problemet er at det er lett å forveksle posisjonen til de To Boolske argumentene som styrer unntaksforklarende oppførsel. Dette kan lett føre til feil som er vanskelig å spore opp. En måte å forbedre lesbarheten av denne koden er å bruke søkeordargumenter. Som standard kan funksjonen være altfor forsiktig og kan alltid øke unntak.

def safe_division_b(number, divisor, ignore_overflow=False, ignore_zero_division=False): # ...

deretter kan innringere bruke søkeordargumenter til å angi hvilke av ignorer flaggene de vil vende for bestemte operasjoner, og overstyre standard virkemåte.

safe_division_b(1, 10**500, ignore_overflow=True)safe_division_b(1, 0, ignore_zero_division=True)

problemet er, siden disse søkeordargumentene er valgfri oppførsel, er det ingenting som tvinger innringere av funksjonene dine til å bruke søkeordargumenter for klarhet. Selv med den nye definisjonen av safe_division_b, kan du fortsatt kalle det den gamle måten med posisjonsargumenter.

safe_division_b(1, 10**500, True, False)

med komplekse funksjoner som dette, er det bedre å kreve at innringere er klare om deres intensjoner. I Python 3 kan du kreve klarhet ved å definere funksjonene dine med søkeord-bare argumenter. Disse argumentene kan bare leveres av sokeord, aldri etter posisjon.

her omdefinerer jeg safe_division-funksjonen for å godta argumenter for bare søkeord. Symbolet * i argumentlisten angir slutten på posisjonsargumenter og begynnelsen på bare søkeord-argumenter.

def safe_division_c(number, divisor, *, ignore_overflow=False, ignore_zero_division=False): # ...

nå vil det ikke fungere å kalle funksjonen med posisjonsargumenter for søkeordargumentene.

safe_division_c(1, 10**500, True, False)>>>TypeError: safe_division_c() takes 2 positional arguments but 4 were given

Søkeordargumenter og standardverdiene fungerer som forventet.

safe_division_c(1, 0, ignore_zero_division=True) # OKtry: safe_division_c(1, 0)except ZeroDivisionError: pass # Expected

Søkeord-Bare Argumenter I Python 2

Dessverre Har Python 2 ikke eksplisitt syntaks for å spesifisere søkeord-bare argumenter som Python 3. Men du kan oppnå samme oppførsel ved å heve TypeErrors for ugyldige funksjonssamtaler ved å bruke * * – operatoren i argumentlister. Operatoren * * ligner på operatoren * (Se Punkt 18: “Reduser Visuell Støy med Variable Posisjonsargumenter”), bortsett fra at i stedet for å godta et variabelt antall posisjonsargumenter, godtar den et hvilket som helst antall nøkkelordargumenter, selv når de ikke er definert.

# Python 2def print_args(*args, **kwargs): print 'Positional:', args print 'Keyword: ', kwargsprint_args(1, 2, foo='bar', stuff='meep')>>>Positional: (1, 2)Keyword: {'foo': 'bar', 'stuff': 'meep'}

for å gjøre safe_division ta søkeord-bare argumenter I Python 2, har du funksjonen godta * * kwargs. Deretter pop søkeord argumenter som du forventer ut av kwargs ordbok, ved hjelp av pop-metoden andre argument for å angi standardverdien når nøkkelen mangler. Til slutt, du sørge for at det ikke er flere søkeord argumenter igjen i kwargs å hindre innringere fra å levere argumenter som er ugyldige.

# Python 2def safe_division_d(number, divisor, **kwargs): ignore_overflow = kwargs.pop('ignore_overflow', False) ignore_zero_div = kwargs.pop('ignore_zero_division', False) if kwargs: raise TypeError('Unexpected **kwargs: %r' % kwargs) # ...

nå kan du ringe funksjonen med eller uten søkeordargumenter.

safe_division_d(1, 10)safe_division_d(1, 0, ignore_zero_division=True)safe_division_d(1, 10**500, ignore_overflow=True)

Prøver å passere søkeord-bare argumenter etter posisjon vil ikke fungere, akkurat som I Python 3.

safe_division_d(1, 0, False, True)>>>TypeError: safe_division_d() takes 2 positional arguments but 4 were given

Prøver å passere uventede søkeord argumenter også vil ikke fungere.

safe_division_d(0, 0, unexpected=True)>>>TypeError: Unexpected **kwargs: {'unexpected': True}

Ting å Huske

  • Søkeordargumenter gjør intensjonen om et funksjonskall tydeligere.
  • Bruk bare søkeord-argumenter for å tvinge innringere til å angi søkeord-argumenter for potensielt forvirrende funksjoner, spesielt de som godtar flere Boolske flagg.
  • Python 3 støtter eksplisitt syntaks for søkeord-bare argumenter i funksjoner.Python 2 kan emulere søkeord-bare argumenter for funksjoner ved å bruke * * kwargs og manuelt heve TypeError unntak.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert.