Sök…


Introduktion

Python är ett språk som är tänkt att vara tydligt och läsbart utan oklarheter och oväntat beteende. Tyvärr kan dessa mål inte uppnås i alla fall, och det är därför som Python har några få hörnfall där det kan göra något annorlunda än vad du förväntade dig.

Det här avsnittet visar några problem som du kan stöta på när du skriver Python-kod.

Ändra sekvensen du iterera över

En for slinga iterates över en sekvens, så att ändra denna sekvens inuti slingan kan leda till oväntade resultat (särskilt när du lägger till eller tar bort element):

alist = [0, 1, 2]
for index, value in enumerate(alist):
    alist.pop(index)
print(alist)
# Out: [1]

Obs: list.pop() används för att ta bort element från listan.

Det andra elementet raderades inte eftersom iterationen går igenom indexen i ordning. Ovanstående slinga upprepas två gånger, med följande resultat:

# 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]

Detta problem uppstår på grund av att indexen förändras medan de upprepas i riktning mot ökande index. För att undvika detta problem kan du iterera igenom öglan bakåt :

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]

Genom att iterera genom slingan som börjar i slutet, eftersom objekt tas bort (eller läggs till) påverkar det inte indexen för objekt tidigare i listan. Så det här exemplet kommer att ta bort alla objekt som till och med från alist .


Ett liknande problem uppstår när du sätter in eller lägger till element i en lista som du itererar över , vilket kan resultera i en oändlig slinga:

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]

Utan break skulle slingan infoga 'a' så länge datorn inte har slut på minnet och programmet får fortsätta. I en situation som denna föredras det vanligtvis att skapa en ny lista och lägga till objekt i den nya listan när du går igenom den ursprungliga listan.


När du använder en for loop kan du inte ändra listelementen med platshållarens variabel :

alist = [1,2,3,4]
for item in alist:
    if item % 2 == 0:
        item = 'even'
print(alist)
# Out: [1,2,3,4]

I exemplet ovan, ändra item faktiskt inte ändra något i den ursprungliga listan. Du måste använda listindex ( alist[2] ) och enumerate() fungerar bra för detta:

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']

A while slinga kan vara ett bättre val i vissa fall:

Om du kommer att ta bort alla objekt i listan:

zlist = [0, 1, 2]
while zlist:
    print(zlist[0])
    zlist.pop(0)
print('After: zlist =', zlist)

# Out: 0
#      1
#      2
# After: zlist = []

Även om du bara återställer zlist kommer du att uppnå samma resultat;

zlist = []

Exemplet ovan kan också kombineras med len() att stoppa efter en viss punkt, eller för att radera alla utom x objekt i listan:

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]

Eller att gå igenom en lista medan du tar bort element som uppfyller ett visst villkor (i detta fall tar du bort alla jämna element):

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]

Lägg märke till att du inte ökar i efter att du har tagit bort ett element. Genom att radera elementet på zlist[i] har indexet för nästa objekt minskat med ett, så genom att kontrollera zlist[i] med samma värde för i i nästa iteration, kommer du att kontrollera nästa objekt i listan korrekt .


Ett motsatt sätt att tänka på att ta bort oönskade objekt från en lista är att lägga till önskade objekt i en ny lista . Följande exempel är ett alternativ till det senare while loop-exempel:

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]

Här tränar vi önskade resultat till en ny lista. Vi kan sedan eventuellt tilldela den temporära listan till den ursprungliga variabeln.

Med denna tänktrend kan du åberopa en av Pythons mest eleganta och kraftfulla funktioner, listförståelser , som eliminerar tillfälliga listor och avviker från den tidigare diskuterade ideologin på platslista / indexmutationer.

zlist = [1,2,3,4,5]
[item for item in zlist if item % 2 != 0]
# Out: [1, 3, 5]

Möjligt standardargument

def foo(li=[]):
    li.append(1)
    print(li)

foo([2])
# Out: [2, 1]
foo([3])
# Out: [3, 1]

Den här koden fungerar som förväntat, men vad händer om vi inte klarar ett argument?

foo()
# Out: [1] As expected...

foo()
# Out: [1, 1]  Not as expected...

Detta beror på att standardargument för funktioner och metoder utvärderas vid definitionstid snarare än körtid. Så vi har bara en enda instans av li listan.

Sättet att ta sig runt det är att använda endast immutable-typer för standardargument:

def foo(li=None):
    if not li:
        li = []
    li.append(1)
    print(li)

foo()
# Out: [1]

foo()
# Out: [1]

Även om en förbättring och även if not li korrekt utvärderar False , gör många andra objekt också, till exempel sekvenser med noll längd. Följande exempelargument kan orsaka oavsiktliga resultat:

x = []
foo(li=x)
# Out: [1]

foo(li="")
# Out: [1]

