Reputation: 172
I'm a beginner making a password generator and need to ensure the password has both numbers and capital letters. The condition for this while loop is redundant. for char in password
appears twice. How would you write it?
while not (any(char.isdigit() for char in password) and (any(char.isupper() for
char in password))):
In the loop it generates another password.
My goal here is to better understand how to construct the while loop's expression, not to solve the problem a different way.
Upvotes: 13
Views: 2372
Reputation: 6284
Leaving aside the question of what are good or bad criteria for a password (and I'm sure we've all had to implement a specification we didn't agree with in our time), there is nothing wrong with the way you have written this logic expression.
It is clear what you mean by the code, and unless you find it is actually too slow in practice then you should not sacrifice clarity for some theoretical gain in performance or efficiency, or making a line a few characters shorter.
Don't forget that you can break code across multiple lines in Python using indentation, so you can add more conditions without losing readability:
while not (
any(char.isdigit() for char in password) and
any(char.isupper() for char in password) and
any(char.islower() for char in password) and
any(char.somethingelse() for char in password) ):
do-something
As a beginner, learning to write clear and understandable code is far more important than worrying about a few microseconds or bytes here and there.
Upvotes: 2
Reputation: 7111
First things first, I wish websites would stop with the inane password requirements. They reduce the entropy of the password AND make it harder for people to remember. It's especially bad when the requirements aren't clearly laid out in the UI so people can design an appropriate password without guessing what traps you may have laid for them.
That said, your syntax is quite a bit shorter than some of the regex implementations. If you wanted to apply De Morgan's laws to break up the question into logic which is arguably easier to reason about you could do the following (at a performance loss with respect to short-circuiting).
while all(not char.isdigit() for char in password)
or all(not char.isupper() for char in password):
It seems your real problem with this though is the two passes through password
. Interestingly, the regex approaches have the same problem, hidden behind some additional syntax. If you're willing to sacrifice the brevity of your solution for a bit of generality, the ability to short circuit, and a single pass through your data then you can extract the condition into its own method like so:
def satisfies(password, *conditions):
flags = [False] * len(conditions)
for c in password:
for i, cond in enumerate(conditions):
if cond(c):
flags[i] = True
if all(flags):
return True
return False
while satisfies(password, str.isdigit, str.isupper):
pass
Stepping through this, it goes through each character and each condition (for example the condition of needing a digit) and checks to see if it has been satisfied. If so, it records that event and checks if it can exit early. At the end, the only possible way the for
loops have exited is if a condition hasn't been met anywhere in password
, so we return False
.
Just for fun, you can get a similar effect (without early stopping) with the use of the reduce()
function. It's built in to Python 2.x, and you'll need to import it from functools
in Python 3.x.
while not all(reduce(
lambda (a, b), (d, e): (a or d, b or e),
((c.isdigit(), c.isupper()) for c in password))):
This effectively keeps a running tally of whether you've satisfied the isdigit and isupper requirements at that point in the password. After checking the whole password, you simply use all()
to read your tallies and make sure you've actually satisfied both requirements.
If your goal is run time rather than some ethereal notion like "passes through the data" (not to be disparaging; they can matter quite a bit in other contexts), your best improvements would come from some sort of high-performance library like numpy
designed to vectorize the queries you perform. Since the bulk of the work being performed here isn't the pass through the data, but it is the check being performed on the characters in each pass, eliminating passes through the data won't do much for run time. You'll realize the most savings by making the actual checks as fast as possible.
Upvotes: 22
Reputation: 12144
Similar to the lambda version. Return a set of tuples matching any of those tests. remove (False, False) from result. length set should be 2.
input_exp = [
("abc", False),
("A3", True),
("A", False),
("ab3", False),
("3", False),
("3A", True),
]
def check(string_):
"""each iteration evaluates to any one of (True,False) /(False,True)/ (False,False).
remove False,False from set.
check len==2"""
res = len(set([(c.isupper(), c.isdigit()) for c in string_]) - set([(False, False)])) == 2
return res
for string_, exp in input_exp:
got = check(string_)
if exp is got:
print("good. %s => %s" % (string_, got))
else:
print("bad . %s => %s" % (string_, got))
output:
good. abc => False
good. A3 => True
good. A => False
good. ab3 => False
good. 3 => False
good. 3A => True
Upvotes: 1
Reputation: 41862
I agree with the answers presented, but to address your original question and your next step:
@moooeeeep: Did you forget to check any(char.islower() for char in password) ?
@Matt Davis: Yes, I was planning on adding that next, but then the expression got REALLY long. You see my problem now :)
simplifying your original logic slightly, we can do something like:
while not all([any(map(str.isdigit, password)), any(map(str.isupper, password)), any(map(str.islower, password))]):
I.e. apply all()
to a list of any()
. For the test cases:
["goat", "Goat", "goat1", "GOAT", "GOAT1", "Goat1"]
only "Goat1"
passes as it meets all three criteria.
Upvotes: 4
Reputation: 1597
Use re
module to do what you want.
import re
pattern = re.compile('[A-Z0-9]+')
password1 = 'asdf1234'
password2 = 'ASDF1234'
for p in (password1, password2):
if pattern.fullmatch(p):
print('Password', p, 'is fine')
else:
print('Password', p, 'is bad')
Upvotes: 0