I coded a python solution for the question below. I am after the fastest solution. Also, I would be grateful if you could point out parts that are not pythonic / inefficient.
Question: A website requires the users to input username and password to register. Write a program to check the validity of password input by users. Following are the criteria for checking the password:
- At least 1 letter between [a-z]
- At least 1 number between [0-9]
- At least 1 letter between [A-Z]
- At least 1 character from [$#@]
- Minimum length of transaction password: 6
import warnings
password = 'P@ss1234'
def check_number(s):
''' Check whether the input string is a digit. '''
try:
int(s)
return True
except:
# do not catch error
return False
def check_validity(pw):
''' Return True if input pw is valid, and return False if invalid.'''
special_chars = ['$','#','@']
if isinstance(pw,str): pw=list(pw) # I could have appointed to a diff var name
else: warnings.warn('Password has to be a string object.')
res = False
valid_dict={'small_let':False, 'num':False, 'special_chars':False,
'cap_let':False, 'len':False } # is using a dict efficient?
if len(pw)>= 6: valid_dict['len']=True
for i in pw:
if i.islower(): valid_dict['small_let'] = True
if i in special_chars: valid_dict['special_chars'] = True
if i.isupper(): valid_dict['cap_let'] = True
if not valid_dict['num']: valid_dict['num'] = check_number(i)
if all(valid_dict.values()): res = True
return res
print check_validity(password)
P.S: I am not interested in a module / package that can do this faster or more securely. There does not actually exist a website or a username, this is a simple coding exercise.
Also, linked to this post.
4 Answers 4
- You shouldn't use bare
except
s, this is as it can prevent things likeKeyboardInterrupt
. That's just bad. password
is a better variable name thanpw
, at first I was quite confused about what it was.- You can return early if you know the password is not valid. This can save you a lot of checks in your program.
- Your program goes through each character and checks if it's one of the four valid groups. Instead you can check if it shares any items with that set. If it doesn't it's not valid.
You're creating warnings un-neededly, currently, excluding the cast, the functionality is the same if it's a list. Instead of a warning you should either raise an error, or return.
This is as if I pass
['P', 'a', '$', '2', 1, 2]
it probably shouldn'treturn True
, which it might on a different implementation. Instead you can either hard limit your input to strings, or check if the contents of the input are strings. Or both.
In short, just change password to a set, and use &
with the groups it needs to be a part of.
This can remove the need to make the function check_number
, and makes the code much more readable:
import string
def check_validity(password):
''' Return True if input pw is valid, and return False if invalid.'''
if isinstance(password, str) \
or not all(isinstance(c, str) for c in password):
return False
if len(password) < 6:
return False
password = set(password)
checks = [
set(string.ascii_lowercase),
set(string.ascii_uppercase),
set(string.digits),
{'$', '#', '@'},
]
for check in checks:
if not check & password:
return False
return True
print check_validity('P@ss1234')
-
\$\begingroup\$ This is great, thank you. By "limiting the use unnecessarily" , are you referring to the string type check and the warning in the beginning of check_validity? \$\endgroup\$Zhubarb– Zhubarb2016年09月30日 08:57:07 +00:00Commented Sep 30, 2016 at 8:57
-
\$\begingroup\$ @Zhubarb Yeah I am, I should probably rephrase that as, you're not limiting the usage. But you're advising against a certain usage. \$\endgroup\$2016年09月30日 08:58:39 +00:00Commented Sep 30, 2016 at 8:58
-
\$\begingroup\$ OK, fair enough. And wrt to checks list you create (with sets of items), I think it is quite elegant and definitely a lot easier to read. But when I compare it to my solution (with all the inefficiencies you point out addressed), the checks list solution runs slightly slower. \$\endgroup\$Zhubarb– Zhubarb2016年09月30日 09:00:23 +00:00Commented Sep 30, 2016 at 9:00
At the moment, you're checking every character for every condition. If it's a lower case character, it's not going to be any of the rest of them, so you can simply abort that iteration of the loop and move onto the next character. Something like:
if i.islower():
valid_dict['small_let'] = True
continue
You can then arrange your checks in the order that is most likely to happen. I'd imagine:
- Lower case letters
- Upper case letters
- Numbers
- Special characters
Would be a better order...
As an aside, whilst the approach taken by @Joe Wallis is probably going to be more performant, and you've said you're not interested in using additional packages, this is the type of validation that regular expressions are good at representing:
import re
password = 'P@ss1234'
required_pattern = re.compile('(?=.{6,})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[@#$])')
def check_validity(pw):
return required_pattern.match(pw) != None
print check_validity(password)
-
\$\begingroup\$ This solution looks nice, but not very flexible. If one day I want to know why my password got rejected, is it possible / simple to implement ? \$\endgroup\$Etsitpab Nioliv– Etsitpab Nioliv2016年09月30日 12:11:01 +00:00Commented Sep 30, 2016 at 12:11
-
\$\begingroup\$ "this is the type of validation that regular expressions are good at representing" Funny you should say this. The original concept of regex would have a serious time doing this sort of validation. Even with extended regexes, your solution is not particularly readable. \$\endgroup\$mike3996– mike39962016年09月30日 12:34:49 +00:00Commented Sep 30, 2016 at 12:34
except: # do not catch error
This is a good example for sometimes it is better to write no comment at all.
You do catch the error. In fact, you catch any errors, even the ones that you didn't anticipate.
That us an old post which is well answered, however I would like to add a short note:
You can simplify and, in the same time, avoid bugs related to the improper use of exception handling within the check_number()
function -highlighted by previous answers- by making use of isinstance()
:
>>> password = 2
>>> isinstance(password, int)
True
>>> password = '2'
>>> isinstance(password, int)
False
So that function would become:
def check_number(s):
return isinstance(s, int)
Explore related questions
See similar questions with these tags.
check_number(s)
you can use built-ins.isdigit()
\$\endgroup\$