foo(li=0) 
# Out: [1]

Den idiomatiska metoden är att direkt kontrollera argumentet mot objektet None :

def foo(li=None):
    if li is None:
        li = []
    li.append(1)
    print(li)

foo()
# Out: [1]

Lista multiplikation och vanliga referenser

Tänk på att skapa en kapslad liststruktur genom att multiplicera:

li = [[]] * 3
print(li)
# Out: [[], [], []]

Vid första anblicken skulle vi tro att vi har en lista med 3 olika kapslade listor. Låt oss försöka lägga till 1 till den första:

li[0].append(1)
print(li)
# Out: [[1], [1], [1]]

1 fick bifogas alla listor i li .

Anledningen är att [[]] * 3 inte skapar en list med 3 olika list . Snarare skapar det en list som håller 3 hänvisningar till samma list objekt. Som sådan, när vi lägger till li[0] är förändringen synlig i alla underelement av li . Detta motsvarar:

li = []
element = [[]]
li = element + element + element
print(li)
# Out: [[], [], []]
element.append(1)
print(li)
# Out: [[1], [1], [1]]

Detta kan bekräftas ytterligare om vi skriver ut minnesadresserna i list med hjälp av id :

li = [[]] * 3
print([id(inner_list) for inner_list in li])
# Out: [6830760, 6830760, 6830760]

Lösningen är att skapa de inre listorna med en slinga:

li = [[] for _ in range(3)]

Istället för att skapa en enda list och sedan göra tre referenser till den, skapar vi nu tre olika distinkta listor. Detta kan igen verifieras med id funktionen:

print([id(inner_list) for inner_list in li])
# Out: [6331048, 6331528, 6331488]

Du kan också göra detta. Det gör att en ny tom lista skapas i varje append samtal.

>>> li = []
>>> li.append([])
>>> li.append([])
>>> li.append([])
>>> for k in li: print(id(k))
... 
4315469256
4315564552
4315564808

Använd inte index för att slinga över en sekvens.

Inte:

for i in range(len(tab)):
    print(tab[i])

Gör :

for elem in tab:
    print(elem)

for kommer att automatisera de flesta iterationsoperationer för dig.

Använd räkna om du verkligen behöver både indexet och elementet .

for i, elem in enumerate(tab):
     print((i, elem))

Var försiktig när du använder "==" för att kontrollera sann eller falskt

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

Kontrollera inte om du kan, bara gör det och hantera felet

Pythonistas säger vanligtvis "Det är lättare att be om förlåtelse än tillåtelse".

Inte:

if os.path.isfile(file_path):
    file = open(file_path)
else:
    # do something

Do:

try:
    file = open(file_path)
except OSError as e:
    # do something

Eller ännu bättre med Python 2.6+ :

with open(file_path) as file:

Det är mycket bättre eftersom det är mycket mer generiskt. Du kan applicera try/except nästan vad som helst. Du behöver inte bry dig om vad du ska göra för att förhindra det, bara bry dig om det fel du riskerar.

Kontrollera inte mot typen

Python skrivs dynamiskt, och därför kontrollerar du om du gör att du tappar flexibiliteten. Använd istället anka-typ genom att kontrollera beteende. Om du förväntar dig en sträng i en funktion använder du str() att konvertera ett objekt till en sträng. Om du förväntar dig en lista använder du list() att konvertera alla iterable till en lista.

Inte:

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)

Do:

def foo(name) :
    print(str(name).lower())

def bar(listing) :
    l = list(listing)
    l.extend((1, 2, 3))
    return ", ".join(l)

På det sista sättet accepterar foo alla objekt. bar accepterar strängar, tuples, uppsättningar, listor och mycket mer. Billig TÖR.

Blanda inte mellanrum och flikar

Använd objekt som första förälder

Det här är svårt, men det kommer att bita dig när ditt program växer. Det finns gamla och nya klasser i Python 2.x De gamla är, ja, gamla. De saknar några funktioner och kan ha besvärligt beteende med arv. För att vara användbar måste någon av din klass vara av "ny stil". För att göra det, få den att ärva från object .

Inte:

class Father:
    pass

class Child(Father):
    pass

Do:

class Father(object):
    pass


class Child(Father):
    pass

I Python 3.x alla klasser ny stil så du behöver inte göra det.

Initiera inte klassattribut utanför init- metoden

Människor som kommer från andra språk tycker det är frestande eftersom det är vad du gör i Java eller PHP. Du skriver klassnamnet, listar sedan dina attribut och ger dem ett standardvärde. Det verkar fungera i Python, men detta fungerar inte som du tror. Om du gör det kommer du att ställa in klassattribut (statiska attribut). När du försöker få objektattributet kommer det att ge dig dess värde om det inte är tomt. I så fall kommer det att returnera klassattributen. Det innebär två stora faror:

  • Om klassattributet ändras ändras det initiala värdet.

  • Om du ställer in ett muterbart objekt som ett standardvärde får du samma objekt som delas över instanser.

