Python 3: local variable referenced before assignment

Permalänk

Python 3: local variable referenced before assignment

Jag skrev bara ett simpelt krypteringsprogram:

alfaphet=('abcdefghijklmnopqrstuvxyz') cryptalfaphet=('defghjiklmnopqrstuvxyzabc') spaceNumber=[] textCopy=[] def crypt(): textCopy=[] print('print the text that you want to encrypt:') text=input() for i in range(len(text)): for j in range(len(alfaphet)): if text[i]==alfaphet[j]: textCopy.append(cryptalfaphet[j]) if text[i]==' ': spaceNumber.append(i) for i in range(len(spaceNumber)): for j in range(len(text)): if list(range(len(text)))[j]==int(spaceNumber[i]): textCopy.insert(j, ' ') textCopy=''.join(textCopy) print(textCopy) crypt()

Det som gör mig förvirrad är att det verkar som att jag måste ha med

textCopy=[]

innanför def-blocket, men när det kommer till "spaceNumber" variabeln så räcker det med att det står

spaceNumber=[]

utanför def-blocket. Jag får nämligen ett errormeddelande om jag tar bort

textCopy=[]

från början av def-blocket som säger:

Traceback (most recent call last): File "C:/Python33/dekrypt.py", line 26, in <module> crypt() File "C:/Python33/dekrypt.py", line 13, in crypt textCopy.append(cryptalfaphet[j]) UnboundLocalError: local variable 'textCopy' referenced before assignment

Vad är det för skillnad på "textCopy" och "spaceNumber" som gör att det ter sig såhär?

Permalänk

Har förstått vad problemet är nu!!!

Permalänk
Hedersmedlem

Dela gärna med dig!

Permalänk
Medlem

Bra att det löste sig! Antagligen behövs:

global textCopy

Raden med spaceNumber.append() körs aldrig om variabeln text inte innehåller mellanslag.

Skickades från m.sweclockers.com

Visa signatur

..:: RiJo ::..
Computer: Lenovo X300
Platform: Gentoo

Permalänk
Hedersmedlem

Lite allmänna kodnotiser:

Skrivet av pineappleexpress:

alfaphet=('abcdefghijklmnopqrstuvxyz') cryptalfaphet=('defghjiklmnopqrstuvxyzabc')

Paranteserna gör ingenting här. I vanliga fall används paranteser för att definiera en tuple, men det händer bara ifall man anger mer än ett element (eller om man avslutar ett enstaka objekt med ett ensamt kommatecken). Om tilldelningen inte innehåller ett komma så kan paranteser användas för "implicit radförlängning", då tolken kommer "äta upp" radbrytningar och automatiskt förena strängar inom paranteser, men eftersom du inte behöver några radbrytningar här så gör de alltså ingenting.

Skrivet av pineappleexpress:

spaceNumber=[] textCopy=[]

Detta är ingen generell regel, men närapå: det är väldigt sällan som man behöver förinitiera listor i Python. Allt som oftast löses sådana uppgifter bättre av att skriva om logiken lite till att använda en list comprehension och om nödvändigt faktorera ut logik i separata funktioner.

Skrivet av pineappleexpress:

def crypt():

För att få bättre överblick så skulle jag faktorera upp programmet i fler funktioner. Funktionen som heter `crypt` borde ta en sträng som input och returnera en krypterad sträng, och inte bry sig om användarinmatning, utskrifter och annat.

Skrivet av pineappleexpress:

textCopy=[]

Den yttre deklarerade variabeln verkar ju inte användas överhuvudtaget så som det är skrivet nu då den återdeklareras här, så den skulle lika gärna kunna tas bort.

Skrivet av pineappleexpress:

print('print the text that you want to encrypt:') text=input()

`input()` kan ta ett argument som är just en sträng som skrivs ut till användaren innan inmatningen sker, så du kan "baka in" `print()`-anropet direkt i `input()`.

Skrivet av pineappleexpress:

for i in range(len(text)): for j in range(len(alfaphet)): if text[i]==alfaphet[j]: textCopy.append(cryptalfaphet[j]) if text[i]==' ': spaceNumber.append(i) for i in range(len(spaceNumber)): for j in range(len(text)): if list(range(len(text)))[j]==int(spaceNumber[i]): textCopy.insert(j, ' ')

Detta ser väldigt icke-Pythonskt ut .

