Eingabe vorschreiben

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
doggy
User
Beiträge: 22
Registriert: Freitag 19. September 2014, 10:32

Guten Tag,

Ich habe ein Programm für eine PIN-Eingabe geschrieben. Diese wird mit einer Textdatei verglichen, die wie folgt aussieht ca.:

Code: Alles auswählen

Norbert 1234
Herbert 5678
usw.
Nun will ich abfragen ob der PIN der eingegeben wird in der Datei vorhanden ist. Es klappt alles soweit, bis auf das ich nicht vorschreiben kann, dass die Eingabe 4-Stellig aus zahlen sein muss. Ansonsten soll Falscher PIN ausgegeben werden.

Hier das Script:

Code: Alles auswählen

while True:
    try:
        a = int(input("Geben die die Zahlen ein"))
        b = open("pin.txt").read()
        if str(a) in b:
            print("Hallo")
        else:
            print("Falscher PIN")

    except:
        print("Fehler")
Die einrückungen sind natürlich richtig im echten Programm.
Vielen Dank
Zuletzt geändert von Hyperion am Freitag 19. September 2014, 12:06, insgesamt 1-mal geändert.
Grund: Code in Python-Code Tags gesetzt.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo und willkommen im Forum!

Dein Programm hat so einige Problemchen. Als erstes fällt das try-except-Konstrukt auf. Verwende *niemals* eine except ohne die Angabe der genauen Ausnahme. Sonst werden alle Fehler verschluckt. Unter Umständen auch Programmierfehler. Dann geht irgend etwas in deinem Programm schief, dein Programm befindet sich anschließend in einem ungültigen Zustand und irgendwo kracht es dann (oder im schlimmsten Fall nicht). Solche Fehler sind extrem schwer zu finden.

Weiter musst du bei try-except beachten, dass die try-Blöcke so wenig Code wie möglich enthalten sollten. In deinem Fall wäre das nur die Umwandlung des Strings in einen Integer nicht mehr. Die Eingabe gehört da schon nicht mehr rein, der ganze restliche Code so wie so nicht. Denn auch da könnten Ausnahmen ausgelöst werden, welche du gar nicht behandeln willst.

Dann solltest du dir angewöhnen vernünftige Namen zu wählen. "a" und "b" sagen gar nichts aus. Da musst du jedes Mal nachschauen, was diese Namen überhaupt bedeuten. Wenn das steht "pin", dann ist die Sache sofort klar.

Dateien solltest du mittels with-Statement öffnen, dann werden diese, auch im Fehlerfall, wieder geschlossen.

Dein Test der PIN ist auch nicht korrekt. Du suchst die PIN in der gesamten Datei. Das heißt, dass für jeden Benutzer jede vorhandene PIN akzeptiert wird. Es ist sogar noch schlimmer. Es könnte auch Benutzer geben, die Ziffern enthalten. Auch die würden als korrekte PIN erkannt werden. Du musst deine Datei Zeilenweise verarbeiten. Oder gleich ein vernünftiges Format, wie csv, wählen. Dafür gibt es in Python auch ein Modul.

Zu deinem eigentlichem Problem: Du kannst die Länge von Strings abfragen, damit kannst du dann feststellen, ob auch vier Zeichen eingegeben wurden. Dieser Test ist aber an sich überflüssig. Wenn du den Test korrekt Implementierst, nicht mit "in" sonder mittels Gleichheit, dann musst du die Länge nicht extra testen.

Edit: Ach ja, das Forum unterstützt Code-Tags, damit wird dein Code vernünftig angezeigt. Der Button dazu befindet sich über dem großen Textfeld und heißt "Code". Dann kann man dein Programm auch vernünftig lesen.
Das Leben ist wie ein Tennisball.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Eine 4-stellige Zahl ist größer als 999 und kleiner als 10000, das kannst Du prüfen:

Code: Alles auswählen

def ist_4_stellig(zahl):
    return 999<zahl<10000

print ist_4_stellig(  567)
print ist_4_stellig( 1567)
print ist_4_stellig(11567)
Der 10er Logarithmus einer 4-stellige Zahl ist größer-gleich 3.0 und kleiner als 4.0:

Code: Alles auswählen

import math
def ist_4_stellig(zahl):
    return int(math.log10(zahl))==3

print ist_4_stellig(  567)
print ist_4_stellig( 1567)
print ist_4_stellig(11567)
Der String aus dem Du die Zahl erzeugst sollte 4 Zeichen lang sein, auch das kannst Du prüfen ...
a fool with a tool is still a fool, www.magben.de, YouTube
BlackJack

Man könnte auch einfach die unsinnige Umwandlung in eine Zahl sein lassen.
doggy
User
Beiträge: 22
Registriert: Freitag 19. September 2014, 10:32