Inte (om du inte vill ha statisk):

class Car(object):
    color = "red"
    wheels = [Wheel(), Wheel(), Wheel(), Wheel()]

Gör:

class Car(object):
    def __init__(self):
        self.color = "red"
        self.wheels = [Wheel(), Wheel(), Wheel(), Wheel()]

Heltal och strängidentitet

Python använder intern cache för en rad heltal för att minska onödiga omkostnader från deras upprepade skapande.

I själva verket kan detta leda till förvirrande beteende när man jämför heltalidentiteter:

>>> -8 is (-7 - 1)
False
>>> -3 is (-2 - 1)
True

och med hjälp av ett annat exempel:

>>> (255 + 1) is (255 + 1)
True
>>> (256 + 1) is (256 + 1)
False

Vänta, va?

Vi kan se att identiteten operation is avkastningen True för några heltal ( -3 , 256 ) men ingen för andra ( -8 , 257 ).

För att vara mer specifika cachas heltal i intervallet [-5, 256] internt vid tolkstart och skapas endast en gång. Som sådan är de identiska och att jämföra sina identiteter med is True ; heltal utanför detta intervall är (vanligtvis) skapade on-the-fly och deras identiteter jämförs med False .

Detta är ett vanligt fallgrop eftersom detta är ett vanligt intervall för testning, men ofta nog, koden misslyckas i den senare iscenesättningsprocessen (eller ännu värre - produktion) utan någon uppenbar anledning efter att ha fungerat perfekt under utveckling.

Lösningen är att alltid jämföra värden med operatören jämlikhet ( == ) och inte operatören för identitet ( is ).


Python håller också hänvisningar till vanligt använda strängar och kan resultera i på samma sätt förvirrande beteende när man jämnar identiteter (dvs. att använda is ) av strängar.

>>> 'python' is 'py' + 'thon'
True

Strängen 'python' används vanligtvis, så Python har ett objekt som alla referenser till strängen 'python' använder.

För ovanliga strängar misslyckas jämförelse av identitet även när strängarna är lika.

>>> '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

Så, precis som regeln för heltal, jämför alltid strängvärden med operatören jämlikhet ( == ) och inte operatören för identitet ( is ).

Få tillgång till int-bokstävernas attribut

Du kanske har hört att allt i Python är ett objekt, till och med bokstavliga. Detta innebär att till exempel 7 är ett objekt, vilket innebär att det har attribut. Till exempel är ett av dessa attribut bit_length . Den returnerar mängden bitar som behövs för att representera det värde det krävs.

x = 7
x.bit_length()
# Out: 3

Om du ser ovanstående kod fungerar du kanske intuitivt att 7.bit_length() skulle fungera också, bara för att ta reda på att det höjer en SyntaxError . Varför? eftersom tolkaren måste skilja mellan ett attributstillträde och ett flytande nummer (till exempel 7.2 eller 7.bit_length() ). Det kan inte, och det är därför som ett undantag tas upp.

Det finns några sätt att få tillgång till en int literals attribut:

# parenthesis
(7).bit_length()
# a space
7 .bit_length()

Att använda två punkter (som den här 7..bit_length() ) fungerar inte i det här fallet, eftersom det skapar en float letteral och floats har inte bit_length() .

Detta problem existerar inte vid åtkomst float literals' attribut eftersom interperter är 'smart' nog att veta att en float bokstavlig inte kan innehålla två . , till exempel:

7.2.as_integer_ratio()
# Out: (8106479329266893, 1125899906842624)

Kedja eller operatör

När du testar för någon av flera jämställdhetsjämförelser:

if a == 3 or b == 3 or c == 3:

det är frestande att förkorta detta till

if a or b or c == 3: # Wrong

Detta är fel; or operatören har lägre prioritet än == , så uttrycket kommer att utvärderas som if (a) or (b) or (c == 3): Rätt sätt är att uttryckligen kontrollera alla villkor:

if a == 3 or b == 3 or c == 3:  # Right Way

Alternativt kan den inbyggda any() funktionen any() användas i stället för kedjade or operatörer:

if any([a == 3, b == 3, c == 3]): # Right

Eller för att göra det mer effektivt:

if any(x == 3 for x in (a, b, c)): # Right

Eller för att göra det kortare:

if 3 in (a, b, c): # Right

Här använder vi in operatören att testa om värdet finns i en tupel innehåller de värden vi vill jämföra mot.

På samma sätt är det felaktigt att skriva

if a == 1 or 2 or 3:

som bör skrivas som

if a in (1, 2, 3):

sys.argv [0] är namnet på filen som körs

