meganlynn21 / Password-Validator

Created a Python program to perform password validation to ensure a safe and secure password. Used Python string methods, arithmetic statements, functions, lists, and looping constructs to check different conditional statements

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Code optimization

Mochitto opened this issue · comments

I've taken a look at your code and I think these improvements could be made!

def get_initials() -> str: 
    """Return User's initials from input.""" # Check comment [1]

    name = input("What is your first and last name? ")

    if name:
        initials = [name_surname[0].upper() for name_surname in name.split()]
    else:
        raise ValueError("There was no input")  # Raise a Value error, useful to avoid bugs.

    return "".join(initials)

[1] Will show up when you hover over the function: this description is called docstring

If the return value is None (you directly print the initials, without return), you won't be able to use the username for anything else in your program. Ex. Saving it to store it in a file and use it for a login feature of another project.

It's best to return a string that you can later print on a function call.

  • Long version, without list comprehension
    name = input("What is your first and last name? ")
    
    if name:
        initials = []
    
        for name_surname in name.split():  # Return a list as [name, surname], iterate through the two strings
            string = name_surname # This is the string: first iteration is name, second is surname
    
            first_letter = string[0]
            first_letter = first_letter.upper() # Make the letter Upper
    
            initials.append(first_letter)
    else:
        raise ValueError("There was no input") # Comment [1]
    
    return initials_string = "".join(initials) # Join links together the initials
    

[1] You can create your own errors that will stop the program; it will make it harder to introduce bugs in the future, when you will not remember well what this function does and you will want to use it in another project.

Why make the function this way:

  • Accepts any input, will always return something.
  • Easy to maintain.
  • Can use the returning value for other features or in other projects.
def check_password(min_len: int, max_len: int) -> str:  #  Check comment [1]
    """Check password input and return password."""

    while True:
        password = input(f"""What is your desired password?
(Must be between {min_len} and {max_len} characters and contain at least an uppercase letter).
""")  # Check comment [2]

        # -------------------- Check for Bad Inputs -------------------- #
        # Check for input
        if not password:  # Check comment [3]
            print("Please enter a password.\n")
            continue

        # Check for weak password (you can also use a list of strings with the most common passwords and list comprehension)
        elif "pass" in password:  # compare with: get_four_char = password[0:4].lower()
            print("Please use a more secure password.\n")
            continue

        # Check for length
        elif min_len < len(password) < max_len:  # compare with: length_password < 8 or length_password > 12:
            print(f"Password must be between {min_len} and {max_len} characters.\n")
            continue

        # Check for Upper
        elif not any(letter.isupper() for letter in password):  # This built-in method returns True if any is True, list comprehension
            print("Password must contain at least 1 uppercase letter.\n")
            continue
        # -------------------- End of Bad Inputs -------------------- #
        else:
            return password

[1] When using parameters, you can specify their type by doing "parameter: type". To specify the output use "-> type".
If you are using an IDE like Pycharm, it will tell you when you use a value with the wrong type, if you specify it in the function definition.

[2] The strings starting with f are called fstrings
They make the code more readable AND you don't have to change the type of the value; it will work also with ints and floats.

[3] This is a falsy statement.

When commenting what the function does, you should use the imperative form, not a descriptive one: https://www.python.org/dev/peps/pep-0257/#id5 (under "Notes")
For comments: https://www.python.org/dev/peps/pep-0008/#comments
I suggest reading through the whole guide!