Vielen Dank für die Antworten.

Zunächst an EyDu:

Ja, ich bin auch absoluter Anfänger. Ich habe vor zwei Wochen begonnen Python mit kaum Vorahnung zu lernen. Ich bin eigentlich gelernter Mechatroniker und muss nun in Rahmen eines Projektes die Steuerung einer Elektroladestation Programmieren. Das Script, welches ich hier gezeigt habe ist auch lediglich ein Test, welchen ich später wenn es funktioniert ordentlich im Programm einbinde und dann auch ordentlich benenne. Ansonsten nehme ich mir deine Tipps dankend zu Herzen und versuche sie bei meinem Programm zu bedenken.


Nun zu MagBen.

Vielen Dank, ich werde versuchen deinen Tipp einzubinden. Darüber könnte ich Abfragen, dass die Eingabe korrekt und 4-stellig ist.


Und dann noch Blackjack.

Ich kann die Umstellung nicht wegnehmen, noch nicht. Würde ich dies tun, könnte in dem Programm dieser Form soger nur mit der eingabe der Eingabetaste ein "okay" ausgelöst werden.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

BlackJack hat geschrieben:Man könnte auch einfach die unsinnige Umwandlung in eine Zahl sein lassen.
Eben, denn dann wird das Validieren kinderleicht:

Code: Alles auswählen

def is_pin_valid(pin):
    return len(pin) == 4 and all(c.isdigit() for c in pin)

def test_is_pin_valid():
    assert is_pin_valid("1234"), "'1234' ist ein gültiger PIN"
    assert not is_pin_valid("123"), "'123' hat nur drei Stellen und ist damit zu kurz!"
    assert not is_pin_valid("12345"), "'12345' hat fünf Stellen und ist damit zu lang!"
    assert not is_pin_valid("a234"), "'a234' enthält andere Zeichen als Ziffern!"
(Ja man sollte eigentlich ein Unittest Framework dafür nehmen ;-) )
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
doggy
User
Beiträge: 22
Registriert: Freitag 19. September 2014, 10:32

xD Kinderleicht, aufjedenfall :) Obwohl ich ein 400 Seiten Buch in 2 Wochen gewälzt habe, sagen mir die Befehle nicht viel, welche du dort auflistest. Ich kanns mir aber ca. denken
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Und weil ich so wenig in Python mache in letzter Zeit habe ich das natürlich viel komplizierter ausgedrückt als notwendig, da ``isdigit`` natürlich auf *strings* arbeitet, also auch auf mehrstelligen :oops:

Korrigiert also so:

Code: Alles auswählen

def is_pin_valid(pin):
    return len(pin) == 4 and pin.isdigit()
Dank der Test-Funktion kann ich mir ziemlich sicher sein, dass das auch so klappt, wie es soll :-) Verbuchen wir obiges also unter dem Gesichtspunkt "Refactoring" des TDD Ansatzes :mrgreen:
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
doggy
User
Beiträge: 22
Registriert: Freitag 19. September 2014, 10:32

Bitte nicht vergessen^^ Ich bin blutiger Anfänger. Wo binde ich nun diese Funktion in mein Programm ein, damit sie dafür sorgt das nur 4-Stellige Eingaben verarbeitet werden?
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Äh... na da, wo Du die PIN prüfen willst‽

Du hast doch schon andere Funktionen *aufgerufen* (``print``, ``open``, ``int``, usw.), was ist denn dann so schwer daran, eine selbst gebaute aufzurufen?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@doggy: Na da wo Du testest ob die PIN in Deiner Textdatei steht, kannst Du mit der Funktion zusätzlich testen ob die PIN den Regeln für PINs entspricht. Man kann zwei Tests logisch mit ``and`` oder ``or`` verknüpfen.
doggy
User
Beiträge: 22
Registriert: Freitag 19. September 2014, 10:32

Ach. Hatte die korrigierte Version nicht gelesen. Dann ist es natürlich auch kein Problem. Dank dir
doggy
User
Beiträge: 22
Registriert: Freitag 19. September 2014, 10:32

Nun klappen alle Funktionen im Programm. Ich denke mal, dass es noch etwas groß ist. Ich will später das gesamte Verifizierungsprogramm darin laufen lassen unter der Funktion in der nun Pin ist korrekt geprintet wird.

Code: Alles auswählen

from time import sleep

def is_pin_valid(PIN):
    return len(PIN) == 4 and PIN.isdigit()

while True:
    
    korrekt = 0

    for i in range(1,6):
        
        try:
            PIN = input("Bitte geben sie Ihren Pin ein: ")
            
            
        except:
            print("Es dürfen nur Zahlen verwendet werden!")
            continue
        
        data = open("pin.txt").read()
        
        if PIN in data and is_pin_valid(PIN):
            
            print("Der PIN war korrekt")
            korrekt = 1
            sleep(5)
            continue
                
        else:
            
            print("Dieser Pin ist nicht korrekt")

    
    if korrekt != 1:
        print("5 Fehlversuche!")
        sleep(5)
Sleep soll eine gewisse Wartezeit zwischen den später mit Tkinker angezeigten Bildern ermöglichen und die Variable korrekt ist lediglich etwas tricksen, damit nicht nach 5 korrekten eingaben auch die Fehlerversuch anzeige kommt
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

``range(1, 6)`` kannst du in diesem Fall einfacher als ``range(5)`` schreiben. Da du den Wert nicht brauchst, kannst du es dir auch einfach machen ;-)

"PIN" sollte besser "pin" heißen, schau dir dazu mal PEP 8 an. Konstanten werden in Python durchgängig in Großbuchstaben geschrieben, daher verwirrt deine Schreibweise etwas.

Dein try-except-Statement ist total sinnlos, der except-Block kann nie betreten werden.

Du hast meinen Tipp zum Öffnen von Dateien noch nicht beachtet. Auch ignorierst du weiterhin den Benutzernamen. Jede vier (oder mehrstellige) Zahl in deiner Datei ist nun eine gültige Eingabe.

Das continue in Zeile 27 ist sinnlos, das macht nichts. Du willst wahrscheinlich break verwenden. Teste deine Programme ;-)

"korrekt" ist etwas umständlich. Teile dein Programm in sinnvolle Funktionen auf, dann sparst du dir diesen unnötigen Schritt.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Neben allen anderen Kritikpunkten stören mich auch die vielen Leerzeilen! Wozu muss nach jedem Block eine Leerzeile davor und dahinter stehen? Finde ich nicht wirklich hilfreich und iirc verstößt das auch gegen PEP8.

@doggy: Du solltest die Daten nur *einmal* einlesen müssen; im Moment passiert das ja bei jedem Versuch...
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
doggy
User
Beiträge: 22
Registriert: Freitag 19. September 2014, 10:32

Vielen Dank für die zahlreichen Antworten und Tipps.

Für meine Zwecke benötige ich keinen Benutzer. Es wird in der Ladestation lediglich eine kleine Zahlentastatur geben. Daher reicht es zunächst allgemein abzufragen, ob der PIN vorhanden ist.

range(1,6) habe ich wegen der if Abfrage genutzt. Würde ich range(5) eingeben, würden die maximalen 5 Versuche nicht funktionieren.

Dennoch habe ich versucht das Programm nun etwas schöner zu gestalten. Ebenfalls habe ich nun eine Liste erstellt, welche die eingegebenen PINS beim ersten mal abspeichert und bei der zweiten Eingabe wieder löscht. Dies soll später die Kabine für den Benutzer reservieren, so dass dieser mit denselben PIN die Kabine auch wieder öffnen kann. Hier das Script in neu.

Code: Alles auswählen

from time import sleep

def is_pin_valid(pin):
    return len(pin) == 4 and pin.isdigit()

speicher = [0,0,0,0,0,0]
gefunden = 0
while True:

    for i in range(1,6):
        pin = input("Bitte geben sie Ihren Pin ein: ")          
        data = open("pin.txt").read()    
            
        if pin in data and is_pin_valid(pin):
            print("Der PIN war korrekt")

            for k in range(6):
                if speicher[k] == pin:
                    speicher[k] = 0
                    gefunden = True
                    print(speicher)
                    break

            

            for j in range(6):
                if speicher[j] == 0 and not gefunden:
                    speicher[j] = pin
                    print(speicher)
                    break

            gefunden = False
            sleep(0.5)
            break
            
        else:
            print(i)
            print("Dieser Pin ist nicht korrekt")
            if i > 4 :
                print("5 Fehlversuche!")
                sleep(0.5)
In meiner Shell läuft dieses Script perfekt. Sobald ich es aber in meinem Raspberry Pi in der Console verwende und den Pin eingeben habe, wird mir folgender Fehler ausgegeben:

Traceback (most recent call last):
File "Ladestation2.py", line 16, in <module>
if PIN in data and is_pin_valid(PIN):
TypeError: 'in <string>' requires string as left operand, not int


ps. Ich finde dafür, dass ich kein gelernter Programmierer oder sonstiges bin und erst vor 2 Wochen überhaupt angefangen habe zu Programmieren ist es auch nicht so schlecht :(
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Also abgesehen davon, dass das nicht einen Deut "schöner" aussieht, hast Du keinen der Kritikpunkte umgesetzt! Ehrlich, die geben wir, weil sie sinnvoll sind :!:

Wie genau soll diese PIN-Datei aussehen? Aktuell steht da einfach *eine* einzige PIN drin, oder wie? Alles andere würde Deine aktuelle Fassung nämlich gar nicht *korrekt* verarbeiten.

Was soll dieser ``speicher`` genau machen? Und wieso ist der Benutzer bezogen, wenn Du angeblich gar keine Benutzer brauchst? Das kapiert vermutlich keiner hier ;-)