I stället för att explicit loopa över hela kryptotabellen varje gång så kan du använda en funktion som "letar" i en sträng. Strängar i Python kan implicit tolkas som listor av tecken, så funktioner som fungerar på listor fungerar alltså på strängar.

Vad du säger här är att du vill loopa över alla tecken i `text`, och om tecknet finns i `alfaphet` så ska motsvarande tecken i `cryptalfaphet` läggas till i `textCopy`. Detta kan ekvivalent skrivas som:

for i in text: if i in alfaphet: textCopy.append(cryptalfaphet[alfaphet.index(i)])

En skillnad är att detta undviker en "bugg" som inträffar ifall mellanslaget skulle vara en del av `alfaphet`; se nedan. Notera att båda varianterna helt enkelt raderar alla tecken i in-strängen som inte finns med i översättningstabellen.

Detta sätt är inte heller speciellt "Pythonskt", men den är troligen enklare att följa utan att behöva blanda in undantagshantering (`try`-`except`-konstruktionen). `alfaphet.index()` returnerar det sifferindex som motsvarar det element som ges som argument; eftersom vi bara kör denna rad om `alfaphet` innehåller elementet så vet vi att det alltid kommer att hittas.

Därefter så testar du (oavsett om tecknet redan hittats i `alfaphet` eller ej) om tecknet är ett mellanslag och lägger i så fall till ett mellanslag i en separat lista. Till att börja med vore det bättre med ett `elif`-villkor för att undvika att testa detta när du redan vet att tecknet fanns i `alfaphet`. Därutöver så känns det onödigt att ha en separat mekanism för just mellanslag: om du lägger till mellanslag i både `alfaphet` och `cryptalfaphet` på samma plats så kommer detta ju hanteras precis som övriga tecken, så då kan du kasta ut hela `spaceNumber`-logiken och ändå ha samma funktionalitet kvar.

Skrivet av pineappleexpress:

textCopy=''.join(textCopy)

Det är korrekt att det mest effektiva sättet att bygga strängar i Python är att samla de i en lista som man sedan förenar på detta sätt, jämfört med att kontinuerligt "addera" strängar. Just i detta fall så kan man misstänka att det redan existerar någon funktion för att "översätta" en sträng teckenvis till en annan, och visst:

crypto_table = str.maketrans( ' abcdefghijklmnopqrstuvxyz', ' defghjiklmnopqrstuvxyzabc' ) while True: user_text = input('Print the text that you want to encrypt: ') print(user_text.translate(crypto_table))

Notera "tricket" med `while True:`: en `while`-sats kör sitt block tills villkoret inte längre är "sant" när blocket är slut. Genom att sätta testet direkt till `True` som är ett inbyggt värde i Python så kommer alltså detta test alltid vara sant, och blocket köra "för evigt". Det är ett vanligt sätt att skapa sådana konstruktioner i Python, snarare än att kalla på funktionen rekursivt i slutet som du gjorde i din nuvarande kod.

Skrivet av pineappleexpress:

print(textCopy)

Som nämndes tidigare så tycker jag inte att en funktion som heter `crypt()` ska vara den som sköter utskrifter till användaren, utan låt den bara sköta "krypteringen" internt, och lös programgången i en annan funktion. Standardsättet att lösa detta i Python är att skapa en funktion som heter `main()` som är inkörsporten till programmet, som man sedan kallar på genom idiomet nedan:

def encrypt(text_input): # …skriv funktionen som sköter krypteringen här, och avsluta med att # returnera den krypterade strängen: return encrypted_text def main(): print('Welcome to StringEncrypter 2000!') while True: user_input = input('Print the text that you want to encrypt: ') crypt_output = encrypt(user_input) print('The encrypted output is: {}'.format(crypt_output)) if __name__ == '__main__': main()

De sista två raderna ser troligen kryptiska ut till en början, men initialt så kan det vara OK att bara acceptera att de fungerar. Vad som i praktiken händer är att man testar om specialvariabeln `__name__` har specialvärdet `__main__`, vilket är sant om man "kör" programmet, så kommer funktionen `main()` köras, vari man definierar programmets logiska struktur. Faktiska beräkningar och aktioner separeras ut i egna funktioner så som `encrypt` i det här fallet.

Det kan vara trevligt att låta användaren avsluta programmet på ett snällare sätt än Ctrl+C eller genom att döda processen (exempelvis genom att ge den tomma strängen som input). Hantering av detta läggs till i `main()`-funktionen.

Visa signatur

Nu med kortare användarnamn, men fortfarande bedövande långa inlägg.