Python Language
Gemeenschappelijke valkuilen
Zoeken…
Invoering
Python is een taal die bedoeld is om duidelijk en leesbaar te zijn zonder enige dubbelzinnigheden en onverwacht gedrag. Helaas zijn deze doelen niet in alle gevallen haalbaar, en daarom heeft Python een paar hoekgevallen waarin het misschien iets anders doet dan je had verwacht.
In deze sectie worden enkele problemen weergegeven die u kunt tegenkomen bij het schrijven van Python-code.
De volgorde wijzigen waarover u herhaalt
Een for
lus herhaalt zich over een reeks, dus het wijzigen van deze reeks in de lus kan leiden tot onverwachte resultaten (vooral bij het toevoegen of verwijderen van elementen):
alist = [0, 1, 2]
for index, value in enumerate(alist):
alist.pop(index)
print(alist)
# Out: [1]
Opmerking: list.pop()
wordt gebruikt om elementen uit de lijst te verwijderen.
Het tweede element is niet verwijderd omdat de iteratie in volgorde door de indices gaat. De bovenstaande lus herhaalt twee keer, met de volgende resultaten:
# Iteration #1
index = 0
alist = [0, 1, 2]
alist.pop(0) # removes '0'
# Iteration #2
index = 1
alist = [1, 2]
alist.pop(1) # removes '2'
# loop terminates, but alist is not empty:
alist = [1]
Dit probleem doet zich voor omdat de indices veranderen terwijl ze in de richting van toenemende index itereren. Om dit probleem te voorkomen, kunt u de lus achteruit doorlopen :
alist = [1,2,3,4,5,6,7]
for index, item in reversed(list(enumerate(alist))):
# delete all even items
if item % 2 == 0:
alist.pop(index)
print(alist)
# Out: [1, 3, 5, 7]
Door de lus te doorlopen die begint aan het einde, wanneer items worden verwijderd (of toegevoegd), heeft dit geen invloed op de indices van items eerder in de lijst. Dus zal dit voorbeeld behoren alle items die zijn zelfs te verwijderen uit alist
.
Een soortgelijk probleem doet zich voor bij het invoegen of toevoegen van elementen aan een lijst waarover u itereert , wat kan resulteren in een oneindige lus:
alist = [0, 1, 2]
for index, value in enumerate(alist):
# break to avoid infinite loop:
if index == 20:
break
alist.insert(index, 'a')
print(alist)
# Out (abbreviated): ['a', 'a', ..., 'a', 'a', 0, 1, 2]
Zonder de break
zou de lus 'a'
invoegen, zolang de computer onvoldoende geheugen heeft en het programma mag doorgaan. In een situatie als deze heeft het meestal de voorkeur om een nieuwe lijst te maken en items aan de nieuwe lijst toe te voegen terwijl u door de originele lijst loopt.
Wanneer u een for
lus gebruikt, kunt u de lijstelementen niet wijzigen met de variabele placeholder :
alist = [1,2,3,4]
for item in alist:
if item % 2 == 0:
item = 'even'
print(alist)
# Out: [1,2,3,4]
In het bovenstaande voorbeeld verandert het wijzigen van een item
eigenlijk niets in de oorspronkelijke lijst . U moet de alist[2]
( alist[2]
) gebruiken en enumerate()
werkt hiervoor goed:
alist = [1,2,3,4]
for index, item in enumerate(alist):
if item % 2 == 0:
alist[index] = 'even'
print(alist)
# Out: [1, 'even', 3, 'even']
Een while
lus is in sommige gevallen een betere keuze:
Als u alle items in de lijst gaat verwijderen :
zlist = [0, 1, 2]
while zlist:
print(zlist[0])
zlist.pop(0)
print('After: zlist =', zlist)
# Out: 0
# 1
# 2
# After: zlist = []
Hoewel het eenvoudigweg resetten van zlist
hetzelfde resultaat zal bereiken;
zlist = []
Het bovenstaande voorbeeld kan ook worden gecombineerd met len()
om na een bepaald punt te stoppen of om alle items behalve x
in de lijst te verwijderen:
zlist = [0, 1, 2]
x = 1
while len(zlist) > x:
print(zlist[0])
zlist.pop(0)
print('After: zlist =', zlist)
# Out: 0
# 1
# After: zlist = [2]
Of om een lijst te doorlopen terwijl elementen worden verwijderd die aan een bepaalde voorwaarde voldoen (in dit geval alle even elementen verwijderen):
zlist = [1,2,3,4,5]
i = 0
while i < len(zlist):
if zlist[i] % 2 == 0:
zlist.pop(i)
else:
i += 1
print(zlist)
# Out: [1, 3, 5]
Merk op dat je i
niet verhoogt na het verwijderen van een element. Door het element op zlist[i]
, is de index van het volgende item met één verlaagd, dus door zlist[i]
met dezelfde waarde voor i
aan te zlist[i]
bij de volgende iteratie, zult u het volgende item in de lijst correct controleren .
Een tegenovergestelde manier om te denken aan het verwijderen van ongewenste items uit een lijst, is door gewenste items aan een nieuwe lijst toe te voegen . Het volgende voorbeeld is een alternatief voor het laatste, while
lus-voorbeeld:
zlist = [1,2,3,4,5]
z_temp = []
for item in zlist:
if item % 2 != 0:
z_temp.append(item)
zlist = z_temp
print(zlist)
# Out: [1, 3, 5]
Hier voegen we de gewenste resultaten toe aan een nieuwe lijst. We kunnen dan optioneel de tijdelijke lijst opnieuw toewijzen aan de oorspronkelijke variabele.
Met deze denktrend kunt u een van de meest elegante en krachtige functies van Python gebruiken, lijstbegrippen , die tijdelijke lijsten elimineert en afwijkt van de eerder besproken in-place lijst- / indexmutatie-ideologie.
zlist = [1,2,3,4,5]
[item for item in zlist if item % 2 != 0]
# Out: [1, 3, 5]
Mutabel standaardargument
def foo(li=[]):
li.append(1)
print(li)
foo([2])
# Out: [2, 1]
foo([3])
# Out: [3, 1]
Deze code gedraagt zich zoals verwacht, maar wat als we geen argument doorgeven?
foo()
# Out: [1] As expected...
foo()
# Out: [1, 1] Not as expected...
Dit komt omdat standaardargumenten van functies en methoden worden geëvalueerd bij het definiëren in plaats van tijdens het uitvoeren. We hebben dus maar één exemplaar van de li
lijst.
De manier om dit te omzeilen is om alleen onveranderlijke typen te gebruiken voor standaardargumenten:
def foo(li=None):
if not li:
li = []
li.append(1)
print(li)
foo()
# Out: [1]
foo()
# Out: [1]
Hoewel een verbetering en hoewel if not li
correct if not li
tot False
evalueert, doen veel andere objecten dat ook, zoals reeksen van nul lengte. De volgende voorbeeldargumenten kunnen onbedoelde resultaten veroorzaken:
x = []
foo(li=x)
# Out: [1]
foo(li="")
# Out: [1]
foo(li=0)
# Out: [1]
De idiomatische benadering is om het argument direct te vergelijken met het None
object:
def foo(li=None):
if li is None:
li = []
li.append(1)
print(li)
foo()
# Out: [1]
Lijstvermenigvuldiging en algemene verwijzingen
Overweeg het creëren van een geneste lijststructuur door te vermenigvuldigen:
li = [[]] * 3
print(li)
# Out: [[], [], []]
Op het eerste gezicht zouden we denken dat we een lijst hebben met 3 verschillende geneste lijsten. Laten we proberen 1
toe te voegen aan de eerste:
li[0].append(1)
print(li)
# Out: [[1], [1], [1]]
1
werd toegevoegd aan alle lijsten in li
.
De reden is dat [[]] * 3
geen list
met 3 verschillende list
. Integendeel, het creëert een list
met 3 referenties naar dezelfde list
object. Als we dus aan li[0]
de verandering zichtbaar in alle subelementen van li
. Dit komt overeen met:
li = []
element = [[]]
li = element + element + element
print(li)
# Out: [[], [], []]
element.append(1)
print(li)
# Out: [[1], [1], [1]]
Dit kan verder worden bevestigd als we de geheugenadressen van de ingesloten list
met behulp van id
:
li = [[]] * 3
print([id(inner_list) for inner_list in li])
# Out: [6830760, 6830760, 6830760]
De oplossing is om de binnenlijsten met een lus te maken:
li = [[] for _ in range(3)]
In plaats van een enkele list
en er vervolgens 3 verwijzingen naar te maken, maken we nu 3 verschillende afzonderlijke lijsten. Dit kan opnieuw worden geverifieerd met behulp van de id
functie:
print([id(inner_list) for inner_list in li])
# Out: [6331048, 6331528, 6331488]
U kunt dit ook doen. Het veroorzaakt een nieuwe lege lijst in elke append
oproep.
>>> li = []
>>> li.append([])
>>> li.append([])
>>> li.append([])
>>> for k in li: print(id(k))
...
4315469256
4315564552
4315564808
Gebruik geen index om een reeks te herhalen.
Niet doen:
for i in range(len(tab)):
print(tab[i])
Doen :
for elem in tab:
print(elem)
for
zal de meeste iteratiebewerkingen for
u automatiseren.
Gebruik opsommen als je echt zowel de index als het element nodig hebt .
for i, elem in enumerate(tab):
print((i, elem))
Wees voorzichtig wanneer u "==" gebruikt om te controleren op Waar of Onwaar
if (var == True):
# this will execute if var is True or 1, 1.0, 1L
if (var != True):
# this will execute if var is neither True nor 1
if (var == False):
# this will execute if var is False or 0 (or 0.0, 0L, 0j)
if (var == None):
# only execute if var is None
if var:
# execute if var is a non-empty string/list/dictionary/tuple, non-0, etc
if not var:
# execute if var is "", {}, [], (), 0, None, etc.
if var is True:
# only execute if var is boolean True, not 1
if var is False:
# only execute if var is boolean False, not 0
if var is None:
# same as var == None
Controleer niet of u kunt, doe het gewoon en behandel de fout
Pythonistas zeggen meestal: "Het is gemakkelijker om vergeving te vragen dan toestemming".
Niet doen:
if os.path.isfile(file_path):
file = open(file_path)
else:
# do something
Doen:
try:
file = open(file_path)
except OSError as e:
# do something
Of nog beter met Python 2.6+
:
with open(file_path) as file:
Het is veel beter omdat het veel generieker is. Je kunt try/except
op bijna alles toepassen. U hoeft zich geen zorgen te maken over wat u moet doen om dit te voorkomen, maar alleen om de fout die u riskeert.
Niet controleren op type
Python wordt dynamisch getypt, dus als u op type controleert, verliest u flexibiliteit. Gebruik in plaats daarvan eendtypen door het gedrag te controleren. Als u een string in een functie verwacht, gebruik dan str()
om een willekeurig object naar een string te converteren. Als u een lijst verwacht, gebruikt u list()
om iterabele naar een lijst te converteren.
Niet doen:
def foo(name):
if isinstance(name, str):
print(name.lower())
def bar(listing):
if isinstance(listing, list):
listing.extend((1, 2, 3))
return ", ".join(listing)
Doen:
def foo(name) :
print(str(name).lower())
def bar(listing) :
l = list(listing)
l.extend((1, 2, 3))
return ", ".join(l)
Op de laatste manier accepteert foo
elk object. bar
accepteert tekenreeksen, tupels, sets, lijsten en nog veel meer. Goedkoop DROOG.
Gebruik geen spaties en tabbladen door elkaar
Gebruik object als eerste ouder
Dit is lastig, maar het zal je bijten naarmate je programma groeit. Er zijn oude en nieuwe klassen in Python 2.x
De oude zijn, nou ja, oud. Ze missen enkele functies en kunnen onaangenaam gedrag vertonen bij overerving. Om bruikbaar te zijn, moet elke klasse van de "nieuwe stijl" zijn. Om dit te doen, laat het erven van het object
.
Niet doen:
class Father:
pass
class Child(Father):
pass
Doen:
class Father(object):
pass
class Child(Father):
pass
In Python 3.x
alle klassen in een nieuwe stijl, dus dat hoef je niet te doen.
Initialiseer geen klassenattributen buiten de init- methode
Mensen uit andere talen vinden het verleidelijk, want dat is wat je doet in Java of PHP. U schrijft de klassenaam, vermeldt vervolgens uw attributen en geeft ze een standaardwaarde. Het lijkt te werken in Python, maar dit werkt niet zoals je denkt. Als u dat doet, worden klasse-attributen (statische attributen) ingesteld, en wanneer u probeert het object-kenmerk te krijgen, krijgt u de waarde tenzij het leeg is. In dat geval worden de klassenkenmerken geretourneerd. Het houdt twee grote gevaren in:
Als het klassenkenmerk wordt gewijzigd, wordt de initiële waarde gewijzigd.
Als u een veranderlijk object als standaardwaarde instelt, krijgt u hetzelfde object gedeeld over instanties.
Niet doen (tenzij u statisch wilt):
class Car(object):
color = "red"
wheels = [Wheel(), Wheel(), Wheel(), Wheel()]
Doen :
class Car(object):
def __init__(self):
self.color = "red"
self.wheels = [Wheel(), Wheel(), Wheel(), Wheel()]
Geheel getal en tekenreeksidentiteit
Python maakt gebruik van interne caching voor een reeks gehele getallen om onnodige overhead van hun herhaalde creatie te verminderen.
Dit kan in feite leiden tot verwarrend gedrag bij het vergelijken van integer identiteiten:
>>> -8 is (-7 - 1)
False
>>> -3 is (-2 - 1)
True
en met een ander voorbeeld:
>>> (255 + 1) is (255 + 1)
True
>>> (256 + 1) is (256 + 1)
False
Wacht wat?
We kunnen zien dat de identiteit operatie is
opbrengsten True
voor sommige integers ( -3
, 256
), maar niet voor anderen ( -8
, 257
).
Meer specifiek, gehele getallen in het bereik [-5, 256]
worden intern in de cache opgeslagen tijdens het opstarten van de tolk en worden slechts eenmaal gemaakt. Als zodanig zijn ze identiek en het vergelijken van hun identiteiten met is
levert True
; gehele getallen buiten dit bereik worden (meestal) on-the-fly gemaakt en hun identiteit is te vergelijken met False
.
Dit is een veel voorkomende valkuil, omdat dit een algemeen testbereik is, maar vaak genoeg mislukt de code in het latere staging-proces (of erger - productie) zonder duidelijke reden na perfect in ontwikkeling te zijn geweest.
De oplossing is om altijd waarden te vergelijken met de operator gelijkheid ( ==
) en niet met de operator identiteit ( is
).
Python houdt ook verwijzingen naar veelgebruikte tekenreeksen bij en kan leiden tot vergelijkbaar verwarrend gedrag bij het vergelijken van identiteiten (dwz het gebruik van is
) van tekenreeksen.
>>> 'python' is 'py' + 'thon'
True
De string 'python'
wordt vaak gebruikt, dus Python heeft één object dat alle verwijzingen naar de string 'python'
gebruiken.
Voor ongewone tekenreeksen mislukt het vergelijken van identiteit, zelfs als de tekenreeksen gelijk zijn.
>>> 'this is not a common string' is 'this is not' + ' a common string'
False
>>> 'this is not a common string' == 'this is not' + ' a common string'
True
Vergelijk dus, net als de regel voor gehele getallen, de tekenreekswaarden altijd met de operator gelijkheid ( ==
) en niet met de operator identiteit ( is
).
Toegang krijgen tot de attributen van int literals
Je hebt misschien gehoord dat alles in Python een object is, zelfs letterlijk. Dit betekent bijvoorbeeld dat 7
ook een object is, wat betekent dat het attributen heeft. Een van deze attributen is bijvoorbeeld de bit_length
. Het geeft het aantal bits terug dat nodig is om de waarde weer te geven waarop het wordt aangeroepen.
x = 7
x.bit_length()
# Out: 3
Als je de bovenstaande code ziet werken, denk je misschien intuïtief dat 7.bit_length()
zou werken, om er vervolgens achter te komen dat het een SyntaxError
oproept. Waarom? omdat de interpreter onderscheid moet maken tussen een attribuuttoegang en een zwevend nummer (bijvoorbeeld 7.2
of 7.bit_length()
). Dat kan niet, en daarom wordt een uitzondering gemaakt.
Er zijn een paar manieren om toegang te krijgen tot de attributen van een int
literals:
# parenthesis
(7).bit_length()
# a space
7 .bit_length()
Het gebruik van twee punten (zoals deze 7..bit_length()
) werkt in dit geval niet, omdat dat een letterlijk float
creëert en floats niet de bit_length()
methode hebben.
Dit probleem bestaat niet bij de toegang tot float
attributen letterlijke, omdat de interperter is 'slimme' genoeg om te weten dat een float
letterlijke geen twee kan bevatten .
, bijvoorbeeld:
7.2.as_integer_ratio()
# Out: (8106479329266893, 1125899906842624)
Keten van of operator
Bij het testen voor een of meer gelijkheidsvergelijkingen:
if a == 3 or b == 3 or c == 3:
het is verleidelijk om dit in te korten
if a or b or c == 3: # Wrong
Dit is fout; de operator or
heeft een lagere prioriteit dan ==
, dus de uitdrukking wordt geëvalueerd if (a) or (b) or (c == 3):
De juiste manier is expliciet alle voorwaarden controleren:
if a == 3 or b == 3 or c == 3: # Right Way
Als alternatief kan de ingebouwde functie any()
worden gebruikt in plaats van chained or
operators:
if any([a == 3, b == 3, c == 3]): # Right
Of, om het efficiënter te maken:
if any(x == 3 for x in (a, b, c)): # Right
Of, om het korter te maken:
if 3 in (a, b, c): # Right
Hier gebruiken we de in
operator om te testen of de waarde die aanwezig zijn in een tupel met de waarden die we willen om mee te vergelijken is.
Evenzo is het onjuist om te schrijven
if a == 1 or 2 or 3:
die moet worden geschreven als
if a in (1, 2, 3):
sys.argv [0] is de naam van het bestand dat wordt uitgevoerd
Het eerste element van sys.argv[0]
is de naam van het python-bestand dat wordt uitgevoerd. De overige elementen zijn de scriptargumenten.
# script.py
import sys
print(sys.argv[0])
print(sys.argv)
$ python script.py
=> script.py
=> ['script.py']
$ python script.py fizz
=> script.py
=> ['script.py', 'fizz']
$ python script.py fizz buzz
=> script.py
=> ['script.py', 'fizz', 'buzz']
Woordenboeken zijn niet geordend
Je zou verwachten dat een Python-woordenboek wordt gesorteerd op sleutels zoals bijvoorbeeld een C ++ std::map
, maar dit is niet het geval:
myDict = {'first': 1, 'second': 2, 'third': 3}
print(myDict)
# Out: {'first': 1, 'second': 2, 'third': 3}
print([k for k in myDict])
# Out: ['second', 'third', 'first']
Python heeft geen ingebouwde klasse die zijn elementen automatisch op sleutel sorteert.
Als sorteren echter geen vereiste is en u gewoon wilt dat uw woordenboek de volgorde van invoeging van de sleutel / waarde-paren onthoudt, kunt u collections.OrderedDict
:
from collections import OrderedDict
oDict = OrderedDict([('first', 1), ('second', 2), ('third', 3)])
print([k for k in oDict])
# Out: ['first', 'second', 'third']
Houd er rekening mee dat het initialiseren van een OrderedDict
met een standaardwoordenboek op geen enkele manier het woordenboek voor u sorteert. Alle dat deze structuur doet is de volgorde van sleutelinsteekopening behouden.
De implementatie van woordenboeken is in Python 3.6 gewijzigd om hun geheugengebruik te verbeteren. Een neveneffect van deze nieuwe implementatie is dat het ook de volgorde van trefwoordargumenten behoudt die aan een functie zijn doorgegeven:
def func(**kw): print(kw.keys())
func(a=1, b=2, c=3, d=4, e=5)
dict_keys(['a', 'b', 'c', 'd', 'e']) # expected order
Voorbehoud : pas op dat " het orderbewarende aspect van deze nieuwe implementatie wordt beschouwd als een implementatiedetail en waarop niet moet worden vertrouwd " , omdat dit in de toekomst kan veranderen.
Global Interpreter Lock (GIL) en blokkerende threads
Er is veel geschreven over Python's GIL . Het kan soms verwarring veroorzaken bij toepassingen met meerdere threads (niet te verwarren met multiprocessen).
Hier is een voorbeeld:
import math
from threading import Thread
def calc_fact(num):
math.factorial(num)
num = 600000
t = Thread(target=calc_fact, daemon=True, args=[num])
print("About to calculate: {}!".format(num))
t.start()
print("Calculating...")
t.join()
print("Calculated")
Je zou verwachten dat Calculating...
direct na het starten van de thread wordt afgedrukt, we wilden de berekening toch in een nieuwe thread laten gebeuren! Maar in werkelijkheid zie je dat het wordt afgedrukt nadat de berekening is voltooid. Dat komt omdat de nieuwe thread afhankelijk is van een C-functie ( math.factorial
) die de GIL vergrendelt terwijl deze wordt uitgevoerd.
Er zijn een paar manieren om dit te omzeilen. De eerste is het implementeren van uw faculteit in native Python. Hierdoor kan de hoofddraad controle krijgen terwijl je in je lus zit. Het nadeel is dat deze oplossing veel langzamer zal zijn, omdat we de C-functie niet meer gebruiken.
def calc_fact(num):
""" A slow version of factorial in native Python """
res = 1
while num >= 1:
res = res * num
num -= 1
return res
Je kunt ook een tijdje sleep
voordat je met je uitvoering begint. Opmerking: hiermee kan uw programma de berekening in de C-functie niet onderbreken, maar uw hoofdthread kan doorgaan na de spawn, wat u mag verwachten.
def calc_fact(num):
sleep(0.001)
math.factorial(num)
Variabele lekken in lijstbegrippen en voor lussen
Overweeg het volgende lijstbegrip
i = 0
a = [i for i in range(3)]
print(i) # Outputs 2
Dit gebeurt alleen in Python 2 vanwege het feit dat het lijstbegrip de lusbesturingsvariabele "lekt" naar het omringende bereik ( bron ). Dit gedrag kan leiden tot moeilijk te vinden bugs en is opgelost in Python 3 .
i = 0
a = [i for i in range(3)]
print(i) # Outputs 0
Evenzo hebben lussen geen privébereik voor hun iteratievariabele
i = 0
for i in range(3):
pass
print(i) # Outputs 2
Dit soort gedrag komt zowel voor in Python 2 als Python 3.
Om problemen met lekkende variabelen te voorkomen, gebruikt u nieuwe variabelen in lijstbegrippen en voor lussen waar van toepassing.
Meerdere retour
De functie xyz retourneert twee waarden a en b:
def xyz():
return a, b
Code die xyz-winkels aanroept, resulteert in één variabele, ervan uitgaande dat xyz slechts één waarde retourneert:
t = xyz()
De waarde van t
is eigenlijk een tuple (a, b), dus elke actie op t
ervan uitgaande dat het geen tuple is, kan diep in de code mislukken met een onverwachte fout in tuples.
TypeError: type tuple definieert de methode niet
De oplossing zou zijn om te doen:
a, b = xyz()
Beginners zullen moeite hebben om de reden van dit bericht te vinden door alleen het foutbericht van de tuple te lezen!
Pythonic JSON-sleutels
my_var = 'bla';
api_key = 'key';
...lots of code here...
params = {"language": "en", my_var: api_key}
Als u gewend bent aan JavaScript, zal de variabele evaluatie in Python-woordenboeken niet zijn wat u ervan verwacht. Deze verklaring in JavaScript zou het params
object als volgt opleveren:
{
"language": "en",
"my_var": "key"
}
In Python zou dit echter het volgende woordenboek opleveren:
{
"language": "en",
"bla": "key"
}
my_var
wordt geëvalueerd en de waarde ervan wordt als sleutel gebruikt.