Was genau sollen diese sonderbaren Schleifen in denen Du den Speicher durchgehst und komische Dinge damit tust? Ich kapiere denn Sinn dahinter nicht!

Die Initialisierungen mit anderen Typen als den später zugewiesenen verwirren auch mehr, als dass sie nützen! Und eine Mischung aus ``0`` und Strings ist... nuja, sonderbar! Ich vermute das könnte man mit ``None`` besser ausdrücken - oder ggf. einen besseren Datentypen finden o.ä. Aber bislang ist mit diese Liste ja auch höchst unklar.

Wenn das später Teil eines "Programms" werden soll, wieso steht dann da quasi alles auf Modulebene, sprich, wird *immer* ausgeführt? Du solltest Dich - wie iirc schon erwähnt - *dringend* noch (?) einmal mit Funktionen und deren Sinn auseinander setzen. Du schreibst hier feinsten Spaghetti-Code! Und das sollte man vermeiden, wenn man später die Übersicht behalten will :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
doggy
User
Beiträge: 22
Registriert: Freitag 19. September 2014, 10:32

Die PIN-Datei soll einfach von irgendwelchen ungeübten beschrieben und neu aufgespielt werden können. Die schreiben einfach

Name PIN

Also

Herbert Schuhmacher 2323 z.B.

Die for-Schleifen sollen eine Liste beschreiben. Ich habe überlegt, diese Liste später zum zuweisen einer Kabine zu nutzen.
Ich steuere mit meinem Raspberry PI eine Ladestation mit 6 Kabinen. Wenn nun einer seinen PIN eingeben hat, soll sich die nächste freie Kabine öffnen und der PIN in die Liste geschrieben werden. Später möchte ich im echten Programm, dann die jeweilige Zeile der Liste abfragen. Das soll dazu dienen, dass derselbe Benutzer bei erneuter eingabe seines PINs auch wieder seine Kabine und nicht irgendeine andere öffnet.

Das heißt die erste Zeile der Liste steht für die Kabine 1, die zweite für Kabine 2 und so weiter.
BlackJack

@doggy: Warum steht in der Datei überhaupt ein Name wenn doch nur die PIN zählt? Und Dir ist klar, dass jede PIN in der Datei die Wahrscheinlichkeit erhöht das ein unbefugter eine PIN richtig erraten kann, weil die nicht noch mal an einen Namen geknüpft ist, der zusammen mit der PIN überprüft wird. Das gilt dann auch für das öffnen von belegten Kabinen.

Für diese Belegt-Geschichte ist das lineare operieren auf einer Liste nicht wirklich schön. Man würde da eher eine Menge mit freien Kabinen(nummern) und ein Wörterbuch das PINs auf Kabinennummern abbildet verwenden. Und hier haben wir dann auch schon genug Zustand, das man mal über eine Klasse nachdenken könnte.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

doggy hat geschrieben: Name PIN

Also

Herbert Schuhmacher 2323 z.B.
Also wirklich *eine* PIN-Datei für das gesamte System? Wozu das dann? Ich kapiere da immer noch nicht den Sinn! Wenn das nur ein Benutzer bedienen kann, wozu dann überhaupt ein PIN?
doggy hat geschrieben: Die for-Schleifen sollen eine Liste beschreiben. Ich habe überlegt, diese Liste später zum zuweisen einer Kabine zu nutzen.
Aber wieso so kompliziert? Und wieso wird einmal der PIN reingeschrieben und *direkt* danach wieder entfernt?
doggy hat geschrieben: Das soll dazu dienen, dass derselbe Benutzer bei erneuter eingabe seines PINs auch wieder seine Kabine und nicht irgendeine andere öffnet.

Das heißt die erste Zeile der Liste steht für die Kabine 1, die zweite für Kabine 2 und so weiter.
Ich empfinde es zwar merkwürdig, dass dann PIN zu nennen, aber ok. Du suchst hier nun einfach ein Dictionary! Damit kannst Du das viel einfacher handhaben, da es die Abbildung "PIN → Kabine" *direkt* unterstützt und Du nicht implizit über einen Index eine Verbindung schaffen musst! Da brauchst Du nicht mehr iterieren, sondern kannst ganz einfach über die PIN direkt herausfinden, welche Kabine jemand hat. (Wie auch immer eine Kabine datentechnisch aussieht!)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Antworten