From e1699a2c1e7c7313179030c5e271b61d9c19b850 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos <23168599+lpozo@users.noreply.github.com> Date: Sat, 24 Oct 2026 00:15:06 +0000 Subject: [PATCH 1/4] Sample code for: Python Skill Assessment: How to Evaluate Your Knowledge Co-Authored-By: Claude Opus 5.5 (1M context) --- python-skill-assessment/README.md | 16 ++++ python-skill-assessment/probes/expert.py | 22 +++++ .../probes/intermediate.py | 8 ++ python-skill-assessment/probes/novice.py | 6 ++ python-skill-assessment/probes/proficient.py | 6 ++ ...thon-skill-assessment-worksheet-example.md | 82 +++++++++++++++++++ .../python-skill-assessment-worksheet.md | 80 ++++++++++++++++++ 7 files changed, 220 insertions(+) create mode 100644 python-skill-assessment/README.md create mode 100644 python-skill-assessment/probes/expert.py create mode 100644 python-skill-assessment/probes/intermediate.py create mode 100644 python-skill-assessment/probes/novice.py create mode 100644 python-skill-assessment/probes/proficient.py create mode 100644 python-skill-assessment/python-skill-assessment-worksheet-example.md create mode 100644 python-skill-assessment/python-skill-assessment-worksheet.md diff --git a/python-skill-assessment/README.md b/python-skill-assessment/README.md new file mode 100644 index 0000000000..78287be895 --- /dev/null +++ b/python-skill-assessment/README.md @@ -0,0 +1,16 @@ +# Python Skill Assessment: How to Evaluate Your Knowledge + +This folder contains the materials for the Real Python tutorial [Python Skill Assessment: How to Evaluate Your Knowledge](https://realpython.com/python-skill-assessment/). + +- `python-skill-assessment-worksheet.md`: The blank worksheet to fill in +- `python-skill-assessment-worksheet-example.md`: A filled-in example worksheet +- `probes/`: One short code probe for each level of the Python Skill Test + +To check a rating, predict a probe's output, and then run it: + +```console +$ cd probes/ +$ python novice.py +``` + +The probes run on Python 3.15 and need no third-party packages, so there's no `requirements.txt`. diff --git a/python-skill-assessment/probes/expert.py b/python-skill-assessment/probes/expert.py new file mode 100644 index 0000000000..5ab757d434 --- /dev/null +++ b/python-skill-assessment/probes/expert.py @@ -0,0 +1,22 @@ +class BaseView: + def handle(self): + return ["base"] + + +class AuthMixin(BaseView): + def handle(self): + return ["auth"] + super().handle() + + +class CacheMixin(BaseView): + def handle(self): + return ["cache"] + super().handle() + + +class ReportView(AuthMixin, CacheMixin): + def handle(self): + return ["report"] + super().handle() + + +print(ReportView().handle()) +print([cls.__name__ for cls in ReportView.__mro__]) diff --git a/python-skill-assessment/probes/intermediate.py b/python-skill-assessment/probes/intermediate.py new file mode 100644 index 0000000000..759d0723a7 --- /dev/null +++ b/python-skill-assessment/probes/intermediate.py @@ -0,0 +1,8 @@ +numbers = range(5) + +squares = [] +for n in numbers: + squares.append(n * n) + +assert squares == [n * n for n in numbers] +print(squares) diff --git a/python-skill-assessment/probes/novice.py b/python-skill-assessment/probes/novice.py new file mode 100644 index 0000000000..7b42dc4932 --- /dev/null +++ b/python-skill-assessment/probes/novice.py @@ -0,0 +1,6 @@ +def greet(name): + return f"Hello, {name}!" + + +for character in "Hi!": + print(greet(character)) diff --git a/python-skill-assessment/probes/proficient.py b/python-skill-assessment/probes/proficient.py new file mode 100644 index 0000000000..d94ba931a1 --- /dev/null +++ b/python-skill-assessment/probes/proficient.py @@ -0,0 +1,6 @@ +def count_up_to(n): + yield from range(1, n + 1) + + +print(list(count_up_to(3))) +print(list(count_up_to(0))) diff --git a/python-skill-assessment/python-skill-assessment-worksheet-example.md b/python-skill-assessment/python-skill-assessment-worksheet-example.md new file mode 100644 index 0000000000..303125cf22 --- /dev/null +++ b/python-skill-assessment/python-skill-assessment-worksheet-example.md @@ -0,0 +1,82 @@ +# Python Skill Assessment Worksheet (Filled-In Example) + +This is an example of a finished worksheet. The ratings and notes are made up. Use the blank worksheet for your own. + +Rate each statement from 1 to 4: + +| Rating | Meaning | +|--------|---------| +| 1 | I can't do this yet. | +| 2 | I've seen it, but I need to look it up. | +| 3 | I can do it, but I'm not sure I could teach it. | +| 4 | I can do it and explain it to someone else. | + +Rate what you can do without looking anything up. For a statement with several skills, rate your weakest one. + +The "Quiz question" column names the question in the Real Python [Python Skill Test](https://realpython.com/quizzes/python-skill-test/) that covers the same topic, so you can compare your rating with your result. + +## Part 1: Rate Yourself Against Each Level + +### Novice (Quiz score 0-3) + +| I can... | Rating (1-4) | Quiz question | Notes | +|----------|--------------|---------------|-------| +| display values with `print()`. | 4 | Using print() | Daily. | +| define a function with `def`, pass arguments, and return a value. | 4 | Define a Function | | +| install a package from PyPI with `python -m pip install`, ideally inside a virtual environment. | 3 | Installing Packages | Forget `-m` sometimes. | +| explain that a `for` loop variable takes each element from an iterable, such as a string or a list. | 4 | Loop Anatomy | | +| use variables, operators, `if` statements, and basic built-in types (numbers, strings, lists). | 4 | Beyond the quiz | | + +### Intermediate (Quiz score 4-7) + +| I can... | Rating (1-4) | Quiz question | Notes | +|----------|--------------|---------------|-------| +| tell mutable types (`list`) from immutable ones (`tuple`, `str`, `frozenset`). | 3 | Mutable Objects | Never used `frozenset`. | +| explain that dictionary keys must be hashable and that insertion order is kept since Python 3.7. | 3 | Dictionary Facts | Hashable: fuzzy. | +| read and write list comprehensions, including an `if` filter, and rewrite them as a `for` loop. | 4 | Understanding List Comprehensions | Nested ones slow me down. | +| use the standard library's common modules, such as `pathlib`, `collections`, and `json`, before reaching for third-party packages. | 3 | Beyond the quiz | | + +### Proficient (Quiz score 8-11) + +| I can... | Rating (1-4) | Quiz question | Notes | +|----------|--------------|---------------|-------| +| explain what problem the `with` statement solves and why files close even when an exception occurs. | 2 | The With Statement | Use it, can't explain it. | +| recognize dunder methods such as `.__init__()` and `.__iadd__()` and say when Python calls them. | 2 | Spot the Dunder | Only `.__init__()`. | +| write a generator function with `yield` and explain why it's lazy. | 1 | Writing a Simple Generator | Never wrote one. | +| write and apply decorators, and explain how iterators and iterables work. | 2 | Beyond the quiz | | + +### Expert (Quiz score 12) + +| I can... | Rating (1-4) | Quiz question | Notes | +|----------|--------------|---------------|-------| +| predict attribute lookup when data descriptors, non-data descriptors, and instance attributes compete. | 1 | Descriptor Precedence in Practice | New to me. | +| trace the C3 method resolution order and predict the output of cooperative `super()` calls. | 1 | Cooperative super() and the C3 MRO | Single inheritance only. | +| explain what a metaclass does, and write basic `async` code with `asyncio`. | 1 | Beyond the quiz | | + +## Part 2: Reconcile Your Self-Rating With the Quiz + +Average your ratings for each level, including the _Beyond the quiz_ row, take the quiz, and then fill in the quiz columns. The quiz reports one overall result, so use the explanations for each question you missed to fill in the per-level column. + +| Level | Your average rating | Quiz questions missed | Gap (rating vs. result) | +|-------|---------------------|-----------------------|-------------------------| +| Novice | 3.8 | 0 | None | +| Intermediate | 3.3 | 1 (Dictionary Facts) | One miss: a hint, so recheck hashable keys | +| Proficient | 1.8 | 2 (Spot the Dunder, Writing a Simple Generator) | Low rating, two of three missed: matches | +| Expert | 1.0 | 2 (both) | Low rating, both missed: matches | + +**Overall quiz result:** Intermediate (7 of 12 correct) + +**Self-rated level** (the highest level whose average, and every average below it, is 3 or more, or _Not yet Novice_ if your Novice average is below 3): Intermediate + +**Do they match?** Yes. Next step: Proficient. + +## Part 3: Turn Your Weakest Area Into a Study Plan + +| Question | Your answer | +|----------|-------------| +| My weakest area (the lowest-level miss that your check confirmed, or your lowest-rated statement at the lowest level) | Dictionary Facts: why keys must be hashable | +| Why it matters for my goals | My scripts group records in dictionaries keyed by tuples | +| One resource I'll use | [Dictionaries in Python](https://realpython.com/python-dicts/) | +| What I'll do with it this week | Try a list as a key, read the error, and explain hashability in my notes | +| Hours I can set aside per week | 3 | +| Date I'll retake the quiz | Four weeks from today | diff --git a/python-skill-assessment/python-skill-assessment-worksheet.md b/python-skill-assessment/python-skill-assessment-worksheet.md new file mode 100644 index 0000000000..873a35ff6a --- /dev/null +++ b/python-skill-assessment/python-skill-assessment-worksheet.md @@ -0,0 +1,80 @@ +# Python Skill Assessment Worksheet + +Rate each statement from 1 to 4: + +| Rating | Meaning | +|--------|---------| +| 1 | I can't do this yet. | +| 2 | I've seen it, but I need to look it up. | +| 3 | I can do it, but I'm not sure I could teach it. | +| 4 | I can do it and explain it to someone else. | + +Rate what you can do without looking anything up. For a statement with several skills, rate your weakest one. + +The "Quiz question" column names the question in the Real Python [Python Skill Test](https://realpython.com/quizzes/python-skill-test/) that covers the same topic, so you can compare your rating with your result. + +## Part 1: Rate Yourself Against Each Level + +### Novice (Quiz score 0-3) + +| I can... | Rating (1-4) | Quiz question | Notes | +|----------|--------------|---------------|-------| +| display values with `print()`. | | Using print() | | +| define a function with `def`, pass arguments, and return a value. | | Define a Function | | +| install a package from PyPI with `python -m pip install`, ideally inside a virtual environment. | | Installing Packages | | +| explain that a `for` loop variable takes each element from an iterable, such as a string or a list. | | Loop Anatomy | | +| use variables, operators, `if` statements, and basic built-in types (numbers, strings, lists). | | Beyond the quiz | | + +### Intermediate (Quiz score 4-7) + +| I can... | Rating (1-4) | Quiz question | Notes | +|----------|--------------|---------------|-------| +| tell mutable types (`list`) from immutable ones (`tuple`, `str`, `frozenset`). | | Mutable Objects | | +| explain that dictionary keys must be hashable and that insertion order is kept since Python 3.7. | | Dictionary Facts | | +| read and write list comprehensions, including an `if` filter, and rewrite them as a `for` loop. | | Understanding List Comprehensions | | +| use the standard library's common modules, such as `pathlib`, `collections`, and `json`, before reaching for third-party packages. | | Beyond the quiz | | + +### Proficient (Quiz score 8-11) + +| I can... | Rating (1-4) | Quiz question | Notes | +|----------|--------------|---------------|-------| +| explain what problem the `with` statement solves and why files close even when an exception occurs. | | The With Statement | | +| recognize dunder methods such as `.__init__()` and `.__iadd__()` and say when Python calls them. | | Spot the Dunder | | +| write a generator function with `yield` and explain why it's lazy. | | Writing a Simple Generator | | +| write and apply decorators, and explain how iterators and iterables work. | | Beyond the quiz | | + +### Expert (Quiz score 12) + +| I can... | Rating (1-4) | Quiz question | Notes | +|----------|--------------|---------------|-------| +| predict attribute lookup when data descriptors, non-data descriptors, and instance attributes compete. | | Descriptor Precedence in Practice | | +| trace the C3 method resolution order and predict the output of cooperative `super()` calls. | | Cooperative super() and the C3 MRO | | +| explain what a metaclass does, and write basic `async` code with `asyncio`. | | Beyond the quiz | | + +## Part 2: Reconcile Your Self-Rating With the Quiz + +Average your ratings for each level, including the _Beyond the quiz_ row, take the quiz, and then fill in the quiz columns. The quiz reports one overall result, so use the explanations for each question you missed to fill in the per-level column. + +| Level | Your average rating | Quiz questions missed | Gap (rating vs. result) | +|-------|---------------------|-----------------------|-------------------------| +| Novice | | | | +| Intermediate | | | | +| Proficient | | | | +| Expert | | | | + +**Overall quiz result:** _______ (___ of 12 correct) + +**Self-rated level** (the highest level whose average, and every average below it, is 3 or more, or _Not yet Novice_ if your Novice average is below 3): _______ + +**Do they match?** _______ + +## Part 3: Turn Your Weakest Area Into a Study Plan + +| Question | Your answer | +|----------|-------------| +| My weakest area (the lowest-level miss that your check confirmed, or your lowest-rated statement at the lowest level) | | +| Why it matters for my goals | | +| One resource I'll use | | +| What I'll do with it this week | | +| Hours I can set aside per week | | +| Date I'll retake the quiz | | From 2ae13b29060c13ce71a9660df72adb2dca40fdbe Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos <23168599+lpozo@users.noreply.github.com> Date: Tue, 27 Oct 2026 09:59:47 +0000 Subject: [PATCH 2/4] Replace probes that gave away Skill Test answers The intermediate, proficient, and expert probes reused code from the Python Skill Test's questions, so readers saw three answers before taking the test. The new probes check the same skills with different code. Also list itertools among the Intermediate modules, as the tutorial does. Co-Authored-By: Claude Opus 5.5 --- python-skill-assessment/probes/expert.py | 26 +++++++++---------- .../probes/intermediate.py | 13 +++++----- python-skill-assessment/probes/proficient.py | 10 ++++--- ...thon-skill-assessment-worksheet-example.md | 2 +- .../python-skill-assessment-worksheet.md | 2 +- 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/python-skill-assessment/probes/expert.py b/python-skill-assessment/probes/expert.py index 5ab757d434..aa22f2d011 100644 --- a/python-skill-assessment/probes/expert.py +++ b/python-skill-assessment/probes/expert.py @@ -1,22 +1,22 @@ -class BaseView: - def handle(self): +class Base: + def setup(self): return ["base"] -class AuthMixin(BaseView): - def handle(self): - return ["auth"] + super().handle() +class Logging(Base): + def setup(self): + return super().setup() + ["logging"] -class CacheMixin(BaseView): - def handle(self): - return ["cache"] + super().handle() +class Metrics(Base): + def setup(self): + return super().setup() + ["metrics"] -class ReportView(AuthMixin, CacheMixin): - def handle(self): - return ["report"] + super().handle() +class Service(Metrics, Logging): + def setup(self): + return super().setup() + ["service"] -print(ReportView().handle()) -print([cls.__name__ for cls in ReportView.__mro__]) +print(Service().setup()) +print([cls.__name__ for cls in Service.__mro__]) diff --git a/python-skill-assessment/probes/intermediate.py b/python-skill-assessment/probes/intermediate.py index 759d0723a7..7890f39eac 100644 --- a/python-skill-assessment/probes/intermediate.py +++ b/python-skill-assessment/probes/intermediate.py @@ -1,8 +1,9 @@ -numbers = range(5) +words = ["apple", "kiwi", "banana", "fig"] -squares = [] -for n in numbers: - squares.append(n * n) +long_words = [] +for word in words: + if len(word) > 3: + long_words.append(word.upper()) -assert squares == [n * n for n in numbers] -print(squares) +print(long_words) +print(long_words == [word.upper() for word in words if len(word) > 3]) diff --git a/python-skill-assessment/probes/proficient.py b/python-skill-assessment/probes/proficient.py index d94ba931a1..e8ff716951 100644 --- a/python-skill-assessment/probes/proficient.py +++ b/python-skill-assessment/probes/proficient.py @@ -1,6 +1,8 @@ -def count_up_to(n): - yield from range(1, n + 1) +def evens_below(n): + for number in range(n): + if number % 2 == 0: + yield number -print(list(count_up_to(3))) -print(list(count_up_to(0))) +print(list(evens_below(7))) +print(list(evens_below(0))) diff --git a/python-skill-assessment/python-skill-assessment-worksheet-example.md b/python-skill-assessment/python-skill-assessment-worksheet-example.md index 303125cf22..c6b003b639 100644 --- a/python-skill-assessment/python-skill-assessment-worksheet-example.md +++ b/python-skill-assessment/python-skill-assessment-worksheet-example.md @@ -34,7 +34,7 @@ The "Quiz question" column names the question in the Real Python [Python Skill T | tell mutable types (`list`) from immutable ones (`tuple`, `str`, `frozenset`). | 3 | Mutable Objects | Never used `frozenset`. | | explain that dictionary keys must be hashable and that insertion order is kept since Python 3.7. | 3 | Dictionary Facts | Hashable: fuzzy. | | read and write list comprehensions, including an `if` filter, and rewrite them as a `for` loop. | 4 | Understanding List Comprehensions | Nested ones slow me down. | -| use the standard library's common modules, such as `pathlib`, `collections`, and `json`, before reaching for third-party packages. | 3 | Beyond the quiz | | +| use the standard library's common modules, such as `pathlib`, `collections`, `itertools`, and `json`, before reaching for third-party packages. | 3 | Beyond the quiz | | ### Proficient (Quiz score 8-11) diff --git a/python-skill-assessment/python-skill-assessment-worksheet.md b/python-skill-assessment/python-skill-assessment-worksheet.md index 873a35ff6a..81ca4197c7 100644 --- a/python-skill-assessment/python-skill-assessment-worksheet.md +++ b/python-skill-assessment/python-skill-assessment-worksheet.md @@ -32,7 +32,7 @@ The "Quiz question" column names the question in the Real Python [Python Skill T | tell mutable types (`list`) from immutable ones (`tuple`, `str`, `frozenset`). | | Mutable Objects | | | explain that dictionary keys must be hashable and that insertion order is kept since Python 3.7. | | Dictionary Facts | | | read and write list comprehensions, including an `if` filter, and rewrite them as a `for` loop. | | Understanding List Comprehensions | | -| use the standard library's common modules, such as `pathlib`, `collections`, and `json`, before reaching for third-party packages. | | Beyond the quiz | | +| use the standard library's common modules, such as `pathlib`, `collections`, `itertools`, and `json`, before reaching for third-party packages. | | Beyond the quiz | | ### Proficient (Quiz score 8-11) From 9a1345d8c521c6b67e50992c48ec8b56a10c1f87 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos <23168599+lpozo@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:22:29 +0000 Subject: [PATCH 3/4] Point the probes at the topics the Skill Test doesn't ask about Each probe now checks its level's Beyond the quiz row: operators and conditionals, Counter and json, decorators, and metaclasses. That way the probes test what the Skill Test can't, and they can't give away any of its answers. Co-Authored-By: Claude Opus 5.5 --- python-skill-assessment/README.md | 2 +- python-skill-assessment/probes/expert.py | 27 +++++++++---------- .../probes/intermediate.py | 13 +++++---- python-skill-assessment/probes/novice.py | 14 +++++++--- python-skill-assessment/probes/proficient.py | 18 ++++++++----- 5 files changed, 41 insertions(+), 33 deletions(-) diff --git a/python-skill-assessment/README.md b/python-skill-assessment/README.md index 78287be895..ad30276ed4 100644 --- a/python-skill-assessment/README.md +++ b/python-skill-assessment/README.md @@ -4,7 +4,7 @@ This folder contains the materials for the Real Python tutorial [Python Skill As - `python-skill-assessment-worksheet.md`: The blank worksheet to fill in - `python-skill-assessment-worksheet-example.md`: A filled-in example worksheet -- `probes/`: One short code probe for each level of the Python Skill Test +- `probes/`: One short code probe for each level, covering the topics that the Python Skill Test doesn't ask about To check a rating, predict a probe's output, and then run it: diff --git a/python-skill-assessment/probes/expert.py b/python-skill-assessment/probes/expert.py index aa22f2d011..e26336ddc9 100644 --- a/python-skill-assessment/probes/expert.py +++ b/python-skill-assessment/probes/expert.py @@ -1,22 +1,19 @@ -class Base: - def setup(self): - return ["base"] +class Registry(type): + names = [] + def __new__(mcs, name, bases, namespace): + cls = super().__new__(mcs, name, bases, namespace) + Registry.names.append(name) + return cls -class Logging(Base): - def setup(self): - return super().setup() + ["logging"] +class Plugin(metaclass=Registry): + pass -class Metrics(Base): - def setup(self): - return super().setup() + ["metrics"] +class CSVExport(Plugin): + pass -class Service(Metrics, Logging): - def setup(self): - return super().setup() + ["service"] - -print(Service().setup()) -print([cls.__name__ for cls in Service.__mro__]) +print(Registry.names) +print(type(CSVExport).__name__) diff --git a/python-skill-assessment/probes/intermediate.py b/python-skill-assessment/probes/intermediate.py index 7890f39eac..0d05de2995 100644 --- a/python-skill-assessment/probes/intermediate.py +++ b/python-skill-assessment/probes/intermediate.py @@ -1,9 +1,8 @@ -words = ["apple", "kiwi", "banana", "fig"] +import json +from collections import Counter -long_words = [] -for word in words: - if len(word) > 3: - long_words.append(word.upper()) +words = "the cat and the hat and the bat".split() +counts = Counter(words) -print(long_words) -print(long_words == [word.upper() for word in words if len(word) > 3]) +print(counts.most_common(2)) +print(json.dumps(counts.most_common(1))) diff --git a/python-skill-assessment/probes/novice.py b/python-skill-assessment/probes/novice.py index 7b42dc4932..4148b56fd0 100644 --- a/python-skill-assessment/probes/novice.py +++ b/python-skill-assessment/probes/novice.py @@ -1,6 +1,12 @@ -def greet(name): - return f"Hello, {name}!" +cookies = 7 +friends = 2 +each = cookies // friends +left_over = cookies % friends -for character in "Hi!": - print(greet(character)) +if left_over == 0: + print(f"Everyone gets {each}, with none left over") +else: + print(f"Everyone gets {each}, with {left_over} left over") + +print(cookies / friends) diff --git a/python-skill-assessment/probes/proficient.py b/python-skill-assessment/probes/proficient.py index e8ff716951..8dda920b03 100644 --- a/python-skill-assessment/probes/proficient.py +++ b/python-skill-assessment/probes/proficient.py @@ -1,8 +1,14 @@ -def evens_below(n): - for number in range(n): - if number % 2 == 0: - yield number +def shout(func): + def wrapper(name): + return func(name).upper() + "!" + return wrapper -print(list(evens_below(7))) -print(list(evens_below(0))) + +@shout +def greet(name): + return f"hello, {name}" + + +print(greet("ada")) +print(greet.__name__) From 08fdff7e7e959b98d5a0d5dbe8e6aa7c954a0e96 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos <23168599+lpozo@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:27:35 +0000 Subject: [PATCH 4/4] Greet Jane in the proficient probe Co-Authored-By: Claude Opus 5.5 --- python-skill-assessment/probes/proficient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python-skill-assessment/probes/proficient.py b/python-skill-assessment/probes/proficient.py index 8dda920b03..97a3670caa 100644 --- a/python-skill-assessment/probes/proficient.py +++ b/python-skill-assessment/probes/proficient.py @@ -10,5 +10,5 @@ def greet(name): return f"hello, {name}" -print(greet("ada")) +print(greet("jane")) print(greet.__name__)