Det första elementet i sys.argv[0] är namnet på pythonfilen som körs. De återstående elementen är skriptargument.

# 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']

Ordböcker är oordnade

Du kan förvänta dig att en Python-ordlista ska sorteras efter nycklar som till exempel en C ++ std::map , men detta är inte fallet:

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 har ingen inbyggd klass som automatiskt sorterar sina element efter nyckel.

Men om sortering inte är ett måste, och du bara vill att din ordbok ska komma ihåg ordningen för infogning av dess nyckel- / värdepar, kan du använda collections.OrderedDict :

from collections import OrderedDict

oDict = OrderedDict([('first', 1), ('second', 2), ('third', 3)])

print([k for k in oDict])
# Out: ['first', 'second', 'third']

Kom ihåg att initiera en OrderedDict med en standardordbok inte på något sätt kommer att OrderedDict ordboken för dig. Allt som denna struktur gör är att bevara ordningen för infogning av nycklar.

Implementeringen av ordböcker ändrades i Python 3.6 för att förbättra deras minneskonsumtion. En biverkning av den nya implementeringen är att den också bevarar ordningen med sökordargument som skickas till en funktion:

Python 3.x 3.6
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 

Varning : se upp för att " den ordningsbevarande aspekten av denna nya implementering betraktas som en implementeringsdetalj och inte bör lita på " , eftersom det kan förändras i framtiden.

Global Interpreter Lock (GIL) och blockerande trådar

Det har skrivits gott om Pythons GIL . Det kan ibland orsaka förvirring när man hanterar flertrådade (inte förväxlas med multiprocess) -applikationer.

Här är ett exempel:

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")

Du kan förvänta dig att se Calculating... skrivs ut omedelbart efter att tråden har startats, vi ville att beräkningen skulle ske i en ny tråd trots allt! Men i själva verket ser du att det skrivs ut när beräkningen är klar. Det beror på att den nya tråden förlitar sig på en C-funktion ( math.factorial ) som låser GIL medan den körs.

Det finns ett par sätt runt detta. Den första är att implementera din faktorfunktion i ursprungliga Python. Detta gör att huvudtråden kan greppa kontrollen när du är inne i din slinga. Nackdelen är att den här lösningen kommer att bli mycket långsammare eftersom vi inte använder C-funktionen längre.

def calc_fact(num):
    """ A slow version of factorial in native Python """
    res = 1
    while num >= 1:
        res = res * num
        num -= 1
    return res

Du kan också sleep under en period innan du börjar körningen. Obs! Detta kommer faktiskt inte att låta ditt program avbryta beräkningen som sker i C-funktionen, men det kommer att låta din huvudtråd fortsätta efter spawn, vilket är vad du kan förvänta dig.

def calc_fact(num):
    sleep(0.001)
    math.factorial(num)

Variabel läcka i listförståelser och för slingor

Tänk på följande listaförståelse

Python 2.x 2.7
i = 0
a = [i for i in range(3)]
print(i) # Outputs 2

Detta inträffar endast i Python 2 på grund av det faktum att listförståelsen "läcker" slingkontrollvariabeln i det omgivande omfånget ( källa ). Detta beteende kan leda till svåra att hitta buggar och det har rättats i Python 3 .

Python 3.x 3.0
i = 0
a = [i for i in range(3)]
print(i) # Outputs 0

På samma sätt har slingor inget privat utrymme för sin iterationsvariabel

i = 0
for i in range(3):
    pass
print(i) # Outputs 2

Denna typ av beteende förekommer både i Python 2 och Python 3.

För att undvika problem med läckande variabler, använd nya variabler i listförståelser och för slingor efter behov.

Flera avkastning

Funktion xyz returnerar två värden a och b:

def xyz():
  return a, b

Kod som ringer xyz-butiker resulterar i en variabel förutsatt att xyz endast returnerar ett värde:

t = xyz()

Värdet på t är faktiskt en tupel (a, b) så varje åtgärd på t antar att det inte är en tupel kan misslyckas djupt i koden med ett oväntat fel om tuples.

TypeError: typ tuple definierar inte ... metod

Fixet skulle vara att göra:

a, b = xyz()

Nybörjare kommer att ha problem med att hitta orsaken till detta meddelande genom att bara läsa felmeddelandet om tuple!

Pythonic JSON-nycklar

my_var = 'bla';
api_key = 'key';
...lots of code here...
params = {"language": "en", my_var: api_key}

Om du är van vid JavaScript är variabel utvärdering i Python-ordböcker inte det du förväntar dig att det ska vara. Detta uttalande i JavaScript skulle resultera i params enligt följande:

{
    "language": "en",
    "my_var": "key"
}

I Python skulle det emellertid resultera i följande ordbok:

{
    "language": "en",
    "bla": "key"
}

my_var utvärderas och dess värde används som nyckel.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow