admin 0Comment

Dzisiaj na tapetę biorę zadanie z siódmego rozdziału kursu Automate the Boring Stuff with Python. Rozdział ten uczy podstaw używania tzw. regular expressions. Jeśli nie znasz kursu lub nie czytałeś tekstu wyjaśniającego pomysł na serię wpisów, powinieneś zrobić to tutaj. Oto tekst zadania:

Strong Password Detection

Write a function that uses regular expressions to make sure the password string it is passed is strong. A strong password is defined as one that is at least eight characters long, contains both uppercase and lowercase characters, and has at least one digit. You may need to test the string against multiple regex patterns to validate its strength.

Zgodnie z wytycznymi zadania musimy przetestować podany string i ocenić czy będzie mógł być wykorzystany, jako silne hasło.  Zajmijmy się więc po kolei koniecznymi warunkami, określając w jaki sposób wykorzystać do tego wyrażenia regularne (jeśli nie znasz tego zagadnienia, sięgnij do dostępnego online rozdziału siódmego wspomnianego kursu – są one tam wytłumaczone w bardzo przystępny sposób):

 

Hasło powinno mieć minimum osiem znaków.

Możemy to określić nie korzystając z wyrażeń regularnych, jednak skupmy się na nich. Proponuję takie rozwiązanie:

re.compile(r'.{8,}').search(password)

Oczywiście pod zmienną password będzie krył się podany do przetestowania łańcuch znaków.

 

Hasło powinno zawierać zarówno wielkie jak i małe litery.

Tutaj możemy łatwo stworzyć zbiory wielkich i małych liter i sprawdzić, czy chociaż jeden ich egzemplarz jest obecny w sprawdzanym łańcuchu:

re.compile(r'[a-z]+').search(password)

re.compile(r'[A-Z]+').search(password)

 

Hasło powinno posiadać minimum jedną cyfrę.

Podobnie jak wyżej, sprawdzamy czy chociaż jedna cyfra jest obecna w łańcuchu:

re.compile(r'\d+').search(password)

 

Mamy więc przygotowane wyrażenia regularne, które przetestują podany przez użytkownika łańcuch.

Jak jednak połączyć je w całość? Na pewno warto ująć nasze warunki w jedną funkcję, która jako argument przyjmie łańcuch znaków do przetestowania. Tekst zadania nie precyzuje, co powinniśmy uzyskać jako wynik, poza ogólnym określeniem, że funkcja ma sprawdzić czy hasło jest wystarczająco mocne czy też nie. W najprostszej wersji wykorzystalibyśmy więc instrukcje  if-elif-else, które, w przypadku niespełnienia jakiegoś warunku, zwróciłyby nam wartość logiczną False. Gdyby jednak wszystkie warunki były spełnione, funkcja w instrukcji else zwróciłaby wartość logiczną True. Jak mógłby wyglądać taki kod:

import re

def is_strong(password):
    if re.compile(r'.{8,}').search(password) == None:
        return False
    elif re.compile(r'[a-z]+').search(password) == None:
        return False
    elif re.compile(r'[A-Z]+').search(password) == None:
        return False
    elif re.compile(r'\d+').search(password) == None:
        return False
    else:
        return True

print(is_strong('ThisIsStr0ngPassword'))
print(is_strong('weakone'))

 

Dwie ostatnie linijki służą sprawdzeniu działania funkcji na przykładach. Funkcja ta zwróci nam wartość logiczną, określającą czy podany argument jest wystarczająco silny.  Warto zauważyć, że funkcja zwróci wartość False już przy pierwszej sytuacji, w której jeden z warunków nie zostanie spełniony. Co może nam się w niej nie podobać? Możemy nie chcieć mieć aż tylu punktów wyjścia (return) z funkcji – skorzystajmy więc z tzw. flagi, którą zwrócimy jednorazowo.

import re

def is_strong(password):
    is_strong = True
    if re.compile(r'.{8,}').search(password) == None:
        is_strong = False
    elif re.compile(r'[a-z]+').search(password) == None:
        is_strong = False
    elif re.compile(r'[A-Z]+').search(password) == None:
        is_strong = False
    elif re.compile(r'\d+').search(password) == None:
        is_strong = False
    return is_strong

print(is_strong('ThisIsStr0ngPassword'))
print(is_strong('weakone'))

 

Unikamy więc wielu punktów wyjścia, zwracając wartość logiczną flagi, która początkowo ustalona jest na True, jednak w przypadku niespełnienia któregoś z warunków, zostanie ona zmieniona na False. Ale, skoro rezultat ćwiczenia nie jest sprecyzowany, możemy zrobić coś więcej. Załóżmy, że chcemy wiedzieć dokładnie, których warunków dane słowo nie spełnia. Zadecydujmy też, że funkcja nie zwróci, ale wypisze nam konkretne informacje na ekranie:

import re

def password_check(password):
    has_error = False
    if re.compile(r'.{8,}').search(password) == None:
        print('Password is not long enough!')
        has_error = True
    if re.compile(r'[a-z]+').search(password) == None:
        print('Password has no lowercase characters!')
        has_error = True
    if re.compile(r'[A-Z]+').search(password) == None:
        print('Password has no uppercase characters!')
        has_error = True
    if re.compile(r'\d+').search(password) == None:
        print('Password has no digits!')
        has_error = True
    if has_error == False:
        print('Password is strong enough!')

password_check('ThisIsStr0ngPassword')
print()
password_check('weakone')

 

Zobaczcie, co zwróci nam ten kod:

Password is strong enough!

 

Password is not long enough!

Password has no uppercase characters!

Password has no digits!

 

Oczywiście tekst przed pustą linią odnosi się do użytego łańcucha, który spełnia warunki a pozostałe do tego, który warunków nie spełnia. Jeżeli dopiero zaczynacie naukę programowania, kilka rzeczy w tym kodzie może was zdziwić. Przede wszystkim – dlaczego nie używam tutaj instrukcji if-elif tylko kilka razy if? Zwróćcie uwagę na to, kiedy zadziała a kiedy nie zadziała instrukcja elif, zależnie od tego czy poprzedzający ją warunek if został, czy też nie został, spełniony. Możecie to zrobić na powyższym przykładzie, zmieniając go (zastąpcie drugi i kolejne if wykorzystując elif) i sprawdzając, co zwróci zmieniony kod. Druga kwestia to użycie przeze mnie flagi has_error. Tutaj zastanówcie się w jakich sytuacjach chciałbym (a w jakich nie) by na ekranie pojawił się tekst informujący, że podane hasło jest wystarczająco silne.

Oczywiście kod ten nadal jest dość toporny – możemy chcieć by był on w stanie zwracać a nie wyświetlać interesujące nas informacje. Jest to możliwe np. poprzez zgrupowanie informacji o niespełnionych warunkach w jednym łańcuchu i zwrócenie go wraz z wartością logiczną True/False w jednym kontenerze, np. w tuple (lub też krócej – zinterpretować wartość logiczną na podstawie tego czy zwrócony zostanie pusty łańcuch czy też taki, który posiada informacje o niespełnionych warunkach). Możliwości jest bardzo wiele i zależą od konkretnego zastosowania danej funkcji.

Jeśli chodzi natomiast o same wyrażenia regularne, to jest to temat bardzo ważny jeśli chodzi o podstawy programowania – są one często wykorzystywane w komercyjnym, produkcyjnym kodzie. Dlatego jeżeli uczysz się programowania z myślą o rozpoczęciu pracy w tym zawodzie, zdecydowanie powinieneś się z nimi zapoznać.

Dodaj komentarz