mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-04-12 19:00:43 -04:00
fix(curriculum/python): improve password generator project (#53456)
This commit is contained in:
@@ -9,11 +9,11 @@ dashedName: step-9
|
||||
|
||||
The `random` module contains a pseudo-random number generator. Most of its functionalities depend on the `random()` function, which returns a floating point number in the range between `0.0` (inclusive) and `1.0` (exclusive).
|
||||
|
||||
Call the `random()` function and print the result.
|
||||
Call the `random()` function from the `random` module and print the result.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should print `random.random()`.
|
||||
You should print the result of calling `random.random()`.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /^print\s*\(\s*random\.random\s*\(\s*\)\s*\)/m) })
|
||||
|
||||
@@ -16,7 +16,7 @@ Now, delete your two `print()` calls.
|
||||
You should delete your two `print()` calls.
|
||||
|
||||
```js
|
||||
({ test: () => assert.isFalse( /print/.test(code)) })
|
||||
({ test: () => assert.isFalse( /^print/m.test(code)) })
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -13,7 +13,7 @@ Just before them, add a comment saying `Define the possible characters for the p
|
||||
|
||||
# --hints--
|
||||
|
||||
You should add the comment just above your three variables.
|
||||
You should add the comment just above the `letters` variable.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /^#\s*Define the possible characters for the password.*^letters/ms) })
|
||||
|
||||
@@ -7,7 +7,7 @@ dashedName: step-20
|
||||
|
||||
# --description--
|
||||
|
||||
Finally, call the `generate_password` function with `8` as the argument and assign the function call to a `new_password` variable.
|
||||
Finally, declare a variable `new_password` and assign it the result of calling `generate_password`. Pass `8` as the argument to your `generate_password` call.
|
||||
|
||||
# --hints--
|
||||
|
||||
@@ -17,7 +17,7 @@ You should call `generate_password` passing `8` as the argument.
|
||||
({ test: () => assert.match(code, /generate_password\s*\(\s*8\s*\)/) })
|
||||
```
|
||||
|
||||
You should assign `generate_password(8)` to a `new_password` variable.
|
||||
You should assign `generate_password(8)` to the variable `new_password`.
|
||||
|
||||
```js
|
||||
({
|
||||
|
||||
@@ -9,7 +9,7 @@ dashedName: step-29
|
||||
|
||||
The `search()` function from the `re` module analyzes the string passed as the argument looking for the first place where the regex pattern matches the string.
|
||||
|
||||
Declare a variable called `quote` and assign the string `Not all those who wander are lost.` to this variable. Then, print the result of `pattern.search(quote)`.
|
||||
Declare a variable called `quote` and assign the string `'Not all those who wander are lost.'` to this variable. Then, print the result of `pattern.search(quote)`.
|
||||
|
||||
# --hints--
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ dashedName: step-42
|
||||
|
||||
# --description--
|
||||
|
||||
The dot character is a wildcard that matches any character in a string — except for a newline character by default. Modify `pattern` to match the entire string. Use a `.` followed by the `+` quantifier.
|
||||
The dot character is a wildcard that matches any character in a string — except for a newline character by default. Modify `pattern` to match the entire string by replacing the current pattern with a `.` followed by the `+` quantifier.
|
||||
|
||||
# --hints--
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Now, turn `pattern` into the shorthand class for non-alphanumeric characters.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `pattern` variable should be `\W`.
|
||||
Your `pattern` variable should be `'\W'`.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /^pattern\s*=\s*r("|')\\W\1/m) })
|
||||
|
||||
@@ -13,7 +13,7 @@ Now turn your `quote` string into a single underscore character.
|
||||
|
||||
# --hints--
|
||||
|
||||
Your `quote` variable should be `_`.
|
||||
Your `quote` variable should be `'_'`.
|
||||
|
||||
```js
|
||||
({ test: () => assert.equal(__userGlobals.get("quote"), "_") })
|
||||
|
||||
@@ -19,9 +19,10 @@ You should delete the last three lines in your code.
|
||||
|
||||
```js
|
||||
({ test: () => {
|
||||
assert.isFalse(/pattern\s*=\s*r("|')\\W\1/.test(code));
|
||||
assert.isFalse(/quote\s*=\s*("|')_\1/.test(code));
|
||||
assert.isFalse(/print\(\s*re\.findall\(\s*pattern\s*,\s*quote\s*\)\s*\)/.test(code));
|
||||
const commentless_code = __helpers.python.removeComments(code);
|
||||
assert.notMatch(commentless_code, /pattern\s*=\s*r("|')\\W\1/);
|
||||
assert.notMatch(commentless_code, /quote\s*=\s*("|')_\1/);
|
||||
assert.notMatch(commentless_code, /print\(\s*re\.findall\(\s*pattern\s*,\s*quote\s*\)\s*\)/);
|
||||
}})
|
||||
```
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Now, combine your raw string with an f-string and interpolate your `symbols` var
|
||||
The second item in your fourth constraint tuple should be the string `fr'[{symbols}]'`.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /\(\s*special_chars\s*,\s*fr("|')\[\{\s*symbols\s*\}\]\1/) })
|
||||
({ test: () => assert.match(code, /\(\s*special_chars\s*,\s*(fr|rf)("|')\[\{\s*symbols\s*\}\]\2/) })
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -7,7 +7,7 @@ dashedName: step-54
|
||||
|
||||
# --description--
|
||||
|
||||
After your new comment, write a `for` loop to iterate over the `constraints` list. Use `constraint` and `pattern` as the iterating variables.
|
||||
After your new comment, write a `for` loop to iterate over the `constraints` list. Use `constraint` and `pattern` as the loop variables.
|
||||
|
||||
# --hints--
|
||||
|
||||
|
||||
@@ -7,20 +7,32 @@ dashedName: step-59
|
||||
|
||||
# --description--
|
||||
|
||||
Turn the expression inside your `for` loop into an `if` statement. Use the expression you wrote in the previous step as the `if` condition. Inside the new conditional statement, increment the `count` value by `1`.
|
||||
Turn the expression inside your `for` loop into an `if` statement. Use the expression you wrote in the previous step as the `if` condition.
|
||||
|
||||
Inside the new conditional statement, increment the `count` value by `1`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should turn `constraint <= len(re.findall(pattern, password))` into the `if` condition.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /^(\s*)for.+:\s*^\1(\s{4})if\s+constraint\s*<=\s*len\s*\(\s*re\.findall\s*\(\s*pattern\s*,\s*password\s*\)\s*\)\s*:/m) })
|
||||
({ test: () => {
|
||||
const commentless_code = __helpers.python.removeComments(code);
|
||||
const {block_body} = __helpers.python.getBlock(commentless_code, /for\s+constraint\s*,\s*pattern\s+in\s+constraints\s*/);
|
||||
assert(block_body.match(/^\s+if\s+constraint\s*<=\s*len\s*\(\s*re\.findall\s*\(\s*pattern\s*,\s*password\s*\)\s*\)\s*:/));
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
You should increment `count` by one inside your new `if` statement.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /^(\s*)for.+:\s*^\1(\s{4})if\s+constraint\s*<=\s*len\s*\(\s*re\.findall\s*\(\s*pattern\s*,\s*password\s*\)\s*\)\s*:\s*^\1\2\2(count\s*\+=\s*1|count\s*=\s*count\s*\+\s*1)/m) })
|
||||
({ test: () => {
|
||||
const commentless_code = __helpers.python.removeComments(code);
|
||||
const {block_body} = __helpers.python.getBlock(commentless_code, /if\s+constraint\s*<=\s*len\s*\(\s*re\.findall\s*\(\s*pattern\s*,\s*password\s*\)\s*\)\s*/);
|
||||
assert(block_body.match(/^\s+(count\s*\+=\s*1|count\s*=\s*count\s*\+\s*1)/));
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -14,13 +14,18 @@ Finally, after the `for` loop, create an `if` statement to check if `count` is e
|
||||
You should create an `if` statement that checks if `count` is equal to `4` after the `for` loop.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /^(\s*)for.+:\s*^\1(\s{4})if\s+constraint\s* <=\s*len\s*\(\s*re\.findall\s*\(\s*pattern\s*,\s*password\s*\)\s*\)\s*:\s*^\1\2\2count\s*\+=\s*1\s*^\1if\s+count\s*==\s*4\s*:/m) })
|
||||
({ test: () => assert.match(code, /^(\s*)for.+:\s*^\1(\s{4})if\s+constraint\s*<=\s*len\s*\(\s*re\.findall\s*\(\s*pattern\s*,\s*password\s*\)\s*\)\s*:\s*^\1\2\2count\s*\+=\s*1\s*^\1if\s+count\s*==\s*4\s*:/m) })
|
||||
```
|
||||
|
||||
You should use `break` inside your new `if` to break out of the `while` loop.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /^(\s*)for.+:\s*^\1(\s{4})if\s+constraint\s* <=\s*len\s*\(\s*re\.findall\s*\(\s*pattern\s*,\s*password\s*\)\s*\)\s*:\s*^\1\2\2count\s*\+=\s*1\s*^\1if\s+count\s*==\s*4\s*:\s*^\1\2break/m) })
|
||||
({ test: () => {
|
||||
const commentless_code = __helpers.python.removeComments(code);
|
||||
const {block_body} = __helpers.python.getBlock(commentless_code, /if\s+count\s*==\s*4\s*/);
|
||||
assert(block_body.match(/^\s+break\s*$/m));
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -7,11 +7,11 @@ dashedName: step-71
|
||||
|
||||
# --description--
|
||||
|
||||
Modify your `print()` call to take the string `Generated password:` as the first argument, before `new_password`.
|
||||
Modify your `print()` call to take the string `'Generated password:'` as the first argument, before `new_password`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should pass the `Generated password:` string and `new_password` to your `print()` call.
|
||||
You should pass the string `'Generated password:'` and `new_password` to your `print()` call.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /^print\s*\(\s*("|')Generated\spassword:\1\s*,\s*new_password\s*\)/m) })
|
||||
|
||||
@@ -13,10 +13,22 @@ With that, the password generator project is complete.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should have an `if` statement that checks if `__name__ == '__main__'` and put the last two lines of your code in it.
|
||||
You should have an `if` statement that checks if `__name__ == '__main__'`.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /^if\s+__name__\s*==\s*("|')__main__\1\s*:\s*^(\s{4})new_password\s*=\s*generate_password\s*\(\s*\)\s*^\2print\s*\(\s*("|')Generated\spassword:\3\s*,\s*new_password\s*\)/m) })
|
||||
const commentless_code = __helpers.python.removeComments(code);
|
||||
assert(commentless_code.match(/^if\s+__name__\s*==\s*("|')__main__\1\s*:\s*$/m));
|
||||
```
|
||||
|
||||
You should put the `new_password` assignment and the following `print()` call in your new `if` statement body.
|
||||
|
||||
```js
|
||||
({ test: () => {
|
||||
const commentless_code = __helpers.python.removeComments(code);
|
||||
const {block_body} = __helpers.python.getBlock(commentless_code, /if\s+__name__\s*==\s*("|')__main__\3\s*/);
|
||||
assert(block_body.match(/^\s+new_password\s*=\s*generate_password\s*\(\s*\)\s*print\s*\(\s*("|')Generated\spassword:\1\s*,\s*new_password\s*\)\s*$/));
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -7,9 +7,9 @@ dashedName: step-30
|
||||
|
||||
# --description--
|
||||
|
||||
The value `None` is returned since `i` is not found inside the parsed string.
|
||||
The value `None` is returned since `'i'` is not found inside the parsed string.
|
||||
|
||||
Now, modify your pattern into `l` and see the result.
|
||||
Now, modify the string passed to `re.compile()` into `'l'` and see the result.
|
||||
|
||||
# --hints--
|
||||
|
||||
|
||||
@@ -14,7 +14,15 @@ Put your `password` variable declaration and the following `for` loop inside a `
|
||||
You should create a `while True` loop enclosing your existing `password` declaration and `for` loop.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /^(\s*)while\s+True\s*:\s*^\1\1password\s*=\s*("|')\2\s*#\s*Generate\spassword\s*^\1\1for\s+_\s+in\s+range\s*\(\s*length\s*\s*\)\s*:\s*^\1\1\1password\s*\+=\s*secrets\.choice\s*\(\s*all_characters\s*\)/m) })
|
||||
({ test: () => {
|
||||
|
||||
const commentless_code = __helpers.python.removeComments(code);
|
||||
const {block_body} = __helpers.python.getBlock(commentless_code, /while\s+True\s*/);
|
||||
assert(block_body.match(/^\s+password\s*=\s*("|')\1\s*^\s+for\s+_\s+in\s+range\s*\(\s*length\s*\s*\)\s*:\s*^\s+password\s*\+=\s*secrets\.choice\s*\(\s*all_characters\s*\)\s*$/m));
|
||||
const {block_body: for_body} = __helpers.python.getBlock(commentless_code, /for\s+_\s+in\s+range\s*\(\s*length\s*\s*\)\s*/);
|
||||
assert(for_body.match(/^\s+password\s*\+=\s*secrets\.choice\s*\(\s*all_characters\s*\)\s*$/m));
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -15,7 +15,7 @@ my_tuple = ('larch', 1, True)
|
||||
|
||||
Your `constraints` list is going to store tuples. The first item of each tuple will be a constraint parameter.
|
||||
|
||||
Add a tuple to your list. Use `nums` as the first item and an empty string as the second item.
|
||||
Modify the `constraints` list assignment by adding a tuple to your list. Use `nums` as the first item and an empty string as the second item.
|
||||
|
||||
# --hints--
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ dashedName: step-32
|
||||
|
||||
# --description--
|
||||
|
||||
You can obtain the same result without using the `compile()` function. Modify your `pattern` variable into the literal string `l+`. Then, change the `print()` call to print `re.search(pattern, quote)`.
|
||||
You can obtain the same result without using the `compile()` function. Modify your `pattern` variable into the literal string `'l+'`. Then, change the `print()` call to print `re.search(pattern, quote)`.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should modify your `pattern` variable into the literal string `l+`.
|
||||
You should modify your `pattern` variable into the literal string `'l+'`.
|
||||
|
||||
```js
|
||||
({ test: () => assert.equal(__userGlobals.get("pattern"), "l+") })
|
||||
|
||||
@@ -7,11 +7,11 @@ dashedName: step-61
|
||||
|
||||
# --description--
|
||||
|
||||
Instead of using a loop and a counter variable, you can achieve the same result with a different approach.
|
||||
Instead of using a loop and a counter variable, you can achieve the same result with a different approach, which you are going to implement in the next few steps.
|
||||
|
||||
Replace your existing `for` loop and two `if` statements with a single `if` statement. For the `if` condition, use a list created with the list comprehension syntax.
|
||||
`all()` is a built-in Python function that returns `True` if all the elements inside a given iterable evaluate to `True`. Otherwise, it returns `False`.
|
||||
|
||||
The list stores the results of evaluating the expression `constraint <= len(re.findall(pattern, password))` for each `constraint`-`pattern` tuple in the `constraints` list.
|
||||
Replace your existing `for` loop and two `if` statements with a single `if` statement. For the `if` condition, use a call to the `all()` function and pass an empty list as the argument to the function call.
|
||||
|
||||
# --hints--
|
||||
|
||||
@@ -24,10 +24,20 @@ You should replace your existing `for` loop and two `if` statements with a singl
|
||||
} })
|
||||
```
|
||||
|
||||
Your new `if` condition should be a list comprehension that is the result of evaluating the expression `constraint <= len(re.findall(pattern, password))` for each `constraint`-`pattern` tuple in the `constraints` list.
|
||||
Your new `if` condition should be `all([])`.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /^(\s{8})if\s*\[\s*constraint\s*<=\s*len\s*\(\s*re\.findall\s*\(\s*pattern\s*,\s*password\s*\)\s*\)\s+for\s+constraint\s*,\s*pattern\s+in\s+constraints\s*\]\s*:\s*^\1\s{4}break/m) })
|
||||
({ test: () => assert(runPython(`
|
||||
_Node(_code).find_function("generate_password").find_while("True").find_while_bodies()[0].find_if("all([])")
|
||||
`)) })
|
||||
```
|
||||
|
||||
You should have `break` inside your new `if` body.
|
||||
|
||||
```js
|
||||
({ test: () => assert(runPython(`
|
||||
_Node(_code).find_function("generate_password").find_while("True").find_while_bodies()[0].find_if("all([])").find_if_bodies()[0].is_equivalent("break")
|
||||
`)) })
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
@@ -7,18 +7,18 @@ dashedName: step-62
|
||||
|
||||
# --description--
|
||||
|
||||
`all()` is a built-in Python function that returns `True` if all the elements inside a given iterable evaluate to `True`. Otherwise, it returns `False`.
|
||||
Right now, `all()` is taking an empty list as the argument. Populate that empty list using the comprehension syntax so that the list stores the results of evaluating the expression `constraint <= len(re.findall(pattern, password))` for each `constraint`-`pattern` tuple in the `constraints` list.
|
||||
|
||||
You can combine the `all()` function with the list comprehension syntax to make your code more concise.
|
||||
|
||||
Modify your `if` condition by passing the list comprehension you created in the previous step to the `all()` function.
|
||||
In this way, you'll break out of the `while` loop only after all the requirements are fulfilled.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should pass your list comprehension to the `all()` function.
|
||||
You should pass `[constraint <= len(re.findall(pattern, password)) for constraint, pattern in constraints]` to the `all()` function.
|
||||
|
||||
```js
|
||||
({ test: () => assert.match(code, /^(\s*)if\s+all\s*\(\s*\[\s*constraint\s*<=\s*len\s*\(\s*re\.findall\s*\(\s*pattern\s*,\s*password\s*\)\s*\)\s*for\s+constraint\s*,\s*pattern\s+in\s+constraints\s*\]\s*\)\s*:\s*^\1\s{4}break/m) })
|
||||
({ test: () => assert(runPython(`
|
||||
_Node(_code).find_function("generate_password").find_while("True").find_while_bodies()[0].find_ifs()[0].find_conditions()[0].is_equivalent("all([constraint <= len(re.findall(pattern, password)) for constraint, pattern in constraints])")
|
||||
`)) })
|
||||
```
|
||||
|
||||
# --seed--
|
||||
@@ -56,8 +56,7 @@ def generate_password(length, nums, special_chars, uppercase, lowercase):
|
||||
# Check constraints
|
||||
count = 0
|
||||
--fcc-editable-region--
|
||||
if [constraint <= len(re.findall(pattern, password))
|
||||
for constraint, pattern in constraints]:
|
||||
if all([]):
|
||||
--fcc-editable-region--
|
||||
break
|
||||
|
||||
|
||||
@@ -14,7 +14,11 @@ You don't need the `count` variable anymore. Delete this variable and its value.
|
||||
You should delete the `count = 0` line.
|
||||
|
||||
```js
|
||||
({ test: () => assert.isFalse( /count\s*=\s*0/.test(code)) })
|
||||
({ test: () => {
|
||||
const commentless_code = __helpers.python.removeComments(code);
|
||||
assert.isFalse( /count\s*=\s*0/.test(commentless_code))
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
Reference in New Issue
Block a user