if __name__ == "__main__":  # Used for testing functions: this way
    print(f"Your username will be: {get_initials()}.\n")
    user_password = check_password(8, 12)
    print(f"\nThis will be your password: {user_password}\nStore it somewhere safe.")```

This part of code checks if you are running the project as main or you are importing it:
https://www.geeksforgeeks.org/what-does-the-if-__name__-__main__-do/
If you import this file (to use the two functions in another project, such as a Password manager app), this part of code WILL NOT execute; it's useful to test the functions features and avoids bugs later on.

  • Complete clean code, to copy:
def get_initials() -> str:
    """Return User's initials from input."""
    name = input("What is your first and last name? ")

    if name:
        initials = [name_surname[0].upper() for name_surname in name.split()]
    else:
        raise ValueError("There was no input")

    return "".join(initials)


def check_password(min_len: int, max_len: int) -> str:
    """Check password input and return password string."""
    while True:
        password = input(f"""What is your desired password?
(Must be between {min_len} and {max_len} characters and contain at least an uppercase letter).
""")
        
        # -------------------- Check for Bad Inputs -------------------- #
        # Check for input.
        if not password:
            print("Please enter a password.\n")
            continue

        # Check for weak password.
        elif "pass" in password:
            print("Please use a more secure password.\n")
            continue

        # Check for length.
        elif min_len < len(password) < max_len:
            print(f"Password must be between {min_len} and {max_len} characters.\n")
            continue

        # Check for Upper.
        elif not any(letter.isupper() for letter in password):
            print("Password must contain at least 1 uppercase letter.\n")
            continue
        # -------------------- End of Bad Inputs -------------------- #
        
        else:
            return password


if __name__ == "__main__":
    print(f"Your username will be: {get_initials()}.\n")

    user_password = check_password(8, 12)
    print(f"\nThis will be your password: {user_password}\nStore it somewhere safe.")

Hope this helped!
.
.
.
.
.
P.S Be aware of for loops.

If possible, when looking for patterns (such as getting a first letter), try avoiding for loops (finding smart slicing solutions or using regex). Iterating over strings could suck up a lot of CPU when used on big strings and take a ton of time.

If I input as name an absurdly_long_string and add it instead of the input in your "get_initials" function:

absurdly_long_string = "Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"*10000000

get_initials(absurdly_long_string)

it will take a very long time to compute (30 seconds on my machine) and will return also all the capital letters INSIDE the names, not just one (bug).

If you iterate with "split" and then use index 0 on the strings, you won't get false matches AND it will take only 1 second, using the same absurdly_long_string.
Always try and think if you can avoid for loops, for more efficient processes.

It doesn't make much sense to use this example on your project, but it's a good thing to keep this in mind for the future: using a for loop to, for example, iterate over the returning data from a webpage or a data file could give a similar response.

commented

This helps a lot thank you so much. I am not done with it yet I just started it yesterday. I get confused on when I should return things in a function and when I should not?

No problem! I hope that everything else is clear; you can ask if it isn't!

Functions SHOULD return something if you think that the value that gets processed in them could be used for other things in the program.
Let's suppose you have a password manager app (to stick to your program's theme) and you want to create a file in which you store all of the useful functions that have to do with creating the credentials, storing them, retrieving them etc.

def get_credentials() -> (str, str):
    """Get username and password from user and return a tuple with the inputs."""
    password = input("What is your password? ")
    username = input("What is your username? ")
    return (username, password)

def check_password(password: str, stored_password: str) -> bool:
    """Return True if password == stored_password, else False."""
    return password == stored_password

def save_credentials(username: str, password: str) -> None:
    """Save the credentials to a file."""
    with open("Passwords.txt", "a") as File:
        File.write(f"{username} | {password}"

def load_password(username: str) -> (str, str):
    """Load the password matching the username, if it exists in the data file. Return (username, password)."
    with open("Passwords.txt", "r") as File:
        entries = File.readlines()
    
    for entry in entries:
        if entry.startswith(username):
            return entry.split(" | ")
    else:
        return None

def delete_credentials(username: str):
    """Delete the credentials that match the username, if they exist in the data file."
    with open("Passwords.txt", "r") as File:
        entries = File.readlines()
    
    for entry in entries:
        if entry.startswith(username):
            entries.remove(entry)
    
    with open("Passwords.txt", "w") as File:
        File.write("".join(entries))

Let's compare the functions that return something with those that do not (I'm not sure if there are typos, I didn't put these in the IDE; they should work, if you want to play around with them).

  • get_credentials: we will use the username in load_password and then check the inputted password with the stored_password.
  • check_password: we will use it in an if statement.
  • load_password: we will use the values in check_password OR to print them out when the user needs the password.
    .
  • save: saves to a file.
  • delete: deletes the credentials.
    We do not need a returning value from these, because they just DO SOMETHING.
    We want them to DO SOMETHING, not to GIVE US something. They just DO stuff.

Think about print(). It just prints stuff, it doesn't need to give us something.
On the other side, think about input(). We use it to get new values so that we can store them, manipulate them etc.

Unless a function is only used for printing something (let's say, a function that prints the ascii-art of an heart), you should always return the string and print it later on; if you do some manipulation on a string, it's best to hold its value instead of destroying it after printing.

To make it even better, let's suppose that in the future you learn how to make GUIs and you want to make one for this password management app, and you printed something in every function.
All the print statements have to be taken away from the functions. You will need them to return the string, so that you can put them in the GUI's labels, buttons, entries etc.

And if you did a good job with dividing your functions from the CLI main code, you will be able to import these functions in the GUI's main and just work on the UI, instead of having to look at these functions, understand them again and modify them.

P.s. Code that you can use to play around with the functions:

def get_credentials() -> (str, str):
    """Get username and password from user and return a tuple with the inputs."""
    password = input("What is your password? ")
    username = input("What is your username? ")
    return username, password


def check_password(password: str, stored_password: str) -> bool:
    """Return True if password == stored_password, else False."""
    return password == stored_password


def save_credentials(username: str, password: str) -> None:
    """Save the credentials to a file."""
    with open("Passwords.txt", "a") as File:
        File.write(f"{username} | {password}")


def load_password(username: str) -> (str, str):
    """Load the password matching the username, if it exists in the data file. Return (username, password)."""
    with open("Passwords.txt", "r") as File:
        entries = File.readlines()

    for entry in entries:
        if entry.startswith(username):
            return entry.split(" | ")
    else:
        return None


def delete_credentials(username: str):
    """ Delete the credentials that match the username, if they exist in the data file."""
    with open("Passwords.txt", "r") as File:
        entries = File.readlines()

    for entry in entries:
        if entry.startswith(username):
            entries.remove(entry)

    with open("Passwords.txt", "w") as File:
        File.write("".join(entries))


# Uncomment these on the first run, to create the entry. Run as many times as the users you want to create:

# credentials = get_credentials()
# save_credentials(credentials[0], credentials[1])

# Uncomment these on the second run:

# username = input("What's your username?")
# stored_credentials = load_password(username)
# password = input("What's your password? ")
# if stored_credentials:
#     if check_password(password, stored_credentials[1]):
#         print(f"Access confirmed. Welcome {username}")
#         if input("Delete credentials?").lower() == "y":
#             delete_credentials(username)
#     else:
#         print("Access denied.")
# else:
#     print("No such username or password.")
# 
# print("End of the program")
commented

wow thank you so much this is so helpful!! I am going to copy that code and play around with it if I have any other questions I will ask thanks so much!!

commented

I have never seen elif combined with a for loop like that how does that work?

I guess you are talking about this? (Please use "quote reply" if you have other specific questions, so that we can be more precise :) )

First code:

# Check for Upper.
        elif not any(letter.isupper() for letter in password):
            print("Password must contain at least 1 uppercase letter.\n")
            continue

This line is made out of three parts, but they are "independent" from one another (the square brackets in the following code do not hold any meaning, they are just to highlight the different parts).

1........1.2....3.......................................3.2
[elif not] [any([letter.isupper() for letter in password])]:
  • 1-1: a simple if not statement
  • 2-2: any function: returns true if ANY element of the iterable object (or generator, but those are an advanced topic) is True, else it will return False.
    (I will use T = True and F = False)
    ex. [T,F,T,T,T,T,T] => True
    (T,T,T,T) => True
    (F,F,F,F,F) => False
    (F,F,T,F,F) => True
    [(F,F,F), (T,T)] => True
  • 3-3 list comprehension: this creates a list that will be [bool, bool, bool... bool] for every letter in password: if they are upper, True will be added to the list, else False will.

Let's suppose password is "AbCd1". The function will evaluate to:

if not         any(                 [True, False, True, False, False]                       )

You could replace the any function with "or statements" (but it's much worse to read, probably also slower).

Try putting this in your terminal:

any([True, False, True, False, False]) == (True or False or True or False or False)

Any() makes the code more readable and easier to maintain, being closer to the English language, in a pure pythonic way.
Also, it makes it so that there is no repetition (still, both pythonic AND a good programming habit).

commented

I guess you are talking about this? (Please use "quote reply" if you have other specific questions, so that we can be more precise :) )

First code:

# Check for Upper.
        elif not any(letter.isupper() for letter in password):
            print("Password must contain at least 1 uppercase letter.\n")
            continue

This line is made out of three parts, but they are "independent" from one another (the square brackets in the following code do not hold any meaning, they are just to highlight the different parts).

1........1.2....3.......................................3.2
[elif not] [any([letter.isupper() for letter in password])]:
  • 1-1: a simple if not statement
  • 2-2: any function: returns true if ANY element of the iterable object (or generator, but those are an advanced topic) is True, else it will return False.
    (I will use T = True and F = False)
    ex. [T,F,T,T,T,T,T] => True
    (T,T,T,T) => True
    (F,F,F,F,F) => False
    (F,F,T,F,F) => True
    [(F,F,F), (T,T)] => True
  • 3-3 list comprehension: this creates a list that will be [bool, bool, bool... bool] for every letter in password: if they are upper, True will be added to the list, else False will.

Let's suppose password is "AbCd1". The function will evaluate to:

if not         any(                 [True, False, True, False, False]                       )

You could replace the any function with "or statements" (but it's much worse to read, probably also slower).

Try putting this in your terminal:

any([True, False, True, False, False]) == (True or False or True or False or False)

Any() makes the code more readable and easier to maintain, being closer to the English language, in a pure pythonic way. Also, it makes it so that there is no repetition (still, both pythonic AND a good programming habit).

commented

How do I do this??
I am stuck on this part
No character can be present more than once. Write code to process each character in the password and keep track of how many occurrences are present. If any character (either uppercase or lowercase versions) is in the password more than 1 output the character and how many occurrences and print out the message: These characters appear more than once:

commented

Sorry I do not know how to use github issues this is my first time using it

commented

Thank you so much I have fixed a bunch of things still working on it though