diff --git a/.appends/.github/labels.yml b/.appends/.github/labels.yml
new file mode 100644
index 00000000000..64285b987de
--- /dev/null
+++ b/.appends/.github/labels.yml
@@ -0,0 +1,184 @@
+# ----------------------------------------------------------------------------------------- #
+# These are the repository-specific labels that augment the Exercise-wide labels defined in #
+# https://github.com/exercism/org-wide-files/blob/main/global-files/.github/labels.yml. #
+# ----------------------------------------------------------------------------------------- #
+
+- name: "abandoned 🏚"
+ description: ""
+ color: "ededed"
+
+- name: "bug 🐛"
+ description: ""
+ color: "B60205"
+
+- name: "chore 🔧"
+ description: ""
+ color: "293028"
+
+- name: "claimed 🐾"
+ description: "For new exercises being written by contributors and maintainers."
+ color: "FFF034"
+
+- name: "dependencies"
+ description: "Pull requests that update a dependency file"
+ color: "257FE3"
+
+- name: "discussion 💬"
+ description: ""
+ color: "257FE3"
+
+- name: "do not merge 🚧"
+ description: "Don't merge until this label is removed."
+ color: "fbca04"
+
+- name: "documentation 📚"
+ description: ""
+ color: "257FE3"
+
+- name: "duplicate"
+ description: ""
+ color: "e6e6e6"
+
+- name: "enhancement 🦄 ⭐"
+ description: "Changing current behaviour or enhancing/adding to what's already there."
+ color: "B5F7FF"
+
+- name: "experimental 🔬"
+ description: "Speculative functionality or implementation."
+ color: "FFFFFF"
+
+- name: "first-timers only 🐣"
+ description: "Issues reserved for first contributions."
+ color: "000000"
+
+- name: "github_actions"
+ description: "Pull requests that update Github_actions code"
+ color: "000000"
+
+- name: "good first issue"
+ description: "Good issue for a new contributor to this project/repo."
+ color: "BED643"
+
+- name: "good first patch"
+ description: "Good fix/patch for a new contributor to this project/repo."
+ color: "BED643"
+
+- name: "hacktoberfest 🍁"
+ description: ""
+ color: "FEF2C0"
+
+- name: "hacktoberfest-accepted ☑"
+ description: ""
+ color: "F5A623"
+
+- name: "help wanted"
+ description: "We'd like your help with this."
+ color: "BD10E0"
+
+- name: "improve documentation 💖"
+ description: "specific improvements to documentation on the track, the exercises, or the repo."
+ color: "FFFFFF"
+
+- name: "improve exercise 💖"
+ description: ""
+ color: "FFFFFF"
+
+- name: "improve reference docs 💖"
+ description: "for issues with links, introductions, or about docs for concepts"
+ color: "FFFFFF"
+
+- name: "improve test case 💖"
+ description: "improve test case(s) for an exercise"
+ color: "FFFFFF"
+
+- name: "in-progress 🌿"
+ description: ""
+ color: "FFFFFF"
+
+- name: "invalid"
+ description: ""
+ color: "e6e6e6"
+
+- name: "maintainer action required❕"
+ description: "A maintainer needs to take action on this."
+ color: "B60205"
+
+- name: "maintainer chore 🔧"
+ description: ""
+ color: "000000"
+
+- name: "new documentation ✨"
+ description: ""
+ color: "5331B5"
+
+- name: "new exercise ✨"
+ description: ""
+ color: "5331B5"
+
+- name: "new reference doc ✨"
+ description: ""
+ color: "5331B5"
+
+- name: "new test case :sparkles:"
+ description: ""
+ color: "5331B5"
+
+- name: "on hold ✋🏽"
+ description: "Action should stop on this issue or PR for now."
+ color: "FEF2C0"
+
+- name: "pinned 📌"
+ description: "Do no mark stale or invalid."
+ color: "FFD141"
+
+- name: "please review 👀"
+ description: ""
+ color: "257FE3"
+
+- name: "python"
+ description: "Pull requests that update Python code"
+ color: "2b67c6"
+
+- name: "security 🚨"
+ description: "Security issue or patch"
+ color: "FFFFFF"
+
+- name: "spam 🚫"
+ description: "This is a spam/nuisance issue or PR."
+ color: "FFFFFF"
+
+- name: "status/draft"
+ description: "This is in draft and open for comment/suggestion."
+ color: "DEA3EA"
+
+- name: "status/wontfix 🙅🏽"
+ description: "While this may be an issue, it will not be fixed at this time."
+ color: "DEA3EA"
+
+- name: "wip/content-checking ☑"
+ description: "content checking tasks for exercises that are in development"
+ color: "A389F1"
+
+- name: "wip/proof-reading 👀"
+ description: "proof reading tasks for exercises that are in development"
+ color: "A389F1"
+
+- name: "wip/story-writing ✍"
+ description: "story writing tasks for exercises that are in development"
+ color: "A389F1"
+
+- name: "wip/style-guideline-checking 📐"
+ description: "style guideline tasks for exercises that are in development"
+ color: "A389F1"
+
+- name: "❔question❔"
+ description: "I have a proposal or question about things, but nothing is broken."
+ color: "293028"
+
+- name: "prickle💢"
+ description: "This issue should be skipped by the auto-responder."
+ color: "FFFFFF"
+
+- name: "smolder🍲"
+ description: "Lower-priority and/or future idea"
+ color: "8B0000"
diff --git a/.flake8 b/.flake8
new file mode 100644
index 00000000000..b7857818b92
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,12 @@
+[flake8]
+max-line-length = 120
+exclude =
+ .git,
+ __pycache__,
+ .pytest_cache,
+ venv
+ignore = E501, F401
+format = pylint
+show-source = True
+max-complexity = 10
+
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 00000000000..1e82ac9978d
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,5 @@
+# Code owners
+.github/CODEOWNERS @exercism/maintainers-admin
+
+# Changes to `fetch-configlet` should be made in the `exercism/configlet` repo
+bin/fetch-configlet @exercism/maintainers-admin
diff --git a/.github/ISSUE_TEMPLATE/new_concept_exercise_template.md b/.github/ISSUE_TEMPLATE/new_concept_exercise_template.md
new file mode 100644
index 00000000000..4bcb4c08768
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/new_concept_exercise_template.md
@@ -0,0 +1,191 @@
+---
+name: "[ MAINTAINERS ] Implement New Concept Exercise"
+about: Use this template to create an issue for implementing a new concept exericse.
+title: "[V3]: Implement new Concept Exercise: "
+labels: "new exercise :sparkles:", help wanted
+assignees: ''
+
+---
+
+
+This issue describes how to implement the `` concept exercise for the python track.
+
+
+
+## Getting started
+
+**Please please please read the docs before starting.** Posting PRs without reading these docs will be a lot more frustrating for you during the review cycle, and exhaust Exercism's maintainers' time. So, before diving into the implementation, please read up on the following documents:
+
+- [The features of v3](https://github.com/exercism/docs/tree/main/product)
+- [The Anatomy of Exercism](https://github.com/exercism/docs/tree/main/anatomy) & [The Anatomy of a Language Track](https://github.com/exercism/docs/tree/main/anatomy/tracks)
+- [Concepts](https://github.com/exercism/docs/blob/main/anatomy/tracks/concepts.md) & [Concept Exercises](https://github.com/exercism/docs/blob/main/anatomy/tracks/concept-exercises.md)
+- [Exercism Formatting and Style Guide](https://github.com/exercism/docs/blob/main/anatomy/tracks/style-guide.md)
+- [Exercism Markdown Specification](https://github.com/exercism/docs/blob/main/contributing/standards/markdown.md)
+
+
+
+Watching the following video is also helpful. It is a bit outdated, but is good for describing the general process:
+
+- [The Anatomy of a Concept Exercise](https://www.youtube.com/watch?v=gkbBqd7hPrA).
+
+
+
+## Goal
+
+This concept exercise is meant to teach an understanding/use of``.
+
+[replace this comment with additional detail for the Goal if needed. Otherwise just delete.]
+
+
+
+## Learning objectives
+
+[_Please list the learning objectives of this concept exercise here. See the below list for an example.
+
+- understanding (_and using the concept_) that the `==` operator calls the dunder method `__eq__()` on a specific object, and uses that object's implementation for comparison. Where no implementation is present, the default `__eq__()` from _generic_ `object` is used.
+- the same applies for operators `<`, `<=`, `!=`, `>=`, `>` and correspondents dunder methods `__lt__`, `__le__`, `__ne__`, `__ge__`, `__gt__`
+- overriding the default implementation of the `__eq__()` dunder method on a specific object to customize comparison behavior.
+- overriding the default implementations of the `__lt__`, `__le__`, `__ne__`, `__ge__`, `__gt__` dunder methods on a specific object to customize comparison behavior.
+- return the singleton `NotImplemented` if the called operation is not implemented]
+
+
+
+## Out of scope
+
+[_Please list the concept considerations that will NOT be covered, and are out of scope. See the below list for an example.
+
+- customizaton options
+- performance considerations]
+
+
+## Concepts
+
+[_Please list the concepts/methods that will be included in the exercise. See the below list for an example.
+
+- `__eq__`, `__lt__`, `__le__`, `__ne__`, `__ge__`, `__gt__` dunder methods
+- comparison behavior customization
+- dunder methods override]
+
+
+## Prerequisites
+
+[_Please list the concepts the student needs to complete/understand before solving this exercise. See the below list for an example.
+
+- Method overriding
+- Comparison priority in Python
+- Comparison operators`==`, `>`, `<`, `!=`
+- Identity methods `is` and `is not`
+- Use the _value comparison operators_ `==`, `>`, `<`, `!=` with _numeric types_
+- Use the _value comparison operators_ `==`, `>`, `<`, `!=` with _non-numeric types_
+- `basics`
+- `booleans`
+- `classes`
+- `numbers`
+- `sequences`
+- `iteration`]
+
+
+## Resources to refer to
+
+[_Below, list/link to resources that the exercise writer can use or consult in writing the exercise. This list is a suggestion to get them started writing.]
+
+
+
+*
+*
+*
+
+
+
+* ### Hints
+
+ For more information on writing hints see [hints](https://github.com/exercism/docs/blob/main/anatomy/tracks/concept-exercises.md#file-docshintsmd)
+
+ * You can refer to one or more of the resources linked above, or analogous resources from a trusted source. We prefer using links within the [Python Docs](https://docs.python.org/3/) as the primary go-to, but other resources listed above are also good.
+
+
+
+* ### `links.json`
+
+ * The same resources can be used for the [ `concepts/links.json`](https://github.com/exercism/docs/blob/main/anatomy/tracks/concepts.md#file-linksjson) file, if it doesn't already exist.
+ * If there are particularly good/interesting informations sources for this concept that extend or supplement the concept exercise material, please add them to the `links.json` document.
+
+
+
+## Concept Description
+
+[_Link to the concept `about.md` file in the Concept section below. If the file still needs to be written, please link to the **GitHub issue** filed for the concept `about.md` file._]
+
+
+Please see the following for more details on these files: [concepts](https://github.com/exercism/docs/blob/main/anatomy/tracks/concepts.md#file-linksjson)
+
+* ### Concept `about.md`
+
+ []() and/or []()
+
+* ### Exercise `introduction.md`
+
+ For more information, see [Exercise `introduction.md`](https://github.com/exercism/docs/blob/main/anatomy/tracks/concept-exercises.md#file-docsintroductionmd)
+
+ * This should summarize the above document, with enough information for the student to complete the tasks in the concept exercise.
+
+* ### Concept `introduction.md`
+
+ For more information, see [Concept `introduction.md`](https://github.com/exercism/docs/blob/main/anatomy/tracks/concepts.md#file-introductionmd)
+
+ * This should also be a summary of the above document, and will provide a brief introduction of the concept for a student who has **not yet** completed the concept exercise.
+
+
+## Test-runner
+[_note any specialized files or changes needed to support the tests for this concept_]
+
+No changes required.
+
+## Representer
+[_note any specalized files, actions or changes needed to support representations of possilbe solutions for this concept_]
+
+No changes required.
+
+
+
+## Analyzer
+[_not any specalized comments, files, actions or changes needed to support the analysis of solutions for this concept_]
+
+No changes required.
+
+
+
+## Exercise UUID
+
+[please generate a UUID for this exercise for `config.json`]
+
+one of these tools can be used, or you can use your favorite method of generating a **V4 UUID**
+
+[exercism configlet](https://github.com/exercism/configlet)
+
+[uuidgenerator.net](https://www.uuidgenerator.net/version4)
+
+
+
+**Exercise UUID** : ``
+
+
+
+## Implementation Notes
+
+
+Code in the `.meta/examplar.py` file should **only use syntax & concepts introduced in this exercise or one of its prerequisite exercises.**
+Please **do not use** comprehensions, generator expressions, or other syntax not previously covered. Please also follow [PEP8](https://www.python.org/dev/peps/pep-0008/) guidelines.
+
+In General, tests should be written using `unittest.TestCase` and the test file should be named `_test.py`.
+
+While we do use [PyTest](https://docs.pytest.org/en/stable/) as our test runner and for some implementation tests, please check with a maintainer before using a PyTest test method, fixture, or feature.
+
+Our markdown and JSON files are checked against [prettier](https://prettier.io/) . We recommend [setting prettier up locally](https://prettier.io/docs/en/install.html) and running it prior to submitting your PR to avoid any CI errors.
+
+
+
+## Help
+
+If you have any questions while implementing the exercise, please post the questions as comments in this issue, or contact one of the maintainers on our Slack channel.
+
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000000..234b07e766c
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,9 @@
+version: 2
+
+updates:
+
+ # Keep dependencies for GitHub Actions up-to-date
+ - package-ecosystem: 'github-actions'
+ directory: '/'
+ schedule:
+ interval: 'monthly'
diff --git a/.github/issue-comment.md b/.github/issue-comment.md
new file mode 100644
index 00000000000..143059bd52e
--- /dev/null
+++ b/.github/issue-comment.md
@@ -0,0 +1,56 @@
+🤖 🤖
+
+Hi! 👋🏽 👋 Welcome to the Exercism Python Repo!
+
+Thank you for opening an issue! 🐍 🌈 ✨
+
+
+
+- If you are **requesting support**, we will be along shortly to help. (*generally within* **72 hours,** *often more quickly*).
+- **Found a problem** with tests, exercises or something else?? 🎉
+ ◦ We'll take a look as soon as we can & identify what work is needed to fix it. *(generally within* **72 hours**).
+
+ ◦ _If you'd also like to make a PR to **fix** the issue, please have a quick look at the [Pull Requests][prs] doc._
+ _We 💙 PRs that follow our [Exercism][exercism-guidelines] & [Track][track-guidelines] contributing guidelines!_
+
+- Here because of an obvious (*and* **small** *set of*) spelling, grammar, or punctuation issues with **one** exercise,
+ concept, or Python document?? 🌟 `Please feel free to submit a PR, linking to this issue.` 🎉
+
+
+
+
+
+ ‼️ Please Do Not ‼️
+
+
+ ❗ Run checks on the whole repo & submit a bunch of PRs.
+ This creates longer review cycles & exhausts reviewers energy & time.
+ It may also conflict with ongoing changes from other contributors.
+ ❗ Insert only blank lines, make a closing bracket drop to the next line, change a word
+ to a synonym without obvious reason, or add trailing space that's not an[ EOL][EOL] for the very end of text files.
+ ❗ Introduce arbitrary changes "just to change things" .
+
+ _...These sorts of things are **not** considered helpful, and will likely be closed by reviewers._
+
+
+
+
+
+
+- For anything complicated or ambiguous, **let's discuss things** -- we will likely welcome a PR from you.
+- _Here to suggest a feature or new exercise??_ **Hooray!** Please keep in mind [_Chesterton's Fence_][chestertons-fence].
+_Thoughtful suggestions will likely result faster & more enthusiastic responses from maintainers._
+
+
+
+💛 💙 _While you are here..._ If you decide to help out with other [open issues][open-issues], you have our **gratitude** 🙌 🙌🏽.
+Anything tagged with `[help wanted]` and without `[Claimed]` is up for grabs.
+Comment on the issue and we will reserve it for you. 🌈 ✨
+
+
+[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md
+[EOL]: https://en.wikipedia.org/wiki/Newline
+[chestertons-fence]: https://github.com/exercism/docs/blob/main/community/good-member/chestertons-fence.md
+[exercism-guidelines]: https://exercism.org/docs/building
+[open-issues]: https://github.com/exercism/python/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22
+[track-guidelines]: https://github.com/exercism/python/blob/main/CONTRIBUTING.md
diff --git a/.github/labels.yml b/.github/labels.yml
new file mode 100644
index 00000000000..3f8780530db
--- /dev/null
+++ b/.github/labels.yml
@@ -0,0 +1,353 @@
+# --------------------------------------------------------------- #
+# This is an auto-generated file - Do not manually edit this file #
+# --------------------------------------------------------------- #
+
+# This file is automatically generated by concatenating two files:
+#
+# 1. The Exercism-wide labels: defined in https://github.com/exercism/org-wide-files/blob/main/global-files/.github/labels.yml
+# 2. The repository-specific labels: defined in the `.appends/.github/labels.yml` file within this repository.
+#
+# If any of these two files change, a pull request is automatically created containing a re-generated version of this file.
+# Consequently, to change repository-specific labels you should update the `.appends/.github/labels.yml` file and _not_ this file.
+#
+# When the pull request has been merged, the GitHub labels will be automatically updated by the "Sync labels" workflow.
+# This typically takes 5-10 minutes.
+
+# --------------------------------------------------------------------- #
+# These are the Exercism-wide labels which are shared across all repos. #
+# --------------------------------------------------------------------- #
+
+# The following Exercism-wide labels are used to show "tasks" on the website, which will point users to things they can contribute to.
+
+# The `x:action/` labels describe what sort of work the contributor will be engaged in when working on the issue
+- name: "x:action/create"
+ description: "Work on something from scratch"
+ color: "ffffff"
+
+- name: "x:action/fix"
+ description: "Fix an issue"
+ color: "ffffff"
+
+- name: "x:action/improve"
+ description: "Improve existing functionality/content"
+ color: "ffffff"
+
+- name: "x:action/proofread"
+ description: "Proofread text"
+ color: "ffffff"
+
+- name: "x:action/sync"
+ description: "Sync content with its latest version"
+ color: "ffffff"
+
+# The `x:knowledge/` labels describe how much Exercism knowledge is required by the contributor
+- name: "x:knowledge/none"
+ description: "No existing Exercism knowledge required"
+ color: "ffffff"
+
+- name: "x:knowledge/elementary"
+ description: "Little Exercism knowledge required"
+ color: "ffffff"
+
+- name: "x:knowledge/intermediate"
+ description: "Quite a bit of Exercism knowledge required"
+ color: "ffffff"
+
+- name: "x:knowledge/advanced"
+ description: "Comprehensive Exercism knowledge required"
+ color: "ffffff"
+
+# The `x:module/` labels indicate what part of Exercism the contributor will be working on
+- name: "x:module/analyzer"
+ description: "Work on Analyzers"
+ color: "ffffff"
+
+- name: "x:module/concept"
+ description: "Work on Concepts"
+ color: "ffffff"
+
+- name: "x:module/concept-exercise"
+ description: "Work on Concept Exercises"
+ color: "ffffff"
+
+- name: "x:module/generator"
+ description: "Work on Exercise generators"
+ color: "ffffff"
+
+- name: "x:module/practice-exercise"
+ description: "Work on Practice Exercises"
+ color: "ffffff"
+
+- name: "x:module/representer"
+ description: "Work on Representers"
+ color: "ffffff"
+
+- name: "x:module/test-runner"
+ description: "Work on Test Runners"
+ color: "ffffff"
+
+# The `x:rep/` labels describe the amount of reputation to award
+#
+# For more information on reputation and how these labels should be used,
+# check out https://exercism.org/docs/using/product/reputation
+- name: "x:rep/tiny"
+ description: "Tiny amount of reputation"
+ color: "ffffff"
+
+- name: "x:rep/small"
+ description: "Small amount of reputation"
+ color: "ffffff"
+
+- name: "x:rep/medium"
+ description: "Medium amount of reputation"
+ color: "ffffff"
+
+- name: "x:rep/large"
+ description: "Large amount of reputation"
+ color: "ffffff"
+
+- name: "x:rep/massive"
+ description: "Massive amount of reputation"
+ color: "ffffff"
+
+# The `x:size/` labels describe the expected amount of work for a contributor
+- name: "x:size/tiny"
+ description: "Tiny amount of work"
+ color: "ffffff"
+
+- name: "x:size/small"
+ description: "Small amount of work"
+ color: "ffffff"
+
+- name: "x:size/medium"
+ description: "Medium amount of work"
+ color: "ffffff"
+
+- name: "x:size/large"
+ description: "Large amount of work"
+ color: "ffffff"
+
+- name: "x:size/massive"
+ description: "Massive amount of work"
+ color: "ffffff"
+
+# The `x:status/` label indicates if there is already someone working on the issue
+- name: "x:status/claimed"
+ description: "Someone is working on this issue"
+ color: "ffffff"
+
+# The `x:type/` labels describe what type of work the contributor will be engaged in
+- name: "x:type/ci"
+ description: "Work on Continuous Integration (e.g. GitHub Actions workflows)"
+ color: "ffffff"
+
+- name: "x:type/coding"
+ description: "Write code that is not student-facing content (e.g. test-runners, generators, but not exercises)"
+ color: "ffffff"
+
+- name: "x:type/content"
+ description: "Work on content (e.g. exercises, concepts)"
+ color: "ffffff"
+
+- name: "x:type/docker"
+ description: "Work on Dockerfiles"
+ color: "ffffff"
+
+- name: "x:type/docs"
+ description: "Work on Documentation"
+ color: "ffffff"
+
+# This Exercism-wide label is added to all automatically created pull requests that help migrate/prepare a track for Exercism v3
+- name: "v3-migration 🤖"
+ description: "Preparing for Exercism v3"
+ color: "e99695"
+
+# This Exercism-wide label can be used to bulk-close issues in preparation for pausing community contributions
+- name: "paused"
+ description: "Work paused until further notice"
+ color: "e4e669"
+
+# ----------------------------------------------------------------------------------------- #
+# These are the repository-specific labels that augment the Exercise-wide labels defined in #
+# https://github.com/exercism/org-wide-files/blob/main/global-files/.github/labels.yml. #
+# ----------------------------------------------------------------------------------------- #
+
+- name: "abandoned 🏚"
+ description: ""
+ color: "ededed"
+
+- name: "bug 🐛"
+ description: ""
+ color: "B60205"
+
+- name: "chore 🔧"
+ description: ""
+ color: "293028"
+
+- name: "claimed 🐾"
+ description: "For new exercises being written by contributors and maintainers."
+ color: "FFF034"
+
+- name: "dependencies"
+ description: "Pull requests that update a dependency file"
+ color: "257FE3"
+
+- name: "discussion 💬"
+ description: ""
+ color: "257FE3"
+
+- name: "do not merge 🚧"
+ description: "Don't merge until this label is removed."
+ color: "fbca04"
+
+- name: "documentation 📚"
+ description: ""
+ color: "257FE3"
+
+- name: "duplicate"
+ description: ""
+ color: "e6e6e6"
+
+- name: "enhancement 🦄 ⭐"
+ description: "Changing current behaviour or enhancing/adding to what's already there."
+ color: "B5F7FF"
+
+- name: "experimental 🔬"
+ description: "Speculative functionality or implementation."
+ color: "FFFFFF"
+
+- name: "first-timers only 🐣"
+ description: "Issues reserved for first contributions."
+ color: "000000"
+
+- name: "github_actions"
+ description: "Pull requests that update Github_actions code"
+ color: "000000"
+
+- name: "good first issue"
+ description: "Good issue for a new contributor to this project/repo."
+ color: "BED643"
+
+- name: "good first patch"
+ description: "Good fix/patch for a new contributor to this project/repo."
+ color: "BED643"
+
+- name: "hacktoberfest 🍁"
+ description: ""
+ color: "FEF2C0"
+
+- name: "hacktoberfest-accepted ☑"
+ description: ""
+ color: "F5A623"
+
+- name: "help wanted"
+ description: "We'd like your help with this."
+ color: "BD10E0"
+
+- name: "improve documentation 💖"
+ description: "specific improvements to documentation on the track, the exercises, or the repo."
+ color: "FFFFFF"
+
+- name: "improve exercise 💖"
+ description: ""
+ color: "FFFFFF"
+
+- name: "improve reference docs 💖"
+ description: "for issues with links, introductions, or about docs for concepts"
+ color: "FFFFFF"
+
+- name: "improve test case 💖"
+ description: "improve test case(s) for an exercise"
+ color: "FFFFFF"
+
+- name: "in-progress 🌿"
+ description: ""
+ color: "FFFFFF"
+
+- name: "invalid"
+ description: ""
+ color: "e6e6e6"
+
+- name: "maintainer action required❕"
+ description: "A maintainer needs to take action on this."
+ color: "B60205"
+
+- name: "maintainer chore 🔧"
+ description: ""
+ color: "000000"
+
+- name: "new documentation ✨"
+ description: ""
+ color: "5331B5"
+
+- name: "new exercise ✨"
+ description: ""
+ color: "5331B5"
+
+- name: "new reference doc ✨"
+ description: ""
+ color: "5331B5"
+
+- name: "new test case :sparkles:"
+ description: ""
+ color: "5331B5"
+
+- name: "on hold ✋🏽"
+ description: "Action should stop on this issue or PR for now."
+ color: "FEF2C0"
+
+- name: "pinned 📌"
+ description: "Do no mark stale or invalid."
+ color: "FFD141"
+
+- name: "please review 👀"
+ description: ""
+ color: "257FE3"
+
+- name: "python"
+ description: "Pull requests that update Python code"
+ color: "2b67c6"
+
+- name: "security 🚨"
+ description: "Security issue or patch"
+ color: "FFFFFF"
+
+- name: "spam 🚫"
+ description: "This is a spam/nuisance issue or PR."
+ color: "FFFFFF"
+
+- name: "status/draft"
+ description: "This is in draft and open for comment/suggestion."
+ color: "DEA3EA"
+
+- name: "status/wontfix 🙅🏽"
+ description: "While this may be an issue, it will not be fixed at this time."
+ color: "DEA3EA"
+
+- name: "wip/content-checking ☑"
+ description: "content checking tasks for exercises that are in development"
+ color: "A389F1"
+
+- name: "wip/proof-reading 👀"
+ description: "proof reading tasks for exercises that are in development"
+ color: "A389F1"
+
+- name: "wip/story-writing ✍"
+ description: "story writing tasks for exercises that are in development"
+ color: "A389F1"
+
+- name: "wip/style-guideline-checking 📐"
+ description: "style guideline tasks for exercises that are in development"
+ color: "A389F1"
+
+- name: "❔question❔"
+ description: "I have a proposal or question about things, but nothing is broken."
+ color: "293028"
+
+- name: "prickle💢"
+ description: "This issue should be skipped by the auto-responder."
+ color: "FFFFFF"
+
+- name: "smolder🍲"
+ description: "Lower-priority and/or future idea"
+ color: "8B0000"
diff --git a/.github/pr-commenter.yml b/.github/pr-commenter.yml
new file mode 100644
index 00000000000..bcaa36dfcb6
--- /dev/null
+++ b/.github/pr-commenter.yml
@@ -0,0 +1,362 @@
+comment:
+ on-update: edit
+ header: |
+ Hi & Welcome! 👋🏽 👋
+
+ **Thank you** for contributing to `exercism/python` 💛 💙 -- we really appreciate it! 🌟 🌈
+
+ footer: |
+
+ 🛠️ Maintainers
+ Please take note 📒 of the following sections/review items 👀 ✨
+
+
+
+ 🌈 Acknowledgements and Reputation
+
+
+
+
+ - ❓ Does this PR need to receive a label with a [`reputation modifier`](https://github.com/exercism/docs/blob/main/using/product/reputation.md#creating-a-pull-request)?
+ - `medium` is awarded by default.
+ - ❓ Does this contributor need to be added to the **exercise** [`authors` or `contributors`](https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md#file-metaconfigjson)?
+ - ❓ Does this contributor need to be added to the **concept** [`authors` or `contributors`](https://github.com/exercism/docs/blob/main/building/tracks/concepts.md#file-metaconfigjson)?
+ - ❓ Is there an associated issue or issues that should be linked to this PR?
+
+
+
+
+
+ 💫 General Code Quality
+
+
+
+ Verify:
+
+ - [ ] The branch was updated & rebased with any (recent) upstream changes.
+ - [ ] All prose was checked for spelling and grammar.
+ - [ ] Files are formatted via [yapf](https://github.com/google/yapf) ([_yapf config_](https://github.com/exercism/python/blob/main/.style.yapf)) & conform to our [coding standards](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#coding-standards)
+ - [ ] Files pass [flake8](http://flake8.pycqa.org/) with [_flake8 config_](https://github.com/exercism/python/blob/main/.flake8) & [pylint](https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html) with [_pylint config_](https://github.com/exercism/python/blob/main/pylintrc).
+ - [ ] Changed `example.py`/`exemplar.py` files still pass their associated **test files**.
+ - [ ] Changed _**test files**_ still work with associated `example.py`/`exemplar.py` files.
+ - Check that tests **fail** properly, as well as succeed.
+ (_**e.g.**, make some tests fail on purpose to "test the tests" & failure messages_).
+ - [ ] All files have proper EOL.
+ - [ ] If a `JinJa2` template was modified/created, was the test file [regenerated](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#auto-generated-test-files-and-test-templates)?
+ - Does the regenerated test file successfully test the exercises `example.py` file?
+ - [ ] The branch passes all CI checks & [`configlet-lint`](https://github.com/exercism/configlet#configlet-lint).
+
+
+
+
+
+
+
+
+ 🌿 Changes to Concept Exercises
+
+
+ - ❓ Are all required files still up-to-date & configured correctly for this change?_
+ - ❓ Does `/.meta/design.md` need to be updated with new implementation/design decisions
+ - ❓ Do these changes require follow-on/supporting changes to related **concept** documents?
+
+
+
+ ✨ Where applicable, check the following ✨
+
+
+
+
+
+ (_as a reminder: [Concept Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md)_)
+ - [ ] Exercise `introduction.md`
+ - [ ] Do all code examples compile, run, and return the shown output?
+ - [ ] Are all the code examples formatted per the Python docs?
+ - [ ] Exercise `instructions.md`
+ - [ ] Exercise `hints.md`
+ - [ ] Check that exercise `design.md` was fulfilled or edited appropriately
+ - [ ] Exercise `exemplar.py`
+ - [ ] Only uses syntax previously introduced or explained.
+ - [ ] Is correct and appropriate for the exercise and story.
+ - [ ] Exercise `.py` (stub)
+ - [ ] Includes appropriate docstrings and function names.
+ - [ ] Includes `pass` for each function
+ - [ ] Includes an EOL at the end
+ - [ ] Exercise `_test.py`
+ - [ ] Tests cover all (reasonable) inputs and scenarios
+ - [ ] At least one test for each task in the exercise
+ - [ ] If using subtests or fixtures they're [formatted correctly](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#Test-File_style) for the runner
+ - [ ] Classnames are `Test`
+ - [ ] Test functions are `test_`
+ - [ ] Exercise `config.json` --> **valid UUID4**
+ - [ ] Corresponding concept `introduction.md`
+ - [ ] Corresponding concept `about.md`
+ - [ ] Concept `config.json`
+ - [ ] All Markdown Files : Prettier linting (_for all markdown docs_)
+ - [ ] All Code files: PyLint linting (**_except for test files_**)
+ - [ ] All files with text: Spell check & grammar review.
+
+
+
+
+
+
+
+
+
+ 🚀 Changes to Practice Exercises
+
+
+
+
+ 🐣 Brand-New Concept Exercises
+
+
+
+
+
+ _Is the exercise is in line with [Concept Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md)?_
+ - [ ] Exercise `introduction.md`
+ - [ ] Do all code examples compile, run, and return the shown output?
+ - [ ] Are all the code examples formatted per the Python docs?
+ - [ ] Exercise `instructions.md`
+ - [ ] Exercise `hints.md`
+ - [ ] Check that exercise `design.md` was fulfilled or edited appropriately
+ - [ ] Exercise `exemplar.py`
+ - [ ] Only uses syntax previously introduced or explained.
+ - [ ] Is correct and appropriate for the exercise and story.
+ - [ ] Exercise `.py` (stub)
+ - [ ] Includes appropriate docstrings and function names.
+ - [ ] Includes `pass` for each function
+ - [ ] Includes an EOL at the end
+ - [ ] Exercise `_test.py`
+ - [ ] Tests cover all (reasonable) inputs and scenarios
+ - [ ] At least one test for each task in the exercise
+ - [ ] If using subtests or fixtures they're formatted correctly for the runner
+ - [ ] Classnames are `Test`
+ - [ ] Test functions are `test_`
+ - [ ] Exercise `config.json` --> valid UUID4
+ - [ ] Corresponding concept `introduction.md`
+ - [ ] Corresponding concept `about.md`
+ - [ ] Concept `config.json`
+ - [ ] All Markdown Files : Prettier linting (_for all markdown docs_)
+ - [ ] All Code files: Flake8 & PyLint linting
+ - [ ] All Code Examples: proper formatting and fencing. Verify they run in the REPL
+ - [ ] All files with text: Spell check & grammar review.
+
+
+
+
+
+
+
+ Our 💖 for all your review efforts! 🌟 🦄
+
+ snippets:
+ - id: any-file-changed
+ files:
+ - any: ["**", "**/**"]
+ body: |
+
+
+ This is an automated [🤖 🤖 ] comment for the **`maintainers`** of this repository, notifying them of your contribution. 🎉
+ Someone will review/reply to your changes shortly. (_usually within **72 hours**._)
+ You can safely ignore the **`maintainers`** section below.
+
+
+
+ ⚠️ Please be aware ⚠️
+
+ _This repo does not generally accept [Pull Requests](https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md) unless they follow our [contributing guidelines](https://github.com/exercism/python/blob/main/CONTRIBUTING.md) and are:_
+
+ 1️⃣ Small, contained fixes for typos/grammar/punctuation/code syntax on [one] exercise,
+ 2️⃣ Medium changes that have been agreed/discussed via a filed issue,
+ 3️⃣ Contributions from our [help wanted][help-wanted] issue list,
+ 4️⃣ Larger (_previously agreed-upon_) contributions from recent & regular (_within the last 6 months_) contributors.
+
+ Pull Requests not in these categories **will be closed**. 😞
+
+
+
+
+
+ - id: change-concept-exercise
+ files:
+ - "concepts/**/*"
+ - "concepts/**/**/*"
+ - "exercises/concept/**/*"
+ - "exercises/concept/**/**/*"
+ body: |
+ ---
+
+ #### **`💙 It looks like you are changing/adding files in a Concept Exercise! 💙 `**
+
+
+
+ ‼️ Did You...
+
+
+
+
+
+ - [ ] Update & rebase your branch with any (recent) upstream changes.
+ - [ ] Spell and grammar check all prose changes.
+ - [ ] Run [Prettier](https://prettier.io/) on all markdown and JSON files.
+ - (_Optionally_) run [yapf](https://github.com/google/yapf) ([_yapf config_](https://github.com/exercism/python/blob/main/.style.yapf)) to help format your code.
+ - [ ] Run [flake8](http://flake8.pycqa.org/) with [_flake8 config_](https://github.com/exercism/python/blob/main/.flake8) to check general code style standards.
+ - [ ] Run [pylint](https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html) with [_pylint config_](https://github.com/exercism/python/blob/main/pylintrc) to check extended code style standards.
+ - [ ] Use pytest or the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed `example.py`/`exemplar.py`files
+ against their associated test files.
+ - [ ] Similarly, use [pytest](https://docs.pytest.org/en/6.2.x/contents.html) or the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed _**test**_ files.
+ - Check that tests **fail** properly, as well as succeed.
+ (_**e.g.**, make some tests fail on purpose to "test the tests" & failure messages_).
+ - [ ] Double-check all files for proper EOL.
+ - [ ] [Regenerate](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#generating-practice-exercise-documents) exercise documents when you modified or created a `hints.md` file for a practice exercise.
+ - [ ] [Regenerate the test file](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#auto-generated-test-files-and-test-templates) if you modified or created a `JinJa2` template file for a practice exercise.
+ - Run the generated test file result against its `example.py`.
+ - [ ] Run [`configlet-lint`](https://github.com/exercism/configlet#configlet-lint) if the track [config.json](https://github.com/exercism/docs/blob/main/building/tracks/config-json.md), or any other exercise `config.json` has been modified.
+
+
+
+
+
+
+
+
+ ✅️ Have You Checked:
+
+
+
+
+ .
+ _**Are there any additional changes you need to make?**_
+ Please make sure all associated files are present and consistent with your changes!
+
+ [Concept Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md)
+
+ - [ ] `.docs/hints.md`
+ - [ ] `.docs/instructions.md`
+ - [ ] `.docs/introduction.md`
+ - [ ] `.meta/config.json`
+ - [ ] `.meta/design.md`
+ - [ ] `.meta/exemplar.py` (_exemplar solution_)
+ - [ ] `_test.py` (_test file_)
+ - [ ] `.py` (_stub file_)
+ - [ ] `concepts/../introduction.md`
+ - [ ] `concepts/../about.md`
+ - [ ] `concepts/../links.json`
+ - [ ] `concepts/../.meta/config.json`
+
+
+
+
+
+
+ - id: change-practice-exercise
+ files:
+ - "exercises/practice/**/*"
+ - "exercises/practice/**/.meta/*"
+ - "exercises/practice/**/.docs/*"
+ - "exercises/practice/**/**/*"
+ body: |
+ ---
+
+ #### **`💙 It looks like you are changing/adding files in a Practice Exercise! 💙 `**
+
+ **`Please note:`** generally, changes to existing [practice exercises](https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md) or the addition
+ of new ones are proposed & discussed in [problem-specifications](https://github.com/exercism/problem-specifications) first, so that all tracks can potentially benefit.
+ Once a change is approved there by three maintainers, it becomes available for various language tracks to then implement.
+
+
+
+ ‼️ Did You...
+
+
+
+
+ - [ ] Update & rebase your branch with any (recent) upstream changes.
+ - [ ] Spell and grammar check all prose changes.
+ - [ ] Run [Prettier](https://prettier.io/) on all markdown and JSON files.
+ - (_Optionally_) run [yapf](https://github.com/google/yapf) ([_yapf config_](https://github.com/exercism/python/blob/main/.style.yapf)) to help format your code.
+ - [ ] Run [flake8](http://flake8.pycqa.org/) with [_flake8 config_](https://github.com/exercism/python/blob/main/.flake8) to check general code style standards.
+ - [ ] Run [pylint](https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html) with [_pylint config_](https://github.com/exercism/python/blob/main/pylintrc) to check extended code style standards.
+ - [ ] Use pytest or the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed `example.py`/`exemplar.py`files
+ against their associated test files.
+ - [ ] Similarly, use [pytest](https://docs.pytest.org/en/6.2.x/contents.html) or the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed _**test**_ files.
+ - Check that tests **fail** properly, as well as succeed.
+ (_**e.g.**, make some tests fail on purpose to "test the tests" & failure messages_).
+ - [ ] Double-check all files for proper EOL.
+ - [ ] [Regenerate](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#generating-practice-exercise-documents) exercise documents when you modified or created a `hints.md` file for a practice exercise.
+ - [ ] [Regenerate the test file](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#auto-generated-test-files-and-test-templates) if you modified or created a `JinJa2` template file for a practice exercise.
+ - Run the generated test file result against its `example.py`.
+ - [ ] Run [`configlet-lint`](https://github.com/exercism/configlet#configlet-lint) if the track [config.json](https://github.com/exercism/docs/blob/main/building/tracks/config-json.md), or any other exercise `config.json` has been modified.
+
+
+
+
+
+
+
+
+ ✅️ Have You Checked...
+
+
+
+
+ .
+ _**Are there any additional changes you need to make?**_
+ Please make sure all associated files are present and consistent with your changes!
+
+ [Practice Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md)
+
+ - [ ] `.docs/instructions.md`(**required**)
+ - [ ] `.docs/introduction.md`(_optional_)
+ - [ ] `.docs/introduction.append.md`(_optional_)
+ - [ ] `.docs/instructions.append.md` (_optional_)
+ - [ ] `.docs/hints.md`(_optional_)
+ - [ ] `.meta/config.json` (**required**)
+ - [ ] `.meta/example.py` (**required**)
+ - [ ] `.meta/design.md` (_optional_)
+ - [ ] `.meta/template.j2` (_template for generating tests from canonical data_)
+ - [ ] `.meta/tests.toml` (_do you need to include or exclude any cases?_)
+ - [ ] `_test.py` (_**do you need to regenerate this?**_)
+ - [ ] `.py` (**required**)
+
+
+
+
+
+
diff --git a/.github/stale.yml b/.github/stale.yml
deleted file mode 100644
index d03d161912c..00000000000
--- a/.github/stale.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-# Number of days of inactivity before an issue becomes stale
-daysUntilStale: 21
-# Number of days of inactivity before a stale issue is closed
-daysUntilClose: 7
-# Issues with these labels will never be considered stale
-exemptLabels:
- - discussion
- - pinned
- - epic
- - enhancement
- - beginner friendly
-# Label to use when marking an issue as stale
-staleLabel: abandoned
-# Comment to post when marking an issue as stale. Set to `false` to disable
-markComment: >
- This issue has been automatically marked as `abandoned` because it has not had
- recent activity. It will be closed if no further activity occurs. Thank you
- for your contributions.
-# Comment to post when closing a stale issue. Set to `false` to disable
-closeComment: false
diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml
new file mode 100644
index 00000000000..e853469c6d0
--- /dev/null
+++ b/.github/workflows/ci-workflow.yml
@@ -0,0 +1,73 @@
+# This workflow will install Python dependencies, run tests and lint with a single version of Python
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
+
+name: Exercises check
+
+on:
+ push:
+ branches:
+ - master
+ - main
+ pull_request:
+
+jobs:
+ housekeeping:
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
+
+ - name: Set up Python
+ uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
+ with:
+ python-version: 3.11.2
+
+ - name: Download & Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install flake8 requests
+ git clone https://github.com/exercism/problem-specifications .problem-specifications
+ pip install -r requirements-generator.txt
+
+ # - name: Check readmes
+ # run: |
+ # ./bin/check-readmes.sh
+
+ - name: Generate tests
+ run: |
+ bin/generate_tests.py --verbose -p .problem-specifications --check
+
+ - name: Lint with flake8
+ run: |
+ # stop the build if there are Python syntax errors or undefined names
+ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+ # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
+ flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+
+ - name: Test template status
+ continue-on-error: true
+ run: |
+ ./bin/template_status.py -v -p .problem-specifications
+
+ canonical_sync:
+ runs-on: ubuntu-22.04
+ needs: housekeeping
+ strategy:
+ matrix:
+ python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2]
+ steps:
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
+
+ - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install dataclasses package
+ if: ${{ matrix.python-version == '3.6' }}
+ run: pip install dataclasses
+
+ - name: Install pytest
+ run: pip install pytest~=7.2.2
+
+ - name: Check exercises
+ run: |
+ ./bin/test_exercises.py
diff --git a/.github/workflows/configlet.yml b/.github/workflows/configlet.yml
new file mode 100644
index 00000000000..47eb8754360
--- /dev/null
+++ b/.github/workflows/configlet.yml
@@ -0,0 +1,15 @@
+name: Configlet
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ configlet:
+ uses: exercism/github-actions/.github/workflows/configlet.yml@main
diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml
new file mode 100644
index 00000000000..4f6bff60471
--- /dev/null
+++ b/.github/workflows/issue-commenter.yml
@@ -0,0 +1,24 @@
+name: "Issue Commenter"
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ comment-on-new-issue:
+ runs-on: ubuntu-24.04
+ name: Comments for every NEW issue.
+ steps:
+ - name: Checkout
+ uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
+
+ - name: Read issue-comment.md
+ id: issue-comment
+ uses: juliangruber/read-file-action@b549046febe0fe86f8cb4f93c24e284433f9ab58
+ with:
+ path: .github/issue-comment.md
+
+ - name: Base comment
+ uses: jd-0001/gh-action-comment-on-new-issue@c443e1151cc69b146fd6918cc983ec1bd27ab254
+ with:
+ message: "${{ steps.issue-comment.outputs.content }}"
+ ignore-label: ":anger: prickle"
diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml
new file mode 100644
index 00000000000..812e9129668
--- /dev/null
+++ b/.github/workflows/no-important-files-changed.yml
@@ -0,0 +1,23 @@
+name: No important files changed
+
+on:
+ pull_request_target:
+ types: [opened]
+ branches: [main]
+ paths:
+ - "exercises/concept/**"
+ - "exercises/practice/**"
+ - "!exercises/*/*/.approaches/**"
+ - "!exercises/*/*/.articles/**"
+ - "!exercises/*/*/.docs/**"
+ - "!exercises/*/*/.meta/**"
+
+permissions:
+ pull-requests: write
+
+jobs:
+ check:
+ uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main
+ with:
+ repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }}
+ ref: ${{ github.head_ref }}
diff --git a/.github/workflows/pause-community-contributions.yml b/.github/workflows/pause-community-contributions.yml
new file mode 100644
index 00000000000..d764bfe8b63
--- /dev/null
+++ b/.github/workflows/pause-community-contributions.yml
@@ -0,0 +1,25 @@
+name: Pause Community Contributions
+
+on:
+ issues:
+ types:
+ - opened
+ pull_request_target:
+ types:
+ - opened
+ paths-ignore:
+ - 'exercises/*/*/.approaches/**'
+ - 'exercises/*/*/.articles/**'
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ pause:
+ if: github.repository_owner == 'exercism' # Stops this job from running on forks
+ uses: exercism/github-actions/.github/workflows/community-contributions.yml@main
+ with:
+ forum_category: python
+ secrets:
+ github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }}
diff --git a/.github/workflows/ping-cross-track-maintainers-team.yml b/.github/workflows/ping-cross-track-maintainers-team.yml
new file mode 100644
index 00000000000..b6ec9c5662f
--- /dev/null
+++ b/.github/workflows/ping-cross-track-maintainers-team.yml
@@ -0,0 +1,16 @@
+name: Ping cross-track maintainers team
+
+on:
+ pull_request_target:
+ types:
+ - opened
+
+permissions:
+ pull-requests: write
+
+jobs:
+ ping:
+ if: github.repository_owner == 'exercism' # Stops this job from running on forks
+ uses: exercism/github-actions/.github/workflows/ping-cross-track-maintainers-team.yml@main
+ secrets:
+ github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }}
diff --git a/.github/workflows/pr-commenter.yml b/.github/workflows/pr-commenter.yml
new file mode 100644
index 00000000000..f12714aec38
--- /dev/null
+++ b/.github/workflows/pr-commenter.yml
@@ -0,0 +1,12 @@
+name: "PR Commenter"
+on:
+ - pull_request_target
+
+jobs:
+ pr-comment:
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: exercism/pr-commenter-action@085ef62d2a541a112c3ade1d24deea83665ea186
+ with:
+ github-token: "${{ github.token }}"
+ config-file: ".github/pr-commenter.yml"
\ No newline at end of file
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 00000000000..4a5a9a772f1
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,44 @@
+name: "Close stale issues"
+
+on:
+ schedule:
+ - cron: "30 1 * * *"
+
+jobs:
+ stale:
+ runs-on: ubuntu-24.04
+ steps:
+ - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+ days-before-stale: 21
+ days-before-close: 7
+
+ # Milestones on an issue or a PR exempted from being marked as stale.
+ exempt-milestones: true
+ # Exempt all issues with milestones from being marked as stale.
+ exempt-all-issue-milestones: true
+
+ # Only check issues and PRs with these labels
+ any-of-labels: "x:status/claimed,claimed 🐾,dependencies,do not merge 🚧,duplicate,experimental 🔬,
+ first-timers only 🐣,github_actions,good first issue,good first patch,hacktoberfest 🍁,
+ hacktoberfest-accepted ☑,in-progress 🌿,invalid,python,security 🚨, wip/content-checking ☑,
+ wip/proof-reading 👀,wip/story-writing ✍,wip/style-guideline-checking 📐,spam 🚫,status/draft,
+ status/wontfix 🙅🏽"
+ stale-issue-label: abandoned 🏚
+ stale-issue-message: >
+ This issue has been automatically marked as `abandoned 🏚`
+ because it has not had recent activity. It will be closed if no
+ further activity occurs. Thank you
+ for your contributions.
+ close-issue-message: >
+ Closing stale issue. If this issue is still relevant,
+ please reopen it.
+ stale-pr-label: 'abandoned 🏚'
+ stale-pr-message: >
+ This pull request has been automatically marked as `abandoned 🏚`
+ because it has not had recent activity. It will be closed if no
+ further activity occurs. Thank you for your contributions.
+ close-pr-message: >
+ Closing stale pull request. If you are still working on this,
+ please reopen this pull request.
diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml
new file mode 100644
index 00000000000..e7b99e50487
--- /dev/null
+++ b/.github/workflows/sync-labels.yml
@@ -0,0 +1,19 @@
+name: Tools
+
+on:
+ push:
+ branches:
+ - main
+ paths:
+ - .github/labels.yml
+ - .github/workflows/sync-labels.yml
+ workflow_dispatch:
+ schedule:
+ - cron: 0 0 1 * * # First day of each month
+
+permissions:
+ issues: write
+
+jobs:
+ sync-labels:
+ uses: exercism/github-actions/.github/workflows/labels.yml@main
diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml
new file mode 100644
index 00000000000..97fcf6e5be3
--- /dev/null
+++ b/.github/workflows/test-runner.yml
@@ -0,0 +1,15 @@
+name: Test Runner
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+jobs:
+ test-runner:
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
+ - name: Run test-runner
+ run: docker compose run test-runner
diff --git a/.gitignore b/.gitignore
index edce8452bfc..86cb73d2037 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,5 +7,9 @@ tmp
bin/configlet
bin/configlet.exe
.idea/
+.vscode/
+.mypy_cache/
.cache
+.pytest_cache
__pycache__
+.venv
diff --git a/.style.yapf b/.style.yapf
new file mode 100644
index 00000000000..73e09113443
--- /dev/null
+++ b/.style.yapf
@@ -0,0 +1,401 @@
+[style]
+# Align closing bracket with visual indentation.
+align_closing_bracket_with_visual_indent=True
+
+# Allow dictionary keys to exist on multiple lines. For example:
+#
+# x = {
+# ('this is the first element of a tuple',
+# 'this is the second element of a tuple'):
+# value,
+# }
+allow_multiline_dictionary_keys=False
+
+# Allow lambdas to be formatted on more than one line.
+allow_multiline_lambdas=False
+
+# Allow splitting before a default / named assignment in an argument list.
+allow_split_before_default_or_named_assigns=True
+
+# Allow splits before the dictionary value.
+allow_split_before_dict_value=True
+
+# Let spacing indicate operator precedence. For example:
+#
+# a = 1 * 2 + 3 / 4
+# b = 1 / 2 - 3 * 4
+# c = (1 + 2) * (3 - 4)
+# d = (1 - 2) / (3 + 4)
+# e = 1 * 2 - 3
+# f = 1 + 2 + 3 + 4
+#
+# will be formatted as follows to indicate precedence:
+#
+# a = 1*2 + 3/4
+# b = 1/2 - 3*4
+# c = (1+2) * (3-4)
+# d = (1-2) / (3+4)
+# e = 1*2 - 3
+# f = 1 + 2 + 3 + 4
+#
+arithmetic_precedence_indication=False
+
+# Number of blank lines surrounding top-level function and class
+# definitions.
+blank_lines_around_top_level_definition=2
+
+# Number of blank lines between top-level imports and variable
+# definitions.
+blank_lines_between_top_level_imports_and_variables=1
+
+# Insert a blank line before a class-level docstring.
+blank_line_before_class_docstring=False
+
+# Insert a blank line before a module docstring.
+blank_line_before_module_docstring=False
+
+# Insert a blank line before a 'def' or 'class' immediately nested
+# within another 'def' or 'class'. For example:
+#
+# class Foo:
+# # <------ this blank line
+# def method():
+# ...
+blank_line_before_nested_class_or_def=True
+
+# Do not split consecutive brackets. Only relevant when
+# dedent_closing_brackets is set. For example:
+#
+# call_func_that_takes_a_dict(
+# {
+# 'key1': 'value1',
+# 'key2': 'value2',
+# }
+# )
+#
+# would reformat to:
+#
+# call_func_that_takes_a_dict({
+# 'key1': 'value1',
+# 'key2': 'value2',
+# })
+coalesce_brackets=True
+
+# The column limit.
+column_limit=120
+
+# The style for continuation alignment. Possible values are:
+#
+# - SPACE: Use spaces for continuation alignment. This is default behavior.
+# - FIXED: Use fixed number (CONTINUATION_INDENT_WIDTH) of columns
+# (ie: CONTINUATION_INDENT_WIDTH/INDENT_WIDTH tabs or
+# CONTINUATION_INDENT_WIDTH spaces) for continuation alignment.
+# - VALIGN-RIGHT: Vertically align continuation lines to multiple of
+# INDENT_WIDTH columns. Slightly right (one tab or a few spaces) if
+# cannot vertically align continuation lines with indent characters.
+continuation_align_style=SPACE
+
+# Indent width used for line continuations.
+continuation_indent_width=4
+
+# Put closing brackets on a separate line, dedented, if the bracketed
+# expression can't fit in a single line. Applies to all kinds of brackets,
+# including function definitions and calls. For example:
+#
+# config = {
+# 'key1': 'value1',
+# 'key2': 'value2',
+# } # <--- this bracket is dedented and on a separate line
+#
+# time_series = self.remote_client.query_entity_counters(
+# entity='dev3246.region1',
+# key='dns.query_latency_tcp',
+# transform=Transformation.AVERAGE(window=timedelta(seconds=60)),
+# start_ts=now()-timedelta(days=3),
+# end_ts=now(),
+# ) # <--- this bracket is dedented and on a separate line
+dedent_closing_brackets=False
+
+# Disable the heuristic which places each list element on a separate line
+# if the list is comma-terminated.
+disable_ending_comma_heuristic=False
+
+# Place each dictionary entry onto its own line.
+each_dict_entry_on_separate_line=False
+
+# Require multiline dictionary even if it would normally fit on one line.
+# For example:
+#
+# config = {
+# 'key1': 'value1'
+# }
+force_multiline_dict=False
+
+# The regex (_regular expression_) for an i18n (_internationalization_) comment. The presence of this comment stops
+# reformatting of that line, because the comments are required to be
+# next to the string they translate.
+i18n_comment=#\..*
+
+
+# The i18n (internationalization) function calls names. The presence of this function stops
+# re-formatting on that line, because the string it has cannot be moved
+# away from the internationalization comment.
+i18n_function_call=N_, _
+
+# Indent blank lines.
+indent_blank_lines=False
+
+# Put closing brackets on a separate line, indented, if the bracketed
+# expression can't fit in a single line. This applies to all kinds of
+# brackets, including function definitions and calls. For example:
+#
+# config = {
+# 'key1': 'value1',
+# 'key2': 'value2',
+# } # <--- this bracket is indented and on a separate line
+#
+# time_series = self.remote_client.query_entity_counters(
+# entity='dev3246.region1',
+# key='dns.query_latency_tcp',
+# transform=Transformation.AVERAGE(window=timedelta(seconds=60)),
+# start_ts=now()-timedelta(days=3),
+# end_ts=now(),
+# ) # <--- this bracket is indented and on a separate line
+#
+# Exercism strongly diverges from this Google-recommended pattern, and favors
+# closing brackets on the same line as the end of the expression.
+indent_closing_brackets=False
+
+# Indent the dictionary value if it cannot fit on the same line as the
+# dictionary key. For example:
+#
+# config = {
+# 'key1':
+# 'value1',
+# 'key2': value1 +
+# value2,
+# }
+indent_dictionary_value=True
+
+# The number of columns to use for indentation.
+indent_width=4
+
+# Join short lines into one line. E.g., single line 'if' statements.
+join_multiple_lines=True
+
+# Do not include spaces around selected binary operators. For example:
+#
+# 1 + 2 * 3 - 4 / 5
+#
+# will be formatted as follows when configured with "*,/":
+#
+# 1 + 2*3 - 4/5
+no_spaces_around_selected_binary_operators=
+
+# Use spaces around default or named assigns.
+spaces_around_default_or_named_assign=False
+
+# Adds a space after the opening '{' and before the ending '}' dict delimiters.
+#
+# {1: 2}
+#
+# will be formatted as:
+#
+# { 1: 2 }
+spaces_around_dict_delimiters=False
+
+# Adds a space after the opening '[' and before the ending ']' list delimiters.
+#
+# [1, 2]
+#
+# will be formatted as:
+#
+# [ 1, 2 ]
+spaces_around_list_delimiters=False
+
+# Use spaces around the power operator.
+spaces_around_power_operator=False
+
+# Use spaces around the subscript / slice operator. For example:
+#
+# my_list[1 : 10 : 2]
+spaces_around_subscript_colon=False
+
+# Adds a space after the opening '(' and before the ending ')' tuple delimiters.
+#
+# (1, 2, 3)
+#
+# will be formatted as:
+#
+# ( 1, 2, 3 )
+spaces_around_tuple_delimiters=False
+
+# The number of spaces required before a trailing comment.
+# This can be a single value (representing the number of spaces
+# before each trailing comment) or list of values (representing
+# alignment column values; trailing comments within a block will
+# be aligned to the first column value that is greater than the maximum
+# line length within the block). For example:
+#
+# With spaces_before_comment=5:
+#
+# 1 + 1 # Adding values
+#
+# will be formatted as:
+#
+# 1 + 1 # Adding values <-- 5 spaces between the end of the statement and comment
+#
+# With spaces_before_comment=15, 20:
+#
+# 1 + 1 # Adding values
+# two + two # More adding
+#
+# longer_statement # This is a longer statement
+# short # This is a shorter statement
+#
+# a_very_long_statement_that_extends_beyond_the_final_column # Comment
+# short # This is a shorter statement
+#
+# will be formatted as:
+#
+# 1 + 1 # Adding values <-- end of line comments in block aligned to col 15
+# two + two # More adding
+#
+# longer_statement # This is a longer statement <-- end of line comments in block aligned to col 20
+# short # This is a shorter statement
+#
+# a_very_long_statement_that_extends_beyond_the_final_column # Comment <-- the end of line comments are aligned based on the line length
+# short # This is a shorter statement
+#
+spaces_before_comment=5
+
+# Insert a space between the ending comma and closing bracket of a list,
+# etc.
+space_between_ending_comma_and_closing_bracket=False
+
+# Use spaces inside brackets, braces, and parentheses. For example:
+#
+# method_call( 1 )
+# my_dict[ 3 ][ 1 ][ get_index( *args, **kwargs ) ]
+# my_set = { 1, 2, 3 }
+space_inside_brackets=False
+
+# Split before arguments
+split_all_comma_separated_values=False
+
+# Split before arguments, but do not split all subexpressions recursively
+# (unless needed).
+split_all_top_level_comma_separated_values=True
+
+# Split before arguments if the argument list is terminated by a
+# comma.
+split_arguments_when_comma_terminated=False
+
+# Set to True to prefer splitting before '+', '-', '*', '/', '//', or '@'
+# rather than after.
+split_before_arithmetic_operator=False
+
+# Set to True to prefer splitting before '&', '|' or '^' rather than
+# after.
+split_before_bitwise_operator=True
+
+# Split before the closing bracket if a list or dict literal doesn't fit on
+# a single line.
+split_before_closing_bracket=False
+
+# Split before a dictionary or set generator (comp_for). For example, note
+# the split before the 'for':
+#
+# foo = {
+# variable: 'Hello world, have a nice day!'
+# for variable in bar if variable != 42
+# }
+split_before_dict_set_generator=True
+
+# Split before the '.' if we need to split a longer expression:
+#
+# foo = ('This is a really long string: {}, {}, {}, {}'.format(a, b, c, d))
+#
+# would reformat to something like:
+#
+# foo = ('This is a really long string: {}, {}, {}, {}'
+# .format(a, b, c, d))
+split_before_dot=False
+
+# Split after the opening paren which surrounds an expression if it doesn't
+# fit on a single line.
+split_before_expression_after_opening_paren=True
+
+# If an argument / parameter list is going to be split, then split before
+# the first argument.
+split_before_first_argument=True
+
+# Set to True to prefer splitting before 'and' or 'or' rather than
+# after.
+split_before_logical_operator=False
+
+# Split named assignments onto individual lines.
+split_before_named_assigns=True
+
+# Set to True to split list comprehensions and generators that have
+# non-trivial expressions and multiple clauses before each of these
+# clauses. For example:
+#
+# result = [
+# a_long_var + 100 for a_long_var in xrange(1000)
+# if a_long_var % 10]
+#
+# would reformat to something like:
+#
+# result = [
+# a_long_var + 100
+# for a_long_var in xrange(1000)
+# if a_long_var % 10]
+split_complex_comprehension=True
+
+# The penalty for splitting right after the opening bracket.
+split_penalty_after_opening_bracket=200
+
+# The penalty for splitting the line after a unary operator.
+split_penalty_after_unary_operator=10000
+
+# The penalty of splitting the line around the '+', '-', '*', '/', '//',
+# ``%``, and '@' operators.
+split_penalty_arithmetic_operator=300
+
+# The penalty for splitting right before an if expression.
+split_penalty_before_if_expr=0
+
+# The penalty of splitting the line around the '&', '|', and '^'
+# operators.
+split_penalty_bitwise_operator=400
+
+# The penalty for splitting a list comprehension or generator
+# expression.
+split_penalty_comprehension=2100
+
+# The penalty for characters over the column limit.
+split_penalty_excess_character=7000
+
+# The penalty incurred by adding a line split to the unwrapped line. The
+# more line splits added the higher the penalty.
+split_penalty_for_added_line_split=30
+
+# The penalty of splitting a list of "import as" names. For example:
+#
+# from a_very_long_or_indented_module_name_yada_yad import (long_argument_1,
+# long_argument_2,
+# long_argument_3)
+#
+# would reformat to something like:
+#
+# from a_very_long_or_indented_module_name_yada_yad import (
+# long_argument_1, long_argument_2, long_argument_3)
+split_penalty_import_names=0
+
+# The penalty of splitting the line around the 'and' and 'or'
+# operators.
+split_penalty_logical_operator=300
+
+# Use the Tab character for indentation.
+use_tabs=False
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 9d703ce3b21..00000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-sudo: false
-
-language: python
-
-python:
- - 2.7
- - 3.3
- - 3.4
- - 3.5
- - 3.6
- - nightly
-
-matrix:
- allow_failures:
- - python: nightly
-
-install:
- - pip install -r requirements-travis.txt
-
-before_script:
- - flake8
- - ./bin/fetch-configlet
- - ./bin/configlet lint .
-
-script:
- - ./test/check-exercises.py
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000000..3f7813de10a
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,93 @@
+# Code of Conduct
+
+## Introduction
+
+Exercism is a platform centered around empathetic conversation.
+We have a low tolerance for communication that makes anyone feel unwelcome, unsupported, insulted or discriminated against.
+
+## Seen or experienced something uncomfortable?
+
+If you see or experience abuse, harassment, discrimination, or feel unsafe or upset, please email [abuse@exercism.org](mailto:abuse@exercism.org?subject=%5BCoC%5D) and include \[CoC\] in the subject line.
+We will follow up with you as a priority.
+
+## Enforcement
+
+We actively monitor for Code of Conduct (CoC) violations and take any reports of violations extremely seriously.
+We have banned contributors, mentors and users due to violations.
+
+After we receive a report of a CoC violation, we view that person's conversation history on Exercism and related communication channels and attempt to understand whether someone has deliberately broken the CoC, or accidentally crossed a line.
+We generally reach out to the person who has been reported to discuss any concerns we have and warn them that repeated violations will result in a ban.
+Sometimes we decide that no violation has occurred and that no action is required and sometimes we will also ban people on a first offense.
+We strive to be fair, but will err on the side of protecting the culture of our community.
+
+Exercism's leadership reserve the right to take whatever action they feel appropriate with regards to CoC violations.
+
+## The simple version
+
+- Be empathetic
+- Be welcoming
+- Be kind
+- Be honest
+- Be supportive
+- Be polite
+
+## The details
+
+Exercism should be a safe place for everybody regardless of
+
+- Gender, gender identity or gender expression
+- Sexual orientation
+- Disability
+- Physical appearance (including but not limited to body size)
+- Race
+- Age
+- Religion
+- Anything else you can think of
+
+As someone who is part of this community, you agree that:
+
+- We are collectively and individually committed to safety and inclusivity
+- We have zero tolerance for abuse, harassment, or discrimination
+- We respect people’s boundaries and identities
+- We refrain from using language that can be considered offensive or oppressive (systemically or otherwise), eg. sexist, racist, homophobic, transphobic, ableist, classist, etc.
+ - this includes (but is not limited to) various slurs.
+- We avoid using offensive topics as a form of humor
+
+We actively work towards:
+
+- Being a safe community
+- Cultivating a network of support & encouragement for each other
+- Encouraging responsible and varied forms of expression
+
+We condemn:
+
+- Stalking, doxxing, or publishing private information
+- Violence, threats of violence or violent language
+- Anything that compromises people’s safety
+- Conduct or speech which might be considered sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory or offensive in nature
+- The use of unwelcome, suggestive, derogatory or inappropriate nicknames or terms
+- Disrespect towards others (jokes, innuendo, dismissive attitudes) and towards differences of opinion
+- Intimidation or harassment (online or in-person).
+ Please read the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md) for how we interpret harassment
+- Inappropriate attention or contact
+- Not understanding the differences between constructive criticism and disparagement
+
+These things are NOT OK.
+
+Be aware of how your actions affect others.
+If it makes someone uncomfortable, stop.
+
+If you say something that is found offensive, and you are called out on it, try to:
+
+- Listen without interruption
+- Believe what the person is saying & do not attempt to disqualify what they have to say
+- Ask for tips / help with avoiding making the offense in the future
+- Apologize and ask forgiveness
+
+## History
+
+This policy was initially adopted from the Front-end London Slack community and has been modified since.
+A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md).
+
+_This policy is a "living" document, and subject to refinement and expansion in the future.
+This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Discord, Forum, Twitter, email) and any other Exercism entity or event._
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000000..d9c30d85e0a
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,428 @@
+
+
+
+
Contributing
+
+ [](https://forum.exercism.org)
+ [](https://exercism.org)
+ [](https://exercism.org/blog/freeing-our-maintainers)
+ [](https://github.com/exercism/python/actions?query=workflow%3A%22Exercises+check%22)
+
+
+> [!IMPORTANT]
+> We are not accepting community contributions at this time.
+>
+>
+>
+>
+> We love our community. We're grateful you are interested in improving the Python track.
+> But our maintainers are **not accepting community contributions at this time.**
+> If you would like to discuss possible future changes, please open a [thread on the forum](https://forum.exercism.org/).
+>
+> This [community blog post](https://exercism.org/blog/freeing-our-maintainers) contains more details.
+>
+>
+>
+
+
+
+
+Hi. 👋🏽 👋 **We are happy you are here.** 🎉 🌟
+
+**`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website].
+This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students.
+
+🌟 Track exercises support Python `3.7` - `3.11.5`.
+Exceptions to this support are noted where they occur.
+🌟 Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.2`.
+
+Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree 🌴 .
+Concept exercises are constrained to a small set of language or syntax features.
+Practice exercises are open-ended, and can be used to practice concepts learned, try out new techniques, and _play_. These two exercise groupings can be found in the track [config.json][config-json], and under the `python/exercises` directory.
+
+
+
+## 🐛 **Did you find a bug?**
+
+It is not uncommon to discover typos, confusing directions, or incorrect implementations of certain tests or code examples. Or you might have a great suggestion for a hint to aid students ( 💙 ), see optimizations for exemplar or test code, find missing test cases to add, or want to correct factual and/or logical errors. Or maybe you have a great idea 💡 for an exercise or feature ( 💙 ).
+
+_Our track is always a work in progress!_ 🌟🌟
+While contributions are paused, we ask that you [**open a thread in our community forum**](https://forum.exercism.org) to let us know what you have found/suggest.
+
+
+
+## 🚧 **Did you write a patch that fixes a bug?**
+
+Our maintainers are not accepting community contributions at this time.
+
+Until the pause on contributions ends, all PRs from the larger community will be **automatically closed** with a note.
+We ask that you [**open a thread in our community forum**](https://forum.exercism.org) to discuss any potential changes. Changes may or may not be approved, depending on the forum discussion.
+
+Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for additional details.
+
+
+We're leaving the track contributing docs below for our long-term collaborators and maintainers.
+
+
+
+ Python Track Contributing Docs
+
+
+
In General
+
+
+- Maintainers are happy to review your work and help troubleshoot with you. 💛 💙
+ - Requests are reviewed as soon as is practical/possible.
+ - (❗ ) Reviewers may be in a different timezone ⌚ , or tied up 🧶 with other tasks.
+ - **Please wait at least 72 hours before pinging.**
+- If you need help, comment in the Pull Request/issue. 🙋🏽♀️
+- If you would like in-progress feedback/discussion, please mark your Pull Request as a **`[draft]`**
+- Pull Requests should be focused around a single exercise, issue, or change.
+- Pull Request titles and descriptions should make clear **what** has changed and **why**.
+ - Please link 🔗 to any related issues the PR addresses.
+- 📛 [ Open an issue ][open-an-issue]📛 and discuss it with 🧰 maintainers _**before**_:
+ - creating a Pull Request making significant or breaking changes.
+ - for changes across multiple exercises, even if they are typos or small.
+ - anything that is going to require doing a lot of work (_on your part or the maintainers part_).
+- Follow coding standards found in [PEP8][pep8] (["For Humans" version here][pep8-for-humans]).
+- All files should have a proper [EOL][eol]. This means one carriage return at the end of the final line of text files.
+- Otherwise, watch out ⚠️ for trailing spaces, extra blank lines, extra spaces, and spaces in blank lines.
+- Continuous Integration is going to run **a lot** of checks. Pay attention to failures & try to understand and fix them.
+
+
+
+
+ ⚠️ Pre-Commit Checklist ⚠️
+
+
+1. Run [`configlet-lint`][configlet-lint] if the track [config.json](config-json) has been modified.
+2. Run [Prettier][prettier] on all markdown files.
+3. (_Optionally_) run [yapf][yapf] ([_config file_][.style.yapf]) to help format your code, and give you a head start on making the linters happy.
+4. Run [flake8][flake8] ([_config file_][.flake8]) & [pylint][pylint] ([_config file_][pylintrc]) to ensure all Python code files conform to general code style standards.
+5. Run `test/check-exercises.py [EXERCISE]` to check if your test changes function correctly.
+6. Run the `example.py` or `exemplar.py` file against the exercise test file to ensure that it passes without error.
+7. If you modified or created a `hints.md` file for a practice exercise, [regenerate](#generating-practice-exercise-documents) it.
+
+
+
+
+
+
+
Prose Writing Style & Standards
+
+
+
+Non-code content (_exercise introductions & instructions, hints, concept write-ups, documentation etc._) should be written in [American English][american-english]. We strive to watch [the words we use][the-words-that-we-use].
+
+When a word or phrase usage is contested | ambiguous, we default to what is best understood by our international community of learners, even if it "sounds a little weird" to a "native" American English speaker.
+
+Our documents use [Markdown][markdown-language], with certain [alterations][exercism-markdown-widgets] & [additions][exercism-internal-linking]. Here is our full [Markdown Specification][exercism-markdown-specification]. 📐 We format/lint our Markdown with [Prettier][prettier]. ✨
+
+
+
+
Coding Standards
+
+
+
+1. We follow [PEP8][pep8] (["For Humans" version here][pep8-for-humans]).
+ In particular, we (mostly) follow the [Google flavor][google-coding-style] of PEP8.
+2. We use [flake8][flake8] to help us format Python code nicely.
+ Our `flake8` config file is [.flake8][.flake8] in the top level of this repo.
+3. We use [pylint][pylint] to catch what `flake8` doesn't.
+ Our `pylint` config file is [pylintrc][pylintrc] in the top level of this repo.
+4. We use [yapf][yapf] to auto-format our python files.
+ Our `.style.yapf` config file is [.style.yapf][.style.yapf] in the top level of this repo.
+
+
+
+
+ General Code Style Summary
+
+
+- _**spaces**_, never `Tabs`
+- **4 space** indentation
+- **120 character per line limit** (_as opposed to the default limit of 79_)
+- Variable, function, and method names should be `lower_case_with_underscores` (_aka "snake case"_)
+- Classes should be named in `TitleCase` (_aka "camel case"_)
+- **No single letter variable names** outside of a `lambda`. This includes loop variables and comprehensions.
+- Refrain from putting `list`, `tuple`, `set`, or `dict` members on their own lines.
+ Fit as many data members as can be easily read on one line, before wrapping to a second.
+- If a data structure spreads to more than one line and a break (_for clarity_) is needed, prefer breaking after the opening bracket.
+- Avoid putting closing brackets on their own lines. Prefer closing a bracket right after the last element.
+- Use **`'`** and not **`"`** as the quote character by default.
+- Use **`"""`** for docstrings.
+- Prefer [implicit line joining][implicit-line-joining] for long strings.
+- Prefer enclosing imports in **`()`**, and putting each on their own line when importing multiple methods.
+- Two lines between `Classes`, one line between `functions`. Other vertical whitespace as needed to help readability.
+- Always use an **`EOL`** to end a file.
+
+
+
+
+ Test File Style (concept exercises )
+
+
+- [Unittest.TestCase][unittest] syntax, with [PyTest][pytest] as a test runner.
+ - We are transitioning to using more PyTest features/syntax, but are leaving `Unittest` syntax in place where possible.
+ - Always check with a maintainer before introducing a PyTest feature into your tests.
+- Test **Classes** should be titled `Test`. **e.g.** `class CardGamesTest(unittest.TestCase):`
+- Test method names should begin with `test_`. Try to make test case names descriptive but not too long.
+- Favor [_parameterizing_][distinguishing-test-iterations] tests that only vary input data. Use [unittest.TestCase.subTest][subtest] for parameterization.
+ - An [example from Guido's Gorgeous Lasagna][guidos-gorgeous-lasagna-testfile].
+ - A second [example from Card Games][card-games-testfile].
+- Avoid excessive line breaks or indentation - particularly in parameterized tests.
+ - Excessive breaks & indentation within the `for` loops cause issues when formatting the test code for display on the website.
+- Use [`enumerate()`][enumerate] where possible when indexes are needed. See [Card Games][card-games-testfile] for example usage.
+- Favor using names like `inputs`, `data`, `input_data`, `test_data`, or `test_case_data` for test inputs.
+- Favor using names like `results`, `expected`, `result_data`, `expected_data`, or `expected_results` for test outcomes.
+- Favor putting the assert failure message outside of `self.assert()`. Name it `failure_msg`. See [Card Games][card-games-testfile] for example usage.
+- Favor `f-strings` for dynamic failure messages. Please make your error messages as relevant and human-readable as possible.
+- We relate test cases to **task number** via a custom [PyTest Marker][pytestmark].
+ - These take the form of `@pytest.mark.task(taskno=)`. See [Guido's Gorgeous Lasagna][guidos-gorgeous-lasagna-testfile] for an example.
+- We prefer **test data files** when test inputs/outputs are verbose.
+ - These should be named with `_data` or `_test_data` at the end of the filename, and saved alongside the test case file.
+ - See the [Cater-Waiter][cater-waiter] exercise directory for an example of this setup.
+ - **Test data files** need to be added under an `editor` key within [`config.json "files"`][exercise-config-json].
+ - Check with a maintainer if you have questions or issues, or need help with an exercise `config.json`.
+- For new test files going forward, omit `if __name__ == "__main__": unittest.main()`.
+- Lint with both `flake8` and `pylint`.
+ - Both linters are known to toss false-positives for some testing patterns.
+ - Where necessary, deploy the [`#noqa`][flake8-noqa] or [`#pylint disable=`][pylint-disable-check] comments to suppress false-positive warnings. - See **line 16** of [Guido's Gorgeous Lasagna][guidos-gorgeous-lasagna-testfile] test file for an example of an override.
+
+
+
+
+If you have any questions or issues, don't hesitate to ask the maintainers -- they're always happy to help 💛 💙
+
+Some of our code is old and does not (yet) conform to all these standards.
+_We know it, and trust us, we are working on fixing it._ But if you see 👀 something, 👄 say something. It'll motivate us to fix it! 🌈
+
+
+
+
+
Language Versions
+
+
+
+This track officially supports Python `3.7 - 3.11.2` for students completing exercises.
+The track `test runner`, `analyzer`, and `representer` run in docker on `python:3.11.2-slim`.
+
+Although the majority of test cases are written using `unittest.TestCase`,
+
+- All exercises should be written for compatibility with Python `3.7` - `3.11.2`.
+- Version backward _incompatibility_ (_e.g_ an exercise using features introduced in `3.8`, `3.9`, or `3.10`) should be clearly noted in any exercise hints, links, introductions or other notes.
+
+- Here is an example of how the Python documentation handles [version-tagged 🏷 ][version-tagged-language-features] feature introduction.
+
+- _Most_ exercises will work with Python `3.6+`, and _many_ are compatible with Python `2.7+`. Please do not change existing exercises to add new language features without consulting with a maintainer first. We 💛 💙 modern Python, but we _also_ want to avoid student confusion when it comes to which Python versions support brand-new features.
+
+* All test suites and example solutions must work in all Python versions that we currently support. When in doubt about a feature, please check with maintainers.
+
+
+
+
+
A Little More on Exercises
+
+
+
+- Each exercise must be self-contained. Please do not use or reference files that reside outside the given exercise directory. "Outside" files will not be included if a student fetches the exercise via the CLI.
+
+- Each exercise/problem should include a complete test suite, an example/exemplar solution, and a stub file ready for student implementation.
+
+- For specifications, refer to [Concept Exercise Anatomy][concept-exercise-anatomy], or [Practice Exercise Anatomy][practice-exercise-anatomy] depending on which type of exercise you are contributing to.
+
+- **Practice exercise**, descriptions and instructions come from a centralized, cross-track [problem specifications][problem-specifications] repository.
+
+ - Any updates or changes need to be proposed/approved in `problem-specifications` first.
+ - If Python-specific changes become necessary, they need to be appended to the canonical instructions by creating a `instructions.append.md` file in this (`exercism/Python`) repository.
+
+- Practice Exercise **Test Suits** for most practice exercises are similarly [auto-generated](#auto-generated-files) from data in [problem specifications][problem-specifications].
+
+ - Any changes to them need to be proposed/discussed in the `problem-specifications` repository and approved by **3 track maintainers**, since changes could potentially affect many (_or all_) exercism language tracks.
+ - If Python-specific test changes become necessary, they can be appended to the exercise `tests.toml` file.
+ - 📛 [ **Please file an issue**][open-an-issue] 📛 and check with maintainers before adding any Python-specific tests.
+
+
+
+
+
+ ✅ Concept Exercise Checklist
+
+
+
+ - [ ] `.docs/hints.md`
+ - [ ] `.docs/instructions.md`
+ - [ ] `.docs/introduction.md`
+ - [ ] `.meta/config.json`
+ - [ ] `.meta/design.md`
+ - [ ] `.meta/exemplar.py` (_exemplar solution_)
+ - [ ] `_test.py` (_test file_)
+ - [ ] `.py` (_stub file_)
+ - [ ] `concepts/../introduction.md`
+ - [ ] `concepts/../about.md`
+ - [ ] `concepts/../links.json`
+ - [ ] `concepts/../.meta/config.json`
+
+
+
+
+
+ ✅ Practice Exercise Checklist
+
+
+
+ - [ ] `.docs/instructions.md`(**required**)
+ - [ ] `.docs/introduction.md`(_optional_)
+ - [ ] `.docs/introduction.append.md`(_optional_)
+ - [ ] `.docs/instructions.append.md` (_optional_)
+ - [ ] `.docs/hints.md`(_optional_)
+ - [ ] `.meta/config.json` (**required**)
+ - [ ] `.meta/example.py` (**required**)
+ - [ ] `.meta/design.md` (_optional_)
+ - [ ] `.meta/template.j2` (_template for generating tests from canonical data_)
+ - [ ] `.meta/tests.toml` (_tests configuration from canonical data_)
+ - [ ] `_test.py` (_**auto-generated from canonical data**_)
+ - [ ] `.py` (**required**)
+
+
+
+
+
+
External Libraries and Dependencies
+
+
+
+Our tooling (_runners, analyzers and representers_) runs in isolated containers within the exercism website. Because of this, **exercises cannot rely on third-party or external libraries.** Any library needed for an exercise or exercise tests must be incorporated as part of the tooling build, and noted for students who are using the CLI to solve problems locally.
+
+If your exercise depends on a third-party library (_aka not part of standard Python_), please consult with maintainers about it. We may or may not be able to accommodate the package.
+
+
+
+
Auto-Generated Test Files and Test Templates
+
+
+
+[**Practice exercises**][practice-exercise-files] inherit their definitions from the [problem-specifications][problem-specifications] repository in the form of _description files_. Exercise introductions, instructions and (_in the case of **many**, but not **all**_) test files are then machine-generated for each language track.
+
+Changes to practice exercise _specifications_ should be raised/PR'd in [problem-specifications][problem-specifications] and approved by **3 track maintainers**. After an exercise change has gone through that process, related documents and tests for the Python track will need to be [re-generated](#generating-practice-exercise-documents) via [configlet][configlet]. Configlet is also used as part of the track CI, essential track and exercise linting, and other verification tasks.
+
+If a practice exercise has an auto-generated `_test.py` file, there will be a `.meta/template.j2` and a `.meta/tests.toml` file in the exercise directory. If an exercise implements Python track-specific tests, there may be a `.meta/additional_tests.json` to define them. These `additional_tests.json` files will automatically be included in test generation.
+
+_Exercise Structure with Auto-Generated Test Files_
+
+```Bash
+[/
+├── .docs
+│ └── instructions.md
+├── .meta
+│ ├── config.json
+│ ├── example.py
+│ ├── template.j2
+│ └── tests.toml
+├── .py #stub file
+└──
+
+Practice exercise `_test.py` files are generated/regenerated via the [Python Track Test Generator][python-track-test-generator].
+Please reach out to a maintainer if you need any help with the process.
+
+
+
+
Implementing Practice Exercise Tests
+
+
+If an unimplemented exercise has a `canonical-data.json` file in the [problem-specifications] repository, a generation template must be created. See the Python track [test generator documentation][python-track-test-generator] for more information.
+
+If an unimplemented exercise does not have a `canonical-data.json` file, the test file must be written manually.
+
+
+
+
Implementing Practice Exercise Example Solutions
+
+
+**Example solution files serve two purposes only:**
+
+1. Verification of the tests
+2. Example implementation for mentor/student reference
+
+Unlike `concept` exercise, practice exercise `example.py` files are **NOT** intended as as a "best practice" or "standard".
+They are provided as proof that there is an acceptable and testable answer to the practice exercise.
+
+
+
+
Implementing Track-specific Practice Exercises
+
+
+Implementing Track-specific Practice Exercises is similar to implementing a `canonical` exercise that has no `canonical-data.json`. But in addition to the tests, the exercise documents (_instructions, etc._) will also need to be written manually. Carefully follow the structure of generated exercise documents and the [exercism practice exercise specification][practice-exercises].
+
+
+
+
Generating Practice Exercise Documents
+
+You will need
+
+1. A local clone of the [problem-specifications] repository.
+2. [configlet]
+
+For Individual Exercises
+
+```bash
+configlet generate --spec-path path/to/problem/specifications --only example-exercise
+```
+
+For all Practice Exercises
+
+```bash
+configlet generate --spec-path path/to/problem/specifications
+```
+
+
+
+
+
+
+[.flake8]: https://github.com/exercism/python/blob/main/.flake8
+[.style.yapf]: https://github.com/exercism/python/blob/main/.style.yapf
+[american-english]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md
+[card-games-testfile]: https://github.com/exercism/python/blob/main/exercises/concept/card-games/lists_test.py
+[cater-waiter]: https://github.com/exercism/python/tree/main/exercises/concept/cater-waiter
+[concept-exercise-anatomy]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md
+[config-json]: https://github.com/exercism/javascript/blob/main/config.json
+[configlet-lint]: https://github.com/exercism/configlet#configlet-lint
+[configlet]: https://github.com/exercism/docs/blob/main/building/configlet/generating-documents.md
+[distinguishing-test-iterations]: https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests
+[enumerate]: https://docs.python.org/3/library/functions.html#enumerate
+[eol]: https://en.wikipedia.org/wiki/Newline
+[exercise-config-json]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md#full-example
+[exercism-internal-linking]: https://github.com/exercism/docs/blob/main/building/markdown/internal-linking.md
+[exercism-markdown-specification]: https://github.com/exercism/docs/blob/main/building/markdown/markdown.md
+[exercism-markdown-widgets]: https://github.com/exercism/docs/blob/main/building/markdown/widgets.md
+[exercism-website]: https://exercism.org/
+[flake8-noqa]: https://flake8.pycqa.org/en/3.1.1/user/ignoring-errors.html#in-line-ignoring-errors
+[flake8]: http://flake8.pycqa.org/
+[google-coding-style]: https://google.github.io/styleguide/pyguide.html
+[guidos-gorgeous-lasagna-testfile]: https://github.com/exercism/python/blob/main/exercises/concept/guidos-gorgeous-lasagna/lasagna_test.py
+[implicit-line-joining]: https://google.github.io/styleguide/pyguide.html#32-line-length
+[markdown-language]: https://guides.github.com/pdfs/markdown-cheatsheet-online.pdf
+[open-an-issue]: https://github.com/exercism/python/issues/new/choose
+[pep8-for-humans]: https://pep8.org/
+[pep8]: https://www.python.org/dev/peps/pep-0008/
+[practice-exercise-anatomy]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md
+[practice-exercise-files]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md#exercise-files
+[practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md
+[prettier]: https://prettier.io/
+[problem-specifications]: https://github.com/exercism/problem-specifications
+[pylint-disable-check]: https://pylint.pycqa.org/en/latest/user_guide/message-control.html#block-disables
+[pylint]: https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html
+[pylintrc]: https://github.com/exercism/python/blob/main/pylintrc
+[pytest]: https://docs.pytest.org/en/6.2.x/contents.html
+[pytestmark]: https://docs.pytest.org/en/6.2.x/example/markers.html
+[python-syllabus]: https://exercism.org/tracks/python/concepts
+[python-track-test-generator]: https://github.com/exercism/python/blob/main/docs/GENERATOR.md
+[subtest]: https://docs.python.org/3/library/unittest.html#unittest.TestCase.subTest
+[the-words-that-we-use]: https://github.com/exercism/docs/blob/main/community/good-member/words.md
+[unittest]: https://docs.python.org/3/library/unittest.html#unittest.TestCase
+[version-tagged-language-features]: https://docs.python.org/3/library/stdtypes.html#dict.popitem
+[yapf]: https://github.com/google/yapf
diff --git a/LICENSE b/LICENSE
index eee993a55dc..90e73be03b5 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2017 Exercism, Inc
+Copyright (c) 2021 Exercism
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 873c8141349..f3d083aab42 100644
--- a/README.md
+++ b/README.md
@@ -1,79 +1,124 @@
-# Exercism Python Track
+
-[](https://travis-ci.org/exercism/python) [](https://pyup.io/repos/github/exercism/python/)
-[](https://gitter.im/exercism/python?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
+
Exercism Python Track
-Exercism exercises in Python
+ [](https://forum.exercism.org)
+ [](https://exercism.org)
+ [](https://exercism.org/blog/freeing-our-maintainers)
+ [](https://github.com/exercism/python/actions?query=workflow%3A%22Exercises+check%22)
+
-## Contributing Guide
+> [!IMPORTANT]
+> We are not accepting community contributions at this time.
+>
+>
+>
+>
+> We love our community. We're grateful you are interested in improving the Python track.
+> But our maintainers are **not accepting community contributions at this time.**
+> If you would like to suggest a change / discuss an issue, please open a [thread on the forum](https://forum.exercism.org/).
+>
+> This [community blog post](https://exercism.org/blog/freeing-our-maintainers) contains more details.
+>
+>
+>
-Please see the [contributing guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md)
+
+Hi. 👋🏽 👋 **We are happy you are here.** 🎉 🌟
-## Working on the Exercises
+
-We welcome both improvements to the existing exercises and new exercises.
-A list of missing exercise can be found here: http://exercism.io/languages/python/todo
+**`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website].
+This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students.
+🌟 Track exercises support Python `3.7` - `3.11.5`.
+Exceptions to this support are noted where they occur.
+🌟 Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.5`.
-### Conventions
+Exercises are grouped into **concept** exercises which teach the [Python syllabus][python-syllabus], and **practice** exercises, which are unlocked by progressing in the syllabus tree 🌴 .
+Concept exercises are constrained to a small set of language or syntax features.
+Practice exercises are open-ended, and can be used to practice concepts learned, try out new techniques, and _play_. These two exercise groupings can be found in the track [config.json][config-json], and under the `python/exercises` directory.
-- We use minimalistic stub files for all exercises ([#272](https://github.com/exercism/python/issues/272)).
-- We use `unittest` (Python Standard Library) and no 3rd-party-framework.
-- We use the parameter order `self.assertEqual(actual, expected)` ([#440](https://github.com/exercism/python/issues/440)).
-- We use context managers (`with self.assertRaises(\):`) for testing for exceptions ([#477](https://github.com/exercism/python/issues/477)).
-- We use `assertIs(actual, True)` and `assertIs(actual, False)` rather than `assertTrue(actual)` or `assertFalse(actual)` ([#419](https://github.com/exercism/python/pull/419)).
-- We use a comment string in the test file to reference the version of the exercise's `canonical-data.json` that tests were adapted from (wording can be found in: [#784](https://github.com/exercism/python/issues/784)).
+
+
+
+
+
+
-### Testing
+🌟🌟 Please take a moment to read our [Code of Conduct][exercism-code-of-conduct] 🌟🌟
+It might also be helpful to look at [Being a Good Community Member][being-a-good-community-member] & [The words that we use][the-words-that-we-use].
-All exercises must be compatible with Python versions 2.7 and 3.3 upwards.
+ Some defined roles in our community: [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers] **|** [Admins][exercism-admins]
-To test a single exercise (e.g., with Python 2.7):
-```
-python2.7 test/check-exercises.py [exercise-name]
-```
+
-To test all exercises (e.g., with Python 3):
-```
-python3 test/check-exercises.py
-```
+
+
+We 💛 💙 our community.
+**But our maintainers are not accepting community contributions at this time.**
+Please read this [community blog post][freeing-maintainers] for details.
-### Code Style
+
+
-The Python code in this repo is meant to follow the [PEP8 style guide](https://www.python.org/dev/peps/pep-0008/) (a stylized version http://pep8.org).
+Here to suggest a new feature or new exercise?? **Hooray!** 🎉
+We'd love if you did that via our [Community Forum](https://forum.exercism.org/).
+Please read [Suggesting Exercise Improvements][suggesting-improvements] & [Chesterton's Fence][chestertons-fence].
+_Thoughtful suggestions will likely result in faster & more enthusiastic responses from volunteers._
-This repo uses [flake8](http://flake8.readthedocs.org/en/latest/) with default settings to enforce the coding standard.
+
+
+✨ 🦄 _**Want to jump directly into Exercism specifications & detail?**_
+ [Structure][exercism-track-structure] **|** [Tasks][exercism-tasks] **|** [Concepts][exercism-concepts] **|** [Concept Exercises][concept-exercises] **|** [Practice Exercises][practice-exercises] **|** [Presentation][exercise-presentation]
+ [Writing Style Guide][exercism-writing-style] **|** [Markdown Specification][exercism-markdown-specification] (_✨ version in [contributing][website-contributing-section] on exercism.org_)
-### CI build
+
+
-This repo uses `travis-ci` in the following configuration: [travis.yml](https://github.com/exercism/python/blob/master/.travis.yml)
+## Python Software and Documentation
-It will automatically check the code style, the problem configuration, and run the unittests with all supported Python versions.
+**Copyright © 2001-2025 Python Software Foundation. All rights reserved.**
+Python software and documentation are licensed under the [PSF License Agreement][psf-license].
-## Pull Requests
+Starting with `Python 3.8.6`, examples, recipes, and other code in the Python documentation are dual licensed under the [PSF License Agreement][psf-license] and the [Zero-Clause BSD license][zero-clause-bsd].
-We :heart: pull requests!
-We even :sparkling_heart: them if they contain well written commit messages!
+Some software incorporated into Python is under different licenses. The licenses are listed with code falling under that license. See [Licenses and Acknowledgements for Incorporated Software](https://docs.python.org/3/license.html#otherlicenses) for an incomplete list of these licenses.
-Please write the first line of your commit message in the following style:
+
-```exercise-name: Change some things```
+## Exercism Python Track License
-Please try to follow the [The seven rules of a great Git commit message](https://chris.beams.io/posts/git-commit/#seven-rules) like to capitalize the subject line and use the imperative mood. If there are more details to add, put those into the body of the commit message.
-
-If you're interested, Tim Pope even has an [entire blog post](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) on good commit messages.
-
-If you're new to Git, take a look at [this short guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md#git-basics).
-
-
-## Python icon
-The Python logo is an unregistered trademark. We are using a derived logo with the permission of the Python Software Foundation.
-
-## License
This repository uses the [MIT License](/LICENSE).
+
+[being-a-good-community-member]: https://github.com/exercism/docs/tree/main/community/good-member
+[chestertons-fence]: https://github.com/exercism/docs/blob/main/community/good-member/chestertons-fence.md
+[concept-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md
+[config-json]: https://github.com/exercism/python/blob/main/config.json
+[exercise-presentation]: https://github.com/exercism/docs/blob/main/building/tracks/presentation.md
+[exercism-admins]: https://github.com/exercism/docs/blob/main/community/administrators.md
+[exercism-code-of-conduct]: https://exercism.org/docs/using/legal/code-of-conduct
+[exercism-concepts]: https://github.com/exercism/docs/blob/main/building/tracks/concepts.md
+[exercism-contributors]: https://github.com/exercism/docs/blob/main/community/contributors.md
+[exercism-markdown-specification]: https://github.com/exercism/docs/blob/main/building/markdown/markdown.md
+[exercism-mentors]: https://github.com/exercism/docs/tree/main/mentoring
+[exercism-tasks]: https://exercism.org/docs/building/product/tasks
+[exercism-track-maintainers]: https://github.com/exercism/docs/blob/main/community/maintainers.md
+[exercism-track-structure]: https://github.com/exercism/docs/tree/main/building/tracks
+[exercism-website]: https://exercism.org/
+[exercism-writing-style]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md
+[freeing-maintainers]: https://exercism.org/blog/freeing-our-maintainers
+[practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md
+[psf-license]: https://docs.python.org/3/license.html#psf-license
+[python-syllabus]: https://exercism.org/tracks/python/concepts
+[suggesting-improvements]: https://github.com/exercism/docs/blob/main/community/good-member/suggesting-exercise-improvements.md
+[the-words-that-we-use]: https://github.com/exercism/docs/blob/main/community/good-member/words.md
+[website-contributing-section]: https://exercism.org/docs/building
+[zero-clause-bsd]: https://docs.python.org/3/license.html#zero-clause-bsd-license-for-code-in-the-python-release-documentation
diff --git a/bin/check-readmes.sh b/bin/check-readmes.sh
new file mode 100755
index 00000000000..bedb8cbcf49
--- /dev/null
+++ b/bin/check-readmes.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+SPEC="${1:-spec}"
+
+get_timestamp()
+{
+ path="$1"
+ git log -n1 --pretty=format:%ct -- "$path"
+}
+
+ret=0
+for exercise in $(ls -d exercises/*/); do
+ exercise="${exercise%/}"
+ slug="$(basename "$exercise")"
+ meta_dir="${exercise}/.meta"
+ hints_file="${meta_dir}/hints.md"
+ readme="${exercise}/README.md"
+ if [ -f "$hints_file" ]; then
+ bin/configlet generate -p "$SPEC" -o "${slug}" .
+ if ! git diff --quiet "${readme}"; then
+ echo "$slug: README is out-of-date. Please regenerate README with configlet."
+ else
+ echo "$slug ok"
+ fi
+ git checkout -- "${readme}"
+ fi
+done
+
+exit $ret
diff --git a/bin/create_issue.py b/bin/create_issue.py
new file mode 100644
index 00000000000..b4a60f4c929
--- /dev/null
+++ b/bin/create_issue.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+import requests
+import json
+
+
+# https://stackoverflow.com/a/17626704
+class GitHub(object):
+ def __init__(self, **config_options):
+ self.__dict__.update(**config_options)
+ self.session = requests.Session()
+ if hasattr(self, 'api_token'):
+ self.session.headers['Authorization'] = 'token %s' % self.api_token
+ elif hasattr(self, 'username') and hasattr(self, 'password'):
+ self.session.auth = (self.username, self.password)
+
+ def create_issue(
+ self,
+ owner,
+ repo,
+ title,
+ body=None,
+ assignees=None,
+ labels=None
+ ):
+ payload = dict(title=title)
+ if body is not None:
+ payload['body'] = body
+ if assignees is not None:
+ payload['assignees'] = assignees
+ if labels is not None:
+ payload['labels'] = labels
+ response = self.session.post(
+ 'https://api.github.com/repos/{}/{}/issues'.format(owner, repo),
+ data=json.dumps(payload),
+ )
+ if response.status_code != 201:
+ raise ValueError('Failed to create issue: ' + response.content)
+ return json.loads(response.content)
diff --git a/bin/data.py b/bin/data.py
new file mode 100644
index 00000000000..7fce51b9087
--- /dev/null
+++ b/bin/data.py
@@ -0,0 +1,381 @@
+from enum import Enum
+from dataclasses import dataclass, asdict, fields
+import dataclasses
+from itertools import chain
+import json
+from pathlib import Path
+from typing import List, Any, Dict, Type
+
+# Tomli was subsumed into Python 3.11.x, but was renamed to to tomllib.
+# This avoids ci failures for Python < 3.11.2.
+try:
+ import tomllib
+except ModuleNotFoundError:
+ import tomli as tomllib
+
+
+
+def _custom_dataclass_init(self, *args, **kwargs):
+ # print(self.__class__.__name__, "__init__")
+ names = [field.name for field in fields(self)]
+ used_names = set()
+
+ # Handle positional arguments
+ for value in args:
+ try:
+ name = names.pop(0)
+ except IndexError:
+ raise TypeError(f"__init__() given too many positional arguments")
+ # print(f'setting {k}={v}')
+ setattr(self, name, value)
+ used_names.add(name)
+
+ # Handle keyword arguments
+ for name, value in kwargs.items():
+ if name in names:
+ # print(f'setting {k}={v}')
+ setattr(self, name, value)
+ used_names.add(name)
+ elif name in used_names:
+ raise TypeError(f"__init__() got multiple values for argument '{name}'")
+ else:
+ raise TypeError(
+ f"Unrecognized field '{name}' for dataclass {self.__class__.__name__}."
+ "\nIf this field is valid, please add it to the dataclass in data.py."
+ "\nIf adding an object-type field, please create a new dataclass for it."
+ )
+
+ # Check for missing positional arguments
+ missing = [
+ f"'{field.name}'" for field in fields(self)
+ if isinstance(field.default, dataclasses._MISSING_TYPE) and field.name not in used_names
+ ]
+ if len(missing) == 1:
+ raise TypeError(f"__init__() missing 1 required positional argument: {missing[0]}")
+ elif len(missing) == 2:
+ raise TypeError(f"__init__() missing 2 required positional arguments: {' and '.join(missing)}")
+ elif len(missing) != 0:
+ missing[-1] = f"and {missing[-1]}"
+ raise TypeError(f"__init__() missing {len(missing)} required positional arguments: {', '.join(missing)}")
+
+ # Run post init if available
+ if hasattr(self, "__post_init__"):
+ self.__post_init__()
+
+
+@dataclass
+class TrackStatus:
+ __init__ = _custom_dataclass_init
+
+ concept_exercises: bool = False
+ test_runner: bool = False
+ representer: bool = False
+ analyzer: bool = False
+
+
+class IndentStyle(str, Enum):
+ Space = "space"
+ Tab = "tab"
+
+
+@dataclass
+class TestRunnerSettings:
+ average_run_time: float = -1
+
+
+@dataclass
+class EditorSettings:
+ __init__ = _custom_dataclass_init
+
+ indent_style: IndentStyle = IndentStyle.Space
+ indent_size: int = 4
+ ace_editor_language: str = "python"
+ highlightjs_language: str = "python"
+
+ def __post_init__(self):
+ if isinstance(self.indent_style, str):
+ self.indent_style = IndentStyle(self.indent_style)
+
+
+class ExerciseStatus(str, Enum):
+ Active = "active"
+ WIP = "wip"
+ Beta = "beta"
+ Deprecated = "deprecated"
+
+
+@dataclass
+class ExerciseFiles:
+ __init__ = _custom_dataclass_init
+
+ solution: List[str]
+ test: List[str]
+ editor: List[str] = None
+ exemplar: List[str] = None
+
+
+ # practice exercises are different
+ example: List[str] = None
+
+ def __post_init__(self):
+ if self.exemplar is None:
+ if self.example is None:
+ raise ValueError(
+ "exercise config must have either files.exemplar or files.example"
+ )
+ else:
+ self.exemplar = self.example
+ delattr(self, "example")
+ elif self.example is not None:
+ raise ValueError(
+ "exercise config must have either files.exemplar or files.example, but not both"
+ )
+
+
+@dataclass
+class ExerciseConfig:
+ __init__ = _custom_dataclass_init
+
+ files: ExerciseFiles
+ authors: List[str] = None
+ forked_from: str = None
+ contributors: List[str] = None
+ language_versions: List[str] = None
+ test_runner: bool = True
+ source: str = None
+ source_url: str = None
+ blurb: str = None
+ icon: str = None
+
+ def __post_init__(self):
+ if isinstance(self.files, dict):
+ self.files = ExerciseFiles(**self.files)
+ for attr in ["authors", "contributors", "language_versions"]:
+ if getattr(self, attr) is None:
+ setattr(self, attr, [])
+
+ @classmethod
+ def load(cls, config_file: Path) -> "ExerciseConfig":
+ with config_file.open() as f:
+ return cls(**json.load(f))
+
+
+@dataclass
+class ExerciseInfo:
+ __init__ = _custom_dataclass_init
+
+ path: Path
+ slug: str
+ name: str
+ uuid: str
+ prerequisites: List[str]
+ type: str = "practice"
+ status: ExerciseStatus = ExerciseStatus.Active
+
+ # concept only
+ concepts: List[str] = None
+
+ # practice only
+ difficulty: int = 1
+ topics: List[str] = None
+ practices: List[str] = None
+
+ def __post_init__(self):
+ if self.concepts is None:
+ self.concepts = []
+ if self.topics is None:
+ self.topics = []
+ if self.practices is None:
+ self.practices = []
+ if isinstance(self.status, str):
+ self.status = ExerciseStatus(self.status)
+
+ @property
+ def solution_stub(self):
+ return next(
+ (
+ p
+ for p in self.path.glob("*.py")
+ if not p.name.endswith("_test.py") and p.name != "example.py"
+ ),
+ None,
+ )
+
+ @property
+ def helper_file(self):
+ return next(self.path.glob("*_data.py"), None)
+
+ @property
+ def test_file(self):
+ return next(self.path.glob("*_test.py"), None)
+
+ @property
+ def meta_dir(self):
+ return self.path / ".meta"
+
+ @property
+ def exemplar_file(self):
+ if self.type == "concept":
+ return self.meta_dir / "exemplar.py"
+ return self.meta_dir / "example.py"
+
+ @property
+ def template_path(self):
+ return self.meta_dir / "template.j2"
+
+ @property
+ def config_file(self):
+ return self.meta_dir / "config.json"
+
+ def load_config(self) -> ExerciseConfig:
+ return ExerciseConfig.load(self.config_file)
+
+
+@dataclass
+class Exercises:
+ __init__ = _custom_dataclass_init
+
+ concept: List[ExerciseInfo]
+ practice: List[ExerciseInfo]
+ foregone: List[str] = None
+
+ def __post_init__(self):
+ if self.foregone is None:
+ self.foregone = []
+ for attr_name in ["concept", "practice"]:
+ base_path = Path("exercises") / attr_name
+ setattr(
+ self,
+ attr_name,
+ [
+ (
+ ExerciseInfo(path=(base_path / e["slug"]), type=attr_name, **e)
+ if isinstance(e, dict)
+ else e
+ )
+ for e in getattr(self, attr_name)
+ ],
+ )
+
+ def all(self, status_filter={ExerciseStatus.Active, ExerciseStatus.Beta}):
+ return [
+ e for e in chain(self.concept, self.practice) if e.status in status_filter
+ ]
+
+
+@dataclass
+class Concept:
+ __init__ = _custom_dataclass_init
+
+ uuid: str
+ slug: str
+ name: str
+
+
+@dataclass
+class Feature:
+ __init__ = _custom_dataclass_init
+
+ title: str
+ content: str
+ icon: str
+
+
+@dataclass
+class FilePatterns:
+ __init__ = _custom_dataclass_init
+
+ solution: List[str]
+ test: List[str]
+ example: List[str]
+ exemplar: List[str]
+ editor: List[str] = None
+
+
+
+@dataclass
+class Config:
+ __init__ = _custom_dataclass_init
+
+ language: str
+ slug: str
+ active: bool
+ status: TrackStatus
+ blurb: str
+ version: int
+ online_editor: EditorSettings
+ exercises: Exercises
+ concepts: List[Concept]
+ key_features: List[Feature] = None
+ tags: List[Any] = None
+ test_runner: TestRunnerSettings = None
+ files: FilePatterns = None
+
+ def __post_init__(self):
+ if isinstance(self.status, dict):
+ self.status = TrackStatus(**self.status)
+ if isinstance(self.online_editor, dict):
+ self.online_editor = EditorSettings(**self.online_editor)
+ if isinstance(self.test_runner, dict):
+ self.test_runner = TestRunnerSettings(**self.test_runner)
+ if isinstance(self.exercises, dict):
+ self.exercises = Exercises(**self.exercises)
+ if isinstance(self.files, dict):
+ self.files = FilePatterns(**self.files)
+ self.concepts = [
+ (Concept(**c) if isinstance(c, dict) else c) for c in self.concepts
+ ]
+ if self.key_features is None:
+ self.key_features = []
+ if self.tags is None:
+ self.tags = []
+
+ @classmethod
+ def load(cls, path="config.json"):
+ try:
+ with Path(path).open() as f:
+ return cls(**json.load(f))
+ except IOError:
+ print(f"FAIL: {path} file not found")
+ raise SystemExit(1)
+ except TypeError as ex:
+ print(f"FAIL: {ex}")
+ raise SystemExit(1)
+
+
+@dataclass
+class TestCaseTOML:
+ __init__ = _custom_dataclass_init
+
+ uuid: str
+ description: str
+ include: bool = True
+ comment: str = ''
+
+
+@dataclass
+class TestsTOML:
+ __init__ = _custom_dataclass_init
+
+ cases: Dict[str, TestCaseTOML]
+
+ @classmethod
+ def load(cls, toml_path: Path):
+ with toml_path.open("rb") as f:
+ data = tomllib.load(f)
+ return cls({uuid: TestCaseTOML(uuid, *opts) for
+ uuid, opts in
+ data.items() if
+ opts.get('include', None) is not False})
+
+
+if __name__ == "__main__":
+
+ class CustomEncoder(json.JSONEncoder):
+ def default(self, obj):
+ if isinstance(obj, Path):
+ return str(obj)
+ return json.JSONEncoder.default(self, obj)
+
+ config = Config.load()
+ print(json.dumps(asdict(config), cls=CustomEncoder, indent=2))
diff --git a/bin/fetch-configlet b/bin/fetch-configlet
index 4f64c5b9adc..6bef43ab722 100755
--- a/bin/fetch-configlet
+++ b/bin/fetch-configlet
@@ -1,32 +1,91 @@
-#!/bin/bash
-
-LATEST=https://github.com/exercism/configlet/releases/latest
-
-OS=$(
-case $(uname) in
- (Darwin*)
- echo "mac";;
- (Linux*)
- echo "linux";;
- (Windows*)
- echo "windows";;
- (*)
- echo "linux";;
-esac)
-
-ARCH=$(
-case $(uname -m) in
- (*64*)
- echo 64bit;;
- (*686*)
- echo 32bit;;
- (*386*)
- echo 32bit;;
- (*)
- echo 64bit;;
-esac)
-
-VERSION="$(curl --head --silent $LATEST | awk -v FS=/ '/Location:/{print $NF}' | tr -d '\r')"
-URL=https://github.com/exercism/configlet/releases/download/$VERSION/configlet-$OS-${ARCH}.tgz
-
-curl -s --location $URL | tar xz -C bin/
+#!/usr/bin/env bash
+
+# This file is a copy of the
+# https://github.com/exercism/configlet/blob/main/scripts/fetch-configlet file.
+# Please submit bugfixes/improvements to the above file to ensure that all tracks benefit from the changes.
+
+set -eo pipefail
+
+curlopts=(
+ --silent
+ --show-error
+ --fail
+ --location
+ --retry 3
+)
+
+if [[ -n "${GITHUB_TOKEN}" ]]; then
+ curlopts+=(--header "authorization: Bearer ${GITHUB_TOKEN}")
+fi
+
+get_download_url() {
+ local os="$1"
+ local ext="$2"
+ local latest='https://api.github.com/repos/exercism/configlet/releases/latest'
+ local arch
+ case "$(uname -m)" in
+ aarch64|arm64) arch='arm64' ;;
+ x86_64) arch='x86-64' ;;
+ *686*) arch='i386' ;;
+ *386*) arch='i386' ;;
+ *) arch='x86-64' ;;
+ esac
+ local suffix="${os}_${arch}.${ext}"
+ curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${latest}" |
+ grep "\"browser_download_url\": \".*/download/.*/configlet.*${suffix}\"$" |
+ cut -d'"' -f4
+}
+
+main() {
+ local output_dir
+ if [[ -d ./bin ]]; then
+ output_dir="./bin"
+ elif [[ $PWD == */bin ]]; then
+ output_dir="$PWD"
+ else
+ echo "Error: no ./bin directory found. This script should be ran from a repo root." >&2
+ return 1
+ fi
+
+ local os
+ case "$(uname -s)" in
+ Darwin*) os='macos' ;;
+ Linux*) os='linux' ;;
+ Windows*) os='windows' ;;
+ MINGW*) os='windows' ;;
+ MSYS_NT-*) os='windows' ;;
+ *) os='linux' ;;
+ esac
+
+ local ext
+ case "${os}" in
+ windows) ext='zip' ;;
+ *) ext='tar.gz' ;;
+ esac
+
+ echo "Fetching configlet..." >&2
+ local download_url
+ download_url="$(get_download_url "${os}" "${ext}")"
+ local output_path="${output_dir}/latest-configlet.${ext}"
+ curl "${curlopts[@]}" --output "${output_path}" "${download_url}"
+
+ case "${ext}" in
+ zip) unzip "${output_path}" -d "${output_dir}" ;;
+ *) tar xzf "${output_path}" -C "${output_dir}" ;;
+ esac
+
+ rm -f "${output_path}"
+
+ local executable_ext
+ case "${os}" in
+ windows) executable_ext='.exe' ;;
+ *) executable_ext='' ;;
+ esac
+
+ local configlet_path="${output_dir}/configlet${executable_ext}"
+ local configlet_version
+ configlet_version="$(${configlet_path} --version)"
+ echo "Downloaded configlet ${configlet_version} to ${configlet_path}"
+}
+
+main
diff --git a/bin/generate_tests.py b/bin/generate_tests.py
new file mode 100755
index 00000000000..2ad23a9b5f1
--- /dev/null
+++ b/bin/generate_tests.py
@@ -0,0 +1,458 @@
+#!/usr/bin/env python3
+"""
+Generates exercise test suites using an exercise's canonical-data.json
+(found in problem-specifications) and $exercise/.meta/template.j2.
+If either does not exist, generation will not be attempted.
+
+Usage:
+ generate_tests.py Generates tests for all exercises
+ generate_tests.py two-fer Generates tests for two-fer exercise
+ generate_tests.py t* Generates tests for all exercises matching t*
+
+ generate_tests.py --check Checks if test files are out of sync with templates
+ generate_tests.py --check two-fer Checks if two-fer test file is out of sync with template
+"""
+import sys
+
+from githelp import Repo
+
+_py = sys.version_info
+if _py.major < 3 or (_py.major == 3 and _py.minor < 7):
+ print("Python version must be at least 3.7")
+ sys.exit(1)
+
+import argparse
+from datetime import datetime
+from datetime import timezone
+import difflib
+import filecmp
+import importlib.util
+import json
+import logging
+from pathlib import Path, PurePath, PureWindowsPath
+import re
+import shutil
+from itertools import repeat
+from string import punctuation, whitespace
+from subprocess import check_call
+from tempfile import NamedTemporaryFile
+from textwrap import wrap
+from typing import Any, Dict, List, NoReturn, Union
+
+# Tomli was subsumed into Python 3.11.x, but was renamed to to tomllib.
+# This avoids ci failures for Python < 3.11.2.
+try:
+ import tomllib
+except ModuleNotFoundError:
+ import tomli as tomllib
+
+from jinja2 import Environment, FileSystemLoader, TemplateNotFound, UndefinedError
+from dateutil.parser import parse
+
+from githelp import clone_if_missing, Repo
+from data import TestsTOML
+
+VERSION = "0.3.0"
+
+TypeJSON = Dict[str, Any]
+
+PROBLEM_SPEC_REPO = "https://github.com/exercism/problem-specifications.git"
+DEFAULT_SPEC_LOCATION = Path(".problem-specifications")
+RGX_WORDS = re.compile(r"[-_\s]|(?=[A-Z])")
+
+logging.basicConfig()
+logger = logging.getLogger("generator")
+logger.setLevel(logging.WARN)
+
+
+def replace_all(string: str, chars: Union[str, List[str]], rep: str) -> str:
+ """
+ Replace any char in chars with rep, reduce runs and strip terminal ends.
+ """
+ trans = str.maketrans(dict(zip(chars, repeat(rep))))
+ return re.sub("{0}+".format(re.escape(rep)), rep, string.translate(trans)).strip(
+ rep
+ )
+
+
+def to_snake(string: str, wordchars_only: bool = False) -> str:
+ """
+ Convert pretty much anything to to_snake.
+
+ By default whitespace and punctuation will be converted
+ to underscores as well, pass wordchars_only=True to preserve these as is.
+ """
+ clean = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", string)
+ clean = re.sub("([a-z0-9])([A-Z])", r"\1_\2", clean).lower()
+ return clean if wordchars_only else replace_all(clean, whitespace + punctuation, "_")
+
+
+def camel_case(string: str) -> str:
+ """
+ Convert pretty much anything to CamelCase.
+ """
+ return "".join(w.title() for w in to_snake(string).split("_"))
+
+
+def wrap_overlong(string: str, width: int = 70) -> List[str]:
+ """
+ Break an overly long string literal into escaped lines.
+ """
+ return ["{0!r} \\".format(w) for w in wrap(string, width)]
+
+
+def parse_datetime(string: str, strip_module: bool = False) -> datetime:
+ """
+ Parse a (hopefully ISO 8601) datestamp to a datetime object and
+ return its repr for use in a jinja2 template.
+
+ If used the template will need to import the datetime module.
+
+ import datetime
+
+ However if strip_module is True then the template will need to
+ import the datetime _class_ instead.
+
+ from datetime import datetime
+ """
+ result = repr(parse(string))
+ if strip_module:
+ return result.replace("datetime.", "", 1)
+ return result
+
+INVALID_ESCAPE_RE = re.compile(
+ r"""
+ \\(?! # a backslash NOT followed by
+ newline # the literal newline
+ |[ # OR precisely one of
+ \\ # another backslash
+ ' # the single quote
+ " # the double quote
+ a # the ASCII bell
+ b # the ASCII backspace
+ f # the ASCII formfeed
+ n # the ASCII linefeed
+ r # the ASCII carriage return
+ t # the ASCII horizontal tab
+ v # the ASCII vertical tab
+ ]| # OR
+ o(?:[0-8]{1,3}) # an octal value
+ | # OR
+ x(?:[0-9A-Fa-f]{2}) # a hexadecimal value
+ | # OR
+ N # a unicode char name composed of
+ \{ # an opening brace
+ [A-Z][A-Z\ \-]*[A-Z] # uppercase WORD, WORDs (or WORD-WORDs)
+ \} # and a closing brace
+ | # OR
+ u(?:[0-9A-Fa-f]{4}) # a 16-bit unicode char
+ | # OR
+ U(?:[0-9A-Fa-f]{8}) # a 32-bit unicode char
+ )""", flags=re.VERBOSE)
+
+def escape_invalid_escapes(string: str) -> str:
+ """
+ Some canonical data includes invalid escape sequences, which
+ need to be properly escaped before template render.
+ """
+ return INVALID_ESCAPE_RE.sub(r"\\\\", string)
+
+ALL_VALID = r"\newline\\\'\"\a\b\f\n\r\t\v\o123" \
+ r"\xFF\N{GREATER-THAN SIGN}\u0394\U00000394"
+
+assert ALL_VALID == escape_invalid_escapes(ALL_VALID)
+
+def get_tested_properties(spec: TypeJSON) -> List[str]:
+ """
+ Get set of tested properties from spec. Include nested cases.
+ """
+ props = set()
+ for case in spec["cases"]:
+ if "property" in case:
+ props.add(case["property"])
+ if "cases" in case:
+ props.update(get_tested_properties(case))
+ return sorted(props)
+
+
+def error_case(case: TypeJSON) -> bool:
+ return (
+ "expected" in case
+ and isinstance(case["expected"], dict)
+ and "error" in case["expected"]
+ )
+
+
+def has_error_case(cases: List[TypeJSON]) -> bool:
+ cases = cases[:]
+ while cases:
+ case = cases.pop(0)
+ if error_case(case):
+ return True
+ cases.extend(case.get("cases", []))
+ return False
+
+
+def regex_replace(s: str, find: str, repl: str) -> str:
+ return re.sub(find, repl, s)
+
+
+def regex_find(s: str, find: str) -> List[Any]:
+ return re.findall(find, s)
+
+
+def regex_split(s: str, find: str) -> List[str]:
+ return re.split(find, s)
+
+def join_test_inputs(test_inputs: list) -> str:
+ return "\n".join(test_inputs)
+
+def filter_test_cases(cases: List[TypeJSON], opts: TestsTOML) -> List[TypeJSON]:
+ """
+ Returns a filtered copy of `cases` where only cases whose UUID is marked True in
+ `opts` are included.
+ """
+ filtered = []
+ for case in cases:
+ if "uuid" in case:
+ uuid = case["uuid"]
+ case_opts = opts.cases.get(uuid, None)
+ if case_opts is not None and case_opts.include:
+ filtered.append(case)
+ else:
+ logger.debug(f"uuid {uuid} either missing or not marked for include")
+ elif "cases" in case:
+ subfiltered = filter_test_cases(case["cases"], opts)
+ if subfiltered:
+ case_copy = dict(case)
+ case_copy["cases"] = subfiltered
+ filtered.append(case_copy)
+ return filtered
+
+
+def load_canonical(exercise: str, spec_path: Path, test_opts: TestsTOML) -> TypeJSON:
+ """
+ Loads the canonical data for an exercise as a nested dictionary
+ """
+ full_path = spec_path / "exercises" / exercise / "canonical-data.json"
+ with full_path.open() as f:
+ spec = json.load(f)
+ spec["cases"] = filter_test_cases(spec["cases"], test_opts)
+ spec["properties"] = get_tested_properties(spec)
+ return spec
+
+
+def load_additional_tests(exercise: Path) -> List[TypeJSON]:
+ """
+ Loads additional tests from .meta/additional_tests.json
+ """
+ full_path = exercise / ".meta/additional_tests.json"
+ try:
+ with full_path.open() as f:
+ data = json.load(f)
+ return data.get("cases", [])
+ except FileNotFoundError:
+ return []
+
+
+def format_file(path: Path) -> NoReturn:
+ """
+ Runs black auto-formatter on file at path
+ """
+ check_call(["black", "-q", path])
+
+
+def check_template(slug: str, tests_path: Path, tmpfile: Path):
+ """Generate a new test file and diff against existing file.
+
+ Note: The timestamp in each test file creates issues with
+ Python difflib, so it is skipped when being prepped
+ for diff.
+
+ You can see this "skipping" on lines 281 & 283.
+ However, this rather crude method creates
+ an empty "false positive" diff. This empty diff is
+ then skipped in lines 293 & 294, so that it can be
+ considered a pass..
+ """
+
+ try:
+ check_ok = True
+ if not tmpfile.is_file():
+ logger.debug(f"{slug}: tmp file {tmpfile} not found")
+ check_ok = False
+ if not tests_path.is_file():
+ logger.debug(f"{slug}: tests file {tests_path} not found")
+ check_ok = False
+ if check_ok and not filecmp.cmp(tmpfile, tests_path):
+ with tests_path.open() as f:
+ current_lines = f.readlines()[3:]
+ with tmpfile.open() as f:
+ rendered_lines = f.readlines()[3:]
+
+ diff = list(difflib.unified_diff(
+ current_lines,
+ rendered_lines,
+ fromfile=f"[current] {tests_path.name}",
+ tofile=f"[generated] {tmpfile.name}",
+ lineterm="\n",
+ ))
+ if not diff:
+ check_ok = True
+ else:
+ logger.debug(f"{slug}: ##### DIFF START #####")
+ for line in diff:
+ logger.debug(line.strip())
+ logger.debug(f"{slug}: ##### DIFF END #####")
+ check_ok = False
+ if not check_ok:
+ logger.error(
+ f"{slug}: check failed; tests must be regenerated with bin/generate_tests.py"
+ )
+ return False
+ logger.debug(f"{slug}: check passed")
+ finally:
+ logger.debug(f"{slug}: removing tmp file {tmpfile}")
+ tmpfile.unlink()
+ return True
+
+
+def generate_exercise(env: Environment, spec_path: Path, exercise: Path, check: bool = False):
+ """
+ Renders test suite for exercise and if check is:
+ True: verifies that current tests file matches rendered
+ False: saves rendered to tests file
+ """
+ slug = exercise.name
+ meta_dir = exercise / ".meta"
+ plugins_module = None
+ plugins_name = "plugins"
+ plugins_source = meta_dir / f"{plugins_name}.py"
+ try:
+ if plugins_source.is_file():
+ plugins_spec = importlib.util.spec_from_file_location(
+ plugins_name, plugins_source
+ )
+ plugins_module = importlib.util.module_from_spec(plugins_spec)
+ sys.modules[plugins_name] = plugins_module
+ plugins_spec.loader.exec_module(plugins_module)
+ try:
+ test_opts = TestsTOML.load(meta_dir / "tests.toml")
+ except FileNotFoundError:
+ logger.error(f"{slug}: tests.toml not found; skipping.")
+ return True
+
+ spec = load_canonical(slug, spec_path, test_opts)
+ additional_tests = load_additional_tests(exercise)
+ spec["additional_cases"] = additional_tests
+ template_path = exercise.relative_to("exercises") / ".meta/template.j2"
+
+ # See https://github.com/pallets/jinja/issues/767 for why this is needed on Windows systems.
+ if "\\" in str(template_path):
+ template_path = PureWindowsPath(template_path).as_posix()
+
+ template = env.get_template(str(template_path))
+ tests_path = exercise / f"{to_snake(slug)}_test.py"
+ spec["has_error_case"] = has_error_case(spec["cases"])
+
+ if plugins_module is not None:
+ spec[plugins_name] = plugins_module
+ logger.debug(f"{slug}: attempting render")
+ rendered = template.render(**spec)
+ with NamedTemporaryFile("w", delete=False) as tmp:
+ logger.debug(f"{slug}: writing render to tmp file {tmp.name}")
+ tmpfile = Path(tmp.name)
+ tmp.write(rendered)
+ try:
+ logger.debug(f"{slug}: formatting tmp file {tmpfile}")
+ format_file(tmpfile)
+ except FileNotFoundError as e:
+ logger.error(f"{slug}: the black utility must be installed")
+ return False
+
+ if check:
+ return check_template(slug, tests_path, tmpfile)
+ else:
+ logger.debug(f"{slug}: moving tmp file {tmpfile}->{tests_path}")
+ shutil.move(tmpfile, tests_path)
+ print(f"{slug} generated at {tests_path}")
+ except (TypeError, UndefinedError, SyntaxError) as e:
+ logger.debug(str(e))
+ logger.error(f"{slug}: generation failed")
+ return False
+ except TemplateNotFound as e:
+ logger.debug(str(e))
+ logger.info(f"{slug}: no template found; skipping")
+ except FileNotFoundError as e:
+ logger.debug(str(e))
+ logger.info(f"{slug}: no canonical data found; skipping")
+ return True
+
+
+def generate(
+ exercise_glob: str,
+ spec_path: Path = DEFAULT_SPEC_LOCATION,
+ stop_on_failure: bool = False,
+ check: bool = False,
+ **_,
+):
+ """
+ Primary entry point. Generates test files for all exercises matching exercise_glob
+ """
+ # black must be installed or all test files will error
+ if not shutil.which("black"):
+ logger.error("the black utility must be installed")
+ sys.exit(1)
+ loader = FileSystemLoader(["config", "exercises"])
+ env = Environment(loader=loader, keep_trailing_newline=True)
+ env.filters["to_snake"] = to_snake
+ env.filters["camel_case"] = camel_case
+ env.filters["wrap_overlong"] = wrap_overlong
+ env.filters["regex_replace"] = regex_replace
+ env.filters["regex_find"] = regex_find
+ env.filters["regex_split"] = regex_split
+ env.filters["join_test_inputs"] = join_test_inputs
+ env.filters["zip"] = zip
+ env.filters["parse_datetime"] = parse_datetime
+ env.filters["escape_invalid_escapes"] = escape_invalid_escapes
+ env.globals["current_date"] = datetime.now(tz=timezone.utc).date()
+ env.tests["error_case"] = error_case
+ result = True
+ for exercise in sorted(Path("exercises/practice").glob(exercise_glob)):
+ if not generate_exercise(env, spec_path, exercise, check):
+ result = False
+ if stop_on_failure:
+ break
+ if not result:
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("exercise_glob", nargs="?", default="*", metavar="EXERCISE")
+ parser.add_argument(
+ "--version",
+ action="version",
+ version="%(prog)s {} for Python {}".format(VERSION, sys.version.split("\n")[0]),
+ )
+ parser.add_argument("-v", "--verbose", action="store_true")
+ parser.add_argument(
+ "-p",
+ "--spec-path",
+ default=DEFAULT_SPEC_LOCATION,
+ type=Path,
+ help=(
+ "path to clone of exercism/problem-specifications " "(default: %(default)s)"
+ ),
+ )
+ parser.add_argument("--stop-on-failure", action="store_true")
+ parser.add_argument(
+ "--check",
+ action="store_true",
+ help="check if tests are up-to-date, but do not modify test files",
+ )
+ opts = parser.parse_args()
+ if opts.verbose:
+ logger.setLevel(logging.DEBUG)
+ with clone_if_missing(repo=Repo.ProblemSpecifications, directory=opts.spec_path):
+ generate(**opts.__dict__)
diff --git a/bin/githelp.py b/bin/githelp.py
new file mode 100644
index 00000000000..3d3be06f664
--- /dev/null
+++ b/bin/githelp.py
@@ -0,0 +1,39 @@
+from contextlib import contextmanager
+from enum import Enum
+from pathlib import Path
+import shutil
+import subprocess
+from typing import Iterator, Union
+
+
+GITHUB_EXERCISM = f"https://github.com/exercism"
+
+
+class Repo(Enum):
+ ProblemSpecifications = f"{GITHUB_EXERCISM}/problem-specifications.git"
+
+
+
+def clone(repo: Union[str, Repo], directory: Union[str, Path, None] = None) -> bool:
+ if isinstance(repo, Repo):
+ repo = repo.value
+ if directory is None:
+ directory = repo.split("/")[-1].split(".")[0]
+ directory = Path(directory)
+ if not directory.is_dir():
+ try:
+ subprocess.run(["git", "clone", repo, str(directory)], check=True)
+ return True
+ except subprocess.CalledProcessError:
+ pass
+ return False
+
+
+@contextmanager
+def clone_if_missing(repo: Union[str, Repo], directory: Union[str, Path, None] = None) -> Iterator[None]:
+ temp_clone = clone(repo, directory)
+ try:
+ yield directory
+ finally:
+ if temp_clone:
+ shutil.rmtree(directory)
diff --git a/bin/template_status.py b/bin/template_status.py
new file mode 100755
index 00000000000..ed5614744d4
--- /dev/null
+++ b/bin/template_status.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3.7
+import argparse
+from argparse import Namespace
+from enum import IntEnum, auto
+from fnmatch import fnmatch
+import logging
+from pathlib import Path
+import shlex
+from subprocess import check_call, DEVNULL, CalledProcessError
+import sys
+from typing import List, Iterator
+
+from data import Config, ExerciseInfo, ExerciseStatus
+from generate_tests import clone_if_missing
+from githelp import Repo
+from test_exercises import check_assignment
+
+DEFAULT_SPEC_LOCATION = Path('.problem-specifications')
+
+logging.basicConfig(format="%(levelname)s:%(message)s")
+logger = logging.getLogger("generator")
+logger.setLevel(logging.WARN)
+
+
+class TemplateStatus(IntEnum):
+ OK = auto()
+ MISSING = auto()
+ INVALID = auto()
+ TEST_FAILURE = auto()
+
+
+def exec_cmd(cmd: str) -> bool:
+ try:
+ args = shlex.split(cmd)
+ if logger.isEnabledFor(logging.DEBUG):
+ check_call(args)
+ else:
+ check_call(args, stderr=DEVNULL, stdout=DEVNULL)
+ return True
+ except CalledProcessError as e:
+ logger.debug(str(e))
+ return False
+
+
+def generate_template(exercise: ExerciseInfo, spec_path: Path) -> bool:
+ script = Path('bin/generate_tests.py')
+ return exec_cmd(f'{script} --verbose --spec-path "{spec_path}" {exercise.slug}')
+
+
+def run_tests(exercise: ExerciseInfo) -> bool:
+ return check_assignment(exercise, quiet=True) == 0
+
+
+def get_status(exercise: ExerciseInfo, spec_path: Path) -> TemplateStatus:
+ if exercise.template_path.is_file():
+ if generate_template(exercise, spec_path):
+ if run_tests(exercise):
+ logging.info(f"{exercise.slug}: OK")
+ return TemplateStatus.OK
+ else:
+ return TemplateStatus.TEST_FAILURE
+ else:
+ return TemplateStatus.INVALID
+ else:
+ return TemplateStatus.MISSING
+
+
+def set_loglevel(opts: Namespace):
+ if opts.quiet:
+ logger.setLevel(logging.FATAL)
+ elif opts.verbose >= 2:
+ logger.setLevel(logging.DEBUG)
+ elif opts.verbose >= 1:
+ logger.setLevel(logging.INFO)
+
+
+def filter_exercises(exercises: List[ExerciseInfo], pattern: str) -> Iterator[ExerciseInfo]:
+ for exercise in exercises:
+ if exercise.status != ExerciseStatus.Deprecated:
+ if exercise.type == 'concept':
+ # Concept exercises are not generated
+ continue
+ if fnmatch(exercise["slug"], pattern):
+ yield exercise
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("exercise_pattern", nargs="?", default="*", metavar="EXERCISE")
+ parser.add_argument("-v", "--verbose", action="count", default=0)
+ parser.add_argument("-q", "--quiet", action="store_true")
+ parser.add_argument("--stop-on-failure", action="store_true")
+ parser.add_argument(
+ "-p",
+ "--spec-path",
+ default=DEFAULT_SPEC_LOCATION,
+ type=Path,
+ help=(
+ "path to clone of exercism/problem-specifications " "(default: %(default)s)"
+ ),
+ )
+ opts = parser.parse_args()
+ set_loglevel(opts)
+
+ if not opts.spec_path.is_dir():
+ logger.error(f"{opts.spec_path} is not a directory")
+ sys.exit(1)
+ with clone_if_missing(repo=Repo.ProblemSpecifications, directory=opts.spec_path):
+
+ result = True
+ buckets = {
+ TemplateStatus.MISSING: [],
+ TemplateStatus.INVALID: [],
+ TemplateStatus.TEST_FAILURE: [],
+ }
+ config = Config.load()
+ for exercise in filter_exercises(config.exercises.all()):
+ status = get_status(exercise, opts.spec_path)
+ if status == TemplateStatus.OK:
+ logger.info(f"{exercise.slug}: {status.name}")
+ else:
+ buckets[status].append(exercise.slug)
+ result = False
+ if opts.stop_on_failure:
+ logger.error(f"{exercise.slug}: {status.name}")
+ break
+
+ if not opts.quiet and not opts.stop_on_failure:
+ for status, bucket in sorted(buckets.items()):
+ if bucket:
+ print(f"The following exercises have status '{status.name}'")
+ for exercise in sorted(bucket):
+ print(f' {exercise}')
+
+ if not result:
+ sys.exit(1)
diff --git a/bin/test_exercises.py b/bin/test_exercises.py
new file mode 100755
index 00000000000..0b6e727b861
--- /dev/null
+++ b/bin/test_exercises.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python3
+"""Meant to be run from inside python-test-runner container,
+where this track repo is mounted at /python
+"""
+import argparse
+from functools import wraps
+from itertools import zip_longest
+import json
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+import tempfile
+from typing import List
+from data import Config, ExerciseConfig, ExerciseInfo, ExerciseStatus
+
+# Allow high-performance tests to be skipped
+ALLOW_SKIP = ['alphametics', 'largest-series-product']
+
+TEST_RUNNER_DIR = Path('/opt/test-runner')
+
+RUNNERS = {}
+
+
+def runner(name):
+ def _decorator(runner_func):
+ RUNNERS[name] = runner_func
+ @wraps(runner_func)
+ def _wrapper(exercise: ExerciseInfo, workdir: Path, quiet: bool = False):
+ return runner_func(exercise, workdir, quiet=quiet)
+ return _wrapper
+ return _decorator
+
+
+def copy_file(src: Path, dst: Path, strip_skips=False):
+ if strip_skips:
+ with src.open('r') as src_file:
+ lines = [line for line in src_file.readlines()
+ if not line.strip().startswith('@unittest.skip')]
+ with dst.open('w') as dst_file:
+ dst_file.writelines(lines)
+ else:
+ shutil.copy2(src, dst)
+
+def copy_solution_files(exercise: ExerciseInfo, workdir: Path, exercise_config: ExerciseConfig = None):
+ if exercise_config is not None:
+ solution_files = exercise_config.files.solution
+ exemplar_files = exercise_config.files.exemplar
+ helper_files = exercise_config.files.editor
+ else:
+ solution_files = []
+ exemplar_files = []
+ helper_files = []
+
+ if helper_files:
+ helper_files = [exercise.path / h for h in helper_files]
+ for helper_file in helper_files:
+ dst = workdir / helper_file.relative_to(exercise.path)
+ copy_file(helper_file, dst)
+
+ if not solution_files:
+ solution_files.append(exercise.solution_stub.name)
+ solution_files = [exercise.path / s for s in solution_files]
+ if not exemplar_files:
+ exemplar_files.append(exercise.exemplar_file.relative_to(exercise.path))
+ exemplar_files = [exercise.path / e for e in exemplar_files]
+
+ for solution_file, exemplar_file in zip_longest(solution_files, exemplar_files):
+ if solution_file is None:
+ copy_file(exemplar_file, workdir / exemplar_file.name)
+ elif exemplar_file is None:
+ copy_file(solution_file, workdir / solution_file.name)
+ else:
+ dst = workdir / solution_file.relative_to(exercise.path)
+ copy_file(exemplar_file, dst)
+
+
+def copy_test_files(exercise: ExerciseInfo, workdir: Path, exercise_config = None):
+ if exercise_config is not None:
+ test_files = exercise_config.files.test
+ helper_files = exercise_config.files.editor
+ else:
+ test_files = []
+ helper_files = []
+
+ if helper_files:
+ for helper_file_name in helper_files:
+ helper_file = exercise.path / helper_file_name
+ helper_file_out = workdir / helper_file_name
+ copy_file(helper_file, helper_file_out, strip_skips=(exercise.slug not in ALLOW_SKIP))
+
+ if not test_files:
+ test_files.append(exercise.test_file.name)
+
+ for test_file_name in test_files:
+ test_file = exercise.path / test_file_name
+ test_file_out = workdir / test_file_name
+ copy_file(test_file, test_file_out, strip_skips=(exercise.slug not in ALLOW_SKIP))
+
+
+def copy_exercise_files(exercise: ExerciseInfo, workdir: Path):
+ exercise_config = None
+ if exercise.config_file.is_file():
+ workdir_meta = workdir / '.meta'
+ workdir_meta.mkdir(exist_ok=True)
+ copy_file(exercise.config_file, workdir_meta / exercise.config_file.name)
+ exercise_config = exercise.load_config()
+ copy_solution_files(exercise, workdir, exercise_config)
+ copy_test_files(exercise, workdir, exercise_config)
+
+
+@runner('pytest')
+def run_with_pytest(_exercise, workdir, quiet: bool = False) -> int:
+ kwargs = {'cwd': str(workdir)}
+ if quiet:
+ kwargs['stdout'] = subprocess.DEVNULL
+ kwargs['stderr'] = subprocess.DEVNULL
+ return subprocess.run([sys.executable, '-m', 'pytest'], **kwargs).returncode
+
+
+@runner('test-runner')
+def run_with_test_runner(exercise, workdir, quiet: bool = False) -> int:
+ kwargs = {}
+ if quiet:
+ kwargs['stdout'] = subprocess.DEVNULL
+ kwargs['stderr'] = subprocess.DEVNULL
+ if TEST_RUNNER_DIR.is_dir():
+ kwargs['cwd'] = str(TEST_RUNNER_DIR)
+ args = ['./bin/run.sh', exercise.slug, workdir, workdir]
+ else:
+ args = [
+ 'docker-compose',
+ 'run',
+ '-w', str(TEST_RUNNER_DIR),
+ '--entrypoint', './bin/run.sh',
+ '-v', f'{workdir}:/{exercise.slug}',
+ 'test-runner',
+ exercise.slug,
+ f'/{exercise.slug}',
+ f'/{exercise.slug}',
+ ]
+ subprocess.run(args, **kwargs)
+ results_file = workdir / 'results.json'
+ if results_file.is_file():
+ with results_file.open() as f:
+ results = json.load(f)
+ if results['status'] == 'pass':
+ return 0
+ return 1
+
+
+def check_assignment(exercise: ExerciseInfo, runner: str = 'pytest', quiet: bool = False) -> int:
+ ret = 1
+ with tempfile.TemporaryDirectory(exercise.slug) as workdir:
+ workdir = Path(workdir)
+ copy_exercise_files(exercise, workdir)
+ ret = RUNNERS[runner](exercise, workdir, quiet=quiet)
+ return ret
+
+
+def get_cli() -> argparse.ArgumentParser:
+ parser = argparse.ArgumentParser()
+ runners = list(RUNNERS.keys())
+ if not runners:
+ print('No runners registered!')
+ raise SystemExit(1)
+ parser.add_argument('-q', '--quiet', action='store_true')
+ parser.add_argument('--deprecated', action='store_true', help='include deprecated exercises', dest='include_deprecated')
+ parser.add_argument('--wip', action='store_true', help='include WIP exercises', dest='include_wip')
+ parser.add_argument('-r', '--runner', choices=runners, default=runners[0])
+ parser.add_argument('exercises', nargs='*')
+ return parser
+
+
+def main():
+ opts = get_cli().parse_args()
+ config = Config.load()
+ status_filter = {ExerciseStatus.Active, ExerciseStatus.Beta}
+ if opts.include_deprecated:
+ status_filter.add(ExerciseStatus.Deprecated)
+ if opts.include_wip:
+ status_filter.add(ExerciseStatus.WIP)
+ exercises = config.exercises.all(status_filter)
+ if opts.exercises:
+ # test specific exercises
+ exercises = [
+ e for e in exercises if e.slug in opts.exercises
+ ]
+ not_found = [
+ slug for slug in opts.exercises
+ if not any(e.slug == slug for e in exercises)
+ ]
+ if not_found:
+ for slug in not_found:
+ if slug not in exercises:
+ print(f"unknown or disabled exercise '{slug}'")
+ raise SystemExit(1)
+
+ print(f'TestEnvironment: {sys.executable.capitalize()}')
+ print(f'Runner: {opts.runner}\n\n')
+
+ failures = []
+ for exercise in exercises:
+ print('# ', exercise.slug)
+ if not exercise.test_file:
+ print('FAIL: File with test cases not found')
+ failures.append('{} (FileNotFound)'.format(exercise.slug))
+ else:
+ if check_assignment(exercise, runner=opts.runner, quiet=opts.quiet):
+ failures.append('{} (TestFailed)'.format(exercise.slug))
+ print('')
+
+ if failures:
+ print('FAILURES: ', ', '.join(failures))
+ raise SystemExit(1)
+ else:
+ print('SUCCESS!')
+
+
+if __name__ == "__main__":
+ main()
diff --git a/concepts/aliasing/.meta/config.json b/concepts/aliasing/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/aliasing/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/aliasing/about.md b/concepts/aliasing/about.md
new file mode 100644
index 00000000000..64da09e27bd
--- /dev/null
+++ b/concepts/aliasing/about.md
@@ -0,0 +1 @@
+#TODO: Add about for this concept.
diff --git a/concepts/aliasing/introduction.md b/concepts/aliasing/introduction.md
new file mode 100644
index 00000000000..54ff7637ac4
--- /dev/null
+++ b/concepts/aliasing/introduction.md
@@ -0,0 +1 @@
+#TODO: Add introduction for this concept.
diff --git a/concepts/aliasing/links.json b/concepts/aliasing/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/aliasing/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/anonymous-functions/.meta/config.json b/concepts/anonymous-functions/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/anonymous-functions/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/anonymous-functions/about.md b/concepts/anonymous-functions/about.md
new file mode 100644
index 00000000000..64da09e27bd
--- /dev/null
+++ b/concepts/anonymous-functions/about.md
@@ -0,0 +1 @@
+#TODO: Add about for this concept.
diff --git a/concepts/anonymous-functions/introduction.md b/concepts/anonymous-functions/introduction.md
new file mode 100644
index 00000000000..54ff7637ac4
--- /dev/null
+++ b/concepts/anonymous-functions/introduction.md
@@ -0,0 +1 @@
+#TODO: Add introduction for this concept.
diff --git a/concepts/anonymous-functions/links.json b/concepts/anonymous-functions/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/anonymous-functions/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/basics/.meta/config.json b/concepts/basics/.meta/config.json
new file mode 100644
index 00000000000..86bb653b158
--- /dev/null
+++ b/concepts/basics/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "Python is a dynamic and strongly typed programming language in which variables can be bound and re-bound to any data type. It employs both duck typing and gradual typing (via type hints). Python uses significant indentation to denote code blocks and puts strong emphasis on code readability.",
+ "authors": ["BethanyG"],
+ "contributors": ["cmccandless", "PaulT89"]
+}
diff --git a/concepts/basics/about.md b/concepts/basics/about.md
new file mode 100644
index 00000000000..ef873ce418f
--- /dev/null
+++ b/concepts/basics/about.md
@@ -0,0 +1,346 @@
+# basics
+
+Python is a [dynamic and strongly typed][dynamic typing in python] programming language.
+It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints].
+Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally **[everything in Python is an object][everythings an object]**.
+
+Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions.
+
+Python was created by Guido van Rossum and first released in 1991.
+The [Python Software Foundation][psf] manages and directs resources for Python and CPython development and receives proposals for changes to the language from [members][psf membership] of the community via [Python Enhancement Proposals or PEPs][peps].
+
+
+Complete documentation for the current release can be found at [docs.python.org][python docs].
+
+- [Python Tutorial][python tutorial]
+- [Python Library Reference][python library reference]
+- [Python Language Reference][python language reference]
+- [Python HOW TOs][python how tos]
+- [Python FAQs][python faqs]
+- [Python Glossary of Terms][python glossary of terms]
+
+
+
+This first concept introduces 4 major Python language features:
+1. Name Assignment (_variables and constants_),
+2. Functions (_the `def` keyword and the `return` keyword_),
+3. Comments, and
+4. Docstrings.
+
+
+
+~~~~exercism/note
+
+In general, content, tests, and analyzer tooling for the Python track follow the style conventions outlined in [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/) for Python code style, with the additional (strong) suggestion that there be no single letter variable names.
+
+The [zen of Python (PEP 20)][the zen of python] and [What is Pythonic?][what is pythonic] lay out additional philosophies.
+
+On the Python track, [variables][variables] are always written in [`snake_case`][snake case], and constants in `SCREAMING_SNAKE_CASE`
+
+
+[snake case]: https://en.wikipedia.org/wiki/Snake_case
+[the zen of python]: https://www.python.org/dev/peps/pep-0020/
+[variables]: https://realpython.com/python-variables/
+[what is pythonic]: https://blog.startifact.com/posts/older/what-is-pythonic.html
+~~~~
+
+
+## Name Assignment (Variables & Constants)
+
+In Python, there are no keywords used in creating variables or constants.
+Instead, programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables_) to any type of object using the assignment `=` operator: ` = `.
+A name can be reassigned (or re-bound) to different values (different object types) over its lifetime.
+
+For example, `my_first_variable` can be re-assigned many times using `=`, and can refer to different object types with each re-assignment:
+
+
+```python
+>>> my_first_variable = 1 # my_first_variable bound to an integer object of value one.
+>>> my_first_variable = 2 # my_first_variable re-assigned to integer value 2.
+
+>>> print(type(my_first_variable))
+
+
+>>> print(my_first_variable)
+2
+
+>>> my_first_variable = "Now, I'm a string." # You may re-bind a name to a different object type and value.
+>>> print(type(my_first_variable))
+
+
+>>> print(my_first_variable)
+"Now, I'm a string." # Strings can be declared using single or double quote marks.
+
+import collections
+>>> my_first_variable = collections.Counter([1,1,2,3,3,3,4,5,6,7]) # Now my_first_variable has been re-bound to a Counter object.
+>>> print(type(my_first_variable))
+
+
+>>> print(my_first_variable)
+>>> Counter({3: 3, 1: 2, 2: 1, 4: 1, 5: 1, 6: 1, 7: 1})
+```
+
+
+### Constants
+
+Constants are names meant to be assigned only once in a program.
+They should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in the program.
+Using `SCREAMING_SNAKE_CASE` signals that the name should not be re-assigned, or its value mutated.
+
+
+```python
+# All caps signal that this is intended as a constant.
+MY_FIRST_CONSTANT = 16
+
+# Re-assignment will be allowed by the compiler & interpreter,
+# but this is VERY strongly discouraged.
+# Please don't do this, it could create problems in your program!
+MY_FIRST_CONSTANT = "Some other value"
+```
+
+
+## Functions
+
+In Python, units of functionality are encapsulated in [_functions._][functions], which are themselves [objects][objects] (_it's [turtles all the way down][turtles all the way down]_).
+
+Functions can be executed by themselves, passed as arguments to other functions, nested, or bound to a class.
+When functions are bound to a [class][classes] name, they're referred to as [methods][method objects].
+Related functions and classes (_with their methods_) can be grouped together in the same file or module, and imported in part or in whole for use in other programs.
+
+The `def` keyword begins a [function definition][function definition].
+Each function can have zero or more formal [parameters][parameters] in `()` parenthesis, followed by a `:` colon.
+Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_:
+
+
+```python
+# The body of a function is indented by 2 spaces, & prints the sum of the numbers.
+def add_two_numbers(number_one, number_two):
+ total = number_one + number_two
+ print(total)
+
+>>> add_two_numbers(3, 4)
+7
+
+
+# Inconsistent indentation in your code blocks will raise an error.
+>>> def add_three_numbers_misformatted(number_one, number_two, number_three):
+... result = number_one + number_two + number_three # This was indented by 4 spaces.
+... print(result) #this was only indented by 3 spaces
+...
+...
+ File "", line 3
+ print(result)
+ ^
+IndentationError: unindent does not match any outer indentation level
+```
+
+
+Functions _explicitly_ return a value or object via the [`return`][return] keyword:
+
+
+```python
+# Function definition on first line, explicit return used on final line.
+def add_two_numbers(number_one, number_two):
+ return number_one + number_two
+
+
+# Calling the function in the Python terminal returns the sum of the numbers.
+>>> add_two_numbers(3, 4)
+7
+
+# Assigning the function call to a variable and printing
+# the variable will also return the value.
+>>> sum_with_return = add_two_numbers(5, 6)
+>>> print(sum_with_return)
+11
+```
+
+Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object.
+The details of `None` will be covered in a later exercise.
+For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null:
+
+
+```python
+# This function does not have an explicit return.
+def add_two_numbers(number_one, number_two):
+ result = number_one + number_two
+
+
+# Calling the function in the Python terminal appears
+# to not return anything at all.
+>>> add_two_numbers(5, 7)
+>>>
+
+
+# Using print() with the function call shows that
+# the function is actually returning the **None** object.
+>>> print(add_two_numbers(5, 7))
+None
+
+
+# Assigning the function call to a variable and printing
+# the variable will also show None.
+>>> sum_without_return = add_two_numbers(5, 6)
+>>> print(sum_without_return)
+None
+```
+
+
+### Calling Functions
+
+Functions are [_called_][calls] or invoked using their name followed by `()`.
+Dot (`.`) notation is used for calling functions defined inside a class or module.
+
+```python
+>>> def number_to_the_power_of(number_one, number_two):
+ return number_one ** number_two
+...
+
+>>> number_to_the_power_of(3,3) # Invoking the function with the arguments 3 and 3.
+27
+
+
+# A mis-match between the number of parameters and the number of arguments will raise an error.
+>>> number_to_the_power_of(4,)
+...
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: number_to_the_power_of() missing 1 required positional argument: 'number_two'
+
+
+# Calling methods or functions in classes and modules.
+>>> start_text = "my silly sentence for examples."
+>>> str.upper(start_text) # Calling the upper() method for the built-in str class.
+"MY SILLY SENTENCE FOR EXAMPLES."
+
+# Importing the math module
+import math
+
+>>> math.pow(2,4) # Calling the pow() function from the math module
+>>> 16.0
+```
+
+
+## Comments
+
+[Comments][comments] in Python start with a `#` that is not part of a string, and end at line termination.
+Unlike many other programming languages, Python **does not support** multi-line comment marks.
+Each line of a comment block must start with the `#` character.
+
+Comments are ignored by the interpreter:
+
+
+```python
+# This is a single line comment.
+
+x = "foo" # This is an in-line comment.
+
+# This is a multi-line
+# comment block over multiple lines --
+# these should be used sparingly.
+```
+
+
+## Docstrings
+
+The first statement of a function body can optionally be a [_docstring_][docstring], which concisely summarizes the function or object's purpose.
+Docstrings are declared using triple double quotes (""") indented at the same level as the code block:
+
+
+```python
+
+# An example from PEP257 of a multi-line docstring.
+def complex(real=0.0, imag=0.0):
+ """Form a complex number.
+
+ Keyword arguments:
+ real -- the real part (default 0.0)
+ imag -- the imaginary part (default 0.0)
+ """
+
+ if imag == 0.0 and real == 0.0:
+ return complex_zero
+
+```
+
+
+Docstrings are read by automated documentation tools and are returned by calling the special attribute `.__doc__` on the function, method, or class name.
+They are recommended for programs of any size where documentation is needed, and their conventions are laid out in [PEP257][pep257].
+
+Docstrings can also function as [lightweight unit tests][doctests], which can be read and run by PyTest, or by importing the `doctest` module.
+Testing and `doctest` will be covered in a later concept.
+
+
+```python
+# An example on a user-defined function.
+>>> def number_to_the_power_of(number_one, number_two):
+ """Raise a number to an arbitrary power.
+
+ :param number_one: int the base number.
+ :param number_two: int the power to raise the base number to.
+ :return: int - number raised to power of second number
+
+ Takes number_one and raises it to the power of number_two, returning the result.
+ """
+
+ return number_one ** number_two
+...
+
+# Calling the .__doc__ attribute of the function and printing the result.
+>>> print(number_to_the_power_of.__doc__)
+Raise a number to an arbitrary power.
+
+ :param number_one: int the base number.
+ :param number_two: int the power to raise the base number to.
+ :return: int - number raised to power of second number
+
+ Takes number_one and raises it to the power of number_two, returning the result.
+
+
+
+# Printing the __doc__ attribute for the built-in type: str.
+>>> print(str.__doc__)
+str(object='') -> str
+str(bytes_or_buffer[, encoding[, errors]]) -> str
+
+Create a new string object from the given object. If encoding or
+errors is specified, then the object must expose a data buffer
+that will be decoded using the given encoding and error handler.
+Otherwise, returns the result of object.__str__() (if defined)
+or repr(object).
+encoding defaults to sys.getdefaultencoding().
+errors defaults to 'strict'.
+```
+
+[PEP257]: https://www.python.org/dev/peps/pep-0257/
+[calls]: https://docs.python.org/3/reference/expressions.html#calls
+[classes]: https://docs.python.org/3/reference/datamodel.html#classes
+[comments]: https://realpython.com/python-comments-guide/#python-commenting-basics
+[docstring]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings
+[doctests]: https://docs.python.org/3/library/doctest.html
+[duck typing]: https://en.wikipedia.org/wiki/Duck_typing
+[dynamic typing in python]: https://stackoverflow.com/questions/11328920/is-python-strongly-typed
+[everythings an object]: https://docs.python.org/3/reference/datamodel.html
+[facts-and-myths-about-python-names]: https://nedbatchelder.com/text/names.html
+[function definition]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions
+[functions]: https://docs.python.org/3/reference/compound_stmts.html#function
+[gradual typing]: https://en.wikipedia.org/wiki/Gradual_typing
+[method objects]: https://docs.python.org/3/c-api/method.html#method-objects
+[module]: https://docs.python.org/3/tutorial/modules.html
+[none]: https://docs.python.org/3/library/constants.html
+[objects]: https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy
+[parameters]: https://docs.python.org/3/glossary.html#term-parameter
+[peps]: https://www.python.org/dev/peps/
+[psf membership]: https://www.python.org/psf/membership/
+[psf]: https://www.python.org/psf/
+[python docs]: https://docs.python.org/3/
+[python faqs]: https://docs.python.org/3/faq/index.html
+[python glossary of terms]: https://docs.python.org/3/glossary.html
+[python how tos]: https://docs.python.org/3/howto/index.html
+[python language reference]: https://docs.python.org/3/reference/index.html
+[python library reference]: https://docs.python.org/3/library/index.html
+[python tutorial]: https://docs.python.org/3/tutorial/index.html
+[return]: https://docs.python.org/3/reference/simple_stmts.html#return
+[significant indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation
+[turtles all the way down]: https://en.wikipedia.org/wiki/Turtles_all_the_way_down
+[type hints]: https://docs.python.org/3/library/typing.html
diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md
new file mode 100644
index 00000000000..818dd47deac
--- /dev/null
+++ b/concepts/basics/introduction.md
@@ -0,0 +1,181 @@
+# Introduction
+
+Python is a [dynamic and strongly typed][dynamic typing in python] programming language.
+It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints].
+Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions.
+
+Python was created by Guido van Rossum and first released in 1991.
+
+Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally **[everything in Python is an object][everythings an object]**.
+
+We'll dig more into what all of that means as we continue through the Python track concepts.
+
+This first concept (`basics`) introduces 4 major Python language features:
+1. Name Assignment (_variables and constants_),
+2. Functions (_the `def` keyword and the `return` keyword_),
+3. Comments, and
+4. Docstrings.
+
+
+
+## Name Assignment (Variables & Constants)
+
+Programmers can bind [_names_][facts-and-myths-about-python-names] (also called _variables_) to any type of object using the assignment `=` operator: ` = `.
+A name can be reassigned (or re-bound) to different values (different object types) over its lifetime:
+
+
+```python
+>>> my_first_variable = 1 # my_first_variable bound to an integer object of value one.
+>>> my_first_variable = 2 # my_first_variable re-assigned to integer value 2.
+
+>>> print(type(my_first_variable))
+
+
+>>> print(my_first_variable)
+2
+
+>>> my_first_variable = "Now, I'm a string." # You may re-bind a name to a different object type and value.
+>>> print(type(my_first_variable))
+
+
+>>> print(my_first_variable)
+"Now, I'm a string." # Strings can be declared using single or double quote marks.
+```
+
+
+### Constants
+
+Constants are names meant to be assigned only once in a program — although Python will not prevent re-assignment.
+Using `SCREAMING_SNAKE_CASE` signals to anyone reading the code that the name should **not** be re-assigned, or its value mutated.
+Constants should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in a program.
+
+
+
+## Functions
+
+The `def` keyword begins a [function definition][function definition].
+Each function can have zero or more formal [parameters][parameters] in `()` parenthesis, followed by a `:` colon.
+Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_.
+
+
+```python
+# The body of this function is indented by 2 spaces,& prints the sum of the numbers.
+def add_two_numbers(number_one, number_two):
+ total = number_one + number_two
+ print(total)
+
+>>> add_two_numbers(3, 4)
+7
+
+
+# Inconsistent indentation in your code blocks will raise an error.
+>>> def add_three_numbers_misformatted(number_one, number_two, number_three):
+... result = number_one + number_two + number_three # This was indented by 4 spaces.
+... print(result) #this was only indented by 3 spaces
+...
+...
+ File "", line 3
+ print(result)
+ ^
+IndentationError: unindent does not match any outer indentation level
+```
+
+
+Functions _explicitly_ return a value or object via the [`return`][return] keyword:
+
+
+```python
+# Function definition on first line, explicit return used on final line.
+def add_two_numbers(number_one, number_two):
+ return number_one + number_two
+
+
+# Calling the function in the Python terminal returns the sum of the numbers.
+>>> add_two_numbers(3, 4)
+7
+
+# Assigning the function call to a variable and printing
+# the variable will also return the value.
+>>> sum_with_return = add_two_numbers(5, 6)
+>>> print(sum_with_return)
+11
+```
+
+
+Functions that do not have an _explicit_ `return` expression will _implicitly_ return the [`None`][none] object.
+This means that if you do not use `return` in a function, Python will return the `None` object for you.
+The details of `None` will be covered in a later exercise.
+For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null:
+
+
+```python
+# This function does not have an explicit return.
+def add_two_numbers(number_one, number_two):
+ result = number_one + number_two
+
+
+# Calling the function in the Python terminal appears
+# to not return anything at all.
+>>> add_two_numbers(5, 7)
+>>>
+
+
+# Using print() with the function call shows that
+# the function is actually returning the **None** object.
+>>> print(add_two_numbers(5, 7))
+None
+
+
+# Assigning the function call to a variable and printing
+# the variable will also show None.
+>>> sum_without_return = add_two_numbers(5, 6)
+>>> print(sum_without_return)
+None
+```
+
+
+## Comments
+
+[Comments][comments] in Python start with a `#` that is not part of a string, and end at line termination.
+Unlike many other programming languages, Python **does not support** multi-line comment marks.
+Each line of a comment block must start with the `#` character.
+
+
+## Docstrings
+
+The first statement of a function body can optionally be a [_docstring_][docstring], which concisely summarizes the function or object's purpose.
+Docstring conventions are laid out in [PEP257][pep257].
+Docstrings are declared using triple double quotes (""") indented at the same level as the code block:
+
+
+```python
+
+# An example from PEP257 of a multi-line docstring.
+def complex(real=0.0, imag=0.0):
+ """Form a complex number.
+
+ Keyword arguments:
+ real -- the real part (default 0.0)
+ imag -- the imaginary part (default 0.0)
+ """
+
+ if imag == 0.0 and real == 0.0:
+ return complex_zero
+
+```
+
+[pep257]: https://www.python.org/dev/peps/pep-0257/
+[comments]: https://realpython.com/python-comments-guide/#python-commenting-basics
+[docstring]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings
+[duck typing]: https://en.wikipedia.org/wiki/Duck_typing
+[dynamic typing in python]: https://stackoverflow.com/questions/11328920/is-python-strongly-typed
+[everythings an object]: https://docs.python.org/3/reference/datamodel.html
+[facts-and-myths-about-python-names]: https://nedbatchelder.com/text/names.html
+[function definition]: https://docs.python.org/3/tutorial/controlflow.html#defining-functions
+[gradual typing]: https://en.wikipedia.org/wiki/Gradual_typing
+[module]: https://docs.python.org/3/tutorial/modules.html
+[none]: https://docs.python.org/3/library/constants.html
+[parameters]: https://docs.python.org/3/glossary.html#term-parameter
+[return]: https://docs.python.org/3/reference/simple_stmts.html#return
+[type hints]: https://docs.python.org/3/library/typing.html
+[significant indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation
diff --git a/concepts/basics/links.json b/concepts/basics/links.json
new file mode 100644
index 00000000000..1d1d640c9e7
--- /dev/null
+++ b/concepts/basics/links.json
@@ -0,0 +1,42 @@
+[
+ {
+ "url": "https://lerner.co.il/2019/06/18/understanding-python-assignment/",
+ "description": "Reuven Lerner: Understanding Python Assignment"
+ },
+ {
+ "url": "https://www.youtube.com/watch?v=owglNL1KQf0",
+ "description": "Sentdex (YouTube): Python 3 Programming Tutorial - Functions"
+ },
+ {
+ "url": "https://realpython.com/documenting-python-code/#commenting-vs-documenting-code",
+ "description": "Real Python: Commenting vs Documenting Code."
+ },
+ {
+ "url": "https://www.pythonmorsels.com/everything-is-an-object/",
+ "description": "Python Morsels: Everything is an Object"
+ },
+ {
+ "url": "https://eli.thegreenplace.net/2012/03/23/python-internals-how-callables-work/",
+ "description": "Eli Bendersky: Python internals: how callables work"
+ },
+ {
+ "url": "https://stackoverflow.com/questions/11328920/is-python-strongly-typed",
+ "description": "dynamic typing and strong typing"
+ },
+ {
+ "url": "https://docs.python.org/3/library/typing.html",
+ "description": "type hints"
+ },
+ {
+ "url": "https://docs.python.org/3/reference/lexical_analysis.html#indentation",
+ "description": "significant indentation"
+ },
+ {
+ "url": "https://www.digitalocean.com/community/tutorials/how-to-write-doctests-in-python",
+ "description": "DigitalOcean: How to Write Doctests in Python."
+ },
+ {
+ "url": "https://nedbatchelder.com/blog/201803/is_python_interpreted_or_compiled_yes.html",
+ "description": "Ned Batchelder: Is Python Interpreted or Compiled? Yes."
+ }
+]
diff --git a/concepts/binary-data/.meta/config.json b/concepts/binary-data/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/binary-data/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/binary-data/about.md b/concepts/binary-data/about.md
new file mode 100644
index 00000000000..64da09e27bd
--- /dev/null
+++ b/concepts/binary-data/about.md
@@ -0,0 +1 @@
+#TODO: Add about for this concept.
diff --git a/concepts/binary-data/introduction.md b/concepts/binary-data/introduction.md
new file mode 100644
index 00000000000..54ff7637ac4
--- /dev/null
+++ b/concepts/binary-data/introduction.md
@@ -0,0 +1 @@
+#TODO: Add introduction for this concept.
diff --git a/concepts/binary-data/links.json b/concepts/binary-data/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/binary-data/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/binary-octal-hexadecimal/.meta/config.json b/concepts/binary-octal-hexadecimal/.meta/config.json
new file mode 100644
index 00000000000..6e2a15b607a
--- /dev/null
+++ b/concepts/binary-octal-hexadecimal/.meta/config.json
@@ -0,0 +1,4 @@
+{
+ "blurb": "Other numerical systems in Python: binary (0b11), octal (0o71), and hex (0xFF)",
+ "authors": ["BethanyG", "meatball133"]
+}
diff --git a/concepts/binary-octal-hexadecimal/about.md b/concepts/binary-octal-hexadecimal/about.md
new file mode 100644
index 00000000000..a7fca3714e3
--- /dev/null
+++ b/concepts/binary-octal-hexadecimal/about.md
@@ -0,0 +1,221 @@
+# Binary, Octal, and Hexadecimal
+
+Binary, octal, and hexadecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases.
+Binary is base 2, octal is base 8, and hexadecimal is base 16.
+Normal integers are base 10 in python.
+Binary, octal, and hexadecimal are all representations of integers.
+Which means that they represent positive and negative numbers (_including zero_) without fractions or decimals, and support all the operations that we can do with integers.
+
+## Binary
+
+[Binary][binary] is a base 2 numeral system, using only the digits 0 and 1.
+It commonly represents the 0 ("off") and 1 ("on") states of electrical flow through transistors and switches in computers, as well as the positive and negative charges in magnetic storage media.
+Binary can represent all the integers that are used in base 10.
+
+A snippet from the base 2 system looks like this, although it continues infinitely and doesn't stop at 128:
+
+| 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
+| -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| 2 \*\* 7 | 2 \*\* 6 | 2 \*\* 5 | 2 \*\* 4 | 2 \*\* 3 | 2 \*\* 2 | 2 \*\* 1 | 2 \*\* 0 |
+
+So if we want to represent the number 6, it would in binary be: 110
+
+| Place value | 4 | 2 | 1 |
+| ------------- | --- | --- | --- |
+| Binary number | 1 | 1 | 0 |
+
+And the operation would be: `4 + 2 + 0 = 6`
+
+Another example: 19
+
+| Place value | 16 | 8 | 4 | 2 | 1 |
+| ------------- | --- | --- | --- | --- | --- |
+| Binary number | 1 | 0 | 0 | 1 | 1 |
+
+The binary number would be: 10011
+And the operation would be: `16 + 0 + 0 + 2 + 1 = 19`
+
+## Binary in Python
+
+In Python, we can represent binary literals using the `0b` prefix.
+If we write `0b10011`, Python will interpret it as a binary number and convert it to base 10.
+
+```python
+# 0b10011
+>>> 0b10011
+19
+
+>>> type(0b10011)
+
+```
+
+Binary in Python is just a different way of writing an integer and so the binary representation **is an integer** for all mathematical operations.
+
+If you write a number with a `0b` prefix that is not in the binary system, it will raise a `SyntaxError`.
+
+```python
+Traceback (most recent call last):
+ File "c:\binary.py", line 1, in
+ 0b10211
+SyntaxError: invalid digit '2' in binary literal
+```
+
+### Operations with Binary Numbers
+
+Since binary numbers are integers, we can perform all operations on them that we can with integers.
+
+```python
+# addition
+>>> 0b10011 + 0b10011
+38
+
+# multiplication
+>>> 0b10011 * 0b10011
+361
+```
+
+We can also perform operations between both binary and integer representations.
+However, the usual mathematical operator rules apply: dividing two binary numbers or integer numbers will return a `float`, even if the division does not result in a decimal portion.
+
+```python
+>>> 0b10011 + 19
+38
+
+>>> 0b10011/0b10011
+1.0
+
+>>> 0b10011/3
+6.333333333333333
+
+### Converting to and from Binary Representation
+
+Python will automatically convert a binary literal into `int`.
+ To convert an `int` into a binary representation, use the built-in [`bin()`][bin] function.
+`bin()` will return a `str` of the binary equivalent with the prefix `0b` .
+
+```python
+>>> bin(19)
+'0b10011'
+```
+
+To convert a binary literal to an integer, we can use the built-in `int()` function, and pass a string of the binary representation and a base argument:
+
+```python
+>>> int("0b10011", 2)
+19
+```
+
+Giving the wrong base (_or an invalid binary representation_) will raise a `ValueError`:
+
+```python
+Traceback (most recent call last):
+ File "c:\binary.py", line 4, in
+ int("0b10011", 3)
+ValueError: invalid literal for int() with base 3: '0b10011'
+```
+
+### Binary Methods
+
+There are also some special [methods][numeral-systems] that we can use on binary numbers.
+
+
+[`.bit_length()`][bit_length] will return the number of bits that are needed to represent the number:
+
+```python
+>>> 0b11011.bit_length()
+5
+```
+
+
+[`.bit_count()`][bit_count] will return the number of **ones** in the binary number.
+For example, `bit_count()` on '0b11011' will return 4:
+
+```python
+>>> 0b11011.bit_count()
+4
+~~~~exercism/note
+If you are working locally, `bit_count()` requires at least Python 3.10.
+The Exercism online editor currently supports all features through Python 3.11.
+~~~~
+
+
+## Octal
+
+[Octal][octal] is a base 8 numeral system.
+It uses the digits 0, 1, 2, 3, 4, 5, 6, and 7.
+
+In Python, we can represent octal numbers using the `0o` prefix.
+As with binary, Python automatically converts an octal representation to an `int`.
+
+```python
+# 0o123
+>>> 0o123
+83
+```
+
+As with binary, octal numbers **are ints** and support all integer operations.
+Prefixing a number with `0o` that is not in the octal system will raise a `SyntaxError`.
+
+ ### Converting to and from Octal Representation
+
+
+To convert an `int` into an octal representation, you can use the built-in [`oct()`][oct] function.
+This acts similarly to the `bin()` function, returning a string:
+
+```python
+>>> oct(83)
+'0o123'
+
+To convert an octal number to an integer, we can use the `int()` function, passing an octal string representation and the base (8) as arguments:
+
+```python
+>>> int("0o123", 8)
+83
+```
+
+As with binary, giving the wrong base will raise a `ValueError`.
+
+### Hexadecimal
+
+[Hexadecimal][hexadecimal] is a base 16 numeral system.
+It uses the digits 0 - 9 and the letters A, B, C, D, E, and F.
+A is 10, B is 11, C is 12, D is 13, E is 14, and F is 15.
+
+We can represent hexadecimal numbers in Python using the `0x` prefix.
+As with binary and octal, Python will automatically convert hexadecimal literals to `int`.
+
+```python
+# 0x123
+>>> 0x123
+291
+```
+
+As with binary and octal - hexadecimal literals **are ints**, and you can perform all integer operations.
+Prefixing a non-hexadecimal number with `0x` will raise a `SyntaxError`.
+
+
+### Converting to and from Hexadecimal Representation
+
+To convert an `int` into a hexadecimal representation, you can use the built-in [`hex()`][hex] function.
+This acts similarly to the `bin()` function, returning a string:
+
+```python
+>>> hex(291)
+'0x123'
+
+To convert a hexadecimal representation to an integer, we can use the `int()` function, passing a hexadecimal string with the base (16) as arguments:
+
+```python
+>>> int("0x123", 16)
+291
+```
+
+As with binary and octal, giving the wrong base will raise a `ValueError`.
+
+
+[binary]: https://en.wikipedia.org/wiki/Binary_number
+[bit_count]: https://docs.python.org/3/library/stdtypes.html#int.bit_count
+[bit_length]: https://docs.python.org/3/library/stdtypes.html#int.bit_length
+[hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal
+[numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system
+[octal]: https://en.wikipedia.org/wiki/Octal
diff --git a/concepts/binary-octal-hexadecimal/introduction.md b/concepts/binary-octal-hexadecimal/introduction.md
new file mode 100644
index 00000000000..a06ac922faf
--- /dev/null
+++ b/concepts/binary-octal-hexadecimal/introduction.md
@@ -0,0 +1,11 @@
+# binary, octal, hexadecimal
+
+Binary, octal, and hexadecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases.
+Binary is base 2, octal is base 8, and hexadecimal is base 16.
+Normal integers are base 10 in python.
+Binary, octal, and hexadecimal literals are all considered `int` subtypes and Python automatically converts between them.
+This means that they can only represent zero, positive, and negative numbers that do not have a fractional or decimal part.
+Binary, octal, and hexadecimal numbers support all integer operations.
+However, division (_as with ints_) will return a `float`.
+
+[numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system
diff --git a/concepts/binary-octal-hexadecimal/links.json b/concepts/binary-octal-hexadecimal/links.json
new file mode 100644
index 00000000000..8826182cd48
--- /dev/null
+++ b/concepts/binary-octal-hexadecimal/links.json
@@ -0,0 +1,10 @@
+[
+ {
+ "url": "https://towardsdatascience.com/binary-hex-and-octal-in-python-20222488cee1",
+ "description": "Binary, octal, hex in python"
+ },
+ {
+ "url": "https://en.wikipedia.org/wiki/Numeral_system",
+ "description": "Numeral system"
+ }
+]
diff --git a/concepts/bitflags/.meta/config.json b/concepts/bitflags/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/bitflags/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/bitflags/about.md b/concepts/bitflags/about.md
new file mode 100644
index 00000000000..64da09e27bd
--- /dev/null
+++ b/concepts/bitflags/about.md
@@ -0,0 +1 @@
+#TODO: Add about for this concept.
diff --git a/concepts/bitflags/introduction.md b/concepts/bitflags/introduction.md
new file mode 100644
index 00000000000..54ff7637ac4
--- /dev/null
+++ b/concepts/bitflags/introduction.md
@@ -0,0 +1 @@
+#TODO: Add introduction for this concept.
diff --git a/concepts/bitflags/links.json b/concepts/bitflags/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/bitflags/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/bitwise-operators/.meta/config.json b/concepts/bitwise-operators/.meta/config.json
new file mode 100644
index 00000000000..7767ff5d740
--- /dev/null
+++ b/concepts/bitwise-operators/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "Python supports bitwise operations such as left/right shift, and, or, xor, and not.",
+ "authors": ["BethanyG", "colinleach"],
+ "contributors": []
+}
diff --git a/concepts/bitwise-operators/about.md b/concepts/bitwise-operators/about.md
new file mode 100644
index 00000000000..a68e5378f12
--- /dev/null
+++ b/concepts/bitwise-operators/about.md
@@ -0,0 +1,197 @@
+# About
+
+Down at the hardware level, transistors can only be on or off: two states that we traditionally represent with `1` and `0`.
+These are the [`binary digits`][binary-digits], abbreviated as [`bits`][bits].
+Awareness of `bits` and `binary` is particularly important for systems programmers working in low-level languages.
+
+However, for most of the history of computing the programming priority has been to find increasingly sophisticated ways to _abstract away_ this binary reality.
+In Python (and many other [high-level programming languages][high-level-language]), we work with `int`, `float`, `string` and other defined _types_, up to and including audio and video formats.
+We let the Python internals take care of (eventually) translating everything to bits.
+
+Nevertheless, using [bitwise-operators][python-bitwise-operators] and [bitwise operations][python-bitwise-operations] can sometimes have significant advantages in speed and memory efficiency, even in a high-level language like Python.
+
+
+## Entering and Displaying Binary Numbers
+
+Unsurprisingly, Python interacts with the user using decimal numbers, but a programmer can override this default.
+In fact, Python will readily accept an `int` in `binary`, `hexadecimal`, or `octal` format, and will happily perform mathematical operations between them.
+For more details, you can review the [concept:python/binary-octal-hexadecimal]() concept.
+
+Binary numbers are entered with a `0b` prefix, just as `0x` can be used for hexadecimal (_hex numbers are a concise way to represent groups of 4 bits_), and `oct` can be used for octal numbers.
+
+There are multiple ways to convert integers to binary strings, varying in whether they include the `0b` prefix and whether they support left-padding with zeros:
+
+
+```python
+# Binary entry.
+>>> 0b10111
+23
+
+# Converting an int display to binary string, with prefix.
+>>> bin(23)
+'0b10111'
+
+>>> number = 23
+
+# Binary without prefix, padded to 8 digits.
+>>> format(number, '08b')
+'00010111'
+
+# Same format, but using an f-string.
+>>> f"{number} in decimal is {number:08b} in binary and {number:x} in hex"
+'23 in decimal is 00010111 in binary and 17 in hex'
+```
+
+
+## [`Bitwise Logic`][python-bitwise-operations]
+
+In the [concept:python/bools]() concept, we discussed the _logical operators_ `and`, `or` and `not` used with Boolean (_`True` and `False`_) values.
+The same logic rules apply when working with bits.
+
+However, the bitwise equivalents of the logical operators `&` (_and_), `|` (_or_), `~` (_not_), and `^` (_[XOR][xor]_), are applied to each _bit_ in a binary representation, treating `1` as `True` ("on") and `0` as `False` ("off").
+An example with the bitwise `&` might make this clearer:
+
+
+```python
+>>> x = 0b01100110
+>>> y = 0b00101010
+
+>>> format(x & y, '08b')
+'00100010'
+```
+
+Only positions with a `1` in _**both**_ the input numbers are set to `1` in the output.
+
+Bitwise `&` is commonly used as a way to isolate single bits in a compacted set of `True`/`False` values, such as user-configurable settings in an app.
+This enables the value of individual bits to control program logic:
+
+
+```python
+>>> number = 0b0110
+>>> number & 0b0001 > 0
+False
+
+>>> number & 0b0010 > 0
+True
+```
+
+
+For a bitwise `|` (or), a `1` is set in the output if there is a `1` in _**either**_ of the inputs:
+
+
+```python
+>>> x = 0b01100110
+>>> y = 0b00101010
+
+>>> format(x | y, '08b')
+'01101110'
+```
+
+
+With the `^` operator for bitwise e**x**clusive **or** (xor), a `1` is set if it appears in _**either**_ of the inputs _**but not both**_ inputs.
+This symbol might seem familiar from the [concept:python/sets]() concept, where it is used for `set` _symmetric difference_, which is the same as [xor applied to sets][symmetric-difference].
+If xor `^` seems strange, be aware that this is by far the [most common operation in cryptography][xor-cipher].
+
+
+```python
+>>> x = 0b01100110
+>>> y = 0b00101010
+
+>>> format(x ^ y, '08b')
+'01001100'
+```
+
+
+Finally, there is the `~` operator (_the [tilde][tilde] character_), which is a bitwise `not` that takes a single input and _**inverts all the bits**_, which might not be the result you were expecting!
+Each `1` in the representation changes to `0`, and vice versa.
+See the section below for details.
+
+
+## Negative Numbers and Binary Representation
+
+In decimal representation, we distinguish positive and negative numbers by using a `+` or `-` sign to the left of the digits.
+Using these symbols at a binary level proved inefficient for digital computing and raised the problem that `+0` is not the same as `-0`.
+
+Rather than using `-` and `+`, all modern computers use a [`twos-complement`][twos-complement] representation for negative numbers, right down to the silicon chip level.
+This means that all bits are inverted and a number is _**interpreted as negative**_ if the left-most bit (also termed the "most significant bit", or MSB) is a `1`.
+Positive numbers have an MSB of `0`.
+This representation has the advantage of only having one version of zero, so that the programmer doesn't have to manage `-0` and `+0`.
+
+This way of representing negative and positive numbers adds a complication for Python: there are no finite-integer concepts like `int32` or `int64` internally in the core language.
+In 'modern' Python, `int`s are of unlimited size (_limited only by hardware capacity_), and a negative or bit-inverted number has a (_theoretically_) infinite number of `1`'s to the left, just as a positive number has unlimited `0`'s.
+
+This makes it difficult to give a useful example of `bitwise not`:
+
+```python
+>>> x = 0b01100110
+>>> format(x, '08b')
+'01100110'
+
+# This is a negative binary (not twos-complement display).
+>>> format(~x, '08b')
+'-1100111'
+
+ # Decimal representation.
+>>> x
+102
+
+# Using the Bitwise not, with an unintuitive result.
+>>> ~x
+-103
+```
+
+This is **not** the `0b10011001` we would see in languages with fixed-size integers.
+
+The `~` operator only works as expected with _**unsigned**_ byte or integer types, or with fixed-sized integer types.
+These numeric types are supported in third-party packages such as [`NumPy`][numpy], [`pandas`][pandas], and [`sympy`][sympy] but not in core Python.
+
+In practice, Python programmers quite often use the shift operators described below and `& | ^` with positive numbers only.
+Bitwise operations with negative numbers are much less common.
+One technique is to add [`2**32 (or 1 << 32)`][unsigned-int-python] to a negative value to make an `int` unsigned, but this gets difficult to manage.
+Another strategy is to work with the [`ctypes`][ctypes-module] module, and use c-style integer types, but this is equally unwieldy.
+
+
+## [`Shift operators`][bitwise-shift-operators]
+
+The left-shift operator `x << y` simply moves all the bits in `x` by `y` places to the left, filling the new gaps with zeros.
+Note that this is arithmetically identical to multiplying a number by `2**y`.
+
+The right-shift operator `x >> y` does the opposite.
+This is arithmetically identical to integer division `x // 2**y`.
+
+Keep in mind the previous section on negative numbers and their pitfalls when shifting.
+
+
+```python
+>>> x = 8
+>>> format(x, '08b')
+'00001000'
+
+# A left bit shift.
+>>> x << 2
+32
+
+>>> format(x << 2, '08b')
+'00100000'
+
+# A right bit shift.
+>>> format(x >> 2, '08b')
+'00000010'
+```
+
+[binary-digits]: https://www.khanacademy.org/computing/computers-and-internet/xcae6f4a7ff015e7d:digital-information/xcae6f4a7ff015e7d:binary-numbers/v/the-binary-number-system
+[bits]: https://en.wikipedia.org/wiki/Bit
+[bitwise-shift-operators]: https://docs.python.org/3/reference/expressions.html#shifting-operations
+[ctypes-module]: https://docs.python.org/3/library/ctypes.html#module-ctypes
+[high-level-language]: https://en.wikipedia.org/wiki/High-level_programming_language
+[numpy]: https://numpy.org/doc/stable/user/basics.types.html
+[pandas]: https://pandas.pydata.org/docs/reference/arrays.html#nullable-integer
+[python-bitwise-operations]: https://docs.python.org/3/reference/expressions.html#binary-bitwise-operations
+[python-bitwise-operators]: https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations
+[symmetric-difference]: https://math.stackexchange.com/questions/84184/relation-between-xor-and-symmetric-difference#:~:text=It%20is%20the%20same%20thing,they%20are%20indeed%20the%20same.
+[sympy]: https://docs.sympy.org/latest/modules/codegen.html#predefined-types
+[tilde]: https://en.wikipedia.org/wiki/Tilde
+[twos-complement]: https://en.wikipedia.org/wiki/Two%27s_complement#:~:text=Two's%20complement%20is%20the%20most,number%20is%20positive%20or%20negative.
+[unsigned-int-python]: https://stackoverflow.com/a/20768199
+[xor-cipher]: https://en.wikipedia.org/wiki/XOR_cipher
+[xor]: https://stackoverflow.com/a/2451393
diff --git a/concepts/bitwise-operators/introduction.md b/concepts/bitwise-operators/introduction.md
new file mode 100644
index 00000000000..88aba3a6a7b
--- /dev/null
+++ b/concepts/bitwise-operators/introduction.md
@@ -0,0 +1,20 @@
+# Introduction
+
+Down at the hardware level, transistors can only be on or off: two states that we traditionally represent with `1` and `0`.
+These are the [`binary digits`][binary-digits], abbreviated as [`bits`][bits].
+Awareness of `bits` and `binary` is particularly important for systems programmers working in low-level languages.
+
+However, for most of the history of computing the programming priority has been to find increasingly sophisticated ways to _abstract away_ this binary reality.
+
+
+In Python (and many other [high-level programming languages][high-level-language]), we work with `int`, `float`, `string` and other defined _types_, up to and including audio and video formats.
+We let the Python internals take care of (eventually) translating everything to bits.
+
+
+Nevertheless, using [bitwise-operators][python-bitwise-operators] and [bitwise operations][python-bitwise-operations] can sometimes have significant advantages in speed and memory efficiency, even in a high-level language like Python.
+
+[high-level-language]: https://en.wikipedia.org/wiki/High-level_programming_language
+[binary-digits]: https://www.khanacademy.org/computing/computers-and-internet/xcae6f4a7ff015e7d:digital-information/xcae6f4a7ff015e7d:binary-numbers/v/the-binary-number-system
+[bits]: https://en.wikipedia.org/wiki/Bit
+[python-bitwise-operations]: https://docs.python.org/3/reference/expressions.html#binary-bitwise-operations
+[python-bitwise-operators]: https://docs.python.org/3/reference/expressions.html#binary-arithmetic-operations
diff --git a/concepts/bitwise-operators/links.json b/concepts/bitwise-operators/links.json
new file mode 100644
index 00000000000..7c103c84630
--- /dev/null
+++ b/concepts/bitwise-operators/links.json
@@ -0,0 +1,22 @@
+[
+ {
+ "url": "https://wiki.python.org/moin/BitwiseOperators/",
+ "description": "BitwiseOperators on the Python wiki."
+ },
+ {
+ "url": "https://realpython.com/python-bitwise-operators",
+ "description": "Real Python: Bitwise Operators in Python."
+ },
+ {
+ "url": "https://stackoverflow.com/a/20768199",
+ "description": "Stack Overflow: Convert a Python int to an unsigned int."
+ },
+ {
+ "url": "https://www.khanacademy.org/computing/computer-science/cryptography/ciphers/a/xor-bitwise-operation",
+ "description": "Khan Academy: The Ultimate Shift Cipher."
+ },
+ {
+ "url": "https://en.wikipedia.org/wiki/XOR_cipher",
+ "description": "The XOR Cipher"
+ }
+]
diff --git a/concepts/bools/.meta/config.json b/concepts/bools/.meta/config.json
new file mode 100644
index 00000000000..e5a063ec663
--- /dev/null
+++ b/concepts/bools/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "Python represents true and false values with the 'bool' type. There are only two values: 'True' and 'False'. These values can be bound to a variable, used in comparisons, or returned from a function. Bools are a subclass of 'int'.",
+ "authors": ["neenjaw"],
+ "contributors": ["cmccandless", "BethanyG"]
+}
diff --git a/concepts/bools/about.md b/concepts/bools/about.md
new file mode 100644
index 00000000000..a2680fc06b3
--- /dev/null
+++ b/concepts/bools/about.md
@@ -0,0 +1,143 @@
+# About
+
+Python represents true and false values with the [`bool`][bools] type, which is a subtype of `int`.
+ There are only two Boolean values in this type: `True` and `False`.
+ These values can be assigned to a variable and combined with the [Boolean operators][boolean-operators] (`and`, `or`, `not`):
+
+
+```python
+>>> true_variable = True and True
+>>> false_variable = True and False
+
+>>> true_variable = False or True
+>>> false_variable = False or False
+
+>>> true_variable = not False
+>>> false_variable = not True
+```
+
+[Boolean operators][boolean-operators] use _short-circuit evaluation_, which means that expression on the right-hand side of the operator is only evaluated if needed.
+
+Each of the operators has a different precedence, where `not` is evaluated before `and` and `or`.
+ Brackets can be used to evaluate one part of the expression before the others:
+
+```python
+>>>not True and True
+False
+
+>>>not (True and False)
+True
+```
+
+All `boolean operators` are considered lower precedence than Python's [`comparison operators`][comparisons], such as `==`, `>`, `<`, `is` and `is not`.
+
+
+## Type Coercion and Truthiness
+
+The `bool` function ([`bool()`][bool-function]) converts any object to a Boolean value.
+ By default all objects return `True` unless defined to return `False`.
+
+A few `built-ins` are always considered `False` by definition:
+
+- the constants `None` and `False`
+- zero of any _numeric type_ (`int`, `float`, `complex`, `decimal`, or `fraction`)
+- empty _sequences_ and _collections_ (`str`, `list`, `set`, `tuple`, `dict`, `range(0)`)
+
+
+```python
+>>>bool(None)
+False
+
+>>>bool(1)
+True
+
+>>>bool(0)
+False
+
+>>>bool([1,2,3])
+True
+
+>>>bool([])
+False
+
+>>>bool({"Pig" : 1, "Cow": 3})
+True
+
+>>>bool({})
+False
+```
+
+When an object is used in a _boolean context_, it is evaluated transparently as _truthy_ or _falsey_ using `bool()`:
+
+
+```python
+>>> a = "is this true?"
+>>> b = []
+
+# This will print "True", as a non-empty string is considered a "truthy" value
+>>> if a:
+... print("True")
+
+# This will print "False", as an empty list is considered a "falsey" value
+>>> if not b:
+... print("False")
+```
+
+
+Classes may define how they are evaluated in truthy situations if they override and implement a `__bool__()` method, and/or a `__len__()` method.
+
+
+## How Booleans work under the hood
+
+The `bool` type is implemented as a _sub-type_ of _int_.
+ That means that `True` is _numerically equal_ to `1` and `False` is _numerically equal_ to `0`.
+ This is observable when comparing them using an _equality operator_:
+
+
+```python
+>>>1 == True
+True
+
+>>>0 == False
+True
+```
+
+However, `bools` are **still different** from `ints`, as noted when comparing them using the _identity operator_, `is`:
+
+
+```python
+>>>1 is True
+False
+
+>>>0 is False
+False
+```
+
+> Note: in python >= 3.8, using a literal (such as 1, '', [], or {}) on the _left side_ of `is` will raise a warning.
+
+
+It is considered a [Python anti-pattern][comparing to true in the wrong way] to use the equality operator to compare a boolean variable to `True` or `False`.
+ Instead, the identity operator `is` should be used:
+
+
+```python
+
+>>> flag = True
+
+# Not "Pythonic"
+>>> if flag == True:
+... print("This works, but it's not considered Pythonic.")
+
+# A better way
+>>> if flag:
+... print("Pythonistas prefer this pattern as more Pythonic.")
+```
+
+
+[bool-function]: https://docs.python.org/3/library/functions.html#bool
+[bool]: https://docs.python.org/3/library/stdtypes.html#truth
+[Boolean-operators]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
+[comparing to true in the wrong way]: https://docs.quantifiedcode.com/python-anti-patterns/readability/comparison_to_true.html
+[comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons
+
+[bools]: https://docs.python.org/3/library/stdtypes.html#typebool
\ No newline at end of file
diff --git a/concepts/bools/introduction.md b/concepts/bools/introduction.md
new file mode 100644
index 00000000000..af24137025e
--- /dev/null
+++ b/concepts/bools/introduction.md
@@ -0,0 +1,25 @@
+# Introduction
+
+Python represents true and false values with the [`bool`][bools] type, which is a subtype of `int`.
+ There are only two values under that type: `True` and `False`.
+ These values can be bound to a variable:
+
+```python
+>>> true_variable = True
+>>> false_variable = False
+```
+
+We can evaluate Boolean expressions using the `and`, `or`, and `not` operators.
+
+```python
+>>> true_variable = True and True
+>>> false_variable = True and False
+
+>>> true_variable = False or True
+>>> false_variable = False or False
+
+>>> true_variable = not False
+>>> false_variable = not True
+```
+
+[bools]: https://docs.python.org/3/library/stdtypes.html#typebool
\ No newline at end of file
diff --git a/concepts/bools/links.json b/concepts/bools/links.json
new file mode 100644
index 00000000000..70ef7dffb6d
--- /dev/null
+++ b/concepts/bools/links.json
@@ -0,0 +1,34 @@
+[
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#bltin-boolean-values",
+ "description": "boolean values"
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not",
+ "description": "boolean-operators"
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#truth",
+ "description": "Truth Value Testing"
+ },
+ {
+ "url": "https://docs.python.org/3/library/functions.html#bool",
+ "description": "bool() function"
+ },
+ {
+ "url": "https://problemsolvingwithpython.com/04-Data-Types-and-Variables/04.02-Boolean-Data-Type/",
+ "description": "Problem Solving with Python - Boolean Data Type"
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#comparisons",
+ "description": "Comparisons in Python"
+ },
+ {
+ "url": "https://docs.quantifiedcode.com/python-anti-patterns/readability/comparison_to_true.html",
+ "description": "Python Anti-Patterns Comparing things to True in the Wrong Way"
+ },
+ {
+ "url": "https://www.python.org/dev/peps/pep-0285/",
+ "description": "PEP285 - Adding a bool type"
+ }
+]
diff --git a/concepts/bytes/.meta/config.json b/concepts/bytes/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/bytes/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/bytes/about.md b/concepts/bytes/about.md
new file mode 100644
index 00000000000..cbc5cd89d96
--- /dev/null
+++ b/concepts/bytes/about.md
@@ -0,0 +1 @@
+#TODO: Add about for this concept.
diff --git a/concepts/bytes/introduction.md b/concepts/bytes/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/bytes/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/bytes/links.json b/concepts/bytes/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/bytes/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/class-composition/.meta/config.json b/concepts/class-composition/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/class-composition/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/class-composition/about.md b/concepts/class-composition/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/class-composition/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/class-composition/introduction.md b/concepts/class-composition/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/class-composition/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/class-composition/links.json b/concepts/class-composition/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/class-composition/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/class-customization/.meta/config.json b/concepts/class-customization/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/class-customization/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/class-customization/about.md b/concepts/class-customization/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/class-customization/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/class-customization/introduction.md b/concepts/class-customization/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/class-customization/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/class-customization/links.json b/concepts/class-customization/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/class-customization/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/class-inheritance/.meta/config.json b/concepts/class-inheritance/.meta/config.json
new file mode 100644
index 00000000000..f6bf6d7d87f
--- /dev/null
+++ b/concepts/class-inheritance/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "In Object Oriented Programming ('OOP'), inheritance refers to a class's capacity to copy or 'inherit' properties and methods from another class. Python allows inheritance from a single class or multiple classes. Existing classes are known as base (or parent) classes, and new classes are known as derived (or child) classes.",
+ "authors": ["girijakar", "bethanyg"],
+ "contributors": [ ]
+}
diff --git a/concepts/class-inheritance/about.md b/concepts/class-inheritance/about.md
new file mode 100644
index 00000000000..9f1bdf30cd9
--- /dev/null
+++ b/concepts/class-inheritance/about.md
@@ -0,0 +1,168 @@
+# About
+
+Inheritance is one of the ['four pillars'][four-pillars] of Object Oriented Programming (`OOP`).
+In situations where only a small amount of functionality needs to be customized for a new class, `inheritance` allows code re-use from one or more parent classes, and can help make programs cleaner and more maintainable.
+
+## Inheritance
+
+`Inheritance` describes `is a kind of` relationship between two or more classes, abstracting common details into super (_base_ or _parent_) class and storing specific ones in the subclass (_derived class_ or _child class_).
+
+To create a child class, specify the parent class name inside the pair of parenthesis, followed by its name.
+Example
+```python
+class Child(Parent):
+ pass
+```
+Every child class inherits all the behaviors (_attributes, constructors, methods_) exhibited by their parent class.
+
+
+## Single Inheritance
+
+When a derived (or child) class inherits only from one base (or parent) class, it is known as _single inheritance_.
+
+
+```python
+# The parent or base class.
+class Person:
+
+ def __init__(self, fname, lname):
+ self.fname = fname
+ self.lname = lname
+
+# The child or derived class, inheriting from Person.
+class Employee(Person):
+
+ all_employees = []
+ def __init__(self, fname, lname, empid):
+ # Using the Parent constructor to create the base object.
+ Person.__init__(self, fname, lname)
+
+ # Adding an attribute specific to the Child class.
+ self.empid = empid
+
+ Employee.all_employees.append(self)
+```
+`Employee` class is derived from `Person`.
+Now, we can create an `Employee` object.
+
+
+```python
+...
+p1 = Person('George', 'smith')
+print(p1.fname, '-', p1.lname)
+e1 = Employee('Jack', 'simmons', 456342)
+e2 = Employee('John', 'williams', 123656)
+print(e1.fname, '-', e1.empid)
+print(e2.fname, '-', e2.empid)
+```
+After running the program we will get the following output
+```bash
+
+George - smith
+Jack - 456342
+John - 123656
+```
+## Multiple Inheritance
+As we've seen, `single inheritance` is where a class inherits directly from another class.
+On the other side, `multiple inheritance` is a Python feature that allows a child class to inherit characteristics and methods from more than one parent class.
+
+```python
+class SubclassName(BaseClass1, BaseClass2, ...):
+ pass
+```
+### Multiple Inheritance and the Diamond Problem
+
+The "diamond problem" (also known as the "deadly diamond of death") refers to an ambiguity that occurs when two classes B and C inherit from a superclass A, while another class D inherits from both B and C. If A has a method "m" that B or C (or even both of them) has overridden, and if it does not override this method, the question becomes which version of the method D inherits. It's possible that it's from A, B, or C.
+Let's have a look at the problem using an example:
+
+```python
+class A:
+ def m(self):
+ print("m of A called")
+class B(A):
+ def m(self):
+ print("m of B called")
+class C(A):
+ def m(self):
+ print("m of C called")
+class D(B,C):
+ pass
+```
+If we call an instance x of class D, we will get the output as `m of B called`. But if we interchange the order of inheritance in class D i.e. `Class D(C, D)`. We will get the output as `m of C called`.
+To solve the diamond problem in python, we will look into a new method `mro()`.
+### Method resolution order(MRO)
+
+To get the method resolution order of a class we can use either `__mro__` attribute or `mro()` method. By using these methods we can display the order in which methods are resolved. For Example
+
+```python
+class A:
+ def m(self):
+ print(" m of A called")
+class B:
+ def m(self):
+ print(" m of B called")
+
+# classes ordering
+class C(A, B):
+ def __init__(self):
+ print("Constructor C")
+
+r = C()
+
+# it prints the lookup order
+print(C.__mro__)
+print(C.mro())
+```
+The output
+```cmd
+Constructor C
+(, , , )
+[, , , ]
+```
+### Mixins
+A mixin is a type of multiple inheritance that is unique. Mixins are typically employed in one of two scenarios:
+
+1. We wish to give a class a number of optional features.
+1. We want to use a specific feature in a variety of classes.
+
+For example
+```python
+class A1(object):
+ def method(self):
+ return 1
+
+class A2(object):
+ def method(self):
+ return 2
+
+class B1(object):
+ def usesMethod(self):
+ return self.method() + 10
+
+class B2(object):
+ def usesMethod(self):
+ return self.method() + 20
+
+class C1_10(A1, B1): pass
+class C1_20(A1, B2): pass
+class C2_10(A2, B1): pass
+class C2_20(A2, B2): pass
+```
+Mixins helps us to recombine functionalities with different choices of base classes.
+#### Pros and Cons of Mixins
+| Advantages | Disadvantages |
+|:-- | :-- |
+|Mixin classes tend to be simple because they represent simple orthogonal concepts. | Execution of statements at run time tends to jump around in different mixins, making it hard to follow and debug|
+|Helps us to recombine functionalities with different choices | Potential for long compile times|
+## __super()__
+In a nutshell, `super()` gives us access to methods in a superclass from the subclass that inherits from it.
+`super()` by itself returns a temporary object of the superclass, which may subsequently be used to call the methods of that superclass.
+
+But why we want to use `super()`?
+
+Using `super()` to call already created methods avoids having to rebuild those methods in our subclass and allows us to swap out superclasses with little code modifications.
+
+[four-pillars]: https://www.educative.io/edpresso/what-are-the-four-pillars-of-oops-in-python
+
+[four-pillars]: https://www.educative.io/edpresso/what-are-the-four-pillars-of-oops-in-python
+
diff --git a/concepts/class-inheritance/introduction.md b/concepts/class-inheritance/introduction.md
new file mode 100644
index 00000000000..fb1cfff6e45
--- /dev/null
+++ b/concepts/class-inheritance/introduction.md
@@ -0,0 +1,7 @@
+# Introduction
+
+[Inheritance](inherit) represents what is known as a relationship. When a Derived class inherits from a Base class, you've established a relationship in which Derived is a specialised version of Base.
+Either by using single or multiple inheritance, we can inherit the properties from the base class. Inheritance is used because it helps in code reusability.
+
+
+[inherit]:https://realpython.com/inheritance-composition-python/#whats-inheritance
diff --git a/concepts/class-inheritance/links.json b/concepts/class-inheritance/links.json
new file mode 100644
index 00000000000..034b9b39359
--- /dev/null
+++ b/concepts/class-inheritance/links.json
@@ -0,0 +1,42 @@
+[
+ {
+ "url": "https://docs.python.org/3/tutorial/classes.html",
+ "description": "Classes in python"
+ },
+ {
+ "url": "https://www.geeksforgeeks.org/access-modifiers-in-python-public-private-and-protected/",
+ "description": "Access Modifiers in python"
+ },
+ {
+ "url": "https://www.geeksforgeeks.org/accessor-and-mutator-methods-in-python/",
+ "description": "Functions in classes"
+ },
+ {
+ "url": "https://realpython.com/inheritance-composition-python/#whats-inheritance",
+ "description": "About inheritance"
+ },
+ {
+ "url": "https://python-course.eu/oop/multiple-inheritance.php",
+ "description": "Multiple inheritance and Diamond inheritance problem"
+ },
+ {
+ "url": "https://stackoverflow.com/questions/533631/what-is-a-mixin-and-why-are-they-useful",
+ "description": "Python mixins"
+ },
+ {
+ "url": "https://raganwald.com/2016/07/16/why-are-mixins-considered-harmful.html",
+ "description": "Mixins cons"
+ },
+ {
+ "url": "https://gaopinghuang0.github.io/2018/12/29/dig-into-python-super-and-mro",
+ "description": "Super and mixins functions"
+ },
+ {
+ "url": "https://www.python.org/download/releases/2.3/mro/",
+ "description": "MRO"
+ },
+ {
+ "url": "https://nedbatchelder.com/blog/201210/multiple_inheritance_is_hard.html",
+ "description": "Multiple inheritancs and mixins"
+ }
+]
diff --git a/concepts/class-interfaces/.meta/config.json b/concepts/class-interfaces/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/class-interfaces/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/class-interfaces/about.md b/concepts/class-interfaces/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/class-interfaces/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/class-interfaces/introduction.md b/concepts/class-interfaces/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/class-interfaces/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/class-interfaces/links.json b/concepts/class-interfaces/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/class-interfaces/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/classes/.meta/config.json b/concepts/classes/.meta/config.json
new file mode 100644
index 00000000000..f1dafcc7829
--- /dev/null
+++ b/concepts/classes/.meta/config.json
@@ -0,0 +1,7 @@
+{
+ "blurb": "Classes are used to create 'instances' (copies) of bundled data and behavior, commonly known as objects. Class instances are integral to Object Oriented Programming (OOP), in which programs are made up of objects that interact with one another, keep state, and act upon data.",
+ "authors": ["bethanyg", "DjangoFett", "PaulT89"],
+ "contributors": ["IsaacG"]
+}
+
+
diff --git a/concepts/classes/about.md b/concepts/classes/about.md
new file mode 100644
index 00000000000..11b03643543
--- /dev/null
+++ b/concepts/classes/about.md
@@ -0,0 +1,325 @@
+# About
+
+`Classes` combine data with behavior.
+Classes are used to create copies or `instances` of bundled data and behavior, commonly known as `objects`.
+Objects can represent real world entities (_such as cars or cats_) - or more abstract concepts (_such as integers, vehicles, or mammals_).
+Classes are integral to an [object oriented programming][oop] (OOP) approach, which asks the programmer to think about modeling a problem as one or more objects that interact with one another, keep state, and act upon data.
+
+## Classes
+
+Classes are the definitions of new _object types_, and from which new `instances` of objects are created.
+They often bundle data (_known as `fields`, `attributes`, `properties`, `data members`, or `variables`_) with code or functions (_known as `methods`_) that operate on that data.
+In this sense, a class is a _blueprint_ or a set of instructions from which many objects of a similar type can be built and used.
+A complex program can have many classes, each building many different flavors of objects.
+The process of building and customizing an object from a class is known as `instantiation` (_or creating an instance of the class_).
+
+A class definition in Python is straightforward:
+
+```python
+class MyClass:
+ # Class body goes here
+```
+
+### Class Attributes
+
+Class fields (_otherwise known as properties, attributes, data members, or variables_) can be added to the body of the class.
+
+```python
+class MyClass:
+ number = 5
+ string = "Hello!"
+```
+
+An instance (_object_) of `MyClass` can be created and bound to a name:
+
+```python
+>>> new_object = MyClass()
+
+# Class is instantiated and resulting object is bound to the "new_object" name (variable).
+# Note: the object address 'at 0x15adc55b0' will vary by computer.
+>>> new_object
+<__main__.MyClass at 0x15adc55b0>
+```
+
+`Class attributes` are shared across all objects (_or instances_) created from a class, and can be accessed via [`dot notation`][dot notation] - a `.` placed after the object name and before the attribute name:
+
+```python
+>>> new_object = MyClass()
+
+# Accessing the class attribute "number" via dot-notation.
+>>> new_object.number
+5
+
+# Accessing the class attribute "string" via dot-notation.
+>>> new_object.string
+'Hello!'
+
+# Instantiating an additional object and binding it to the "second_new_object" name.
+>>> second_new_object = MyClass()
+
+>>> second_new_object
+<__main__.MyClass at 0x15ad99970>
+
+# Second_new_object shares the same class attributes as new_object.
+>>> new_object.number == second_new_object.number
+True
+```
+
+Class attributes are defined in the body of the class itself, before any other methods.
+They are owned by the class - allowing them to be shared across instances.
+Because these attributes are shared by all instances of the class, their value can be accessed and manipulated from the class directly.
+Altering the value of class attributes alters the value _**for all objects instantiated from the class**_:
+
+```python
+>>> obj_one = MyClass()
+>>> obj_two = MyClass()
+
+# Accessing a class attribute from an object.
+>>> obj_two.number
+5
+
+# Accessing the class attribute from the class itself.
+>>> MyClass.number
+5
+
+# Modifying the value of the "number" class attribute.
+>>> MyClass.number = 27
+
+# Modifying the "number" class attribute changes the "number" attribute for all objects.
+>>> obj_one.number
+27
+
+>>> obj_two.number
+27
+```
+
+Having a bunch of objects with synchronized data at all times is not particularly useful.
+Fortunately, objects created from a class can be customized with their own `instance attributes` (_or instance properties, variables, or fields_).
+As their name suggests, instance attributes are unique to each object, and can be modified independently.
+
+## Customizing Object Instantiation with `__init__()`
+
+The special ["dunder method"][dunder] (_short for "double underscore method"_) `__init__()` is used to customize class instances, and can be used to initialize instance attributes or properties for objects.
+For its role in initializing instance attributes, `__init__()` is also referred to as a `class constructor` or `initializer`.
+`__init__()` takes one required parameter called `self`, which refers to the newly initiated or created object.
+Data for instance attributes or properties can then be passed as arguments of `__init__()`, following the `self` parameter.
+
+Below, `MyClass` now has instance attributes called `location_x` and `location_y`.
+As you can see, the two attributes have been assigned to the first and second indexes of the `location` (_a tuple_) argument that has been passed to `__init__()`.
+The `location_x` and `location_y` attributes for a class instance will now be initialized when you instantiate the class and an object is created:
+
+```python
+class MyClass:
+ # These are class attributes, variables, or fields.
+ number = 5
+ string = "Hello!"
+
+ # This is the class "constructor", with a "location" parameter that is a tuple.
+ def __init__(self, location):
+
+ # This is an instance or object property, attribute, or variable.
+ # Note that we are unpacking the tuple argument into two separate instance variables.
+ self.location_x = location[0]
+ self.location_y = location[1]
+
+# Create a new object "new_object_one", with object property (1, 2).
+>>> new_object_one = MyClass((1, 2))
+
+# Create a second new object "new_object_two" with object property (-8, -9).
+>>> new_object_two = MyClass((-8, -9))
+
+# Note that new_object_one.location_x and new_object_two.location_x two different values.
+>>> new_object_one.location_x
+1
+
+>>> new_object_two.location_x
+-8
+```
+
+Note that you only need to pass one argument when initializing `MyClass` above -- Python takes care of passing `self` when the class is called.
+
+~~~~exercism/advanced
+Another way of creating an instance variable is to simply access a class variable via an object, and change it to something else:
+
+```python
+>>> obj_one = MyClass()
+>>> obj_two = MyClass()
+>>> MyClass.string
+'Hello!'
+
+>>> obj_two.string = "Hi!"
+>>> obj_two.string
+'Hi!'
+
+>>> obj_one.string
+'Hello!'
+```
+
+Now, watch what happens when the class variable changes:
+
+```python
+>>> MyClass.string = "World!"
+>>> obj_two.string
+
+# obj_two.string has not changed
+'Hi!'
+
+>>> obj_one.string
+
+# obj_one.string changed
+'World!'
+```
+
+The attribute `string` is now an _instance variable_ in the case of `obj_two`, but remains a _class variable_ in `obj_one`.
+
+This can also be done from within the class:
+
+```python
+class Demo:
+ new_var = 3
+
+ def add_two(self):
+ self.new_var += 2
+```
+
+The moment that `.add_two()` is called, and `self.new_var += 2` is read, `new_var` changes from a class variable to an instance variable of the same name.
+
+This can be useful during initialization when all instances of a class will need some attribute(s) to start with the same value.
+However, the instance variable then shadows* the class variable, making the class variable inaccessible from the instance where it is shadowed.
+Given this situation, it may be safer and clearer to set instance attributes from the `__init__()` method as `self.`.
+~~~~
+_*[_shadows_][shadowing]
+
+## Methods
+
+A `method` is a `function` that is bound to either the class itself (_known as a [class method][class method], which will be discussed in depth in a later exercise_) or an _instance_ of the class (object).
+Methods that operate on an object or instance need to be defined with `self` as the first parameter.
+You can then define the rest of the parameters as you would for a "normal" or non-bound function.
+Methods that operate on a class need to be defined with the `@classmethod` decorator and (_by convention_) with `cls` as the first parameter.
+Class methods are called on a class directly without first creating an object from the class.
+
+```python
+class MyClass:
+ number = 5
+ string = "Hello!"
+
+ # Class constructor.
+ def __init__(self, location):
+
+ # Instance properties
+ self.location_x = location[0]
+ self.location_y = location[1]
+
+ # Class method. Uses the @classmethod decorator, and cls as the first parameter.
+ # Can be called without first making an instance of the class.
+ @classmethod
+ def change_string(cls, new_string):
+ #Class attributes are referred to with cls.
+ cls.string = new_string
+ return cls.string
+
+ # Instance method. Note "self" as first parameter.
+ def change_location(self, amount):
+ self.location_x += amount
+ self.location_y += amount
+ return self.location_x, self.location_y
+```
+
+Like attribute access, calling a method is as simple as putting a `.` after the object name and before the method name.
+The called method does not need a copy of the object as a first parameter -- Python fills in `self` automatically:
+
+```python
+class MyClass:
+ number = 5
+ string = "Hello!"
+
+ def __init__(self, location):
+ self.location_x = location[0]
+ self.location_y = location[1]
+
+ def change_location(self, amount):
+ self.location_x += amount
+ self.location_y += amount
+ return self.location_x, self.location_y
+
+# Make a new test_object with location (3,7)
+>>> test_object = MyClass((3,7))
+>>> (test_object.location_x, test_object.location_y)
+(3,7)
+
+# Call change_location to increase location_x and location_y by 7.
+>>> test_object.change_location(7)
+(10, 14)
+```
+
+Class attributes can be accessed from within instance methods in the same way that they are accessed outside of the class:
+
+```python
+class MyClass:
+ number = 5
+ string = "Hello!"
+
+ def __init__(self, location):
+ self.location_x = location[0]
+ self.location_y = location[1]
+
+ # Alter instance variable location_x and location_y
+ def change_location(self, amount):
+ self.location_x += amount
+ self.location_y += amount
+ return self.location_x, self.location_y
+
+ # Alter class variable number for all instances from within an instance.
+ def increment_number(self):
+ # Increment the 'number' class variable by 1.
+ MyClass.number += 1
+
+
+>>> test_object_one = MyClass((0,0))
+>>> test_object_one.number
+5
+
+>>> test_object_two = MyClass((13, -3))
+>>> test_object_two.increment_number()
+>>> test_object_one.number
+6
+```
+
+## Placeholding or Stubbing Implementation with `pass`
+
+In previous concept exercises and practice exercise stubs, you will have seen the `pass` keyword used within the body of functions in place of actual code.
+
+The `pass` keyword is a syntactically valid placeholder - it prevents Python from throwing a syntax error for an empty function or class definition.
+Essentially, it is a way to say to the Python interpreter, 'Don't worry! I _will_ put code here eventually, I just haven't done it yet.'
+
+```python
+class MyClass:
+ number = 5
+ string = "Hello!"
+
+ def __init__(self, location):
+ self.location_x = location[0]
+ self.location_y = location[1]
+
+ # Alter instance variable location_x and location_y.
+ def change_location(self, amount):
+ self.location_x += amount
+ self.location_y += amount
+ return self.location_x, self.location_y
+
+ # Alter class variable number for all instances.
+ def increment_number(self):
+ # Increment the 'number' class variable by 1.
+ MyClass.number += 1
+
+ # This will compile and run without error, but has no current functionality.
+ def pending_functionality(self):
+ # Stubbing or place-holding the body of this method.
+ pass
+```
+
+[class method]: https://stackoverflow.com/questions/17134653/difference-between-class-and-instance-methods
+[dunder]: https://mathspp.com/blog/pydonts/dunder-methods
+[oop]: https://www.educative.io/blog/object-oriented-programming
+[dot notation]: https://stackoverflow.com/questions/45179186/understanding-the-dot-notation-in-python
+[shadowing]: https://oznetnerd.com/2017/07/17/python-shadowing/
diff --git a/concepts/classes/introduction.md b/concepts/classes/introduction.md
new file mode 100644
index 00000000000..aef39f76c75
--- /dev/null
+++ b/concepts/classes/introduction.md
@@ -0,0 +1,9 @@
+# Introduction
+
+Classes are definitions combining data (_otherwise known as `attributes`, `properties`,`data members`, `variables`, or `fields`_) with `functions` (_otherwise known as `methods`_).
+Class definitions are used to create copies or `instances` of the `class`, commonly known as `objects`.
+Objects can represent real world entities (_such as cars or cats_) - or more abstract concepts (_such as integers, vehicles, or mammals_).
+Each object is unique in computer memory and represents some part of an overall model.
+Classes and objects can be found in several programming paradigms, but are integral to [object oriented programming][oop] (OOP), in which programs are made up of objects that interact with one another.
+
+[oop]:https://www.educative.io/blog/object-oriented-programming
diff --git a/concepts/classes/links.json b/concepts/classes/links.json
new file mode 100644
index 00000000000..8cc9ba5926e
--- /dev/null
+++ b/concepts/classes/links.json
@@ -0,0 +1,42 @@
+[
+ {
+ "url": "https://docs.python.org/3/tutorial/classes.html",
+ "description": "The official Python classes tutorial."
+ },
+ {
+ "url": "https://pybit.es/articles/when-classes/",
+ "description": "A handy guide as to when to use classes, and when not to."
+ },
+ {
+ "url": "https://realpython.com/python3-object-oriented-programming/",
+ "description": "Another overview of what classes are and how to apply them in Python."
+ },
+ {
+ "url": "https://www.pythonmorsels.com/topics/classes/",
+ "description": "Python Morsels Rundown on Classes."
+ },
+ {
+ "url": "https://dbader.org/blog/6-things-youre-missing-out-on-by-never-using-classes-in-your-python-code",
+ "description": "6 Things You are Missing out on by never using classes in your Python code."
+ },
+ {
+ "url": "http://python-history.blogspot.com/2010/06/inside-story-on-new-style-classes.html",
+ "description": "The History of Python: The Inside Story on New-Style Classes."
+ },
+ {
+ "url": "https://realpython.com/instance-class-and-static-methods-demystified/",
+ "description": "Pythons Instance, Class, and Static Methods Demystified."
+ },
+ {
+ "url": "https://stackabuse.com/pythons-classmethod-and-staticmethod-explained/",
+ "description": "Python's @classmethod and @staticmethod Explained."
+ },
+ {
+ "url": "https://www.digitalocean.com/community/tutorials/how-to-construct-classes-and-define-objects-in-python-3",
+ "description": "An even gentler introduction to classes and objects from Digital Ocean."
+ },
+ {
+ "url": "https://www.digitalocean.com/community/tutorials/understanding-class-and-instance-variables-in-python-3",
+ "description": "An even gentler introduction to instance variables and class variables from Digital Ocean."
+ }
+]
diff --git a/concepts/collections/.meta/config.json b/concepts/collections/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/collections/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/collections/about.md b/concepts/collections/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/collections/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/collections/introduction.md b/concepts/collections/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/collections/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/collections/links.json b/concepts/collections/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/collections/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/comparisons/.meta/config.json b/concepts/comparisons/.meta/config.json
new file mode 100644
index 00000000000..1ef446724dc
--- /dev/null
+++ b/concepts/comparisons/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "Comparison operators evaluate two operand values, returning 'True' or 'False' based on whether the comparison condition is met.",
+ "authors": ["gurupratap-matharu", "bethanyg"],
+ "contributors": []
+}
diff --git a/concepts/comparisons/about.md b/concepts/comparisons/about.md
new file mode 100644
index 00000000000..1d2c677d22a
--- /dev/null
+++ b/concepts/comparisons/about.md
@@ -0,0 +1,278 @@
+# About
+
+A [comparison operator][comparisons] in Python (_also called a Python relational operator_), looks at the _values_ of two [operands][operand] and returns a boolean `True` or `False` if the `comparison` condition is or is not met.
+
+The table below shows the most common Python comparison operators:
+
+| Operator | Operation | Description |
+| -------- | -------------------------- | ------------------------------------------------------------------------- |
+| `>` | "greater than" | `a > b` is `True` if `a` is **strictly** greater in value than `b` |
+| `<` | "less than" | `a < b` is `True` if `a` is **strictly** less in value than `b` |
+| `==` | "equal to" | `a == b` is `True` if `a` is **strictly** equal to `b` in value |
+| `>=` | "greater than or equal to" | `a >= b` is `True` if `a > b` OR `a == b` in value |
+| `<=` | "less than or equal to" | `a <= b` is `True` if `a < b` or `a == b` in value |
+| `!=` | "not equal to" | `a != b` is `True` if `a == b` is `False` |
+| `is` | "identity" | `a is b` is `True` if **_and only if_** `a` and `b` are the same _object_ |
+| `is not` | "negated identity" | `a is not b` is `True` if `a` and `b` are **not** the same _object_ |
+| `in` | "containment test" | `a in b` is `True` if `a` is member, subset, or element of `b` |
+| `not in` | "negated containment test" | `a not in b` is `True` if `a` is not a member, subset, or element of `b` |
+
+They all have the same priority (_which is higher than that of [Boolean operations][boolean operations], but lower than that of arithmetic or bitwise operations_).
+
+## Comparison between different data types
+
+Objects that are different types (_except numeric types_) never compare equal by default.
+Non-identical instances of a `class` will also _**not**_ compare as equal unless the `class` defines special [rich comparison][rich comparisons] methods that customize the default `object` comparison behavior.
+For (much) more detail, see [Value comparisons][value comparisons] in the Python documentation.
+
+Numeric types are (mostly) an exception to this type matching rule.
+An `integer` **can** be considered equal to a `float` (_or an [`octal`][octal] equal to a [`hexadecimal`][hex]_), as long as the types can be implicitly converted for comparison.
+
+For the other numeric types ([complex][complex numbers], [decimal][decimal numbers], [fractions][rational numbers]), comparison operators are defined where they "make sense" (_where implicit conversion does not change the outcome_), but throw a `TypeError` if the underlying objects cannot be accurately converted for comparison.
+For more information on the rules that Python uses for numeric conversion, see [arithmetic conversions][arithmetic conversions] in the Python documentation.
+
+```python
+>>> import fractions
+
+# A string cannot be converted to an int.
+>>> 17 == '17'
+False
+
+# An int can be converted to float for comparison.
+>>> 17 == 17.0
+True
+
+# The fraction 6/3 can be converted to the int 2
+# The int 2 can be converted to 0b10 in binary.
+>>> 6/3 == 0b10
+True
+
+# An int can be converted to a complex
+# number with a 0 imaginary part.
+>>> 17 == complex(17)
+True
+
+# The fraction 2/5 can be converted to the float 0.4
+>>> 0.4 == 2/5
+True
+
+>>> complex(2/5, 1/2) == complex(0.4, 0.5)
+True
+```
+
+Any ordered comparison of a number to a `NaN` (_not a number_) type is `False`.
+A confusing side effect of Python's `NaN` definition is that `NaN` never compares equal to `NaN`.
+If you are curious as to why `NaN` was defined this way in Python, this [Stack Overflow Post on NaN][so nan post] around the setting of the international standard is an interesting read.
+
+```python
+>>> x = float('NaN')
+
+>>> 3 < x
+False
+
+>>> x < 3
+False
+
+# NaN never compares equal to NaN
+>>> x == x
+False
+```
+
+## Comparing Strings
+
+Strings (`str`) are compared [_lexicographically_][lexographic order], using their individual Unicode code points (_the result of passing each code point in the `str` to the built-in function [`ord()`][ord], which returns an `int`_).
+If all code points in both strings match and are _**in the same order**_, the two strings are considered equal.
+This comparison is done in a 'pair-wise' fashion - first-to-first, second-to-second, etc.
+In Python 3.x, `str` and `bytes` cannot be directly coerced/compared.
+
+```python
+>>> 'Python' > 'Rust'
+False
+
+>>> 'Python' > 'JavaScript'
+True
+
+# Examples with Mandarin.
+# hello < goodbye
+>>> '你好' < '再见'
+True
+
+# ord() of first characters
+>>> ord('你'), ord('再')
+(20320, 20877)
+
+# ord() of second characters
+>>> ord('好'), ord('见')
+(22909, 35265)
+
+# And with Korean words.
+# Pretty < beautiful.
+>>> '예쁜' < '아름다운'
+False
+
+>>> ord('예'), ord('아')
+(50696, 50500)
+```
+
+## Comparing Container Data Types
+
+Container data types (_`lists`, `tuples`, `sets`, `dicts`, etc._) also compare [_lexicographically_][lexographic order] - they are equal if both containers have the same data **and** the same data types (_in the case of `lists` and `tuples`, they must also have the same **ordering**_), unequal otherwise.
+
+```python
+>>> [1, 2] == [1, 2]
+True
+
+# But if the data is not in the same order, they are not equal.
+>>> [2, 1] == [1, 2]
+False
+
+# The same holds true for tuples
+>>> (3,4,5) == (5,4,3)
+False
+
+# Length is also compared
+>>> [1, 2] < [1, 2, 3]
+True
+
+# Comparing dicts
+>>> {'name': 'John', 'age': 19} == {'name': 'John', 'age': 18}
+False
+
+>>> {'name': 'John', 'age': 19} == {'name': 'John', 'age': 19}
+True
+```
+
+## Comparison Chaining
+
+Comparison operators can be chained _arbitrarily_.
+Note that the evaluation of an expression takes place from `left` to `right`.
+For example, `x < y <= z` is equivalent to `x < y` `and` `y <= z`, except that `y` is evaluated **only once**.
+In both cases, `z` is _not_ evaluated **at all** when `x < y` is found to be `False`.
+This is often called `short-circuit evaluation` - the evaluation stops if the truth value of the expression has already been determined.
+
+`Short circuiting` is supported by various boolean operators, functions, and also by comparison chaining in Python.
+Unlike many other programming languages, including `C`, `C++`, `C#`, and `Java`, chained expressions like `a < b < c` in Python have a conventional [mathematical interpretation][three way boolean comparison] and precedence.
+
+```python
+>>> x = 2
+>>> y = 5
+>>> z = 10
+
+>>> x < y < z
+True
+
+>>> x < y > z
+False
+
+>>> x > y < z
+False
+```
+
+## Identity comparisons
+
+The operators `is` and `is not` test for object [_identity_][object identity], as opposed to object _value_.
+An object's identity never changes after creation and can be found by using the [`id()`][id function] function.
+
+` is ` evaluates to `True` if _**and only if**_ `id()` == `id()`.
+` is not ` yields the inverse.
+
+Due to their singleton status, `None` and `NotImplemented` should always be compared to items using `is` and `is not`.
+See the Python reference docs on [value comparisons][value comparisons none] and [PEP8][PEP8 programming recommendations] for more details on this convention.
+
+```python
+>>>
+# A list of favorite numbers.
+>>> my_fav_numbers = [1, 2, 3]
+
+>>> your_fav_numbers = my_fav_numbers
+
+>>> my_fav_numbers is your_fav_numbers
+True
+
+# The returned id will differ by system and Python version.
+>>> id(my_fav_numbers)
+4517478208
+
+# your_fav_numbers is only an alias pointing to the original my_fav_numbers object.
+# Assigning a new name does not create a new object.
+>>> id(your_fav_numbers)
+4517478208
+
+
+>>> my_fav_numbers is not your_fav_numbers
+False
+
+>>> my_fav_numbers is not None
+True
+
+>>> my_fav_numbers is NotImplemented
+False
+```
+
+## Membership comparisons
+
+The operators `in` and `not in` test for _membership_.
+` in ` evaluates to `True` if `` is a member of `` (_if `` is a subset of or is contained within ``_), and evaluates `False` otherwise.
+` not in ` returns the negation, or _opposite of_ ` in `.
+
+For string and bytes types, ` in ` is `True` _**if and only if**_ `` is a substring of ``.
+
+```python
+>>>
+# A set of lucky numbers.
+>>> lucky_numbers = {11, 22, 33}
+>>> 22 in lucky_numbers
+True
+
+>>> 44 in lucky_numbers
+False
+
+# A dictionary of employee information.
+>>> employee = {'name': 'John Doe', 'id': 67826, 'age': 33, 'title': 'ceo'}
+
+# Checking for the membership of certain keys.
+>>> 'age' in employee
+True
+
+>>> 33 in employee
+False
+
+>>> 'lastname' not in employee
+True
+
+# Checking for substring membership
+>>> name = 'Super Batman'
+>>> 'Bat' in name
+True
+
+>>> 'Batwoman' in name
+False
+```
+
+# Customizing comparison behavior
+
+Comparison behavior for objects can be customized through the implementation of `rich comparison methods`.
+For more information, see [Python Tutorial: classes][classes], [Python Classes and Magic Methods (Dan Bader)][magic methods], and [Special method names][dunder methods].
+
+[PEP8 programming recommendations]: https://pep8.org/#programming-recommendations
+[arithmetic conversions]: https://docs.python.org/3/reference/expressions.html?highlight=number%20conversion#arithmetic-conversions
+[boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
+[classes]: https://docs.python.org/3/tutorial/classes.html#classes
+[comparisons]: https://docs.python.org/3/library/stdtypes.html?highlight=comparisons#comparisons
+[complex numbers]: https://docs.python.org/3/library/functions.html#complex
+[decimal numbers]: https://docs.python.org/3/library/decimal.html
+[dunder methods]: https://docs.python.org/3/reference/datamodel.html#special-method-names
+[hex]: https://docs.python.org/3/library/functions.html?highlight=hex#hex
+[id function]: https://docs.python.org/3/library/functions.html#id
+[lexographic order]: https://en.wikipedia.org/wiki/Lexicographic_order
+[magic methods]: https://dbader.org/blog/python-dunder-methods
+[object identity]: https://docs.python.org/3/reference/datamodel.html
+[octal]: https://docs.python.org/3/library/functions.html?#oct
+[operand]: https://www.computerhope.com/jargon/o/operand.htm
+[ord]: https://docs.python.org/3/library/functions.html#ord
+[rational numbers]: https://docs.python.org/3/library/fractions.html
+[rich comparisons]: https://docs.python.org/3/reference/datamodel.html#object.__lt__
+[so nan post]: https://stackoverflow.com/questions/1565164/what-is-the-rationale-for-all-comparisons-returning-false-for-ieee754-nan-values
+[three way boolean comparison]: https://en.wikipedia.org/wiki/Three-way_comparison
+[value comparisons none]: https://docs.python.org/3/reference/expressions.html?highlight=none#value-comparisons
+[value comparisons]: https://docs.python.org/3/reference/expressions.html?highlight=nan#value-comparisons
diff --git a/concepts/comparisons/introduction.md b/concepts/comparisons/introduction.md
new file mode 100644
index 00000000000..e597063c621
--- /dev/null
+++ b/concepts/comparisons/introduction.md
@@ -0,0 +1,57 @@
+# Introduction
+
+A [comparison operator][comparisons] in Python (_also called a Python relational operator_), looks at the _values_ of two [operands][operand] and returns a boolean `True` or `False` if the `comparison` condition is or is not met.
+
+The table below shows the most common Python comparison operators:
+
+| Operator | Operation | Description |
+| -------- | -------------------------- | ------------------------------------------------------------------------- |
+| `>` | "greater than" | `a > b` is `True` if `a` is **strictly** greater in value than `b` |
+| `<` | "less than" | `a < b` is `True` if `a` is **strictly** less in value than `b` |
+| `==` | "equal to" | `a == b` is `True` if `a` is **strictly** equal to `b` in value |
+| `>=` | "greater than or equal to" | `a >= b` is `True` if `a > b` OR `a == b` in value |
+| `<=` | "less than or equal to" | `a <= b` is `True` if `a < b` or `a == b` in value |
+| `!=` | "not equal to" | `a != b` is `True` if `a == b` is `False` |
+| `is` | "identity" | `a is b` is `True` if **_and only if_** `a` and `b` are the same _object_ |
+| `is not` | "negated identity" | `a is not b` is `True` if `a` and `b` are **not** the same _object_ |
+| `in` | "containment test" | `a in b` is `True` if `a` is member, subset, or element of `b` |
+| `not in` | "negated containment test" | `a not in b` is `True` if `a` is not a member, subset, or element of `b` |
+
+They all have the same priority (_which is higher than that of [Boolean operations][boolean operations], but lower than that of arithmetic or bitwise operations_).
+
+## Comparison between different data types
+
+Objects that are different types (_except numeric types_) never compare equal by default.
+Non-identical instances of a `class` will also _**not**_ compare as equal unless the `class` defines special methods that customize the default `object` comparison behavior.
+
+Numeric types are (mostly) an exception to this type matching rule.
+An `integer` **can** be considered equal to a `float` (_or an [`octal`][octal] equal to a [`hexadecimal`][hex]_), as long as the types can be implicitly converted for comparison.
+
+For the other numeric types ([complex][complex numbers], [decimal][decimal numbers], [fractions][rational numbers]), comparison operators are defined where they "make sense" (_where implicit conversion does not change the outcome_), but throw a `TypeError` if the underlying objects cannot be accurately converted for comparison.
+
+## Comparing object identity
+
+The operators `is` and `is not` test for object [_identity_][object identity], as opposed to object _value_.
+An object's identity never changes after creation and can be found by using the [`id()`][id function] function.
+
+` is ` evaluates to `True` if _**and only if**_ `id()` == `id()`.
+` is not ` yields the inverse.
+
+## Membership comparisons
+
+The operators `in` and `not in` test for _membership_.
+` in ` evaluates to `True` if `` is a member of `` (_if `` is a subset of or is contained within ``_), and evaluates `False` otherwise.
+` not in ` returns the negation, or _opposite of_ ` in `.
+
+For string and bytes types, ` in ` is `True` _**if and only if**_ `` is a substring of ``.
+
+[boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
+[comparisons]: https://docs.python.org/3/library/stdtypes.html?highlight=comparisons#comparisons
+[complex numbers]: https://docs.python.org/3/library/functions.html#complex
+[decimal numbers]: https://docs.python.org/3/library/decimal.html
+[hex]: https://docs.python.org/3/library/functions.html?highlight=hex#hex
+[id function]: https://docs.python.org/3/library/functions.html#id
+[object identity]: https://docs.python.org/3/reference/datamodel.html
+[octal]: https://docs.python.org/3/library/functions.html?#oct
+[operand]: https://www.computerhope.com/jargon/o/operand.htm
+[rational numbers]: https://docs.python.org/3/library/fractions.html
diff --git a/concepts/comparisons/links.json b/concepts/comparisons/links.json
new file mode 100644
index 00000000000..f16869e2b92
--- /dev/null
+++ b/concepts/comparisons/links.json
@@ -0,0 +1,46 @@
+[
+ {
+ "url": "https://www.tutorialspoint.com/python/python_basic_operators.htm",
+ "description": "Python basic operators on Tutorials Point"
+ },
+ {
+ "url": "https://docs.python.org/3/reference/expressions.html#comparisons",
+ "description": "Comparisons in Python (Python language reference)"
+ },
+ {
+ "url": "https://docs.python.org/3/reference/expressions.html#is-not",
+ "description": "Identity comparisons in Python (Python language reference)"
+ },
+ {
+ "url": "https://docs.python.org/3/reference/expressions.html#value-comparisons",
+ "description": "Value comparisons in Python (Python language reference)"
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#typesnumeric",
+ "description": "Numeric types (Python Docs)"
+ },
+ {
+ "url": "https://docs.python.org/3/library/decimal.html#decimal.Decimal",
+ "description": "Decimal types (Python Docs)"
+ },
+ {
+ "url": "https://docs.python.org/3/library/fractions.html#fractions.Fraction",
+ "description": "Fractions (Python Docs)"
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range",
+ "description": "Sequence types (Python Docs)"
+ },
+ {
+ "url": "https://docs.python.org/3/reference/datamodel.html#objects",
+ "description": "Python Object Model (Python docs)"
+ },
+ {
+ "url": "https://www.python.org/dev/peps/pep-0207/",
+ "description": "PEP 207 to allow Operator Overloading for Comparison"
+ },
+ {
+ "url": "https://docs.python.org/3/reference/datamodel.html#customization",
+ "description": "Basic Customization (Python language reference)"
+ }
+]
diff --git a/concepts/complex-numbers/.meta/config.json b/concepts/complex-numbers/.meta/config.json
new file mode 100644
index 00000000000..ca6ccc8811d
--- /dev/null
+++ b/concepts/complex-numbers/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "Complex numbers are a fundamental data type in Python, along with int and float. Further support is added with the cmath module, which is part of the Python standard library.",
+ "authors": ["BethanyG", "colinleach"],
+ "contributors": []
+}
diff --git a/concepts/complex-numbers/about.md b/concepts/complex-numbers/about.md
new file mode 100644
index 00000000000..dfe067be4ee
--- /dev/null
+++ b/concepts/complex-numbers/about.md
@@ -0,0 +1,260 @@
+# About
+
+`Complex numbers` are not complicated.
+They just need a less alarming name.
+
+They are so useful, especially in engineering and science, that Python includes [`complex`][complex] as a standard numeric type alongside integers ([`int`s][ints]) and floating-point numbers ([`float`s][floats]).
+
+
+## Basics
+
+A `complex` value in Python is essentially a pair of floating-point numbers.
+These are called the "real" and "imaginary" parts, for unfortunate historical reasons.
+Again, it is best to focus on the underlying simplicity and not the strange names.
+
+There are two common ways to create complex numbers.
+
+1) The [`complex(real, imag)`][complex] constructor takes two `float` parameters:
+
+```python
+>>> z1 = complex(1.5, 2.0)
+>>> z1
+(1.5+2j)
+```
+
+The constructor can also parse string input.
+This has the odd limitation that it fails if the string contains spaces.
+
+```python
+>>> complex('4+2j')
+(4+2j)
+
+>>> complex('4 + 2j')
+Traceback (most recent call last):
+ File "", line 1, in
+ValueError: complex() arg is a malformed string
+```
+
+
+2) The complex number can be specified as ` + j` literal, or just `j` if the real part is zero:
+
+
+```python
+>>> z2 = 2.0 + 1.5j
+>>> z2
+(2+1.5j)
+```
+The end result is identical to using the `complex()` constructor.
+
+
+There are two rules for that imaginary part of the complex number:
+
+
+- It is designated with `j` (not `i` as you may see in math textbooks).
+
+- The `j` must immediately follow a number, to prevent Python seeing it as a variable name. If necessary, use `1j`.
+
+```python
+>>> j
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name 'j' is not defined
+
+>>> 1j
+1j
+
+>>> type(1j)
+
+```
+
+Most engineers are happy with `j`.
+Most scientists and mathematicians prefer the mathematical notation `i` for _imaginary_, but that notation conflicts with the use of `i` to mean _current_ in Electrical Engineering.
+So in designing Python, the Electrical Engineers won.
+
+
+To access the parts of a complex number individually:
+
+```python
+>>> z2.real
+2.0
+>>> z2.imag
+1.5
+```
+
+Either part can be zero and mathematicians may then talk of the number being "wholly real" or "wholly imaginary".
+However, it is still a complex number in Python:
+
+
+```python
+>>> complex(0, 1)
+1j
+>>> type(complex(0, 1))
+
+
+>>> complex(1, 0)
+(1+0j)
+```
+
+You may have heard that "`i` (or `j`) is the square root of -1".
+
+For now, all this means is that the imaginary part _by definition_ satisfies the equality
+```python
+1j * 1j == -1 # => True
+```
+
+This is a simple idea, but it leads to interesting consequences.
+
+## Arithmetic
+
+Most of the [`operators`][operators] used with floats and ints also work with complex numbers:
+
+
+```python
+>>> z1 = (1.5+2j)
+>>> z2 = (2+1.5j)
+
+
+>>> z1 + z2 # addition
+(3.5+3.5j)
+
+>>> z1 - z2 # subtraction
+(-0.5+0.5j)
+
+>>> z1 * z2 # multiplication
+6.25j
+
+>>> z1 / z2 # division
+(0.96+0.28j)
+
+>>> z1 ** 2 # exponentiation
+(-1.75+6j)
+
+>>> 2 ** z1 # another exponentiation
+(0.5188946835878313+2.7804223253571183j)
+
+>>> 1j ** 2 # j * j == -1
+(-1+0j)
+```
+
+Explaining the rules for complex number multiplication and division is out of scope for this concept (_and you are unlikely to have to perform those operations "by hand" very often_).
+
+Any [mathematical][math-complex] or [electrical engineering][engineering-complex] introduction to complex numbers will cover this, should you want to dig into the topic.
+
+Alternatively, Exercism has a `Complex Numbers` practice exercise where you can implement a complex number class with these operations from first principles.
+
+
+Integer division is ___not___ possible on complex numbers, so the `//` and `%` operators and `divmod()` functions will fail for the complex number type.
+
+
+There are two functions implemented for numeric types that are very useful when working with complex numbers:
+
+- `.conjugate()` simply flips the sign of the imaginary part of a complex number (_from + to - or vice-versa_).
+ - Because of the way complex multiplication works, this is more useful than you might think.
+- `abs()` is guaranteed to return a real number with no imaginary part.
+
+
+```python
+>>> z1
+(1.5+2j)
+
+>>> z1.conjugate() # flip the z1.imag sign
+(1.5-2j)
+
+>>> abs(z1) # sqrt(z1.real ** 2 + z1.imag ** 2)
+2.5
+```
+
+## The `cmath` module
+
+The Python standard library has a [`math`][math-module] module full of useful functionality for working with real numbers.
+
+It also has an equivalent [`cmath`][cmath] module for working with complex numbers.
+
+
+We encourage you to read through the module and experiment, but the main categories are:
+
+- Conversion between Cartesian and polar coordinates,
+- Exponential and log functions,
+- Trigonometric functions,
+- Hyperbolic functions,
+- Classification functions, and
+- Useful constants.
+
+Here is an example using some constants:
+
+```python
+>>> import cmath
+
+>>> euler = cmath.exp(1j * cmath.pi) # Euler's equation
+
+>>> euler.real
+-1.0
+>>> round(euler.imag, 15) # round to 15 decimal places
+0.0
+```
+
+So a simple expression with three of the most important constants in nature `e`, `i` (or `j`) and `pi` gives the result `-1`.
+Some people believe this is the most beautiful result in all of mathematics.
+It dates back to around 1740.
+
+-----
+
+## Optional section: a Complex Numbers FAQ
+
+This part can be skipped, unless you are interested.
+
+### Isn't this some strange new piece of pure mathematics?
+
+It was strange and new in the 16th century.
+
+500 years later, it is central to most of engineering and the physical sciences.
+
+### Why would anyone use these?
+
+It turns out that complex numbers are the simplest way to describe anything that rotates or anything with a wave-like property.
+So they are used widely in electrical engineering, audio processing, physics, computer gaming, and navigation - to name only a few applications.
+
+You can see things rotate.
+Complex numbers may not make the world go round, but they are great for explaining _what happens_ as a result of the world going round: look at any satellite image of a major storm.
+
+
+Less obviously, sound is wave-like, light is wave-like, radio signals are wave-like, and even the economy of your home country is at least partly wave-like.
+
+
+A lot of this wave processing can be done with trig functions (`sin()` and `cos()`) but that gets messy quite quickly.
+
+Complex exponentials are ___much___ easier to work with.
+
+### But I don't need complex numbers!
+
+
+Only true if you are living in a cave and foraging for your food.
+
+If you are reading this on any sort of screen, you are utterly dependent on some useful 20th-Century advances made through the use of complex numbers.
+
+
+1. __Semiconductor chips__.
+ - These make no sense in classical physics and can only be explained (and designed) by quantum mechanics (QM).
+ - In QM, everything is complex-valued by definition. (_its waveforms all the way down_)
+
+2. __The Fast Fourier Transform algorithm__.
+ - FFT is an application of complex numbers, and it is in _everything_ connected to sound transmission, audio processing, photos, and video.
+
+ -MP3 and other audio formats use FFT for compression, ensuring more audio can fit within a smaller storage space.
+ - JPEG compression and MP4 video, among many other image and video formats also use FTT for compression.
+
+ - FFT is also deployed in the digital filters that allow cellphone towers to separate your personal cell signal from everyone else's.
+
+
+So, you are probably using technology that relies on complex number calculations thousands of times per second.
+
+
+[complex]: https://docs.python.org/3/library/functions.html#complex
+[cmath]: https://docs.python.org/3/library/cmath.html
+[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex
+[math-module]: https://docs.python.org/3/library/math.html
+[math-complex]: https://www.nagwa.com/en/videos/143121736364/
+[engineering-complex]: https://www.khanacademy.org/science/electrical-engineering/ee-circuit-analysis-topic/ee-ac-analysis/v/ee-complex-numbers
+[ints]: https://docs.python.org/3/library/functions.html#int
+[floats]: https://docs.python.org/3/library/functions.html#float
+
diff --git a/concepts/complex-numbers/introduction.md b/concepts/complex-numbers/introduction.md
new file mode 100644
index 00000000000..a82f47cb6cb
--- /dev/null
+++ b/concepts/complex-numbers/introduction.md
@@ -0,0 +1,93 @@
+# Introduction
+
+`Complex numbers` are not complicated.
+They just need a less alarming name.
+
+They are so useful, especially in engineering and science (_everything from JPEG compression to quantum mechanics_), that Python includes [`complex`][complex] as a standard numeric type alongside integers ([`int`s][ints]) and floating-point numbers ([`float`s][floats]).
+
+A `complex` value in Python is essentially a pair of floating-point numbers:
+
+```python
+>>> my_complex = 5.443+6.77j
+(5.443+6.77j)
+```
+
+These are called the "real" and "imaginary" parts.
+You may have heard that "`i` (or `j`) is the square root of -1".
+For now, all this means is that the imaginary part _by definition_ satisfies the equality `1j * 1j == -1`.
+This is a simple idea, but it leads to interesting mathematical consequences.
+
+In Python, the "imaginary" part is designated with `j` (_not `i` as you would see in math textbooks_), and
+the `j` must immediately follow a number, to prevent Python seeing it as a variable name:
+
+
+```python
+>>> j
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name 'j' is not defined
+
+>>> 1j
+1j
+
+>>> type(1j)
+
+```
+
+
+There are two common ways to create complex numbers.
+
+1) The [`complex(real, imag)`][complex] constructor takes two `float` parameters:
+
+ ```python
+ >>> z1 = complex(1.5, 2.0)
+ >>> z1
+ (1.5+2j)
+ ```
+
+ The constructor can also parse string input.
+ This has the odd limitation that it fails if the string contains spaces.
+
+ ```python
+ >>> complex('4+2j')
+ (4+2j)
+
+ >>> complex('4 + 2j')
+ Traceback (most recent call last):
+ File "", line 1, in
+ ValueError: complex() arg is a malformed string
+ ```
+
+
+2) The complex number can be specified as ` + j` literal, or just `j` if the real part is zero:
+
+
+ ```python
+ >>> z2 = 2.0 + 1.5j
+ >>> z2
+ (2+1.5j)
+ ```
+ The end result is identical to using the `complex()` constructor.
+
+
+## Arithmetic
+
+Most of the [`operators`][operators] used with floats and ints also work with complex numbers.
+
+Integer division is _**not**_ possible on complex numbers, so the `//` and `%` operators and `divmod()` functions will fail for the complex number type.
+
+Explaining the rules for complex number multiplication and division is out of scope for this concept (_and you are unlikely to have to perform those operations "by hand" very often_).
+
+Any [mathematical][math-complex] or [electrical engineering][engineering-complex] introduction to complex numbers will cover these scenarios, should you want to dig into the topic.
+
+The Python standard library has a [`math`][math-module] module full of useful functionality for working with real numbers and the [`cmath`][cmath] module is its equivalent for working with complex numbers.
+
+
+[cmath]: https://docs.python.org/3/library/cmath.html
+[complex]: https://docs.python.org/3/library/functions.html#complex
+[engineering-complex]: https://www.khanacademy.org/science/electrical-engineering/ee-circuit-analysis-topic/ee-ac-analysis/v/ee-complex-numbers
+[floats]: https://docs.python.org/3/library/functions.html#float
+[ints]: https://docs.python.org/3/library/functions.html#int
+[math-complex]: https://www.nagwa.com/en/videos/143121736364/
+[math-module]: https://docs.python.org/3/library/math.html
+[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex
diff --git a/concepts/complex-numbers/links.json b/concepts/complex-numbers/links.json
new file mode 100644
index 00000000000..759ef1689ff
--- /dev/null
+++ b/concepts/complex-numbers/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex/",
+ "description": "Operations on numeric types."
+ },
+ {
+ "url": "https://docs.python.org/3/library/functions.html#complex/",
+ "description": "The complex class."
+ },
+ {
+ "url": "https://docs.python.org/3/library/cmath.html/",
+ "description": "Module documentation for cmath."
+ },
+ {
+ "url": "https://docs.python.org/3/library/math.html/",
+ "description": "Module documentation for math."
+ }
+]
diff --git a/concepts/conditionals/.meta/config.json b/concepts/conditionals/.meta/config.json
new file mode 100644
index 00000000000..453b29d643b
--- /dev/null
+++ b/concepts/conditionals/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "The conditionals 'if', 'elif' ('else + if'), and 'else' are used to control the flow of execution and make decisions in a program. Python doesn't have a formal case-switch statement ,and uses multiple 'elif's to serve a similar purpose. Conditionals pair with expressions and objects that must resolve to 'True' or 'False'.",
+ "authors": ["bethanyg", "sachsom95"],
+ "contributors": []
+}
diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md
new file mode 100644
index 00000000000..2060905b335
--- /dev/null
+++ b/concepts/conditionals/about.md
@@ -0,0 +1,185 @@
+# About
+
+In Python, [`if`][if statement], `elif` (_a contraction of 'else and if'_) and `else` statements are used to [control the flow][control flow tools] of execution and make decisions in a program.
+Unlike many other programming languages, Python versions 3.9 and below do not offer a formal case-switch statement, instead using multiple `elif` statements to serve a similar purpose.
+
+Python 3.10 introduces a variant case-switch statement called `pattern matching`, which will be covered separately in another concept.
+
+Conditional statements use expressions that must resolve to `True` or `False` -- either by returning a `bool` directly, or by evaluating ["truthy" or "falsy"][truth value testing].
+
+
+```python
+x = 10
+y = 5
+
+# The comparison '>' returns the bool 'True',
+# so the statement is printed.
+if x > y:
+ print("x is greater than y")
+...
+>>> x is greater than y
+```
+
+When paired with `if`, an optional `else` code block will execute when the original `if` condition evaluates to `False`:
+
+```python
+x = 5
+y = 10
+
+# The comparison '>' here returns the bool False,
+# so the 'else' block is executed instead of the 'if' block.
+if x > y:
+ print("x is greater than y")
+else:
+ print("y is greater than x")
+...
+>>> y is greater than x
+```
+
+`elif` allows for multiple evaluations/branches.
+
+```python
+x = 5
+y = 10
+z = 20
+
+# The elif statement allows for the checking of more conditions.
+if x > y > z:
+ print("x is greater than y and z")
+elif y > x > z:
+ print("y is greater than x and z")
+else:
+ print("z is greater than x and y")
+...
+>>> z is greater than x and y
+```
+
+[Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing:
+
+```python
+
+>>> def classic_fizzbuzz(number):
+ if number % 3 == 0 and number % 5 == 0:
+ say = 'FizzBuzz!'
+ elif number % 5 == 0:
+ say = 'Buzz!'
+ elif number % 3 == 0:
+ say = 'Fizz!'
+ else:
+ say = str(number)
+
+ return say
+
+>>> classic_fizzbuzz(15)
+'FizzBuzz!'
+
+>>> classic_fizzbuzz(13)
+'13'
+```
+
+As an alternative, the example above can be re-written to only use `if` statements with `returns`.
+However, re-writing in this way might obscure that the conditions are intended to be [_mutually exclusive_][mutually-exclusive] and could lead to future bugs or maintenance issues.
+
+
+```python
+>>> def classic_fizzbuzz(number):
+ if number % 3 == 0 and number % 5 == 0:
+ return 'FizzBuzz!'
+ if number % 5 == 0:
+ return 'Buzz!'
+ if number % 3 == 0:
+ return 'Fizz!'
+
+ return str(number)
+
+>>> classic_fizzbuzz(15)
+'FizzBuzz!'
+
+>>> classic_fizzbuzz(13)
+'13'
+```
+
+
+Conditionals can also be nested.
+
+```python
+>>> def driving_status(driver_age, test_score):
+ if test_score >= 80:
+ if 18 > driver_age >= 16:
+ status = "Student driver, needs supervision."
+ elif driver_age == 18:
+ status = "Permitted driver, on probation."
+ elif driver_age > 18:
+ status = "Fully licensed driver."
+ else:
+ status = "Unlicensed!"
+
+ return status
+
+
+>>> driving_status(63, 78)
+'Unlicensed!'
+
+>>> driving_status(16, 81)
+'Student driver, needs supervision.'
+
+>>> driving_status(23, 80)
+'Fully licensed driver.'
+```
+
+## Conditional expressions or "ternary operators"
+
+While Python has no specific `?` ternary operator, it is possible to write single-line `conditional expressions`.
+These take the form of `` if `` else ``.
+Since these expressions can become hard to read, it's recommended to use this single-line form only if it shortens code and helps readability.
+
+
+```python
+def just_the_buzz(number):
+ return 'Buzz!' if number % 5 == 0 else str(number)
+
+>>> just_the_buzz(15)
+'Buzz!'
+
+>>> just_the_buzz(7)
+'7'
+```
+
+## Truthy and Falsy
+
+In Python, any object can be tested for [truth value][truth value testing], and can therefore be used with a conditional, comparison, or boolean operation.
+Objects that are evaluated in this fashion are considered "truthy" or "falsy", and used in a `boolean context`.
+
+```python
+>>> def truthy_test(thing):
+ if thing:
+ print('This is Truthy.')
+ else:
+ print("Nope. It's Falsey.")
+
+
+# Empty container objects are considered Falsey.
+>>> truthy_test([])
+Nope. It's Falsey.
+
+>>> truthy_test(['bear', 'pig', 'giraffe'])
+This is Truthy.
+
+# Empty strings are considered Falsey.
+>>> truthy_test('')
+Nope. It's Falsey.
+
+>>> truthy_test('yes')
+This is Truthy.
+
+# 0 is also considered Falsey.
+>>> truthy_test(0)
+Nope. It's Falsey.
+```
+
+[boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
+[comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons
+[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools
+[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement
+[mutually-exclusive]: https://stackoverflow.com/a/22783232
+[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
diff --git a/concepts/conditionals/introduction.md b/concepts/conditionals/introduction.md
new file mode 100644
index 00000000000..ee1d4336207
--- /dev/null
+++ b/concepts/conditionals/introduction.md
@@ -0,0 +1,83 @@
+# Introduction
+
+In Python, [`if`][if statement], `elif` (_a contraction of 'else and if'_) and `else` statements are used to [control the flow][control flow tools] of execution and make decisions in a program.
+Unlike many other programming languages, Python versions 3.9 and below do not offer a formal case-switch statement, instead using multiple `elif` statements to serve a similar purpose.
+
+Python 3.10 introduces a variant case-switch statement called `pattern matching`, which will be covered separately in another concept.
+
+Conditional statements use expressions that must resolve to `True` or `False` -- either by returning a `bool` directly, or by evaluating ["truthy" or "falsy"][truth value testing].
+
+
+```python
+x = 10
+y = 5
+
+# The comparison '>' returns the bool 'True',
+# so the statement is printed.
+if x > y:
+ print("x is greater than y")
+...
+>>> x is greater than y
+```
+
+When paired with `if`, an optional `else` code block will execute when the original `if` condition evaluates to `False`:
+
+```python
+x = 5
+y = 10
+
+# The comparison '>' here returns the bool False,
+# so the 'else' block is executed instead of the 'if' block.
+if x > y:
+ print("x is greater than y")
+else:
+ print("y is greater than x")
+...
+>>> y is greater than x
+```
+
+`elif` allows for multiple evaluations/branches.
+
+```python
+x = 5
+y = 10
+z = 20
+
+# The elif statement allows for the checking of more conditions.
+if x > y > z:
+ print("x is greater than y and z")
+elif y > x > z:
+ print("y is greater than x and z")
+else:
+ print("z is greater than x and y")
+...
+>>> z is greater than x and y
+```
+
+[Boolean operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing:
+
+```python
+>>> def classic_fizzbuzz(number):
+ if number % 3 == 0 and number % 5 == 0:
+ say = 'FizzBuzz!'
+ elif number % 5 == 0:
+ say = 'Buzz!'
+ elif number % 3 == 0:
+ say = 'Fizz!'
+ else:
+ say = str(number)
+
+ return say
+
+>>> classic_fizzbuzz(15)
+'FizzBuzz!'
+
+>>> classic_fizzbuzz(13)
+'13'
+```
+
+[boolean operations]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
+[comparisons]: https://docs.python.org/3/library/stdtypes.html#comparisons
+[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools
+[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement
+[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
diff --git a/concepts/conditionals/links.json b/concepts/conditionals/links.json
new file mode 100644
index 00000000000..c1778b526b4
--- /dev/null
+++ b/concepts/conditionals/links.json
@@ -0,0 +1,22 @@
+[
+ {
+ "url": "https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools",
+ "description": "Python Docs: Control flow tools."
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#truth-value-testing",
+ "description": "Python Docs: Truth value testing."
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not",
+ "description": "Python Docs: Standard Types - boolean operations."
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#comparisons",
+ "description": "Python Docs: Comparisons."
+ },
+ {
+ "url": "https://realpython.com/python-conditional-statements/",
+ "description": "Real Python: Conditional statements in Python."
+ }
+]
diff --git a/concepts/context-manager-customization/.meta/config.json b/concepts/context-manager-customization/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/context-manager-customization/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/context-manager-customization/about.md b/concepts/context-manager-customization/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/context-manager-customization/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/context-manager-customization/introduction.md b/concepts/context-manager-customization/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/context-manager-customization/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/context-manager-customization/links.json b/concepts/context-manager-customization/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/context-manager-customization/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/dataclasses/.meta/config.json b/concepts/dataclasses/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/dataclasses/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/dataclasses/about.md b/concepts/dataclasses/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/dataclasses/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/dataclasses/introduction.md b/concepts/dataclasses/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/dataclasses/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/dataclasses/links.json b/concepts/dataclasses/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/dataclasses/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/decorators/.meta/config.json b/concepts/decorators/.meta/config.json
new file mode 100644
index 00000000000..57692af26e6
--- /dev/null
+++ b/concepts/decorators/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "Decorators are higher-order functions that take other functions as arguments to extend/modify their behavior and return the modified functions.",
+ "authors": ["BethanyG", "bobahop", "kotp", "mathstrains21", "velaco"],
+ "contributors": []
+}
diff --git a/concepts/decorators/about.md b/concepts/decorators/about.md
new file mode 100644
index 00000000000..3b29864dbbb
--- /dev/null
+++ b/concepts/decorators/about.md
@@ -0,0 +1,246 @@
+# About
+
+Decorators are functions that take another function as an argument for the purpose of extending or replacing the behavior of the passed-in function.
+If function `A` is a decorator, and function `B` is its argument, then function `A` modifies, extends, or replaces function `B`'s **behavior** _without modifying_ function `B`'s code.
+We say that the decorator function `A` _wraps_ function `B`.
+While we talk about "modifying" behavior, the wrapped function is _not actually changed_.
+Behavior is either added _around_ the wrapped function (_and what it returns_), or the wrapped function's behavior is _substituted_ for some other behavior.
+
+## A Decorator is a Higher-Order Function
+
+A [higher-order function][higher-order functions] is a function that accepts one or more functions as arguments and/or returns one or more functions.
+A function, used as an argument or returned from another function, is a [first-class function][first-class functions].
+A Python function, as a [callable object][callable objects], is a first-class function which can be stored in a variable or used as an argument, much like any other value or object.
+Higher-order functions and first-class functions work together to make decorators possible.
+
+## What Using a Decorator Looks Like
+
+The `@` symbol is prepended to the name of the decorator function and placed just above the function to be decorated, like so:
+
+```python
+@decorator
+def decorated_function():
+ pass
+```
+
+Some decorators accept arguments:
+
+```python
+@decorator_with_arg(name="Bob")
+def decorated_function2():
+ pass
+```
+
+If a decorator has defined default arguments, you must use parenthesis in the `@decorator()` call for the decorator to work, as you would in calling any function:
+
+```python
+@decorator_with_default_arg()
+def decorated_function3():
+ pass
+```
+
+If a decorator takes a _positional_ arguments, not supplying the arguments will result in an error which will look something like:
+
+```
+TypeError: decorator_with_pos_arg() missing 1 required positional argument: 'name'
+```
+
+The `@decorator` syntax is syntactic sugar or a shorthand for calling the _decorating function_ and passing the _decorated function_ to it as an argument.
+Following are examples of alternative ways for calling a decorator:
+
+```python
+def function():
+ pass
+
+function = decorator(function)
+
+
+def function2():
+ pass
+
+function2 = decorator_with_arg(name="Bob")(function2)
+
+
+def function3():
+ pass
+
+function3 = decorator_with_default_arg()(function3)
+```
+
+## Writing a Simple Decorator
+
+Most decorators are intended to _extend_ or _replace_ the behavior of another function, but some decorators may do nothing but return the functions they are wrapping.
+
+Decorators are functions which take at least one argument - the function which they are wrapping.
+They usually return either the wrapped function or the result of an expression that uses the wrapped function.
+
+A simple decorator - one that simply returns its wrapped function - can be written as follows:
+```python
+>>> def do_nothing(func):
+... return func
+...
+... @do_nothing
+... def function4():
+... return 4
+...
+>>> print(function4())
+4
+```
+
+A decorator may only add side effects, such as additional information used for logging:
+
+```python
+>>> def my_logger(func):
+... print(f"Entering {func.__name__}")
+... return func
+...
+... @my_logger
+... def my_func():
+... print("Hello")
+...
+>>> my_func()
+Entering my_func
+Hello
+```
+
+A decorator does not return itself.
+It may return its function arguments, another function, or one or more values that replace the return from the passed-in or decorated function.
+If a decorator returns another function, it will usually return an [inner function][inner functions].
+
+## Inner Functions
+
+A function can be defined within a function.
+Such a nested function is called an [inner function][inner functions].
+A decorator may use an inner function to wrap its function argument.
+The decorator then returns its inner function.
+The inner function may then return the original function argument.
+
+### A Validating Decorator Using an Inner Function
+
+Following is an example of a decorator being used for validation:
+
+```python
+>>> def my_validator(func):
+... def my_wrapper(world):
+... print(f"Entering {func.__name__} with {world} argument")
+... if ("Pluto" == world):
+... print("Pluto is not a planet!")
+... else:
+... return func(world)
+... return my_wrapper
+...
+... @my_validator
+... def my_func(planet):
+... print(f"Hello, {planet}!")
+...
+>>> my_func("World")
+Entering my_func with World argument
+Hello, World!
+...
+>>> my_func("Pluto")
+Entering my_func with Pluto argument
+Pluto is not a planet!
+```
+
+On the first line, we have the definition for the decorator with its `func` argument.
+On the next line is the definition for the decorators _inner function_, which wraps the `func` argument.
+Since the _inner function_ wraps the decorator's `func` argument, it is passed the same argument that is passed to `func`.
+Note that the wrapper doesn't have to use the same name for the argument that was defined in `func`.
+The original function uses `planet` and the decorator uses `world`, and the decorator still works.
+
+The inner function returns either `func` or, if `planet` equals `Pluto`, it will print that Pluto is not a planet.
+It could be coded to raise a `ValueError` instead.
+So, the inner function wraps `func`, and returns either `func` or does something that substitutes what `func` would do.
+The decorator returns its _inner function_.
+The _inner_function_ may or may not return the original, passed-in function.
+Depending on what code conditionally executes in the wrapper function or _inner_function_, `func` may be returned, an error could be raised, or a value of `func`'s return type could be returned.
+
+### Decorating a Function that Takes an Arbitrary Number of Arguments
+
+Decorators can be written for functions that take an arbitrary number of arguments by using the `*args` and `**kwargs` syntax.
+
+Following is an example of a decorator for a function that takes an arbitrary number of arguments:
+
+```python
+>>> def double(func):
+... def wrapper(*args, **kwargs):
+... return func(*args, **kwargs) * 2
+... return wrapper
+...
+... @double
+... def add(*args):
+... return sum(args)
+...
+>>> print(add(2, 3, 4))
+18
+>>> print(add(2, 3, 4, 5, 6))
+40
+```
+
+This works for doubling the return value from the function argument.
+If we want to triple, quadruple, etc. the return value, we can add a parameter to the decorator itself, as we show in the next section.
+
+### Decorators Which Have Their own Parameters
+
+Following is an example of a decorator that can be configured to multiply the decorated function's return value by an arbitrary amount:
+
+```python
+>>> def multi(factor=1):
+... if (factor == 0):
+... raise ValueError("factor must not be 0")
+...
+... def outer_wrapper(func):
+... def inner_wrapper(*args, **kwargs):
+... return func(*args, **kwargs) * factor
+... return inner_wrapper
+... return outer_wrapper
+...
+... @multi(factor=3)
+... def add(*args):
+... return sum(args)
+...
+>>> print(add(2, 3, 4))
+27
+>>> print(add(2, 3, 4, 5, 6))
+60
+```
+
+The first lines validate that `factor` is not `0`.
+Then the outer wrapper is defined.
+This has the same signature we expect for an unparameterized decorator.
+The outer wrapper has an inner function with the same signature as the original function.
+The inner wrapper does the work of multiplying the returned value from the original function by the argument passed to the decorator.
+The outer wrapper returns the inner wrapper, and the decorator returns the outer wrapper.
+
+Following is an example of a parameterized decorator that controls whether it validates the argument passed to the original function:
+
+```python
+>>> def check_for_pluto(check=True):
+... def my_validator(func):
+... def my_wrapper(world):
+... print(f"Entering {func.__name__} with {world} argument")
+... if (check and "Pluto" == world):
+... print("Pluto is not a planet!")
+... else:
+... return func(world)
+... return my_wrapper
+... return my_validator
+...
+... @check_for_pluto(check=False)
+... def my_func(planet):
+... print(f"Hello, {planet}!")
+...
+>>> my_func("World")
+Entering my_func with World argument
+Hello, World!
+>>> my_func("Pluto")
+Entering my_func with Pluto argument
+Hello, Pluto!
+```
+
+This allows for easy toggling between checking for `Pluto` or not, and is done without having to modify `my_func`.
+
+[callable objects]: https://www.pythonmorsels.com/callables/
+[first-class functions]: https://www.geeksforgeeks.org/first-class-functions-python/
+[higher-order functions]: https://www.geeksforgeeks.org/higher-order-functions-in-python/
+[inner functions]: https://www.geeksforgeeks.org/python-inner-functions/
diff --git a/concepts/decorators/introduction.md b/concepts/decorators/introduction.md
new file mode 100644
index 00000000000..5bfc4720f20
--- /dev/null
+++ b/concepts/decorators/introduction.md
@@ -0,0 +1,7 @@
+# Introduction
+
+Functions are first-class objects in Python, which means they can also be passed as arguments to other functions.
+Decorators are [higher-order functions][hofunc] that take another function as an argument and return it after extending or modifying its behavior.
+Decorators are defined in the same way as any other function, but they are applied on the line above the functions they are decorating using the `@` symbol before their names (`@my_decorator`). While they can take multiple arguments, decorators must take _at least_ the function they are decorating as an argument.
+
+[hofunc]:https://en.wikipedia.org/wiki/Higher-order_function
diff --git a/concepts/decorators/links.json b/concepts/decorators/links.json
new file mode 100644
index 00000000000..dfaa663c436
--- /dev/null
+++ b/concepts/decorators/links.json
@@ -0,0 +1,22 @@
+[
+ {
+ "url": "https://peps.python.org/pep-0318/",
+ "description": "PEP 318 – Decorators for Functions and Methods"
+ },
+ {
+ "url": "https://www.geeksforgeeks.org/first-class-functions-python/",
+ "description": "First Class Functions in Python"
+ },
+ {
+ "url": "https://www.geeksforgeeks.org/higher-order-functions-in-python/",
+ "description": "Higher Order Functions in Python"
+ },
+ {
+ "url": "https://realpython.com/primer-on-python-decorators/",
+ "description": "Primer on Python Decorators"
+ },
+ {
+ "url": "https://www.geeksforgeeks.org/chain-multiple-decorators-in-python/",
+ "description": "Chain Multiple Decorators in Python"
+ }
+]
diff --git a/concepts/descriptors/.meta/config.json b/concepts/descriptors/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/descriptors/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/descriptors/about.md b/concepts/descriptors/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/descriptors/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/descriptors/introduction.md b/concepts/descriptors/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/descriptors/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/descriptors/links.json b/concepts/descriptors/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/descriptors/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/dict-methods/.meta/config.json b/concepts/dict-methods/.meta/config.json
new file mode 100644
index 00000000000..ab2c0731830
--- /dev/null
+++ b/concepts/dict-methods/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "The dict class in Python provides many useful methods for working with dictionaries. Some are introduced in the concept for dicts. This concept tackles a few more - along with some techniques for iterating through and manipulating dicts.",
+ "authors": ["bethanyg", "valentin-p"],
+ "contributors": []
+}
\ No newline at end of file
diff --git a/concepts/dict-methods/about.md b/concepts/dict-methods/about.md
new file mode 100644
index 00000000000..6dcf9b4ae7a
--- /dev/null
+++ b/concepts/dict-methods/about.md
@@ -0,0 +1,361 @@
+# Dictionary Methods in Python
+
+The `dict` class in Python provides many useful [methods][dict-methods] for working with dictionaries.
+Some were introduced in the concept for `dicts`.
+Here we cover a few more - along with some techniques for iterating through and manipulating dictionaries.
+
+- `dict.setdefault()` automatically adds keys without throwing a KeyError.
+- `dict.fromkeys(iterable, )` creates a new `dict` from any number of iterables.
+- `.keys()`, `.values()`, and `.items()` provide convenient iterators.
+- `sorted(.items())`. can easily re-order entries in a `dict`.
+- `dict_one.update()` updates one `dict` with overlapping values from another `dict`.
+- `dict | other_dict` and `dict |= other_dict` merges or updates two `dict`s via operators.
+- `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())` produce _reversed_ views.
+- `.popitem()` removes and returns a `key`, `value` pair.
+
+
+## `setdefault()` for Error-Free Insertion
+
+The dictionary concept previously covered that `.get(key, )` returns an existing `value` or the `default value` if a `key` is not found in a dictionary, thereby avoiding a `KeyError`.
+This works well in situations where you would rather not have extra error handling but cannot trust that a looked-for `key` will be present.
+
+For a similarly "safe" (_without KeyError_) insertion operation, there is the `.setdefault(key, )` method.
+`setdefault(key, )` will return the `value` if the `key` is found in the dictionary.
+If the key is **not** found, it will _insert_ the (`key`, `default value`) pair and return the `default value` for use.
+
+```python
+>>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'}
+
+# Looking for the value associated with key "Rock Brown".The key does not exist,
+# so it is added with the default value, and the value is returned.
+>>> palette.setdefault('Rock Brown', '#694605')
+'#694605'
+
+# The (key, default value) pair has now been added to the dictionary.
+>>> palette_I
+{'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Rock Brown': '#694605'}
+```
+
+## `fromkeys()` to Populate a Dictionary from an Iterable
+
+To quickly populate a dictionary with various `keys` and default values, the _class method_ [`fromkeys(iterable, )`][fromkeys] will iterate through an iterable of `keys` and create a new `dict`.
+All `values` will be set to the `default value` provided:
+
+```python
+>>> new_dict = dict.fromkeys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'], 'fill in hex color here')
+
+{'Grassy Green': 'fill in hex color here',
+ 'Purple Mountains Majesty': 'fill in hex color here',
+ 'Misty Mountain Pink': 'fill in hex color here'}
+```
+
+## Remove and Return a (key, value) Pair With `.popitem()`
+
+`.popitem()` removes & returns a single (`key`, `value`) pair from a dictionary.
+Pairs are returned in Last-in-First-out (`LIFO`) order.
+If the dictionary is empty, calling `popitem()` will raise a `KeyError`:
+
+```python
+>>> palette_I = {'Grassy Green': '#9bc400',
+ 'Purple Mountains Majesty': '#8076a3',
+ 'Misty Mountain Pink': '#f9c5bd'}
+>>> palette_I.popitem()
+('Misty Mountain Pink', '#f9c5bd')
+
+>>> palette_I.popitem()
+('Purple Mountains Majesty', '#8076a3')
+
+>>> palette_I.popitem()
+('Grassy Green', '#9bc400')
+
+# All (key, value) pairs have been removed.
+>>> palette_I.popitem()
+Traceback (most recent call last):
+
+ line 1, in
+ palette_I.popitem()
+
+KeyError: 'popitem(): dictionary is empty'
+```
+
+## Iterating Over Entries in a Dictionary Via Views
+
+The `.keys()`, `.values()`, and `.items()` methods return [_iterable views_][dict-views] of a dictionary.
+
+These views can be used to easily loop over entries without altering them.
+Views are also _dynamic_ -- when underlying dictionary data changes, the associated `view object` will reflect the change:
+
+```python
+>>> palette_I = {'Grassy Green': '#9bc400',
+ 'Purple Mountains Majesty': '#8076a3',
+ 'Misty Mountain Pink': '#f9c5bd'}
+
+# Using .keys() returns a list of keys.
+>>> palette_I.keys()
+dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'])
+
+# Using .values() returns a list of values.
+>>> palette_I.values()
+dict_values(['#9bc400', '#8076a3', '#f9c5bd'])
+
+# Using .items() returns a list of (key, value) tuples.
+>>> palette_I.items()
+dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', '#8076a3'), ('Misty Mountain Pink', '#f9c5bd')])
+
+# Views are dynamic. Changing values in the dict
+# changes all of the associated views.
+>>> palette_I['Purple Mountains Majesty'] = (128, 118, 163)
+>>> palette_I['Deep Red'] = '#932432'
+
+>>> palette_I.values()
+dict_values(['#9bc400', (128, 118, 163), '#f9c5bd', '#932432'])
+
+>>> palette_I.keys()
+dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink', 'Deep Red'])
+
+>>> palette_I.items()
+dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', (128, 118, 163)), ('Misty Mountain Pink', '#f9c5bd'), ('Deep Red', '#932432')])
+```
+
+## More on `.keys()`, `.values()`, and `.items()`
+
+In Python 3.7+, `dicts` preserve the order in which entries are inserted allowing First-in, First-out (_`FIFO`_), iteration when using `.keys()`, `.values()`, or `.items()`.
+
+In Python 3.8+, views are also _reversible_.
+This allows keys, values, or (`key`, `value`) pairs to be iterated over in Last-in, First-out (`LIFO`) order by using `reversed(.keys())`, `reversed(.values())`, or `reversed(.items())`:
+
+```python
+>>> palette_II = {'Factory Stone Purple': '#7c677f',
+ 'Green Treeline': '#478559',
+ 'Purple baseline': '#161748'}
+>>> for item in palette_II.items():
+... print(item)
+...
+('Factory Stone Purple', '#7c677f')
+('Green Treeline', '#478559')
+('Purple baseline', '#161748')
+
+>>> for item in reversed(palette_II.items()):
+... print (item)
+...
+('Purple baseline', '#161748')
+('Green Treeline', '#478559')
+('Factory Stone Purple', '#7c677f')
+```
+
+## Combine Dictionaries with `.update()`
+
+`.update()` can be used to _combine_ two dictionaries.
+This method will take the (`key`,`value`) pairs of `` and write them into ``:
+
+```python
+>>> palette_I = {'Grassy Green': '#9bc400',
+ 'Purple Mountains Majesty': '#8076a3',
+ 'Misty Mountain Pink': '#f9c5bd'}
+>>> palette_II = {'Factory Stone Purple': '#7c677f',
+ 'Green Treeline': '#478559',
+ 'Purple Baseline': '#161748'}
+
+>>> palette_I.update(palette_II)
+
+# Note that new items from palette_II are added.
+>>> palette_I
+{'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple Baseline': '#161748'}
+```
+
+Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be _overwritten_ by the corresponding `value` from `dict_two`:
+
+```python
+>>> palette_I = {'Grassy Green': '#9bc400',
+ 'Purple Mountains Majesty': '#8076a3',
+ 'Misty Mountain Pink': '#f9c5bd',
+ 'Factory Stone Purple': '#7c677f',
+ 'Green Treeline': '#478559',
+ 'Purple baseline': '#161748'}
+
+>>> palette_III = {'Grassy Green': (155, 196, 0),
+ 'Purple Mountains Majesty': (128, 118, 163),
+ 'Misty Mountain Pink': (249, 197, 189)}
+>>> palette_I.update(palette_III)
+
+# Overlapping values in palette_I are replaced with
+# values from palette_III
+>>> palette_I
+{'Grassy Green': (155, 196, 0),
+ 'Purple Mountains Majesty': (128, 118, 163),
+ 'Misty Mountain Pink': (249, 197, 189),
+ 'Factory Stone Purple': '#7c677f',
+ 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
+```
+
+## Merge or Update Dictionaries Via the Union (`|`) Operators
+
+Python 3.9 introduces a different means of merging `dicts`: the `union` operators.
+`dict_one | dict_two` will create a **new dictionary**, made up of the (`key`, `value`) pairs of `dict_one` and `dict_two`.
+When both dictionaries share keys, `dict_two` values take precedence.
+
+```python
+>>> palette_I = {'Grassy Green': '#9bc400',
+ 'Purple Mountains Majesty': '#8076a3',
+ 'Misty Mountain Pink': '#f9c5bd'}
+
+>>> palette_II = {'Factory Stone Purple': '#7c677f',
+ 'Green Treeline': '#478559',
+ 'Purple baseline': '#161748'}
+
+>>> new_dict = palette_I | palette_II
+>>> new_dict
+...
+{'Grassy Green': '#9bc400',
+ 'Purple Mountains Majesty': '#8076a3',
+ 'Misty Mountain Pink': '#f9c5bd',
+ 'Factory Stone Purple': '#7c677f',
+ 'Green Treeline': '#478559',
+ 'Purple baseline': '#161748'}
+```
+
+`dict_one |= other` behaves similar to `.update()`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs:
+
+```python
+>>> palette_III = {'Grassy Green': (155, 196, 0),
+ 'Purple Mountains Majesty': (128, 118, 163),
+ 'Misty Mountain Pink': (249, 197, 189)}
+>>> new_dict |= palette_III
+>>> new_dict
+...
+{'Grassy Green': (155, 196, 0),
+'Purple Mountains Majesty': (128, 118, 163),
+'Misty Mountain Pink': (249, 197, 189),
+'Factory Stone Purple': '#7c677f',
+'Green Treeline': '#478559',
+'Purple baseline': '#161748'}
+```
+
+## Sorting a Dictionary
+
+Dictionaries do not have a built-in sorting method.
+However, it is possible to sort a `dict` _view_ using the built-in function `sorted()` with `.items()`.
+The sorted view can then be used to create a new dictionary.
+Unless a _sort key_ is specified, the default sort is over dictionary `keys`.
+
+```python
+# Default ordering for a dictionary is last in, first out (LIFO).
+>>> color_palette = {'Grassy Green': '#9bc400',
+ 'Purple Mountains Majesty': '#8076a3',
+ 'Misty Mountain Pink': '#f9c5bd',
+ 'Factory Stone Purple': '#7c677f',
+ 'Green Treeline': '#478559',
+ 'Purple baseline': '#161748'}
+
+
+# The default sort order for a dictionary uses the keys.
+>>> sorted_palette = dict(sorted(color_palette.items()))
+>>> sorted_palette
+{'Factory Stone Purple': '#7c677f',
+ 'Grassy Green': '#9bc400',
+ 'Green Treeline': '#478559',
+ 'Misty Mountain Pink': '#f9c5bd',
+ 'Purple Mountains Majesty': '#8076a3',
+ 'Purple baseline': '#161748'}
+
+
+# A sort key can be provided in the form
+# of an anonymous function (lambda).
+>>> value_sorted_palette = dict(sorted(color_palette.items(), key=lambda color: color[1]))
+>>> value_sorted_palette
+{'Purple baseline': '#161748',
+ 'Green Treeline': '#478559',
+ 'Factory Stone Purple': '#7c677f',
+ 'Purple Mountains Majesty': '#8076a3',
+ 'Grassy Green': '#9bc400',
+ 'Misty Mountain Pink': '#f9c5bd'}
+```
+
+## Transposing a Dictionaries Keys and Values
+
+Swapping keys and values reliably in a dictionary takes a little work, but can be accomplished via a `loop` using `dict.items()` or in a dictionary comprehension.
+Safe swapping assumes that `dict` keys and values are both _hashable_.
+
+```python
+color_reference = {'Purple Mountains Majesty': '#8076a3',
+ 'Misty Mountain Pink': '#f9c5bd',
+ 'Factory Stone Purple': '#7c677f',
+ 'Green Treeline': '#478559',
+ 'Purple baseline': '#161748'}
+
+# Creating a new dictionary to hold the swapped entries.
+>>> swapped_color_reference = {}
+
+# Iterating through the dictionary, using values as keys.
+>>> for key, value in color_reference.items():
+... swapped_color_reference[value] = key
+
+>>> swapped_color_reference
+{'#8076a3': 'Purple Mountains Majesty',
+ '#f9c5bd': 'Misty Mountain Pink',
+ '#7c677f': 'Factory Stone Purple',
+ '#478559': 'Green Treeline',
+ '#161748': 'Purple baseline'}
+
+
+# A dictionary comprehension can also be used to swap entries.
+>>> swapped = {value: key for key, value in
+ color_reference.items()}
+>>> swapped
+{'#8076a3': 'Purple Mountains Majesty',
+ '#f9c5bd': 'Misty Mountain Pink',
+ '#7c677f': 'Factory Stone Purple',
+ '#478559': 'Green Treeline',
+ '#161748': 'Purple baseline'}
+```
+
+If the values stored in the `dict` are not unique, extra checks become necessary before key and value swapping can happen:
+
+```python
+# Things become more complicated if there are duplicates in
+# potential key values.This dict is arranged by hex, RGB, and HSL
+# keys, but values repeat.
+>>> extended_colors = {'#8076a3': 'Purple Mountains Majesty',
+ (128, 118, 163): 'Purple Mountains Majesty',
+ (21, 28, 0, 36): 'Purple Mountains Majesty',
+ '#f9c5bd': 'Misty Mountain Pink',
+ (249, 197, 189): 'Misty Mountain Pink',
+ (0, 21, 24, 2): 'Misty Mountain Pink',
+ '#7c677f': 'Factory Stone Purple',
+ (124, 103, 127): 'Factory Stone Purple',
+ (2, 19, 0, 50): 'Factory Stone Purple',
+ '#478559': 'Green Treeline',
+ (71, 133, 89): 'Green Treeline',
+ (47, 0, 33, 48): 'Green Treeline'}
+
+# New empty dictionary for holding swapped entries.
+>>> consolidated_colors = {}
+
+# Iterating over (key, value) pairs using .items()
+>>> for key, value in extended_color_reference.items():
+... if value in consolidated_colors: #Check if key has already been created.
+... consolidated_colors[value].append(key)
+... else:
+... consolidated_colors[value] = [key] #Create a value list with the former key in it.
+
+>>> consolidated_colors
+{'Purple Mountains Majesty': ['#8076a3', (128, 118, 163), (21, 28, 0, 36)],
+ 'Misty Mountain Pink': ['#f9c5bd', (249, 197, 189), (0, 21, 24, 2)],
+ 'Factory Stone Purple': ['#7c677f', (124, 103, 127), (2, 19, 0, 50)],
+ 'Green Treeline': ['#478559', (71, 133, 89), (47, 0, 33, 48)]}
+```
+
+For a detailed explanation of dictionaries and methods for working with them, the [official tutorial][dicts-docs] and the [official library reference][mapping-types-dict] are excellent starting places.
+
+[Real Python][how-to-dicts] and [Finxter][fi-dict-guide] also have very thorough articles on Python dictionaries.
+
+For more on sorting, see the [Sorting HOW TO][sorting-howto] in the Python docs.
+
+[dict-methods]: https://docs.python.org/3/library/stdtypes.html#dict
+[dict-views]: https://docs.python.org/3/library/stdtypes.html#dict-views
+[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
+[fi-dict-guide]: https://blog.finxter.com/python-dictionary
+[fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys
+[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp
+[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
+[sorting-howto]: https://docs.python.org/3/howto/sorting.html
diff --git a/concepts/dict-methods/introduction.md b/concepts/dict-methods/introduction.md
new file mode 100644
index 00000000000..c15fbc113de
--- /dev/null
+++ b/concepts/dict-methods/introduction.md
@@ -0,0 +1,16 @@
+# Dictionary Methods in Python
+
+The `dict` class in Python provides many useful [methods][dict-methods], some of which are introduced in the concept exercise for dictionaries.
+
+This concept tackles a few more:
+
+- `dict.setdefault()` automatically adds keys without throwing a `KeyError`.
+- `dict.fromkeys(iterable, )` creates a new `dict` from any number of iterables.
+- `.keys()`, `.values()`, and `.items()` provide convenient iterators.
+- `sorted(.items())`. can easily re-order entries in a `dict`.
+- `dict_one.update()` updates one `dict` with overlapping values from another `dict`.
+- `dict | other_dict` and `dict |= other_dict` merges or updates two `dict`s via operators.
+- `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())` produce _reversed_ views.
+- `.popitem()` removes and returns a `key`, `value` pair.
+
+[dict-methods]: https://docs.python.org/3/library/stdtypes.html#dict
diff --git a/concepts/dict-methods/links.json b/concepts/dict-methods/links.json
new file mode 100644
index 00000000000..05e57f3b159
--- /dev/null
+++ b/concepts/dict-methods/links.json
@@ -0,0 +1,46 @@
+[
+ {
+ "url": "https://docs.python.org/3/glossary.html#term-hashable",
+ "description": "What is hashable?"
+ },
+ {
+ "url": "https://en.wikipedia.org/wiki/Hash_table",
+ "description": "What is a hashtable?"
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#mapping-types-dict",
+ "description": "Pythons mapping type."
+ },
+ {
+ "url": "https://docs.python.org/3/tutorial/datastructures.html#dictionaries",
+ "description": "Python Tutorial: dictionaries"
+ },
+ {
+ "url": "https://realpython.com/python-dicts/",
+ "description": "Real Python: Dictionaries in Python."
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#dict.fromkeys",
+ "description": "Classmethod fromkeys"
+ },
+ {
+ "url": "https://docs.python.org/3/library/collections.html",
+ "description": "Python Collections Module."
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#dict-views",
+ "description": "dict views"
+ },
+ {
+ "url": "https://blog.finxter.com/python-dictionary",
+ "description": "Python Dictionary - The Ultimate Guide"
+ },
+ {
+ "url": "https://www.youtube.com/watch?v=lyDLAutA88s",
+ "description": "David Beazley: PyData Keynote - Built in Super Heroes"
+ },
+ {
+ "url": "https://docs.python.org/3/howto/sorting.html",
+ "description": "The Python Docs Sorting How-to."
+ }
+]
diff --git a/concepts/dicts/.meta/config.json b/concepts/dicts/.meta/config.json
new file mode 100644
index 00000000000..54726e423a4
--- /dev/null
+++ b/concepts/dicts/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "A dictionary (\"dict\") is a data structure that associates hashable keys to values -- known in other programming languages as a hash table or hashmap. Like most collections, dicts can hold reference to any/multiple data type(s) -- including other dictionaries. \"dicts\" enable the retrieval of a value in constant time, given the key.",
+ "authors": ["j08k"],
+ "contributors": ["valentin-p", "bethanyG"]
+}
diff --git a/concepts/dicts/about.md b/concepts/dicts/about.md
new file mode 100644
index 00000000000..72ea9079c6d
--- /dev/null
+++ b/concepts/dicts/about.md
@@ -0,0 +1,292 @@
+# About
+
+A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array].
+Dictionaries are Python's only built-in [mapping type][mapping-types-dict].
+
+`Keys` must be hashable and unique across the dictionary.
+Key types can include `numbers`, `str`, or `tuples` (of _immutable_ values).
+They cannot contain _mutable_ data structures such as `lists`, `dict`s, or `set`s.
+As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted.
+
+`values` can be of any data type or structure.
+ Values can also nest _arbitrarily_, so they can include lists-of-lists, sub-dictionaries, and other custom or compound data structures.
+
+Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_).
+Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval.
+
+Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently.
+
+
+## Dictionary Construction
+
+Dictionaries can be created in many different ways, including:
+ - Using the [`fromkeys()`][fromkeys] classmethod
+ - Creating [dictionary comprehensions][dict-comprehensions]
+ - Merging two dictionaries via unpacking (`**`)
+ - Merging dictionaries via the `|` (_update_) operator
+ - Using a loop to iteratively add entries to a previously created empty `dict`.
+
+The two most straightforward methods are the dictionary _constructor_ and the dictionary _literal_.
+
+### The Dictionary Constructor
+
+`dict()` (_the constructor for the `dict` class_) can be used with any iterable of `key`, `value` pairs.
+ It can also be called with a series of `=` _arguments_:
+
+```python
+# Passing a list of key,value tuples.
+>>> wombat = dict([('name', 'Wombat'),('speed', 23),
+ ('land_animal', True)])
+{'name': 'Wombat', 'speed': 23, 'land_animal': True}
+
+
+# Using key=value arguments.
+>>> bear = dict(name="Black Bear", speed=40, land_animal=True)
+{'name': 'Black Bear', 'speed': 40, 'land_animal': True}
+```
+
+The [documentation on `dicts`][dicts-docs] outlines additional variations and options in constructor use.
+
+
+### Dictionary Literals
+
+A dictionary can also be directly entered as a _dictionary literal_, using curly brackets (`{}`) enclosing `key : value` pairs.
+Entries that are enclosed in the `{}` can also appear on separate lines:
+
+```python
+>>> whale = {"name": "Blue Whale",
+ "speed": 35,
+ "land_animal": False}
+{'name': 'Blue Whale', 'speed': 35, 'land_animal': False}
+
+>>> wombat = {'name': 'Wombat',
+ 'speed': 23,
+ 'land_animal': True,
+ 'color': 'Brindle'}
+
+>>> wombat
+{'name': 'Wombat', 'speed': 23, 'land_animal': True, 'color': 'Brindle'}
+```
+
+### Nested Dictionaries
+
+Dictionaries can be arbitrarily nested:
+
+```python
+animals = {
+ "Real" : {
+ "Winged" : {
+ "Sparrow" : {'name': 'sparrow','speed': 12, 'land_animal': True},
+ "Kestrel" : {'name': 'kestrel', 'speed': 15, 'land_animal': True}
+ },
+ "Legged" : {
+ "Wombat" : {'name': 'Wombat', 'speed': 23, 'land_animal': True},
+ "Black Bear": {'name': 'Black Bear', 'speed': 40, 'land_animal': True},
+ "Polecat" : {'name': 'Polecat', 'speed': 15, 'land_animal': True}
+ },
+ "Other" : {
+ "Whale" : {'name': 'Blue Whale', 'speed': 35, 'land_animal': False},
+ "Orca" : {'name': 'Orca', 'speed': 45, 'land_animal': False},
+ "Snake" : {'name': 'Python', 'speed': 25, 'land_animal': True}
+ }
+ },
+
+ "Imaginary": {
+ "Winged" : {
+ "Dragon" : {'name': 'Fire Dragon','speed': 100, 'land_animal': True},
+ "Phoenix" : {'name': 'Phoenix', 'speed': 1500, 'land_animal': True}
+ },
+ "Legged" : {
+ "Sphinx" : {'name': 'Sphinx','speed': 10, 'land_animal': True},
+ "Minotaur" : {'name': 'Minotaur', 'speed': 5, 'land_animal': True}
+ },
+ "Other" : {}
+ }
+ }
+```
+
+## Accessing Values in a `dict`
+
+You can access a `value` in a dictionary using a _key_ in square brackets.
+If a key does not exist, a `KeyError` is thrown:
+
+```python
+>>> bear["speed"]
+40
+
+>>> bear["color"]
+Traceback (most recent call last):
+ File "", line 1, in
+KeyError: 'color'
+```
+
+Accessing an entry via the `get(, )` method can avoid the `KeyError`:
+
+```python
+>>> bear.get("color", 'not found')
+'not found'
+```
+
+### Accessing Nested Dictionary Entries
+
+To access entries in nested dictionaries, use successive brackets.
+If a given key is missing, the usual KeyError will be thrown:
+
+```python
+# Using the animals nested dictionary.
+>>> animals["Real"]["winged"]["Kestrel"]["speed"]
+15
+
+>>> animals["Imaginary"]["winged"]["Kestrel"]["speed"]
+Traceback (most recent call last):
+ File "", line 1, in
+KeyError: 'Kestrel'
+```
+
+To avoid the `KeyError`, `.get()` can be used, but the calls to `.get()` must be _chained_:
+
+```python
+# Using the animals nested dictionary.
+# Note the use of parenthesis to enable placing the
+# .get() calls on separate lines.
+>>> (animals.get("Imaginary", {})
+ .get("Legged", {})
+ .get("Sphinx", {})
+ .get("Color", "I have no idea!"))
+'I have no idea!'
+```
+
+## Changing or Adding Dictionary Values
+
+You can change an entry `value` by assigning to its _key_:
+
+```python
+# Assigning the value "Grizzly Bear" to the name key.
+>>> bear["name"] = "Grizzly Bear"
+{'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True}
+
+>>> whale["speed"] = 25
+{'name': 'Blue Whale', 'speed': 25, 'land_animal': False}
+```
+
+New `key`:`value` pairs can be _added_ in the same fashion:
+
+```python
+# Adding a new "color" key with a new "tawney" value.
+>>> bear["color"] = 'tawney'
+{'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True, 'color': 'tawney'}
+
+>>> whale["blowholes"] = 1
+{'name': 'Blue Whale', 'speed': 25, 'land_animal': False, 'blowholes': 1}
+```
+
+
+## Removing (Pop-ing and del) Dictionary Entries
+
+You can use the `.pop()` method to delete a dictionary entry.
+`.pop()` removes the (`key`, `value`) pair and returns the `value` for use.
+Like `.get()`, `.pop()` accepts second argument (_`dict.pop(, )`_) that will be returned if the `key` is not found.
+This prevents a `KeyError` being raised:
+
+```python
+# Using .pop() removes both the key and value, returning the value.
+>>> bear.pop("name")
+'Grizzly Bear'
+
+
+# The "name" key is now removed from the dictionary.
+# Attempting .pop() a second time will throw a KeyError.
+>>> bear.pop("name")
+Traceback (most recent call last):
+ File "", line 1, in
+KeyError: 'name'
+
+
+# Using a default argument with .pop() will
+# prevent a KeyError from a missing key.
+>>> bear.pop("name", "Unknown")
+'Unknown'
+```
+
+You can also use the `del` statement to remove a single or multiple entries.
+A `KeError` is raised if the entry to be removed is not found in the dictionary:
+
+```python
+>>> wombat = {'name': 'Wombat',
+ 'speed': 23,
+ 'land_animal': True,
+ 'color': 'Brindle',
+ 'talent': 'Singing',
+ 'size': 'small'}
+
+# Remove a single entry from the dictionary.
+>>> del wombat["color"]
+>>> wombat
+{'name': 'Wombat', 'speed': 23, 'land_animal': True, 'talent': 'Singing', 'size': 'small'}
+
+
+# Remove multiple entries from the dictionary.
+>>> del wombat["talent"], wombat["size"]
+>>> wombat
+{'name': 'Wombat', 'speed': 23, 'land_animal': True}
+
+
+# Attempting a deletion of a non-existent key raises a KeyError
+>>> del wombat["number_of_legs"]
+Traceback (most recent call last):
+ File "", line 1, in
+KeyError: 'number_of_legs'
+```
+
+## Looping Through/Iterating over a Dictionary
+
+Looping through a dictionary using `for item in dict` or `while item` will iterate over the _keys_ by default.
+You can access _values_ within the same loop by using _square brackets_:
+
+```python
+>>> for key in bear:
+>>> print((key, bear[key])) #this prints a tuple of (key, value)
+('name', 'Black Bear')
+('speed', 40)
+('land_animal', True)
+```
+
+You can also use the `.items()` method, which returns (`key`, `value`) tuples:
+
+```python
+# dict.items() forms (key, value tuples) that can be
+# unpacked and iterated over.
+>>> for key, value in whale.items():
+>>> print(key, ":", value)
+name : Blue Whale
+speed : 25
+land_animal : False
+blowholes : 1
+```
+
+Likewise, `.keys()` will return the `keys` and `.values()` will return the `values`.
+
+For a detailed explanation of dictionaries in Python, the [official documentation][dicts-docs] is an excellent starting place, or you can also check out the [W3-Schools][how-to-dicts] tutorial.
+
+
+## Extending Dictionary Functionality: The Collections Module
+
+The [`collections`][collections-docs] module adds specialized functionality to Python's standard collection-based datatypes (`dictionary`, `set`, `list`, `tuple`).
+Three of the most useful dictionary-based classes are:
+
+- [`Counter`][counter-dicts] automatically counts items and returns them in a `dict` with the items as keys and their counts as values.
+- [`OrderedDict`][ordered-dicts-docs], has methods specialized for arranging the order of dictionary entries.
+- [`defaultdict`][default-dicts] uses a factory method to set a default value if a `key` is not found when trying to retrieve or assign to a dictionary entry.
+
+[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain.
+[collections-docs]: https://docs.python.org/3/library/collections.html
+[counter-dicts]: https://docs.python.org/3/library/collections.html#collections.Counter
+[default-dicts]: https://docs.python.org/2/library/collections.html#collections.defaultdict
+[dict-comprehensions]: https://www.learnbyexample.org/python-dictionary-comprehension/
+[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
+[fromkeys]: https://www.w3schools.com/python/ref_dictionary_fromkeys.asp
+[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table
+[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp
+[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
+[ordered-dicts-docs]: https://docs.python.org/3/library/collections.html#collections.OrderedDict
+[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable
diff --git a/concepts/dicts/introduction.md b/concepts/dicts/introduction.md
new file mode 100644
index 00000000000..5c8a772480b
--- /dev/null
+++ b/concepts/dicts/introduction.md
@@ -0,0 +1,24 @@
+# Introduction
+
+A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array].
+Dictionaries are Python's only built-in [mapping type][mapping-types-dict].
+
+
+`Keys` must be hashable and unique across the dictionary.
+Key types can include `numbers`, `str`, or `tuples` (of _immutable_ values).
+They cannot contain _mutable_ data structures such as `lists`, `dict`s, or `set`s.
+As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted.
+
+`values` can be of any data type or structure.
+ Values can also nest _arbitrarily_, so they can include lists-of-lists, sub-dictionaries, and other custom or compound data structures.
+
+Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_).
+Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval.
+
+Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently.
+
+
+[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain.
+[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table
+[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
+[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable
diff --git a/concepts/dicts/links.json b/concepts/dicts/links.json
new file mode 100644
index 00000000000..53ea3863bc6
--- /dev/null
+++ b/concepts/dicts/links.json
@@ -0,0 +1,34 @@
+[
+ {
+ "url": "https://docs.python.org/3/glossary.html#term-hashable",
+ "description": "Term: hashable"
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#mapping-types-dict",
+ "description": "Mapping object: dict"
+ },
+ {
+ "url": "https://docs.python.org/3/tutorial/datastructures.html#dictionaries",
+ "description": "Docs: dicts"
+ },
+ {
+ "url": "https://www.w3schools.com/python/python_dictionaries.asp",
+ "description": "how to use dicts"
+ },
+ {
+ "url": "https://docs.python.org/3/library/collections.html",
+ "description": "Docs: collections"
+ },
+ {
+ "url": "https://docs.python.org/3/library/collections.html#collections.Counter",
+ "description": "Docs: Counter"
+ },
+ {
+ "url": "https://docs.python.org/3/library/collections.html#collections.OrderedDict",
+ "description": "Docs: ordered dicts"
+ },
+ {
+ "url": "https://docs.python.org/2/library/collections.html#collections.defaultdict",
+ "description": "Docs: default dicts"
+ }
+]
diff --git a/concepts/enums/.meta/config.json b/concepts/enums/.meta/config.json
new file mode 100644
index 00000000000..164e2638177
--- /dev/null
+++ b/concepts/enums/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "An enum is a set of unique names that are bound to unique, *constant* values and thereafter are immutable. Enums are defined by inheriting an \"Enum\" class. Built-in enum types are available in the module \"enum\" and the class \"Enum\" can be imported using \"from enum import Enum\" syntax.",
+ "authors": ["mohanrajanr", "BethanyG"],
+ "contributors": ["valentin-p"]
+}
diff --git a/concepts/enums/about.md b/concepts/enums/about.md
new file mode 100644
index 00000000000..27b264c22e1
--- /dev/null
+++ b/concepts/enums/about.md
@@ -0,0 +1,113 @@
+# About
+
+In Python, [an enum][enum-docs] is a set of unique names that are bound unique, **constant** values. Enums are defined by inheriting an `Enum` class. Built-in enum types are available in the module `enum` and the class `Enum` can be imported using `from enum import Enum`.
+
+```python
+class Color(Enum):
+ RED = 1
+ GREEN = 2
+```
+
+Note that the values of the enum members can be any data types such as str, tuple, float, etc.
+
+```python
+class Color(Enum):
+ RED = 'red'
+ GREEN = 'green'
+```
+
+Enums can also be created via the following [functional API][enum-functional-api].
+
+```python
+Animal = Enum('Animal', 'ANT BEE CAT DOG')
+list(Animal)
+#=> [, , , ]
+
+Animal.ANT.value
+#=> 1
+```
+
+When assigning the same value to two members in an enum, the latter assigned member will be an alias to the formed one. It is not allowed to use the same name for two members of an enum.
+
+```python
+class Color(Enum):
+ RED = 1
+ GREEN = 2
+ ALIAS_OF_RED = 1
+
+Color.ALIAS_OF_RED
+#=>
+```
+
+Iterating through the members of the enum can be done with the standard `for member in` syntax:
+
+```python
+for member in Color:
+ print((member.name, member.value))
+#=> (RED, 1)
+#=> (GREEN, 2)
+
+# __members__.items() helps you to loop through alias as well
+for member in Color.__members__.items():
+ print(member)
+#=>('RED', )
+#=>('GREEN', )
+#=>('ALIAS_OF_RED', )
+```
+
+Enum members can be compared using [`is` (_identity operator_)][identity-keyword] or `is not`. The `==` or `!=` (_equality operators_) work likewise.
+
+```python
+a = Color.RED
+
+a is Color.RED
+#=> True
+
+a == Color.RED
+#=> True
+```
+
+To assign integer values, the [`auto()` function][enum-auto-docs] starts with `1` and automatically sets subsequent values.
+
+```python
+class Shape(Enum):
+ CIRCLE = auto()
+ SQUARE = auto()
+ OVAL = auto()
+```
+
+To disallow aliasing (_preventing duplicate values with different names_), the `@unique` decorator may be used.
+
+```python
+@unique
+class Shape(Enum):
+ CIRCLE = 1
+ SQUARE = 2
+ TRIANGLE = 1
+#=> ValueError: duplicate values found in : TRIANGLE -> CIRCLE
+```
+
+To access an enum member for a given value, this notation can be used: `EnumName(value)`.
+
+```python
+g = Color(2)
+
+g is Color.GREEN
+#=> True
+
+g
+#=>
+```
+
+A custom [restricted `Enum`][restricted-enums] can be written by subclassing `Enum` with any mix-in or data-type. For example:
+
+```python
+class StrEnum(str, Enum):
+ pass
+```
+
+[enum-docs]: https://docs.python.org/3/library/enum.html
+[enum-auto-docs]: https://docs.python.org/3/library/enum.html#using-auto
+[enum-functional-api]: https://docs.python.org/3/library/enum.html#functional-api
+[restricted-enums]: https://docs.python.org/3/library/enum.html#restricted-enum-subclassing
+[identity-keyword]: https://www.w3schools.com/python/ref_keyword_is.asp
diff --git a/concepts/enums/introduction.md b/concepts/enums/introduction.md
new file mode 100644
index 00000000000..ea9c9000e07
--- /dev/null
+++ b/concepts/enums/introduction.md
@@ -0,0 +1,65 @@
+# Introduction
+
+In Python, [an enum](https://docs.python.org/3/library/enum.html) is a set of names that are bound to unique `literal`, or `constant` values. Enums are defined by inheriting an `Enum` class. Built-in enum types are available in the module `enum` and the class `Enum` can be imported using `from enum import Enum`.
+
+```python
+class Color(Enum):
+ RED = 1
+ GREEN = 2
+```
+
+Note that the values of the enum members can be any data types such as str, tuple, float, etc.
+
+```python
+class Color(Enum):
+ RED = 'red'
+ GREEN = 'green'
+```
+
+When assigning the same value to two members in an enum, the latter assigned member will be an alias to the formed one. It is not allowed to use the same name for two members of an enum.
+
+```python
+class Color(Enum):
+ RED = 1
+ GREEN = 2
+ ALIAS_OF_RED = 1
+
+Color.ALIAS_OF_RED
+#=>
+
+Color.ALIAS_OF_RED.value
+#=> 1
+```
+
+Iterating through the members of the enum can be done with the standard `for member in` syntax:
+
+```python
+for member in Color:
+ print((member.name, member.value))
+#=> (RED, 1)
+#=> (GREEN, 2)
+```
+
+Enum members can be compared using [`is` (_identity operator_)](https://www.w3schools.com/python/ref_keyword_is.asp) or `is not`. The `==` or `!=` (_equality_operators_) work likewise.
+
+```python
+a = Color.RED
+
+a is Color.RED
+#=> True
+
+a == Color.RED
+#=> True
+```
+
+To access an enum member for a given value, `EnumName(value)` can be used:
+
+```python
+g = Color(2)
+
+g is Color.GREEN
+#=> True
+
+g
+#=>
+```
diff --git a/concepts/enums/links.json b/concepts/enums/links.json
new file mode 100644
index 00000000000..e5febb59c7b
--- /dev/null
+++ b/concepts/enums/links.json
@@ -0,0 +1,22 @@
+[
+ {
+ "url": "https://docs.python.org/3/library/enum.html",
+ "description": "enum documentation"
+ },
+ {
+ "url": "https://www.w3schools.com/python/ref_keyword_is.asp",
+ "description": "identity keyword"
+ },
+ {
+ "url": "https://docs.python.org/3/library/enum.html#using-auto",
+ "description": "enum auto documentation"
+ },
+ {
+ "url": "https://docs.python.org/3/library/enum.html#functional-api",
+ "description": "enum functional api"
+ },
+ {
+ "url": "https://docs.python.org/3/library/enum.html#restricted-enum-subclassing",
+ "description": "restricted enum"
+ }
+]
diff --git a/concepts/fractions/.meta/config.json b/concepts/fractions/.meta/config.json
new file mode 100644
index 00000000000..621a3766d84
--- /dev/null
+++ b/concepts/fractions/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "The fractions module enables working with rational numbers, which preserve exact values and avoid the rounding errors common with floats.",
+ "authors": ["BethanyG", "colinleach"],
+ "contributors": []
+}
diff --git a/concepts/fractions/about.md b/concepts/fractions/about.md
new file mode 100644
index 00000000000..d41124c39c4
--- /dev/null
+++ b/concepts/fractions/about.md
@@ -0,0 +1,122 @@
+# About
+
+The [`Fractions`][fractions] module allows us to create and work with [`rational numbers`][rational]: fractions with an integer numerator divided by an integer denominator.
+
+For example, we can store `2/3` as an exact fraction instead of the approximate `float` value `0.6666...`
+
+## Creating Fractions
+
+
+Unlike `int`, `float`, and `complex` numbers, fractions do not have a literal form.
+However, the fractions constructor is quite flexible.
+
+Most obviously, it can take take two integers.
+Common factors are automatically removed, converting the fraction to its "lowest form": the smallest integers that accurately represent the fraction.
+
+
+```python
+>>> from fractions import Fraction
+
+>>> f1 = Fraction(2, 3) # 2/3
+>>> f1
+Fraction(2, 3)
+
+>>> f2 = Fraction(6, 9)
+>>> f2
+Fraction(2, 3) # automatically simplified
+
+>>> f1 == f2
+True
+```
+
+The fractions constructor can also parse a string representation:
+
+
+```python
+>>> f3 = Fraction('2/3')
+>>> f3
+Fraction(2, 3)
+```
+
+It can also work with `float` parameters, but this may run into problems with the approximate nature of representing the decimal value internally as binary.
+For more on this representation issue, see the [0.30000000000000004][0.30000000000000004] website, and [Floating Point Arithmetic: Issues and Limitations ][fp-issues] in the Python documentation.
+
+For a more reliable result when using floats with fractions, there is the `.limit_denominator()` method.
+
+
+[`.limit_denominator()`][limit_denominator] can take an integer parameter if you have specific requirements, but even the default (`max_denominator=1000000`) can work well and give an acceptable, simple approximation.
+
+```python
+>>> Fraction(1.2)
+Fraction(5404319552844595, 4503599627370496)
+
+>>> Fraction(1.2).limit_denominator()
+Fraction(6, 5)
+```
+
+## Arithmetic with Fractions
+
+
+The usual [`arithmetic operators`][operators] `+ - * / **` work with fractions, as with other numeric types.
+
+Integers and other `Fraction`s can be included and give a `Fraction` result.
+Including a `float` in the expression results in `float` output, with a consequent (possible) loss in precision.
+
+
+```python
+>>> Fraction(2, 3) + Fraction(1, 4) # addition
+Fraction(11, 12)
+
+>>> Fraction(2, 3) * Fraction(6, 5) # multiply fractions
+Fraction(4, 5)
+
+>>> Fraction(2, 3) * 6 / 5 # fraction with integers
+Fraction(4, 5)
+
+>>> Fraction(2, 3) * 1.2 # fraction with float -> float
+0.7999999999999999
+
+>>> Fraction(2, 3) ** 2 # exponentiation with integer
+Fraction(4, 9)
+```
+
+## Conversions to and from Fractions
+
+
+Fractions are great for preserving precision during intermediate calculations, but may not be what you want for the final output.
+
+It is possible to get the numerator and denominator individually or as a tuple ([`tuples`][tuple] will be discussed in a later Concept):
+
+```python
+>>> Fraction(2, 3).numerator
+2
+>>> Fraction(2, 3).denominator
+3
+>>> Fraction(2, 3).as_integer_ratio()
+(2, 3)
+```
+
+Various standard Python numeric functions also give the result you might expect from working with `int` and `float` types:
+
+```python
+>>> round(Fraction(11, 3))
+4
+
+>>> from math import floor, ceil
+>>> floor(Fraction(11, 3))
+3
+>>> ceil(Fraction(11, 3))
+4
+
+>>> float(Fraction(11, 3))
+3.6666666666666665
+```
+
+[fractions]: https://docs.python.org/3/library/fractions.html
+[0.30000000000000004]: https://0.30000000000000004.com/
+[fp-issues]: https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues
+[tuple]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences
+
+[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex
+[rational]: https://en.wikipedia.org/wiki/Rational_number
+[limit_denominator]: https://docs.python.org/3/library/fractions.html
diff --git a/concepts/fractions/introduction.md b/concepts/fractions/introduction.md
new file mode 100644
index 00000000000..437ccbbeb07
--- /dev/null
+++ b/concepts/fractions/introduction.md
@@ -0,0 +1,85 @@
+# Introduction
+
+The [`Fractions`][fractions] module allows us to create and work with [`rational numbers`][rational]: fractions with an integer numerator divided by an integer denominator.
+For example, we can store `2/3` as an exact fraction instead of the approximate `float` value `0.6666...`.
+
+Unlike `int`, `float`, and `complex` numbers, fractions do not have a literal form.
+However, the fractions constructor is quite flexible.
+
+Most obviously, it can take take two integers as arguments.
+Common factors are automatically removed, converting the fraction to its "lowest form": the smallest integers that accurately represent the fraction:
+
+```python
+>>> from fractions import Fraction
+
+>>> f1 = Fraction(2, 3) # 2/3
+>>> f1
+Fraction(2, 3)
+
+>>> f2 = Fraction(6, 9)
+>>> f2
+Fraction(2, 3) # automatically simplified
+
+>>> f1 == f2
+True
+```
+
+The fractions constructor can also parse a string representation:
+
+```python
+>>> f3 = Fraction('2/3')
+>>> f3
+Fraction(2, 3)
+```
+
+Fractions can also work with `float` parameters, but this may run into problems with the approximate nature of representing the decimal value internally as binary.
+For more on this representation issue, see the [0.30000000000000004][0.30000000000000004] website, and [Floating Point Arithmetic: Issues and Limitations ][fp-issues] in the Python documentation.
+
+For a more reliable result when using floats with fractions, there is the `.limit_denominator()` method.
+
+
+## Arithmetic with Fractions
+
+The usual [`arithmetic operators`][operators] `+ - * / **` will work with fractions, as with other numeric types.
+
+Integers and other `Fraction`s can be included in the equation and give a `Fraction` result.
+Including a `float` in the expression results in `float` output, with a consequent (possible) loss in precision:
+
+```python
+>>> Fraction(2, 3) + Fraction(1, 4) # addition
+Fraction(11, 12)
+
+>>> Fraction(2, 3) * Fraction(6, 5) # multiply fractions
+Fraction(4, 5)
+
+>>> Fraction(2, 3) * 6 / 5 # fraction with integers
+Fraction(4, 5)
+
+>>> Fraction(2, 3) * 1.2 # fraction with float -> float
+0.7999999999999999
+
+>>> Fraction(2, 3) ** 2 # exponentiation with integer
+Fraction(4, 9)
+```
+
+Various standard Python numeric functions also give the result you might expect from working with `int` and `float` types:
+
+```python
+>>> round(Fraction(11, 3))
+4
+
+>>> from math import floor, ceil
+>>> floor(Fraction(11, 3))
+3
+>>> ceil(Fraction(11, 3))
+4
+
+>>> float(Fraction(11, 3))
+3.6666666666666665
+```
+
+[0.30000000000000004]: https://0.30000000000000004.com/
+[fp-issues]: https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues
+[fractions]: https://docs.python.org/3/library/fractions.html
+[operators]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex
+[rational]: https://en.wikipedia.org/wiki/Rational_number
diff --git a/concepts/fractions/links.json b/concepts/fractions/links.json
new file mode 100644
index 00000000000..78d349bcfc3
--- /dev/null
+++ b/concepts/fractions/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "https://docs.python.org/3/library/fractions.html/",
+ "description": "Documentation for the Fractions module."
+ },
+ {
+ "url": "https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues",
+ "description": "Limitations of Floating Point Arithmetic."
+ },
+ {
+ "url": "https://leancrew.com/all-this/2023/08/decimal-to-fraction/",
+ "description": "And now it's all this: Decimal to fraction."
+ },
+ {
+ "url": "https://nrich.maths.org/2515",
+ "description": "History of Fractions."
+ }
+]
diff --git a/concepts/function-arguments/.meta/config.json b/concepts/function-arguments/.meta/config.json
new file mode 100644
index 00000000000..a28633b9959
--- /dev/null
+++ b/concepts/function-arguments/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "Python supports positional, keyword, variadic-positional, variadic-keyword, and default function arguments. Argument types can be any valid Python object, including other functions or classes. This allows for a wide range of options when defining and calling functions in a program.",
+ "authors": ["bobahop", "bethanyg"],
+ "contributors": []
+}
diff --git a/concepts/function-arguments/about.md b/concepts/function-arguments/about.md
new file mode 100644
index 00000000000..0f2ab5dddda
--- /dev/null
+++ b/concepts/function-arguments/about.md
@@ -0,0 +1,295 @@
+# About
+
+For the basics on function arguments, please see the [function concept][function concept].
+
+## Parameter Names
+
+Parameter names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers.
+Parameter names should not contain spaces or punctuation.
+
+## Positional Arguments
+
+Positional arguments are values passed to a function in the same order as the parameters which bind to them.
+Positional arguments can optionally be passed by using their parameter name.
+
+Following is an example of positional arguments being passed by position and by their parameter name:
+
+```python
+>>> def concat(greeting, name):
+... return f"{greeting}{name}"
+...
+# Passing data to the function by position.
+>>> print(concat("Hello, ", "Bob"))
+
+Hello, Bob
+...
+# Passing data to the function using the parameter name.
+>>> print(concat(name="Bob", greeting="Hello, "))
+
+Hello, Bob
+
+```
+
+The first call to `concat` passes the arguments by position.
+The second call to `concat` passes the arguments by name, allowing their positions to be changed.
+
+Note that positional arguments cannot follow keyword arguments.
+
+This
+
+```python
+>>> print(concat(greeting="Hello, ", "Bob"))
+```
+
+results in this error:
+
+```
+SyntaxError: positional argument follows keyword argument
+```
+
+Requiring positional-only arguments for function calls can be done through the use of the `/` operator in the parameter list.
+
+
+Following is an example of positional-only arguments:
+
+```python
+# Parameters showing a position-only operator.
+>>> def concat(greeting, name, /):
+... return f"{greeting}{name}"
+
+...
+>>> print(concat("Hello, ", "Bob"))
+Hello, Bob
+...
+# Call to the function using keyword arguments.
+>>> print(concat(name="Bob", greeting="Hello, "))
+Traceback (most recent call last):
+ print(concat(name="Bob", greeting="Hello, "))
+TypeError: concat() got some positional-only arguments passed as keyword arguments: 'greeting, name'
+
+
+```
+
+## Keyword Arguments
+
+Keyword arguments use the parameter name when calling a function.
+Keyword arguments can optionally be referred to by position.
+
+Following is an example of keyword arguments being referred to by their parameter name and by position:
+
+```python
+>>> def concat(greeting, name):
+... return f"{greeting}{name}"
+...
+# Function call using parameter names as argument keywords.
+>>> print(concat(name="Bob", greeting="Hello, "))
+Hello, Bob
+...
+# Function call with positional data as arguments.
+>>> print(concat("Hello, ", "Bob"))
+Hello, Bob
+
+```
+
+Requiring keyword-only arguments for function calls can be done through the use of the `*` operator in the parameter list.
+
+
+Following is an example of keyword-only arguments:
+
+```python
+# Function definition requiring keyword-only arguments.
+>>> def concat(*, greeting, name):
+... return f"{greeting}{name}"
+...
+# Keyword arguments can be in an arbitrary order.
+>>> print(concat(name="Bob", greeting="Hello, "))
+Hello, Bob
+...
+# Calling the function with positional data raises an error.
+>>> print(concat("Hello, ", "Bob"))
+Traceback (most recent call last):
+ print(concat("Hello, ", "Bob"))
+TypeError: concat() takes 0 positional arguments but 2 were given
+
+
+```
+
+## Default Argument Values
+
+Default values for one or more arguments can be supplied in the parameter list.
+This allows the function to be called with _fewer_ arguments if needed.
+Default values can be overridden by calling the function with a new argument value, overriding the default:
+
+```python
+# Function with default argument values.
+>>> def concat(greeting, name="you", punctuation="!"):
+... return f"{greeting}, {name}{punctuation}"
+...
+>>> print(concat("Hello"))
+Hello, you!
+
+# Overriding the default values
+>>> print(concat("Hello", name="Bob", punctuation="."))
+Hello, Bob.
+```
+
+## Positional or Keyword Arguments
+
+Arguments can be positional or keyword if neither the `/` nor `*` operators are used in the parameter definitions.
+Alternately, the positional-or-keyword arguments can be placed between the positional-only parameters on the left and the keyword-only parameters on the right.
+
+Following is an example of positional-only, positional-or-keyword, and keyword-only arguments:
+
+```python
+# Position-only argument followed by position-or-keyword, followed by keyword-only.
+>>> def concat(greeting, /, name, *, ending):
+... return f"{greeting}{name}{ending}"
+...
+>>> print(concat("Hello, ", "Bob", ending="!"))
+Hello, Bob!
+>>> print(concat("Hello, ", name="Bob", ending="!"))
+Hello, Bob!
+...
+>>> print(concat(greeting="Hello, ", name="Bob", ending="!"))
+Traceback (most recent call last):
+ print(concat(greeting="Hello, ", name="Bob", ending="!"))
+TypeError: concat() got some positional-only arguments passed as keyword arguments: 'greeting'
+
+```
+
+## `*args`
+
+Code examples will often use a function definition something like the following:
+
+```python
+def my_function(*args, **kwargs):
+ # code snipped
+
+```
+
+`*args` is a two-part name that represents a `tuple` with an indefinite number of separate positional arguments, also known as a [`variadic argument`][variadic argument].
+`args` is the name given to the `tuple` of arguments, but it could be any other valid Python name, such as `my_args`, `arguments`, etc.
+The `*` is the operator which transforms the group of separate arguments into a [`tuple`][tuple].
+
+~~~~exercism/note
+If you have ever [unpacked a tuple][unpack a tuple] you may find the `*` in `*args` to be confusing.
+The `*` in a _parameter_ definition, instead of unpacking a tuple, converts one or more positional arguments _into_ a tuple.
+
+We say that the `*` operator is [overloaded], as it has different behavior in different contexts.
+For instance, `*` is used for multiplication, it is used for unpacking, and it is used to define an arbitrary number of positional parameters.
+~~~~
+
+Since a tuple can be iterated, `args` can be passed to any other function which takes an iterable.
+Although `*args` is commonly juxtaposed with `**kwargs`, it doesn't have to be.
+
+Following is an example of an arbitrary number of values being passed to a function:
+
+```python
+
+>>> def add(*args):
+# args is passed to the sum function, which takes an iterable
+... return sum(args)
+...
+>>> print(add(1, 2, 3))
+6
+```
+
+If `*args` follows one or more positional arguments, then `*args` will be what is left over after the positional arguments.
+
+Following is an example of an arbitrary number of values being passed to a function after a positional argument:
+
+```python
+
+>>> def add(first, *args):
+# first will be 1, leaving the values 2 and 3 in *args
+... return first + sum(args)
+...
+>>> print(add(1, 2, 3))
+6
+```
+
+If one or more default arguments are defined after `*args` they are separate from the `*args` values.
+
+To put it all together is an example of an arbitrary number of values being passed to a function that also has a positional argument and a default argument:
+
+```python
+
+>>> def add(first, *args, last=0):
+... return first + sum(args) + last
+...
+>>> print(add(1, 2, 3))
+6
+>>> print(add(1, 2, 3, last=4))
+10
+# This uses the unpacking operator * to separate the list elements into positional arguments.
+# It does not have the same behavior as the * in *args.
+>>> print(add(*[1, 2, 3]))
+6
+
+```
+
+Note that when an argument is already in an iterable, such as a tuple or list, it needs to be unpacked before being passed to a function that takes an arbitrary number of separate arguments.
+This is accomplished by using `*`, which is the [unpacking operator][unpacking operator].
+
+`*` in this context _unpacks_ the container into its separate elements which are then transformed by `*args` into a tuple.
+Where there are only positional arguments, the unpacking action must result in the same number of arguments as there are formal parameters.
+
+Without unpacking the list passed into `add`, the program would error.
+
+```python
+>>>> def add(first, *args, last=0):
+... return first + sum(args) + last
+...
+>>>> print(add([1, 2, 3]))
+Traceback (most recent call last):
+ print(add([1, 2, 3]))
+ return first + sum(args) + last
+TypeError: can only concatenate list (not "int") to list
+
+```
+
+## `**kwargs`
+
+`**kwargs` is a two-part name that represents an indefinite number of separate [key-value pair][key-value] arguments.
+`kwargs` is the name of the group of arguments and could be any other name, such as `my_args`, `arguments`, etc.
+The `**` transforms the group of named arguments into a [`dictionary`][dictionary] of `{argument name: argument value}` pairs.
+
+Since a dictionary can be iterated, `kwargs` can be passed to any other function which takes an iterable.
+Although `**kwargs` is commonly juxtaposed with `*args`, it doesn't have to be.
+
+Following is an example of an arbitrary number of key-value pairs being passed to a function:
+
+```python
+>>> def add(**kwargs):
+... return sum(kwargs.values())
+...
+>>> print(add(one=1, two=2, three=3))
+6
+```
+
+Note that the `dict.values()` method is called to iterate through the `kwargs` dictionary values.
+
+When iterating a dictionary the default is to iterate the keys.
+
+Following is an example of an arbitrary number of key-value pairs being passed to a function that then iterates over `kwargs.keys()`:
+
+```python
+>>> def concat(**kwargs):
+ # Join concatenates the key names from `kwargs.keys()`
+... return " ".join(kwargs)
+...
+>>> print(concat(one=1, two=2, three=3))
+one two three
+
+```
+
+
+[default arguments]: https://www.geeksforgeeks.org/default-arguments-in-python/
+[dictionary]: https://www.w3schools.com/python/python_dictionaries.asp
+[function concept]: ../functions/about.md
+[key-value]: https://www.pythontutorial.net/python-basics/python-dictionary/
+[overloaded]: https://www.geeksforgeeks.org/operator-overloading-in-python/
+[tuple]: https://www.w3schools.com/python/python_tuples.asp
+[unpack a tuple]: https://www.geeksforgeeks.org/unpacking-a-tuple-in-python/
+[unpacking operator]: https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists
+[variadic argument]: https://en.wikipedia.org/wiki/Variadic_function
diff --git a/concepts/function-arguments/introduction.md b/concepts/function-arguments/introduction.md
new file mode 100644
index 00000000000..07b885f332e
--- /dev/null
+++ b/concepts/function-arguments/introduction.md
@@ -0,0 +1,73 @@
+# Introduction
+
+For the basics on function arguments, please see the [function concept][function concept].
+
+## Parameter Names
+
+Parameter names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers.
+Parameter names should not contain spaces or punctuation.
+
+## Positional Arguments
+
+Positional arguments are values passed to a function in the same order as the parameters which bind to them.
+Positional arguments can optionally be passed by using their parameter name.
+
+Following is an example of positional arguments being passed by position and by their parameter name:
+
+```python
+>>> def concat(greeting, name):
+... return f"{greeting}{name}"
+...
+# Passing data to the function by position.
+>>> print(concat("Hello, ", "Bob"))
+Hello, Bob
+...
+# Passing data to the function using the parameter name.
+>>> print(concat(name="Bob", greeting="Hello, "))
+Hello, Bob
+
+```
+
+The first call to concat passes the arguments by position.
+The second call to concat passes the arguments by name, allowing their positions to be changed.
+
+Note that positional arguments cannot follow keyword arguments.
+
+This
+
+```python
+>>> print(concat(greeting="Hello, ", "Bob"))
+```
+
+results in this error:
+
+```
+SyntaxError: positional argument follows keyword argument
+```
+
+## Keyword Arguments
+
+Keyword arguments use the parameter name when calling a function.
+Keyword arguments can optionally be referred to by position.
+
+Following is an example of keyword arguments being referred to by their parameter name and by position:
+
+```python
+>>> def concat(greeting="Hello, ", name="you"):
+... return f"{greeting}{name}"
+...
+# Function call using parameter names as argument keywords.
+>>> print(concat(name="Bob", greeting="Hello, "))
+Hello, Bob
+...
+# Function call with positional data as arguments.
+>>> print(concat("Hello, ", name="Bob"))
+Hello, Bob
+>>> print(concat())
+Hello, you
+
+```
+
+[default arguments]: https://www.geeksforgeeks.org/default-arguments-in-python/
+[function concept]: ../functions/about.md
+[parameters]: https://www.codecademy.com/learn/flask-introduction-to-python/modules/learn-python3-functions/cheatsheet
diff --git a/concepts/function-arguments/links.json b/concepts/function-arguments/links.json
new file mode 100644
index 00000000000..c433e7bd546
--- /dev/null
+++ b/concepts/function-arguments/links.json
@@ -0,0 +1,26 @@
+[
+ {
+ "url": "https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions",
+ "description": "Python Documentation Tutorial: More on Defining Functions"
+ },
+ {
+ "url": "https://docs.python.org/3/reference/compound_stmts.html#function-definitions",
+ "description": "Python Reference Documentation: Function definitions"
+ },
+ {
+ "url": "https://www.pythonmorsels.com/positional-vs-keyword-arguments/",
+ "description": "Python Morsels: Positional vs Keyword Arguments"
+ },
+ {
+ "url": "https://www.pythonmorsels.com/accepting-any-number-arguments-function/",
+ "description": "Python Morsels: Accepting any Number of Arguments"
+ },
+ {
+ "url": "https://www.pythonmorsels.com/accepting-arbitrary-keyword-arguments/",
+ "description": "Python Morsels: Accepting any Number of Keyword Arguments"
+ },
+ {
+ "url": "https://realpython.com/defining-your-own-python-function/",
+ "description": "Real Python: Defining Your Own Python Function"
+ }
+]
diff --git a/concepts/functional-tools/.meta/config.json b/concepts/functional-tools/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/functional-tools/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/functional-tools/about.md b/concepts/functional-tools/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/functional-tools/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/functional-tools/introduction.md b/concepts/functional-tools/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/functional-tools/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/functional-tools/links.json b/concepts/functional-tools/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/functional-tools/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/functions/.meta/config.json b/concepts/functions/.meta/config.json
new file mode 100644
index 00000000000..fcbb7bbe3b4
--- /dev/null
+++ b/concepts/functions/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "A function is any Python object to which the function call operation can be applied. Functions can receive zero or more arguments of any valid Python object, including other functions or classes. Function bodies contain one or more statements and will return some value to the caller. Functions without explicit return values will return None.",
+ "authors": ["bethanyg", "bobahop","mukeshgurpude"],
+ "contributors": []
+}
diff --git a/concepts/functions/about.md b/concepts/functions/about.md
new file mode 100644
index 00000000000..f3630af763c
--- /dev/null
+++ b/concepts/functions/about.md
@@ -0,0 +1,393 @@
+# About
+
+A [`function`][function] is a block of organized, reusable code that is used to perform a specific task.
+`Functions` provide better modularity for your application and a high degree of code reuse.
+Python, like other programming languages, has [_built-in functions_][built-in functions] ([`print`][print], [`map`][map], and so on) that are readily available.
+You can also define your own functions. Those are called [`user-defined functions`][user defined functions].
+Functions can run something as simple as _printing a message to the console_ or they can be quite complex.
+
+To execute the code inside a function, you need to call the function, which is done by using the function name followed by parentheses [`()`].
+Data, known as [`arguments`][arguments], can be passed to the function by placing them inside the parenthesese.
+A function definition may include zero or more [`parameters`][parameters].
+Parameters define what argument(s) the function accepts.
+A parameter defines what _type_ of argument is to be passed.
+The argument is the actual _value_ passed when the function is called.
+Functions can perform different tasks depending on the arguments passed to the parameters.
+
+A function can also return a value using the [`return`][return] keyword.
+The value returned by the function is known as the `return value`.
+The return value is returned to the caller of the function.
+
+## Creation
+
+In python, functions are created using the [`def`][def] keyword.
+The function definition is followed by the function name and parentheses [`()`].
+Inside the parentheses, the parameters are specified, separated by commas.
+After the close parenthesis, the colon (`:`) is used to separate the function signature from the function body.
+
+The function body is a block of code that is executed when the function is called.
+The body of the function is indented.
+The indentation is important because Python relies on it to know where that block of code ends.
+A value can be returned from the function by using the `return` keyword, which can then be used by the caller of the function.
+
+```python
+def function_name(parameter1, parameter2, ...):
+ # function body
+ return parameter1 + parameter2
+
+```
+
+We can also define a function without any parameters or return value.
+
+```python
+def function_name():
+ # function body
+ pass
+
+```
+
+Note that the function does need a body, even if the body does nothing, or trying to run the program will generate an indentation error:
+
+```python
+>>> def my_bodyless_func():
+...
+
+File ~/temp.py:1
+
+ ^
+IndentationError: expected an indented block
+```
+
+## Calling a Function
+
+To call a function, use the function name followed by parenthesese [`()`].
+Parameters passed to the function are placed inside the parenthesese, separated by commas.
+
+Consider the following function:
+
+```python
+def greet():
+ print("Hello")
+
+```
+
+The above function can be called by using the following syntax:
+
+```python
+>>> greet()
+Hello
+```
+
+## Parameters and their Arguments
+
+Arguments are values that are passed to the function when it is called.
+They can be of any data type, including other functions or classes.
+
+Let's define a function `add` which adds two numbers together:
+
+```python
+def add(first, second):
+ print(first + second)
+
+```
+
+The parameters `first` and `second` define what arguments the `add` function will accept.
+(It should be noted that the words `parameter` and `argument` are often used interchangeably, albeit imprecisely.)
+When the function is called, the arguments are passed to the function.
+We need to pass arguments for both of the parameters, otherwise a [`TypeError`][type-error] will be raised.
+
+```python
+>>> add(2, 3)
+5
+
+# Function can be called multiple times, with different parameters
+>>> add(4, 3)
+7
+>>> add(5, 6)
+11
+
+# Passing an incorrect number of parameters will raise a `TypeError`
+>>> add(2)
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: add() missing 1 required positional argument: 'second'
+
+>>> add(2, 3, 4)
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: add() takes 2 positional arguments but 3 were given
+```
+
+## Return Value
+
+The return value is a value that is returned to the caller of the function.
+A `return value` can be any data type including other functions or classes.
+It can be used by caller of the function to perform further operations.
+If a function does not explicitly define a `return value`, the value `None` will be returned by the Python interpreter.
+
+Let's define a function `add`:
+
+```python
+def add(first, second):
+ return first + second
+
+```
+
+We can store the return value in a variable and then print it:
+
+```python
+>>> result = add(2, 3)
+>>> print(result)
+5
+# Type of result is `int`
+>>> type(result)
+
+
+# We can also perform operations on the return value
+>>> result * 2
+10
+
+# A function without an explicit return value will return `None`
+>>> def log(message):
+ print(message)
+
+# Hello is printed because of print(message), but return value is `None`
+>>> return_value = log("Hello")
+Hello
+>>> return_value
+None
+```
+
+Use of `return` immediately exits the function and returns the value to the caller.
+
+```python
+>>> def show(first, second):
+ print(first)
+ return first
+ print(second)
+
+# second never gets printed, because the function exits after the return statement
+>>> show(1, 2)
+1
+```
+
+## Modularity
+
+Complex programs can be broken down into smaller parts.
+Different functions can be used to perform different specific tasks.
+
+Assume a program has to perform the following tasks:
+
+* Calculate the area of a circle
+* Calculate the area of a rectangle
+* Calculate the area of a triangle
+
+We can break down the program into smaller parts.
+
+```python
+def circle_area(radius):
+ return 3.14 * radius * radius
+
+def rectangle_area(length, breadth):
+ return length * breadth
+
+def triangle_area(base, height):
+ return 0.5 * base * height
+
+```
+
+Now, we can call the functions in the order we want.
+
+```python
+>>> circle_area(2)
+12.56
+>>> triangle_area(2, 3)
+3.0
+>>> rectangle_area(2, 3)
+6
+>>> rectangle_area(1, 2) + circle_area(2) + triangle_area(1, 2)
+15.56
+```
+
+## Scope of Variables
+
+If a variable (_or name_) is defined inside a function, it will be only accessible _inside_ the function scope (_or local namespace_), even if there is a variable with the same name outside the function scope.
+
+Variables defined outside a function at the _module level_ are considered in the _global namespace_.
+Variables defined outside a function but _inside_ an enclosing function or class are in the _nonlocal namespace_.
+
+
+Python uses the [LEGB Rule][LEGB Rule] (**L**ocal, **E**nclosing, **G**lobal, **B**uilt-in) to resolve variable names when a program is running:
+
+1. Lookup in the **local** (or _function_) namespace.
+2. Lookup in the **enclosing** (or _nonlocal_) namespace if a name is not found in local.
+3. Lookup in the **global** namespace (_module level_) if the name is not found in nonlocal/enclosing.
+4. Lookup in the **built-in** (_program or python-wide_) namespace if the name is not found in global.
+
+If the name remains unresolved, Python will raise a `NameError exception`.
+
+```python
+# Global namespace.
+general_favorite = "apples"
+
+
+def alices_favorite():
+ # Local namespace.
+ favorite = "cherries"
+
+ # This works because Python will eventually find general_favorite in the global namespace.
+ # Python will find 'print' in the built-in namespace.
+ print(f'Alice has always liked {favorite}, but most people like {general_favorite}.')
+
+
+def our_favorite_fruits():
+ # Enclosing or nonlocal namespace.
+ yours = "peaches"
+
+ def my_favorite():
+ # Local namespace.
+ mine = "pears"
+
+ # This works because Python will eventually find 'yours' in the enclosing/nonlocal namespace.
+ print(f'My favorite is {mine}, but you like {yours} instead.')
+
+ # This works because Python will eventually find 'general_favorite' in the global namespace.
+ print(f'Everyone seems to like {general_favorite}')
+
+ # This function is in the local namespace of the 'our_favorite_fruits' function.
+ my_favorite()
+
+
+# This will raise NameError: name 'favorite' is not defined, because the variable favorite is local to alices_favorite.
+print(favorite)
+
+```
+
+If we want to make a variable name accessible _outside_ the local function scope (_or modify a variable that has been defined outside the function scope_), we need to use either the [`global`][global] or [`nonlocal`][nonlocal] keywords.
+
+Using the `global` keyword signals Python to start the lookup in the _global namespace_.
+Assignments to the variable will then modify the _global_ variable, instead of creating a _local_ version.
+When `global` is used to declare a variable, the variable will be _added_ to the global namespace.
+As a result, `global` should be used cautiously, as adding or modifying a global variable could have effects on all other code that uses its value.
+
+The `nonlocal` keyword signals to Python to look for/create the variable in the _nonlocal or enclosing namespace_.
+It is used when a function is nested inside another function or class, and needs access to the outer functions variables and scope.
+
+
+## Functions as first class objects
+
+In python, functions can be assigned to variables and passed as arguments to other functions.
+They can be used as return values.
+Functions can also be placed into a sequence([`list`][list], [`tuple`][tuple] etc) or as value in a [`dict`][dict].
+Functions can be used anywhere any other object can be used.
+This is because _functions are [_first class objects_][first class objects]_.
+As such, they carry special attributes, and can even define getting and setting custom attributes of their own.
+
+```python
+# print is a function
+
+# A function can be assigned to a variable.
+>>> function_as_variable = print
+>>> function_as_variable("Hello")
+Hello
+>>> type(fake_print)
+
+
+# Functions can be passed as an argument to another function.
+>>> def check_print(func):
+ func("Hello")
+>>> check_print(print)
+Hello
+
+# Function can be used as an item in a sequence.
+>>> my_list = [print, "Hello"]
+>>> my_list[0]("Hello")
+Hello
+
+# Functions can be used as a value in a dictionary.
+>>> my_dict = {"key": print}
+>>> my_dict["key"]("Hello")
+Hello
+
+# Functions can be returned from a function.
+>>> def return_func():
+ return print
+>>> return_func()("Hello")
+Hello
+```
+
+Functions can also be nested inside other functions.
+
+```python
+def outer():
+ num = 10
+
+ # This is a nested, or "inner" function.
+ def inner():
+ print(num)
+
+ # Outer function calling inner function
+ inner()
+```
+
+The inner function can access the variable `num` defined in the outer function.
+
+```python
+>>> outer()
+10
+```
+
+
+## Special Attributes
+
+Functions in python have special attributes. Some of them are:
+
+* `__name__`: Name of the function
+* `__doc__`: Documentation string of the function
+* `__module__`: Module in which the function is defined
+* `__globals__`: Dictionary of global variables in the function
+* `__code__`: Code object containing the instructions of the function
+
+```python
+>>> def add(first, second):
+ return first + second
+
+# Function name
+>>> print.__name__
+'print'
+>>> len.__name__
+'len'
+>>> add.__name__
+'add'
+
+# Function documentation
+>>> abs.__doc__
+'Return the absolute value of the argument.'
+
+# Module
+>>> len.__module__
+'builtins'
+>>> add.__module__
+'__main__'
+```
+
+The full list of function attributes can be found at [Python DataModel][attributes].
+
+[LEGB Rule]: https://realpython.com/python-scope-legb-rule/
+[arguments]: https://www.w3schools.com/python/gloss_python_function_arguments.asp
+[attributes]: https://docs.python.org/3/reference/datamodel.html#index-33
+[built-in functions]: https://docs.python.org/3/library/functions.html
+[def]: https://www.geeksforgeeks.org/python-def-keyword/
+[dict]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
+[first class objects]: https://en.wikipedia.org/wiki/First-class_object
+[function]: https://docs.python.org/3/glossary.html#term-function
+[global]: https://www.programiz.com/python-programming/global-keyword
+[list]: https://docs.python.org/3/tutorial/datastructures.html#list-objects
+[map]: https://docs.python.org/3/library/functions.html#map
+[nonlocal]: https://www.geeksforgeeks.org/python-nonlocal-keyword/
+[parameters]: https://www.codecademy.com/learn/flask-introduction-to-python/modules/learn-python3-functions/cheatsheet
+[print]: https://docs.python.org/3/library/functions.html#print
+[return]: https://www.geeksforgeeks.org/python-return-statement/
+[tuple]: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences
+[type-error]: https://docs.python.org/3/library/exceptions.html#TypeError
+[user defined functions]: https://en.wikipedia.org/wiki/User-defined_function
diff --git a/concepts/functions/introduction.md b/concepts/functions/introduction.md
new file mode 100644
index 00000000000..a6db0ad25d9
--- /dev/null
+++ b/concepts/functions/introduction.md
@@ -0,0 +1,110 @@
+# Introduction
+
+A [`function`][function] is a way to compartmentalize code so it can be called by name from one or more places in the program.
+Functions are used to perform specific and repetitive tasks.
+
+More formally: a function is any Python object to which the [`function call`][calls] operation can be applied.
+A function may be used to [`return`][return] one or more values as a result of some operation(s), or it may be used for one or more [`side effects`][side effects].
+If a function does not specify a return value it will still return `None`.
+
+Following is an example of a function with a side effect:
+
+```python
+>>> def hello():
+... print("Hello")
+...
+>>> hello()
+Hello
+
+```
+
+In the example above, the [`def`][def] keyword is used to signal the beginning of the function definition.
+`hello` is the name of the function.
+The empty parentheses represent that no values are being passed to the function.
+The body of the function is the single `print("Hello")` statement.
+`print` is also a function.
+The `"Hello"` in the `print` function's parentheses is known as an [`argument`][arguments].
+The argument is used by the `print` function to know what to print.
+Note that the body of the function is indented.
+The indentation is important because Python relies on it to know where that block of code ends.
+The function body ends at either the end of the program or just before the next line of code that is _not_ indented.
+Since `hello()` does not specify a `return` value, it executes its side effect - which is calling `print()` -- and then returns `None`.
+Finally, we call the function by using its name and the parentheses - which signals to the Python interpreter that this is a _callable_ name.
+
+Following is an example of a function with a return value:
+
+```python
+>>> def hello():
+... return "Hello"
+...
+print(hello())
+Hello
+
+```
+
+The body of this function has been changed from _calling_ `print` to _returning_ the `string` "Hello".
+In the end, the code still prints `"Hello"`, but the side effect takes place in the _calling code_ instead of in the `hello` function.
+If the parentheses were left off from calling the `hello` function (e.g. `print(hello)`), then `print` would output something like
+
+```
+
+```
+
+It's important that, even if a function accepts no arguments (_i.e. has no parameters_), the empty parentheses must be used to _call_ it correctly.
+This is different from a language such as Ruby which does not require empty parentheses for calling a function without any arguments.
+
+A function can define zero or more [`parameters`][parameters]. A parameter defines what argument(s) the function accepts.
+
+Following is an example of a function which accepts an argument:
+
+```python
+>>> def hello(name):
+... return f"Hello, {name}"
+...
+>>>print(hello("Bob"))
+Hello, Bob
+
+```
+
+The parameter is defined as `name`.
+`"Bob"` is the argument which the program passes to the `hello` function.
+The function _returns_ the `string` "Hello, Bob".
+
+What if someone calls `hello` without passing an argument?
+The program throws an error:
+
+```python
+print(hello())
+Traceback (most recent call last):
+
+ Input In in
+ print(hello())
+
+TypeError: hello() missing 1 required positional argument: 'name'
+If we don't want the program to error with no argument (_but want to allow the calling code to not supply one_), we can define a [default argument][default arguments].
+A default argument defines what value to use if the argument is missing when the function is called.
+
+Following is an example of a function with a default argument:
+
+```python
+>>> def hello(name="you"):
+... return f"Hello, {name}"
+...
+>>> print(hello())
+Hello, you
+
+```
+
+The `name` parameter is given the default argument of `"you"`, so the program outputs `Hello, you`, if not passed a `name` argument.
+
+For more about function arguments, please see the [function arguments][function arguments] concept.
+
+
+[arguments]: https://www.w3schools.com/python/gloss_python_function_arguments.asp
+[calls]: https://docs.python.org/3/reference/expressions.html#calls
+[def]: https://www.geeksforgeeks.org/python-def-keyword/
+[function arguments]: ../function-arguments/about.md
+[function]: https://docs.python.org/3/glossary.html#term-function
+[parameters]: https://www.codecademy.com/learn/flask-introduction-to-python/modules/learn-python3-functions/cheatsheet
+[return]: https://www.geeksforgeeks.org/python-return-statement/
+[side effects]: https://python.plainenglish.io/side-effects-in-python-a50caad1169
diff --git a/concepts/functions/links.json b/concepts/functions/links.json
new file mode 100644
index 00000000000..489f4a2dc74
--- /dev/null
+++ b/concepts/functions/links.json
@@ -0,0 +1,30 @@
+[
+ {
+ "url": "https://docs.python.org/3/tutorial/controlflow.html#defining-functions",
+ "description": "Python Documentation Tutorial: Defining Functions"
+ },
+ {
+ "url": "https://docs.python.org/3/reference/compound_stmts.html#function-definitions",
+ "description": "Python Documentation: Function definitions"
+ },
+ {
+ "url": "https://dbader.org/blog/python-first-class-functions",
+ "description": "Dan Bader: Python's Functions are First-Class"
+ },
+ {
+ "url": "https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces",
+ "description": "Python Documentation Tutorial: Python Scopes and Namespaces"
+ },
+ {
+ "url": "https://docs.python.org/3/reference/executionmodel.html#naming-and-binding",
+ "description": "Python Reference Documentation: Naming and Binding"
+ },
+ {
+ "url": "https://medium.com/swlh/the-global-and-nonlocal-keywords-in-python-842b13433b24",
+ "description": "Maghfoor Ahmad Bilal: Global and Nonlocal Keywords"
+ },
+ {
+ "url": "https://betterprogramming.pub/know-python-functions-better-going-beyond-4-special-attributes-720183078c8e",
+ "description": "Yong Cui: 4 Special (Function) Attributes"
+ }
+]
diff --git a/concepts/functools/.meta/config.json b/concepts/functools/.meta/config.json
new file mode 100644
index 00000000000..efc3ad883f2
--- /dev/null
+++ b/concepts/functools/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "The functools module provides utilities for working with and acting on higher-order functions and callables.",
+ "authors": ["Tarun193"],
+ "contributors": ["bethanyg", "cmccandless"]
+}
diff --git a/concepts/functools/about.md b/concepts/functools/about.md
new file mode 100644
index 00000000000..e5afb577d39
--- /dev/null
+++ b/concepts/functools/about.md
@@ -0,0 +1,312 @@
+# About
+
+The functools module is for higher-order functions: functions that act on or return other ***[functions](https://docs.python.org/3/tutorial/controlflow.html#defining-functions)***. It provides functions for working with other functions and callable objects to use or extend them without completely rewriting them.
+
+## Memoizing the function calls
+
+**Memoizing:** Storing the result of some expensive function, which is called with the same input again and again. So, we don't have to run the function repeatedly.
+
+### ```@functools.lru_cache(maxsize=128, typed=False)```
+
+***[@functools.lru_cache(maxsize=128, typed=False)](https://docs.python.org/3/library/functools.html#functools.lru_cache)*** Decorator to wrap a function with a memoizing callable that saves up to the maxsize most recent calls. It can save time when an expensive or I/O bound function is periodically called with the same arguments.
+
+Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable.
+
+Here ```maxsize = 128``` means that it is going to memoize latest 128 function calls at max.
+
+The lru_cache works the same way but it can cache at max maxsize calls and if type = True, then the function arguments of different types will be cached separately i.e. 5 and 5.0 will be cached differently.
+
+### ```@functools.cache(user_function)```
+
+***[@functools.cache(user_function)](https://docs.python.org/3/library/functools.html#functools.cache)*** the same as lru_cache(maxsize=None), creating a thin wrapper around a dictionary lookup for the function arguments. Because it never needs to evict old values, this is smaller and faster than ```lru_cache()``` with a size limit.
+
+```python
+
+>>> @cache
+>>> def factorial(n):
+>>> return n * factorial(n-1) if n else 1
+
+>>> factorial(10) # no previously cached result, makes 11 recursive calls
+3628800
+>>> factorial(5) # just looks up cached value result
+120
+>>> factorial(12) # makes two new recursive calls, the other 10 are cached
+479001600
+
+# The lru_cache works the same way but it can cache at max maxsize calls and if type = True, then the function arguments of different types will be cached separately.
+
+# Some types such as str and int may be cached separately even when typed is false.
+
+>>> @lru_cache(maxsize = 128)
+>>> def factorial(n):
+>>> return n * factorial(n-1) if n else 1
+
+>>> factorial(10)
+3628800
+
+# by the Following we can fetch the information about the cache.
+>>> factorial.cache_info()
+CacheInfo(hits=0, misses=11, maxsize=128, currsize=11)
+```
+
+## Generic functions
+
+***[Generic functions](https://pymotw.com/3/functools/#generic-functions)*** are those which perform the operation based on the argument given to them. In statically typed languages it can be done by function overloading.
+
+In python functools provides the `singledispatch()` decorator to register a set of generic functions for automatic switching based on the type of the first argument to a function.
+
+The ```register()``` attribute of the function serves as another decorator for registering alternative implementations.To add overloaded implementations to the function, use the ```register(type)``` attribute of the generic function.
+
+When user is going to call the function with the integer argument, then it will be redirected to the function decorated with ```register(int)``` decorator.
+
+The first function wrapped with singledispatch() is the default implementation if no other type-specific function is found, default implementation will be called.
+
+```python
+
+>>> from functools import singledispatch
+
+>>> @singledispatch
+ def fun(arg):
+ print("default argument string: ", arg)
+
+
+>>> fun.register(int)
+ def _(arg):
+ print("This is an integer: ", arg)
+
+>>> fun.register(list)
+ def _(arg):
+ print("This is a list: ", arg)
+
+>>> fun("Hello")
+"default argument string: Hello"
+
+>>> fun(10)
+"This is an integer: 10"
+
+>>> fun([1,2,3])
+"This is a list: [1,2,3]"
+
+# This will call the default function as we didn't registered any function with float.
+>>> fun(2.45)
+"default argument string: 2.45"
+
+```
+
+For class methods we can use ***[singledispatchmethod(func)](https://docs.python.org/3/library/functools.html#functools.singledispatchmethod)*** to register a set of generic methods for automatic switching based on the type of the first non-self or non-class argument to a function.
+
+```python
+
+>>> class Negator:
+ @singledispatchmethod
+ def neg(self, arg):
+ raise NotImplementedError("Cannot negate a")
+
+ @neg.register(int)
+ def _(self, arg):
+ return -arg
+
+ @neg.register(bool)
+ def _(self, arg):
+ return not arg
+
+>>> obj = Negator()
+
+# Going to call function which is register with bool datatype.
+>>> obj.neg(True)
+False
+
+# Going to call function which is register with int datatype.
+>>> obj.neg(10)
+-10
+
+# Going to call default function and will display an error message.
+>>> obj.neg("String")
+
+```
+
+## Partial
+
+`functools.partial(func, /, *args, **keywords)` return a new ***[partial object](https://docs.python.org/3/library/functools.html#partial-objects)*** which when called will behave like func called with the positional arguments args and keyword arguments keywords. If more arguments are supplied to the call, they are appended to args.The ***[partial](https://docs.python.org/3/library/functools.html#functools.partial)*** is used for partial function application which “freezes” some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature.
+
+```python
+
+>>> def add(a, b):
+ print(f"got a={a}, b={b}")
+ print(a+b)
+
+>>> a = partial(add, 10)
+>>> a(4)
+"got a=10, b=4"
+14
+
+# 10 got assigned to a because partial start assigning arguments from the left.
+
+>>> a = partial(add, b=10)
+>>> a(4)
+"got a=4, b=10"
+14
+
+# But By using the keywords we can assign the value to the arguments at right
+
+```
+
+### partial Objects
+
+partial objects are callable objects created by partial(). They have three read-only attributes:
+
+```partial.func```
+
+A callable object or function. Calls to the partial object will be forwarded to func with new arguments and keywords.
+
+```partial.args```
+
+The leftmost positional arguments that will be prepended to the positional arguments provided to a partial object call.
+
+```partial.keywords```
+
+The keyword arguments that will be supplied when the partial object is called.
+
+```python
+
+>>> from functools import partial
+
+>>> pow_2 = partial(pow, exp = 2)
+
+>>> pow_2.func == pow
+True
+
+>>> pow_2.args
+()
+
+>>> pow_2.keywords
+{'exp': 2}
+
+>>> two_pow = partial(pow, 2)
+
+>>> two_pow(3) # 2(frezzed) ^ 3 = 8 == pow(2 [fixed] ,3 [passed by user])
+8
+
+>>> pow_2.args
+(2,)
+
+```
+
+The ```pow_2.func``` is same as the ```pow``` function.
+
+Here ```pow_2.args``` returns an empty tuple because we do not pass any positional argument to our partial object call.
+
+```pow_2.keywords``` returns a dictionary of keywords argument which will be supplied when the partial object is called.
+
+Here ```two_pow.args``` returns a ```(2,)``` tuple because we passed 2 as an argument while creating the partial object, which fixed the value of ```base``` argument as ```2```.
+
+### ```partialmethod```
+
+***[functools.partialmethod(func, /, *args, **keywords)](https://docs.python.org/3/library/functools.html#functools.partialmethod)*** Return a new partialmethod descriptor which behaves like partial except that it is designed to be used as a method definition rather than being directly callable.
+
+```python
+
+>>> class Cell:
+ def __init__(self):
+ self.alive = False
+
+ def set_state(self, state):
+ self.alive = bool(state)
+
+ # going to return a method set_state with argument state = True
+ set_alive = partialmethod(set_state, True)
+ # going to return a method set_state with argument state = False
+ set_dead = partialmethod(set_state, False)
+
+>>> c = Cell()
+>>> c.alive
+False
+>>> c.set_alive()
+>>> c.alive
+True
+
+```
+
+## Wraps
+
+### `functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)`
+
+***[functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)](https://docs.python.org/3/library/functools.html#functools.update_wrapper)*** Update a wrapper function to look like the wrapped function. The optional arguments are tuples to specify which attributes of the original function are assigned directly to the matching attributes on the wrapper function and which attributes of the wrapper function are updated with the corresponding attributes from the original function.
+
+WRAPPER_ASSIGNMENTS (which assigns to the wrapper function’s `__module__`, `__name__`, `__qualname__`, `__annotations__` and `__doc__`, the documentation string)
+
+WRAPPER_UPDATES (which updates the wrapper function’s `__dict__`, i.e. the instance dictionary).
+
+```python
+
+# without update_wrapper()
+
+>>> def decorator(func):
+ def wrapper(name):
+ """Going to say Hello"""
+ print("hello",name)
+ func(name)
+ return wrapper
+
+
+>>> @decorator
+ def fun(name):
+ """Going to Wish"""
+ print("good morning",name)
+
+# In bigger python code base this will cause problem while debugging the code.
+>>> fun.__name__
+'wrapper'
+>>> fun.__doc__
+'Going to say Hello'
+
+# with update_wrapper()
+
+>>> def decorator(func):
+ def wrapper(name):
+ """Going to say Hello"""
+ print("hello",name)
+ func(name)
+ update_wrapper(wrapper, func)
+ return wrapper
+
+
+>>> @decorator
+ def fun(name):
+ """Going to Wish"""
+ print("good morning",name)
+
+# Now the wrapper function just look like the wrapped(fun) function
+>>> fun.__name__
+'fun'
+>>> fun.__doc__
+'Going to Wish'
+```
+
+### `functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)`
+
+***[functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)](https://docs.python.org/3/library/functools.html#functools.wraps)*** is a convenience function for invoking update_wrapper() as a function decorator when defining a wrapper function. It is equivalent to partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated).
+
+```python
+
+# This going to work same as the above where we are using the update_wrapper() function
+>>> def decorator(func):
+ @wraps(fun)
+ def wrapper(name):
+ """Going to say Hello"""
+ print("hello",name)
+ func(name)
+ return wrapper
+
+
+>>> @decorator
+ def fun(name):
+ """Going to Wish"""
+ print("good morning",name)
+
+# Now the wrapper function just look like the wrapped(fun) function
+>>> fun.__name__
+'fun'
+>>> fun.__doc__
+'Going to Wish'
+```
diff --git a/concepts/functools/introduction.md b/concepts/functools/introduction.md
new file mode 100644
index 00000000000..c91aedc81bd
--- /dev/null
+++ b/concepts/functools/introduction.md
@@ -0,0 +1,43 @@
+# Introduction
+
+The functools module is for higher-order functions: functions that act on or return other ***[functions](https://docs.python.org/3/tutorial/controlflow.html#defining-functions)***. It provides functions for working with other functions and callable objects to use or extend them without completely rewriting them.
+
+## Memoizing the function calls
+
+**Memoizing:** Storing the result of some expensive function, which is called with the same input again and again. So, we don't have to run the function repeatedly.
+
+### ```@functools.lru_cache(maxsize=128, typed=False)```
+
+***[@functools.lru_cache(maxsize=128, typed=False)](https://docs.python.org/3/library/functools.html#functools.lru_cache)*** Decorator to wrap a function with a memoizing callable that saves up to the maxsize most recent calls. It can save time when an expensive or I/O bound function is periodically called with the same arguments.
+
+Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable.
+
+Here ```maxsize = 128``` means that it is going to memoize latest 128 function calls at max.
+
+### ```@functools.cache(user_function)```
+
+***[@functools.cache(user_function)](https://docs.python.org/3/library/functools.html#functools.cache)*** the same as lru_cache(maxsize=None), creating a thin wrapper around a dictionary lookup for the function arguments. Because it never needs to evict old values, this is smaller and faster than ```lru_cache()``` with a size limit.
+
+## Generic functions
+
+***[Generic functions](https://pymotw.com/3/functools/#generic-functions)*** are those which preform the operation based on the argument given to them.
+
+In statically typed languages it can be done by function overloading, In python functools provides the ```singledispatch(func)``` decorator to register a set of generic functions for automatic switching based on the type of the first argument to a function.
+
+For class methods we can use ***[singledispatchmethod(func)](https://docs.python.org/3/library/functools.html#functools.singledispatchmethod)*** to register a set of generic methods for automatic switching based on the type of the first non-self or non-class argument to a function.
+
+## Partial
+
+`functools.partial(func, /, *args, **keywords)` return a new ***[partial object](https://docs.python.org/3/library/functools.html#partial-objects)*** which when called will behave like func called with the positional arguments args and keyword arguments keywords. If more arguments are supplied to the call, they are appended to args.The ***[partial](https://docs.python.org/3/library/functools.html#functools.partial)*** is used for partial function application which “freezes” some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature.
+
+***[functools.partialmethod(func, /, *args, **keywords)](https://docs.python.org/3/library/functools.html#functools.partialmethod)*** Return a new partialmethod descriptor which behaves like partial except that it is designed to be used as a method definition rather than being directly callable.
+
+## Wraps
+
+### `functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)`
+
+***[functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)](https://docs.python.org/3/library/functools.html#functools.update_wrapper)*** Update a wrapper function to look like the wrapped function. The optional arguments are tuples to specify which attributes of the original function are assigned directly to the matching attributes on the wrapper function and which attributes of the wrapper function are updated with the corresponding attributes from the original function.
+
+### `functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)`
+
+***[functools.wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)](https://docs.python.org/3/library/functools.html#functools.wraps)*** is a convenience function for invoking update_wrapper() as a function decorator when defining a wrapper function. It is equivalent to partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated).
diff --git a/concepts/functools/links.json b/concepts/functools/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/functools/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/generator-expressions/.meta/config.json b/concepts/generator-expressions/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/generator-expressions/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/generator-expressions/about.md b/concepts/generator-expressions/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/generator-expressions/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/generator-expressions/introduction.md b/concepts/generator-expressions/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/generator-expressions/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/generator-expressions/links.json b/concepts/generator-expressions/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/generator-expressions/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/generators/.meta/config.json b/concepts/generators/.meta/config.json
new file mode 100644
index 00000000000..3322727ef74
--- /dev/null
+++ b/concepts/generators/.meta/config.json
@@ -0,0 +1,9 @@
+{
+ "blurb": "Generators are functions or expressions that return a special type of iterator called a 'generator-iterator'. Generator-iterators are lazy: they do not store their values in memory but generate them when needed.",
+ "authors": ["J08K"],
+ "contributors": [
+ "BethanyG",
+ "kytrinyx",
+ "meatball133"
+ ]
+}
diff --git a/concepts/generators/about.md b/concepts/generators/about.md
new file mode 100644
index 00000000000..59b5035d6b9
--- /dev/null
+++ b/concepts/generators/about.md
@@ -0,0 +1,147 @@
+# About
+
+A `generator` is a function or expression that returns a special type of [iterator][iterator] called [generator iterator][generator-iterator].
+`Generator-iterators` are [lazy][lazy iterator]: they do not store their `values` in memory, but _generate_ their values when needed.
+
+A generator function looks like any other function, but contains one or more [yield expressions][yield expression].
+Each `yield` will suspend code execution, saving the current execution state (_including all local variables and try-statements_).
+When the generator resumes, it picks up state from the suspension - unlike regular functions which reset with every call.
+
+
+## Constructing a generator
+
+Generators are constructed much like other looping or recursive functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later.
+
+An example is a function that returns the _squares_ from a given list of numbers.
+As currently written, all input must be processed before any values can be returned:
+
+```python
+>>> def squares(list_of_numbers):
+... squares = []
+... for number in list_of_numbers:
+... squares.append(number ** 2)
+... return squares
+```
+
+You can convert that function into a generator like this:
+
+```python
+>>> def squares_generator(list_of_numbers):
+... for number in list_of_numbers:
+... yield number ** 2
+```
+
+The rationale behind this is that you use a generator when you do not need all the values _at once_.
+
+This saves memory and processing power, since only the value you are _currently working on_ is calculated.
+
+## Using a generator
+
+Generators may be used in place of most `iterables` in Python. This includes _functions_ or _objects_ that require an `iterable`/`iterator` as an argument.
+
+To use the `squares_generator()` generator:
+
+```python
+>>> squared_numbers = squares_generator([1, 2, 3, 4])
+
+>>> for square in squared_numbers:
+... print(square)
+...
+1
+4
+9
+16
+```
+
+Values within a generator can also be produced/accessed via the `next()` function.
+`next()` calls the `__next__()` method of a generator object, "advancing" or evaluating the generator code up to its `yield` expression, which then "yields" or returns the value.
+
+```python
+>>> squared_numbers = squares_generator([1, 2])
+
+>>> next(squared_numbers)
+1
+>>> next(squared_numbers)
+4
+```
+
+When a `generator` is fully consumed and has no more values to return, it throws a `StopIteration` error.
+
+```python
+>>> next(squared_numbers)
+Traceback (most recent call last):
+ File "", line 1, in
+StopIteration
+```
+
+### Difference between iterables and generators
+
+Generators are a special sub-set of _iterators_.
+`Iterators` are the mechanism/protocol that enables looping over _iterables_.
+Generators and the iterators returned by common Python [`iterables`][iterables] act very similarly, but there are some important differences to note:
+
+- Generators are _one-way_; there is no "backing up" to a previous value.
+
+- Iterating over generators consume the returned values; no resetting.
+
+- Generators (_being lazily evaluated_) are not sortable and can not be reversed.
+
+- Generators do _not_ have `indexes`, so you can't reference a previous or future value using addition or subtraction.
+
+- Generators cannot be used with the `len()` function.
+
+- Generators can be _finite_ or _infinite_, be careful when collecting all values from an _infinite_ generator.
+
+## The yield expression
+
+The [yield expression][yield expression] is very similar to the `return` expression.
+
+_Unlike_ the `return` expression, `yield` gives up values to the caller at a _specific point_, suspending evaluation/return of any additional values until they are requested.
+
+When `yield` is evaluated, it pauses the execution of the enclosing function and returns any values of the function _at that point in time_.
+
+The function then _stays in scope_, and when `__next__()` is called, execution resumes until `yield` is encountered again.
+
+~~~~exercism/note
+Using `yield` expressions is prohibited outside of functions.
+~~~~
+
+```python
+>>> def infinite_sequence():
+... current_number = 0
+... while True:
+... yield current_number
+... current_number += 1
+
+>>> lets_try = infinite_sequence()
+>>> lets_try.__next__()
+0
+>>> lets_try.__next__()
+1
+```
+
+## Why generators?
+
+Generators are useful in a lot of applications.
+
+When working with a large collection, you might not want to put all of its values into `memory`.
+A generator can be used to work on larger data piece-by-piece, saving memory and improving performance.
+
+Generators are also very helpful when a process or calculation is _complex_, _expensive_, or _infinite_:
+
+```python
+>>> def infinite_sequence():
+... current_number = 0
+... while True:
+... yield current_number
+... current_number += 1
+```
+
+Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1.
+
+
+[generator-iterator]: https://docs.python.org/3.11/glossary.html#term-generator-iterator
+[iterables]: https://wiki.python.org/moin/Iterator
+[iterator]: https://docs.python.org/3.11/glossary.html#term-iterator
+[lazy iterator]: https://en.wikipedia.org/wiki/Lazy_evaluation
+[yield expression]: https://docs.python.org/3.11/reference/expressions.html#yield-expressions
diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md
new file mode 100644
index 00000000000..ad1175ca0b6
--- /dev/null
+++ b/concepts/generators/introduction.md
@@ -0,0 +1,13 @@
+# Introduction
+
+A generator in Python is a _callable function_ or expression that returns a special type of [iterator][iterator] called [generator iterator][generator-iterator].
+`Generator-iterators` are [lazy][lazy iterator]: they do not store their `values` in memory, but _generate_ their values when needed.
+
+A generator function looks like any other function, but contains one or more [yield expressions][yield expression].
+Each `yield` will suspend code execution, saving the current execution state (_including all local variables and try-statements_).
+When the generator function resumes, it picks up state from the suspension - unlike regular functions which reset with every call.
+
+[lazy iterator]: https://en.wikipedia.org/wiki/Lazy_evaluation
+[iterator]: https://docs.python.org/3.11/glossary.html#term-iterator
+[yield expression]: https://docs.python.org/3.11/reference/expressions.html#yield-expressions
+[generator-iterator]: https://docs.python.org/3.11/glossary.html#term-generator-iterator
diff --git a/concepts/generators/links.json b/concepts/generators/links.json
new file mode 100644
index 00000000000..134a723c693
--- /dev/null
+++ b/concepts/generators/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "https://docs.python.org/3.11/reference/expressions.html#yield-expressions",
+ "description": "Official Python 3.10 docs for the yield expression."
+ },
+ {
+ "url": "https://en.wikipedia.org/wiki/Lazy_evaluation",
+ "description": "Wikipedia page about lazy evaluation"
+ },
+ {
+ "url": "https://www.pythonmorsels.com/iterators/",
+ "description": "Python Morsels: Iterators & Generators"
+ },
+ {
+ "url": "https://realpython.com/introduction-to-python-generators/",
+ "description": "Real python, introduction to generators and yield"
+ }
+]
diff --git a/concepts/higher-order-functions/.meta/config.json b/concepts/higher-order-functions/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/higher-order-functions/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/higher-order-functions/about.md b/concepts/higher-order-functions/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/higher-order-functions/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/higher-order-functions/introduction.md b/concepts/higher-order-functions/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/higher-order-functions/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/higher-order-functions/links.json b/concepts/higher-order-functions/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/higher-order-functions/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/iteration/.meta/config.json b/concepts/iteration/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/iteration/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/iteration/about.md b/concepts/iteration/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/iteration/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/iteration/introduction.md b/concepts/iteration/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/iteration/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/iteration/links.json b/concepts/iteration/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/iteration/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/iterators/.meta/config.json b/concepts/iterators/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/iterators/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/iterators/about.md b/concepts/iterators/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/iterators/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/iterators/introduction.md b/concepts/iterators/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/iterators/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/iterators/links.json b/concepts/iterators/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/iterators/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/itertools/.meta/config.json b/concepts/itertools/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/itertools/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/itertools/about.md b/concepts/itertools/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/itertools/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/itertools/introduction.md b/concepts/itertools/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/itertools/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/itertools/links.json b/concepts/itertools/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/itertools/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/list-comprehensions/.meta/config.json b/concepts/list-comprehensions/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/list-comprehensions/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/list-comprehensions/about.md b/concepts/list-comprehensions/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/list-comprehensions/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/list-comprehensions/introduction.md b/concepts/list-comprehensions/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/list-comprehensions/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/list-comprehensions/links.json b/concepts/list-comprehensions/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/list-comprehensions/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/list-methods/.meta/config.json b/concepts/list-methods/.meta/config.json
new file mode 100644
index 00000000000..601167922f1
--- /dev/null
+++ b/concepts/list-methods/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "The list class has many useful methods for working with lists like .append(), .insert(), .remove(), .reverse(), and .sort(). Additionally, lists support all common sequence operations as well as mutable sequence operations. They can be iterated over using \"for item in \" syntax, and copied via slice notation or by calling .copy().",
+ "authors": ["mohanrajanr", "BethanyG"],
+ "contributors": ["valentin-p"]
+}
diff --git a/concepts/list-methods/about.md b/concepts/list-methods/about.md
new file mode 100644
index 00000000000..1c9686360d4
--- /dev/null
+++ b/concepts/list-methods/about.md
@@ -0,0 +1,272 @@
+# About
+
+A [`list`][list] is a mutable collection of items in _sequence_.
+ Like most collections (_see the built-ins [`tuple`][tuple], [`dict`][dict] and [`set`][set]_), lists can hold references to any (or multiple) data type(s) - including other lists.
+ They're considered a [sequence][sequence type] in Python, and can be copied in whole or in part via [slice notation][slice notation].
+Like any sequence, elements within `lists` can be referenced by `0-based index` number from the left, or `-1-based index` number from the right.
+
+Lists support both [common][common sequence operations] and [mutable][mutable sequence operations] sequence operations such as `min()`/`max()`, `.index()`, `.append()` and `.reverse()`.
+ Items can be iterated over using the `for item in ` construct, and `for index, item in enumerate()` when both the element and element index are needed.
+
+Python provides many useful [methods][list-methods] for working with lists.
+
+ Because lists are mutable, list-methods **alter the original list object** passed into the method.
+If mutation is undesirable, a `shallow copy` (_at minimum__) of the original `list` needs to be made via `slice` or `.copy()`.
+
+
+## Adding Items
+
+Adding items to the end of an existing list can be done via `.append(- )`:
+
+```python
+>>> numbers = [1, 2, 3]
+>>> numbers.append(9)
+
+>>> numbers
+[1, 2, 3, 9]
+```
+
+Rather than _appending_, `
.insert(, - )` adds the item to a _specific index_ within the list.
+`
` is the index of the item _before which_ you want the new item to appear.
+`- ` is the element to be inserted.
+
+**Note**: If `
` is 0, the item will be added to the start of the list.
+If `` is greater than the final index on the list, the item will be added in the final position -- the equivalent of using `.append(- )`.
+
+```python
+>>> numbers = [1, 2, 3]
+>>> numbers.insert(0, -2)
+
+>>> numbers
+[-2, 1, 2, 3]
+
+>>> numbers.insert(1, 0)
+
+>>> numbers
+[-2, 0, 1, 2, 3]
+```
+
+An `iterable` can be _combined_ with an existing list (concatenating the two) via `
.extend()`.
+`.extend()` will _unpack_ the supplied iterable, adding its elements in the same order to the end of the target list (_using `.append(- )` in this circumstance would add the entire iterable as a **single item**._).
+
+
+```python
+>>> numbers = [1, 2, 3]
+>>> other_numbers = [5, 6, 7]
+
+>>> numbers.extend(other_numbers)
+
+>>> numbers
+[1, 2, 3, 5, 6, 7]
+
+>>> numbers.extend([8, 9])
+
+>>> numbers
+[1, 2, 3, 5, 6, 7, 8, 9]
+```
+
+
+## Removing Items
+
+`
.remove(- )` can be used to remove an element from the list.
+ `
.remove(- )` will throw a `ValueError` if the element is not present in the list.
+
+
+```python
+>>> numbers = [1, 2, 3]
+>>> numbers.remove(2)
+
+>>> numbers
+[1, 3]
+
+# Trying to remove a value that is not in the list throws a ValueError.
+>>> numbers.remove(0)
+ValueError: list.remove(x): x not in list
+```
+
+Alternatively, using `
.pop()` method will both remove **and** `return` an element for use.
+
+`.pop()` takes one optional parameter: the `index` of the element to remove and return.
+If the optional `` argument is not specified, the last element of the list will be removed and returned.
+If `` is a higher number than the final index of the list, an `IndexError` will be thrown.
+
+
+```python
+>>> numbers = [1, 2, 3]
+>>> numbers.pop(0)
+1
+
+>>> numbers
+[2, 3]
+>>> numbers.pop()
+3
+
+>>> numbers
+[2]
+
+# This will throw an error because there is only index 0.
+>>> numbers.pop(1)
+Traceback (most recent call last):
+ File "", line 1, in
+IndexError: pop index out of range
+```
+
+All items can be removed from a `list` via `.clear()`. It does not take any parameters.
+
+
+```python
+>>> numbers = [1, 2, 3]
+>>> numbers.clear()
+
+>>> numbers
+[]
+```
+
+
+## Reversing and reordering
+
+The order of list elements can be reversed _**in place**_ with `.reverse()`. This will mutate the original list.
+
+
+```python
+>>> numbers = [1, 2, 3]
+>>> numbers.reverse()
+
+>>> numbers
+[3, 2, 1]
+```
+
+A list can be re-ordered _**in place**_ with the help of [`.sort()`][sort].
+Default sort order is _ascending_ from the left.
+The Python docs offer [additional tips and techniques for sorting][sorting how to].
+
+
+~~~~exercism/note
+ From 2002 to 2022, Python used an algorithm called [`Timsort`][timsort] internally to arrange lists, but switched to [`Powersort`][powersort] from `Python 3.11` onward.
+You can read more details and discussion on the change from the core Python team in the GitHub [issue 78742][78742].
+
+For technical details on the algorithm, see the J. Ian Munro and Sebastian Wild paper [Nearly-Optimal Mergesorts: Fast, Practical Sorting Methods That Optimally Adapt to Existing Runs][nearly-optimal-mergesorts]
+
+[78742]: https://github.com/python/cpython/issues/78742
+[nearly-optimal-mergesorts]: https://arxiv.org/abs/1805.04154
+[powersort]: https://www.wild-inter.net/publications/munro-wild-2018
+[timsort]: https://en.wikipedia.org/wiki/Timsort
+~~~~
+
+
+```python
+>>> names = ["Tony", "Natasha", "Thor", "Bruce"]
+
+# The default sort order is *ascending*.
+>>> names.sort()
+
+>>> names
+["Bruce", "Natasha", "Thor", "Tony"]
+```
+
+To sort a list in _descending_ order, pass a `reverse=True` argument to the method:
+
+
+```python
+>>> names = ["Tony", "Natasha", "Thor", "Bruce"]
+>>> names.sort(reverse=True)
+
+>>> names
+["Tony", "Thor", "Natasha", "Bruce"]
+```
+
+
+For cases where mutating the original `list` is undesirable, the built-in functions [`sorted()`][sorted] and [`reversed()`][reversed] can be used.
+ `sorted()` will return a sorted _copy_, and takes the same parameters as `.sort()`.
+ `reversed()` returns an _iterator_ that yields the list's items in reverse order.
+ `Iterators` will be covered in detail in another exercise.
+
+
+## Occurrences of an item in a list
+
+Finding the number of occurrences of an element in a list can be done with the help of `.count(- )`.
+ It returns the total number of times `
- ` appears as an element in the list.
+
+
+```python
+>>> items = [1, 4, 7, 8, 2, 9, 2, 1, 1, 0, 4, 3]
+
+>>> items.count(1)
+3
+```
+
+
+## Finding the index of items
+
+`
.index(- )` will return the index number of the _first occurrence_ of an item passed in.
+ If there are no occurrences, a `ValueError` is raised.
+ Indexing is `0-based` from the left, meaning the position of the first item is index `0`.
+ Indexing from the right is also supported, starting with index `-1`.
+
+
+```python
+>>> items = [7, 4, 1, 0, 2, 5]
+>>> items.index(4)
+1
+
+>>> items.index(10)
+ValueError: 10 is not in list
+```
+
+Providing `start` and `end` indices will search within a specific section of the list:
+
+
+```python
+>>> names = ["Tina", "Leo", "Thomas", "Tina", "Emily", "Justin"]
+>>> names.index("Tina")
+0
+
+>>> names.index("Tina", 2, 5)
+3
+```
+
+If the exact position of an element is not needed, the built-in `in` operator is more efficient for verifying membership.
+
+
+```python
+>>> names = ["Tina", "Leo", "Thomas", "Tina", "Emily", "Justin"]
+
+>>> "Thomas" in names
+True
+```
+
+
+## Making Copies
+
+Remember that variables in Python are names that point to underlying objects.
+ Names can be bound or re-bound to different objects over the life of a program.
+ Assigning a `list` object to a new variable _**name**_ does not copy the object or any of its referenced data.
+ The new name and old name will both point at the same `list` object.
+ Additionally, lists are a _container_ type object - to save space, containers only hold _references_ to member items, not the items themselves.
+ This "optimization" can have unintended consequences for the unwary.
+
+`
.copy()` will create a new `list` object, but **will not** create new objects for the referenced list _elements_ -- the copy is "shallow".
+ A `shallow copy` is usually enough when you want to add or remove items from one of the `list` objects without modifying the other.
+ But if there is any chance that the _underlying elements_ of a `list` might be accidentally mutated (_thereby mutating all related shallow copies_), [`copy.deepcopy()`][deepcopy] in the `copy` module should be used to create a complete or "deep" copy of **all** references and objects.
+
+For a detailed explanation of names, values, list, and nested list behavior, take a look at this excellent blog post from [Ned Batchelder][ned batchelder] -- [Names and values: making a game board][names and values].
+ [Shallow vs Deep Copying of Python Objects][shallow vs deep] also offers a good rundown of copy considerations.
+
+
+[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations
+[deepcopy]: https://docs.python.org/3/library/copy.html
+[dict]: https://docs.python.org/3/library/stdtypes.html#dict
+[list-methods]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists
+[list]: https://docs.python.org/3/library/stdtypes.html#list
+[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#typesseq-mutable
+[names and values]: https://nedbatchelder.com/blog/201308/names_and_values_making_a_game_board.html
+[ned batchelder]: https://nedbatchelder.com/
+[reversed]: https://docs.python.org/3/library/functions.html#reversed
+[sequence type]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range
+[set]: https://docs.python.org/3/library/stdtypes.html#set
+[shallow vs deep]: https://realpython.com/copying-python-objects/
+[slice notation]: https://docs.python.org/3/reference/expressions.html#slicings
+[sort]: https://docs.python.org/3/library/stdtypes.html#list.sort
+[sorted]: https://docs.python.org/3/library/functions.html#sorted
+[sorting how to]: https://docs.python.org/3/howto/sorting.html
+[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
diff --git a/concepts/list-methods/introduction.md b/concepts/list-methods/introduction.md
new file mode 100644
index 00000000000..e7054394370
--- /dev/null
+++ b/concepts/list-methods/introduction.md
@@ -0,0 +1,15 @@
+# Introduction
+
+A [list][list] is a mutable collection of items in _sequence_.
+ Lists can hold reference to any (or multiple) data type(s) - including other lists or data structures such as [tuples][tuples], [sets][sets], or [dicts][dicts].
+ Content can be iterated over using `for item in ` construct.
+ If indexes are needed with the content, `for index, item in enumerate()` can be used.
+ Elements within a `list` can be accessed via `0-based index` number from the left, or `-1-based index` number from the right.
+ Lists can be copied in whole or in part using _slice notation_ or `.copy()`.
+ Python provides many useful and convenient [methods][list-methods] for working with lists.
+
+[dicts]: https://github.com/exercism/python/tree/main/concepts/dicts
+[list-methods]: https://docs.python.org/3/tutorial/datastructures.html#more-on-lists
+[list]: https://docs.python.org/3/library/stdtypes.html#list
+[sets]: https://github.com/exercism/python/tree/main/concepts/sets
+[tuples]: https://github.com/exercism/python/tree/main/concepts/tuples
diff --git a/concepts/list-methods/links.json b/concepts/list-methods/links.json
new file mode 100644
index 00000000000..45df805f623
--- /dev/null
+++ b/concepts/list-methods/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "https://docs.python.org/3/tutorial/datastructures.html#more-on-lists",
+ "description": "data structure: lists"
+ },
+ {
+ "url": "https://en.wikipedia.org/wiki/Timsort",
+ "description": "Timsort algorithm"
+ },
+ {
+ "url": "https://docs.python.org/3/library/functions.html#sorted",
+ "description": "sorted method"
+ },
+ {
+ "url": "https://docs.python.org/3/howto/sorting.html",
+ "description": "Sorting: How To"
+ }
+]
diff --git a/concepts/lists/.meta/config.json b/concepts/lists/.meta/config.json
new file mode 100644
index 00000000000..afe555a6b8e
--- /dev/null
+++ b/concepts/lists/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "A \"list\" is a mutable collection of items in sequence. Like most collections, lists can hold any/multiple data type(s) - including other lists. They support all common sequence operations as well as mutable sequence operations, and can be iterated over using the \"for item in \" or \"for index, item in enumerate()\" syntax.",
+ "authors": ["BethanyG"],
+ "contributors": []
+}
diff --git a/concepts/lists/about.md b/concepts/lists/about.md
new file mode 100644
index 00000000000..f7d4054eef0
--- /dev/null
+++ b/concepts/lists/about.md
@@ -0,0 +1,462 @@
+# About
+
+A [`list`][list] is a mutable collection of items in _sequence_.
+ Like most collections (_see the built-ins [`tuple`][tuple], [`dict`][dict] and [`set`][set]_), lists can hold reference to any (or multiple) data type(s) - including other lists.
+ Like any [sequence][sequence type], items can be accessed via `0-based index` number from the left and `-1-based index` from the right.
+ Lists can be copied in whole or in part via [slice notation][slice notation] or `.copy()`.
+
+
+Lists support both [common][common sequence operations] and [mutable][mutable sequence operations] sequence operations such as `min()`/`max()`, `.index()`, `.append()` and `.reverse()`.
+ List elements can be iterated over using the `for item in ` construct. `for index, item in enumerate()` can be used when both the element index and the element value are needed.
+
+
+Lists are implemented as [dynamic arrays][dynamic array] -- similar to Java's [`Arraylist`][arraylist] type, and are most often used to store groups of similar data (_strings, numbers, sets etc._) of unknown length (_the number of entries may arbitrarily expand or shrink_).
+
+
+Accessing elements, checking for membership via `in`, or appending items to the "right-hand" side of a list are all very efficient.
+ Prepending (_appending to the "left-hand" side_) or inserting into the middle of a list are much _less_ efficient because those operations require shifting elements to keep them in sequence.
+ For a similar data structure that supports memory efficient `appends`/`pops` from both sides, see [`collections.deque`][deque], which has approximately the same O(1) performance in either direction.
+
+
+Because lists are mutable and can contain references to arbitrary Python objects, they also take up more space in memory than an [`array.array`][array.array] or a [`tuple`][tuple] (_which is immutable_) of the same apparent length.
+ Despite this, lists are an extremely flexible and useful data structure and many built-in methods and operations in Python produce lists as their output.
+
+
+## Construction
+
+A `list` can be declared as a _literal_ with square `[]` brackets and commas between elements:
+
+
+```python
+>>> no_elements = []
+
+>>> no_elements
+[]
+
+>>> one_element = ["Guava"]
+
+>>> one_element
+['Guava']
+
+>>> elements_separated_with_commas = ["Parrot", "Bird", 334782]
+
+>>> elements_separated_with_commas
+['Parrot', 'Bird', 334782]
+```
+
+For readability, line breaks can be used when there are many elements or nested data structures within a list:
+
+
+```python
+>>> lots_of_entries = [
+ "Rose",
+ "Sunflower",
+ "Poppy",
+ "Pansy",
+ "Tulip",
+ "Fuchsia",
+ "Cyclamen",
+ "Lavender"
+ ]
+
+>>> lots_of_entries
+['Rose', 'Sunflower', 'Poppy', 'Pansy', 'Tulip', 'Fuchsia', 'Cyclamen', 'Lavender']
+
+
+# Each data structure is on its own line to help clarify what they are.
+>>> nested_data_structures = [
+ {"fish": "gold", "monkey": "brown", "parrot": "grey"},
+ ("fish", "mammal", "bird"),
+ ['water', 'jungle', 'sky']
+ ]
+
+>>> nested_data_structures
+[{'fish': 'gold', 'monkey': 'brown', 'parrot': 'grey'}, ('fish', 'mammal', 'bird'), ['water', 'jungle', 'sky']]
+```
+
+The `list()` constructor can be used empty or with an _iterable_ as an argument.
+ Elements in the iterable are cycled through by the constructor and added to the list in order:
+
+
+```python
+>>> no_elements = list()
+>>> no_elements
+[]
+
+# The tuple is unpacked and each element is added.
+>>> multiple_elements_from_tuple = list(("Parrot", "Bird", 334782))
+
+>>> multiple_elements_from_tuple
+['Parrot', 'Bird', 334782]
+
+# The set is unpacked and each element is added.
+>>> multiple_elements_from_set = list({2, 3, 5, 7, 11})
+
+>>> multiple_elements_from_set
+[2, 3, 5, 7, 11]
+```
+
+Results when using a list constructor with a string or a dict may be surprising:
+
+```python
+# String elements (Unicode code points) are iterated through and added *individually*.
+>>> multiple_elements_string = list("Timbuktu")
+
+>>> multiple_elements_string
+['T', 'i', 'm', 'b', 'u', 'k', 't', 'u']
+
+# Unicode separators and positioning code points are also added *individually*.
+>>> multiple_code_points_string = list('अभ्यास')
+
+>>> multiple_code_points_string
+['अ', 'भ', '्', 'य', 'ा', 'स']
+
+# The iteration default for dictionaries is over the keys, so only key data is inserted into the list.
+>>> source_data = {"fish": "gold", "monkey": "brown"}
+>>> list(source_data)
+['fish', 'monkey']
+```
+
+Because the `list()` constructor will only take iterables (or nothing) as arguments, objects that are **not** iterable will raise a `TypeError`. Consequently, it is much easier to create a one-item list via the literal method.
+
+```python
+# Numbers are not iterable, and so attempting to create a list with a number passed to the constructor fails.
+>>> one_element = list(16)
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: 'int' object is not iterable
+
+# Tuples *are* iterable, so passing a one-element tuple to the constructor does work, but it's awkward
+>>> one_element_from_iterable = list((16,))
+
+>>> one_element_from_iterable
+[16]
+```
+
+## Accessing elements
+
+Items inside lists (_as well as elements in other sequence types such as [`str`][string] & [`tuple`][tuple]_), can be accessed using _bracket notation_.
+Indexes can be from **`left`** --> **`right`** (_starting at zero_) or **`right`** --> **`left`** (_starting at -1_).
+
+
+
+
+ index from left ⟹
+
+| 0 👇🏾 | 1 👇🏾 | 2 👇🏾 | 3 👇🏾 | 4 👇🏾 | 5 👇🏾 |
+|:--------: |:--------: |:--------: |:--------: |:--------: |:--------: |
+| P | y | t | h | o | n |
+| 👆🏾 -6 | 👆🏾 -5 | 👆🏾 -4 | 👆🏾 -3 | 👆🏾 -2 | 👆🏾 -1 |
+ ⟸ index from right
+
+
+
+
+```python
+>>> breakfast_foods = ["Oatmeal", "Fruit Salad", "Eggs", "Toast"]
+
+# Oatmeal is at index 0 or index -4.
+>>> breakfast_foods[0]
+'Oatmeal'
+
+>>> breakfast_foods[-4]
+'Oatmeal'
+
+# Eggs are at index -2 or 2
+>>> breakfast_foods[-2]
+'Eggs'
+
+>>> breakfast_foods[2]
+'Eggs'
+
+# Toast is at -1
+>>> breakfast_foods[-1]
+'Toast'
+```
+
+A section of a list can be accessed via _slice notation_ (`[start:stop]`).
+A _slice_ is defined as an element sequence at position `index`, such that `start <= index < stop`.
+[_Slicing_][slice notation] returns a copy of the "sliced" items and does not modify the original `list`.
+
+A `step` parameter can also be used in the slice (`[::]`) to "skip over" or filter the returned elements (_for example, a `step` of 2 will select every other element in the section_):
+
+```python
+>>> colors = ["Red", "Purple", "Green", "Yellow", "Orange", "Pink", "Blue", "Grey"]
+
+# If there is no step parameter, the step is assumed to be 1.
+>>> middle_colors = colors[2:6]
+
+>>> middle_colors
+['Green', 'Yellow', 'Orange', 'Pink']
+
+# If the start or stop parameters are omitted, the slice will
+# start at index zero, and will stop at the end of the list.
+>>> primary_colors = colors[::3]
+
+>>> primary_colors
+['Red', 'Yellow', 'Blue']
+```
+
+
+## Working with lists
+
+Lists supply an [_iterator_][iterator], and can be looped through/over in the same manner as other _sequence types_, using either `for item in ` or `for index, item in enumerate()`:
+
+```python
+# Make a list, and then loop through it to print out the elements
+>>> colors = ["Orange", "Green", "Grey", "Blue"]
+>>> for item in colors:
+... print(item)
+...
+Orange
+Green
+Grey
+Blue
+
+
+# Print the same list, but with the indexes of the colors included
+>>> colors = ["Orange", "Green", "Grey", "Blue"]
+>>> for index, item in enumerate(colors):
+... print(item, ":", index)
+...
+Orange : 0
+Green : 1
+Grey : 2
+Blue : 3
+
+
+# Start with a list of numbers and then loop through and print out their cubes.
+>>> numbers_to_cube = [5, 13, 12, 16]
+>>> for number in numbers_to_cube:
+... print(number**3)
+...
+125
+2197
+1728
+4096
+```
+
+One common way to compose a list of values is to use `.append()` within a loop:
+
+```python
+>>> cubes_to_1000 = []
+>>> for number in range(11):
+... cubes_to_1000.append(number**3)
+
+>>> cubes_to_1000
+[0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
+```
+
+Lists can also be combined via various techniques:
+
+```python
+# Using the plus + operator unpacks each list and creates a new list, but it is not efficient.
+>>> new_via_concatenate = ["George", 5] + ["cat", "Tabby"]
+
+>>> new_via_concatenate
+['George', 5, 'cat', 'Tabby']
+
+# Likewise, using the multiplication operator * is the equivalent of using + n times.
+>>> first_group = ["cat", "dog", "elephant"]
+>>> multiplied_group = first_group * 3
+
+>>> multiplied_group
+['cat', 'dog', 'elephant', 'cat', 'dog', 'elephant', 'cat', 'dog', 'elephant']
+
+# Another method for combining 2 lists is to use slice assignment or a loop-append.
+# This assigns the second list to index 0 in the first list.
+>>> first_one = ["cat", "Tabby"]
+>>> second_one = ["George", 5]
+>>> first_one[0:0] = second_one
+
+>>> first_one
+['George', 5, 'cat', 'Tabby']
+
+# This loops through the first list and appends its items to the end of the second list.
+>>> first_one = ["cat", "Tabby"]
+>>> second_one = ["George", 5]
+
+>>> for item in first_one:
+... second_one.append(item)
+
+>>> second_one
+['George', 5, 'cat', 'Tabby']
+```
+
+
+## Some cautions
+
+Recall that variables in Python are _labels_ that point to _underlying objects_.
+`lists` add one more layer as _container objects_ -- they hold object _references_ for their collected items.
+This can lead to multiple potential issues when working with lists, if not handled properly.
+
+
+### Assigning more than one variable name
+Assigning a `list` object to a new variable _name_ **does not copy the `list` object nor its elements**.
+Any change made to the elements in the `list` under the _new_ name _impact the original_.
+
+
+Making a `shallow_copy` via `list.copy()` or slice will avoid this first-level referencing complication.
+A `shallow_copy` will create a new `list` object, but **will not** create new objects for the contained list _elements_. This type of copy will usually be enough for you to add or remove items from the two `list` objects independently, and effectively have two "separate" lists.
+
+
+```python
+>>> actual_names = ["Tony", "Natasha", "Thor", "Bruce"]
+
+# Assigning a new variable name does not make a copy of the container or its data.
+>>> same_list = actual_names
+
+# Altering the list via the new name is the same as altering the list via the old name.
+>>> same_list.append("Clarke")
+["Tony", "Natasha", "Thor", "Bruce", "Clarke"]
+
+>>> actual_names
+["Tony", "Natasha", "Thor", "Bruce", "Clarke"]
+
+# Likewise, altering the data in the list via the original name will also alter the data under the new name.
+>>> actual_names[0] = "Wanda"
+['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']
+
+# If you copy the list, there will be two separate list objects which can be changed independently.
+>>> copied_list = actual_names.copy()
+>>> copied_list[0] = "Tony"
+
+>>> actual_names
+['Wanda', 'Natasha', 'Thor', 'Bruce', 'Clarke']
+
+>>> copied_list
+["Tony", "Natasha", "Thor", "Bruce", "Clarke"]
+```
+
+
+This reference complication becomes exacerbated when working with nested or multiplied lists (_the following examples are from the excellent 2013 [`Ned Batchelder`][ned batchelder] blog post [Names and values: making a game board][names and values]_):
+
+```python
+from pprint import pprint
+
+# This will produce a game grid that is 8x8, pre-populated with zeros.
+>>> game_grid = [[0]*8] *8
+
+>>> pprint(game_grid)
+[[0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0]]
+
+# An attempt to put a "X" in the bottom right corner.
+>>> game_grid[7][7] = "X"
+
+# This attempt doesn't work because all the rows are referencing the same underlying list object.
+>>> pprint(game_grid)
+[[0, 0, 0, 0, 0, 0, 0, 'X'],
+ [0, 0, 0, 0, 0, 0, 0, 'X'],
+ [0, 0, 0, 0, 0, 0, 0, 'X'],
+ [0, 0, 0, 0, 0, 0, 0, 'X'],
+ [0, 0, 0, 0, 0, 0, 0, 'X'],
+ [0, 0, 0, 0, 0, 0, 0, 'X'],
+ [0, 0, 0, 0, 0, 0, 0, 'X'],
+ [0, 0, 0, 0, 0, 0, 0, 'X']]
+```
+
+But in this circumstance, a `shallow_copy` is enough to allow the behavior we'd like:
+
+```python
+from pprint import pprint
+
+# This loop will safely produce a game grid that is 8x8, pre-populated with zeros
+>>> game_grid = []
+>>> filled_row = [0] * 8
+>>> for row in range(8):
+... game_grid.append(filled_row.copy()) # This is making a new shallow copy of the inner list object each iteration.
+
+>>> pprint(game_grid)
+[[0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0]]
+
+# An attempt to put a "X" in the bottom right corner.
+>>> game_grid[7][7] = "X"
+
+# The game grid now works the way we expect it to!
+>>> pprint(game_grid)
+[[0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 'X']]
+```
+
+As mentioned earlier, lists are containers of _references_, so there is a second layer of potential complication.
+If a list contains variables, objects, or nested data structures, those second-level references **will not be copied** via `shallow_copy` or slice.
+Mutating the underlying objects will then affect _any and all_ copies, since each `list` object only contains _references pointing to_ the contained elements.
+
+```python
+from pprint import pprint
+
+>>> pprint(game_grid)
+[[0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 'X']]
+
+# We'd like a new board, so we make a shallow copy.
+>>> new_game_grid = game_grid.copy()
+
+# But a shallow copy doesn't copy the contained references or objects.
+>>> new_game_grid[0][0] = 'X'
+
+# So changing the items in the copy also changes the originals items.
+>>> pprint(game_grid)
+[['X', 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 'X']]
+```
+
+## Related data types
+
+Lists are often used as _stacks_ and _queues_ -- although their underlying implementation makes prepending and inserting slow.
+The [collections][collections] module offers a [deque][deque] variant optimized for fast appends and pops from either end that is implemented as a [doubly linked list][doubly linked list].
+Nested lists are also used to model small _matrices_ -- although the [Numpy][numpy] and [Pandas][pandas] libraries are much more robust for efficient matrix and tabular data manipulation.
+The collections module also provides a `UserList` type that can be customized to fit specialized list needs.
+
+[array.array]: https://docs.python.org/3/library/array.html
+[arraylist]: https://beginnersbook.com/2013/12/java-arraylist/
+[collections]: https://docs.python.org/3/library/collections.html
+[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations
+[deque]: https://docs.python.org/3/library/collections.html#collections.deque
+[dict]: https://docs.python.org/3/library/stdtypes.html#dict
+[doubly linked list]: https://en.wikipedia.org/wiki/Doubly_linked_list
+[dynamic array]: https://en.wikipedia.org/wiki/Dynamic_array
+[iterator]: https://docs.python.org/3/glossary.html#term-iterator
+[list]: https://docs.python.org/3/library/stdtypes.html#list
+[mutable sequence operations]: https://docs.python.org/3/library/stdtypes.html#typesseq-mutable
+[names and values]: https://nedbatchelder.com/blog/201308/names_and_values_making_a_game_board.html
+[ned batchelder]: https://nedbatchelder.com/
+[numpy]: https://numpy.org/
+[pandas]: https://pandas.pydata.org/
+[sequence type]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range
+[set]: https://docs.python.org/3/library/stdtypes.html#set
+[slice notation]: https://docs.python.org/3/reference/expressions.html#slicings
+[string]: https://docs.python.org/3/library/stdtypes.html#text-sequence-type-str
+[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
diff --git a/concepts/lists/introduction.md b/concepts/lists/introduction.md
new file mode 100644
index 00000000000..bd68b9a9d59
--- /dev/null
+++ b/concepts/lists/introduction.md
@@ -0,0 +1,14 @@
+# Introduction
+
+A [list][list] is a mutable collection of items in _sequence_.
+ They are an extremely flexible and useful data structure that many `built-in` methods and operations in Python produce as output.
+ Lists can hold reference to any (or multiple) data type(s) - including other lists or data structures such as [tuples][tuples], [sets][sets], or [dicts][dicts].
+ Content can be iterated over using `for item in ` construct.
+ If indexes are needed with the content, `for index, item in enumerate()` can be used.
+ Elements within a `list` can be accessed via `0-based index` number from the left, or `-1-based index` number from the right.
+ Lists can be copied in whole or in part using _slice notation_ or `.copy()`.
+
+[dicts]: https://github.com/exercism/python/tree/main/concepts/dicts
+[list]: https://docs.python.org/3/library/stdtypes.html#list
+[sets]: https://github.com/exercism/python/tree/main/concepts/sets
+[tuples]: https://github.com/exercism/python/tree/main/concepts/tuples
diff --git a/concepts/lists/links.json b/concepts/lists/links.json
new file mode 100644
index 00000000000..c9ade573ba8
--- /dev/null
+++ b/concepts/lists/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "https://docs.python.org/3/tutorial/datastructures.html",
+ "description": "Python Tutorial: Lists"
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#list",
+ "description": "Lists Type in Python Docs"
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range",
+ "description": "Sequence Types in Python Docs"
+ },
+ {
+ "url": "https://realpython.com/python-lists-tuples/",
+ "description": "Real Python: Lists and Tuples"
+ }
+]
diff --git a/concepts/loops/.meta/config.json b/concepts/loops/.meta/config.json
new file mode 100644
index 00000000000..f3b540e8b8f
--- /dev/null
+++ b/concepts/loops/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "There are two looping constructs in Python: while loops for indefinite/uncounted iteration, and for loops for definite/counted iteration. The keywords break, continue, and else help customize loop behavior. The range() and enumerate() functions help with loop counting and indexing.",
+ "authors": ["BethanyG"],
+ "contributors": []
+}
diff --git a/concepts/loops/about.md b/concepts/loops/about.md
new file mode 100644
index 00000000000..0f39e733d0c
--- /dev/null
+++ b/concepts/loops/about.md
@@ -0,0 +1,251 @@
+# Loops
+
+Python has two looping constructs.
+`while` loops for _indefinite_ (uncounted) iteration and `for` loops for _definite_, (counted) iteration.
+The keywords `break`, `continue`, and `else` help customize loop behavior.
+`range()` and `enumerate()` help with loop counting and indexing.
+
+## While
+
+[`while`][while statement] loops will continue to execute as long as the `loop expression` or "test" evaluates to `True` in a [`boolean context`][truth value testing], terminating when it evaluates to `False`:
+
+```python
+# Lists are considered "truthy" in a boolean context if they
+# contain one or more values, and "falsy" if they are empty.
+
+>>> placeholders = ["spam", "ham", "eggs", "green_spam", "green_ham", "green_eggs"]
+
+>>> while placeholders:
+... print(placeholders.pop(0))
+...
+'spam'
+'ham'
+'eggs'
+'green_spam'
+'green_ham'
+'green_eggs'
+```
+
+## For
+
+The basic [`for`][for statement] `loop` in Python is better described as a _`for each`_ which cycles through the values of any [iterable object][iterable], terminating when there are no values returned from calling [`next()`][next built-in] (_raising a [`StopIteration`][stopiteration]_).
+
+```python
+
+>>> word_list = ["bird", "chicken", "barrel", "bongo"]
+
+>>> for word in word_list:
+... if word.startswith("b"):
+... print(f"{word.title()} starts with a B.")
+... else:
+... print(f"{word.title()} doesn't start with a B.")
+...
+'Bird starts with a B.'
+'Chicken doesn\'t start with a B.'
+'Barrel starts with a B.'
+'Bongo starts with a B.'
+```
+
+
+## Sequence Object `range()`
+
+When there isn't a specific `iterable` given, the special [`range()`][range] sequence is used as a loop counter.
+`range()` requires an `int` before which to `stop` the sequence, and can optionally take `start` and `step` parameters.
+If no `start` number is provided, the sequence will begin with 0.
+`range()` objects are **lazy** (_values are generated on request_), support all [common sequence operations][common sequence operations], and take up a fixed amount of memory, no matter how long the sequence specified.
+Interestingly, `range()` [_is not an iterator_][range is not an iterator], and can be used in many non-looping contexts where a sequence of numbers is needed.
+
+
+```python
+# Here we use range to produce some numbers, rather than creating a list of them in memory.
+# The values will start with 1 and stop *before* 7.
+
+>>> for number in range(1, 7):
+... if number % 2 == 0:
+... print(f"{number} is even.")
+... else:
+... print(f"{number} is odd.")
+'1 is odd.'
+'2 is even.'
+'3 is odd.'
+'4 is even.'
+'5 is odd.'
+'6 is even.'
+
+# range() can also take a *step* parameter.
+# Here we use range to produce only the "odd" numbers, starting with 3 and stopping *before* 15.
+
+>>> for number in range(3, 15, 2):
+... if number % 2 == 0:
+... print(f"{number} is even.")
+... else:
+... print(f"{number} is odd.")
+...
+'3 is odd.'
+'5 is odd.'
+'7 is odd.'
+'9 is odd.'
+'11 is odd.'
+'13 is odd.'
+```
+
+
+## Values and Indexes with `enumerate()`
+
+If both values and indexes are needed, the built-in [`enumerate()`][enumerate] will return an [`iterator`][iterator] over (`index`, `value`) pairs:
+
+```python
+
+>>> word_list = ["bird", "chicken", "barrel", "apple"]
+
+# *index* and *word* are the loop variables.
+# Loop variables can be any valid python name.
+
+>>> for index, word in enumerate(word_list):
+... if word.startswith("b"):
+... print(f"{word.title()} (at index {index}) starts with a B.")
+... else:
+... print(f"{word.title()} (at index {index}) doesn't start with a B.")
+...
+'Bird (at index 0) starts with a B.'
+'Chicken (at index 1) doesn\'t start with a B.'
+'Barrel (at index 2) starts with a B.'
+'Apple (at index 3) doesn\'t start with a B.'
+
+
+# The same method can be used as a "lookup" for pairing items between two lists.
+# Of course, if the lengths or indexes don't line up, this doesn't work.
+
+>>> word_list = ["cat", "chicken", "barrel", "apple", "spinach"]
+>>> category_list = ["mammal", "bird", "thing", "fruit", "vegetable"]
+
+>>> for index, word in enumerate(word_list):
+... print(f"{word.title()} is in category: {category_list[index]}.")
+...
+'Cat is in category: mammal.'
+'Chicken is in category: bird.'
+'Barrel is in category: thing.'
+'Apple is in category: fruit.'
+'Spinach is in category: vegetable.'
+```
+
+The `enumerate()` function can also be set to `start` the index count at a different number:
+
+
+```python
+# Here, the index count will start at 1.
+>>> for position, word in enumerate(word_list, start=1):
+... if word.startswith("b"):
+... print(f"{word.title()} (at position {position}) starts with a B.")
+... else:
+... print(f"{word.title()} (at position {position}) doesn't start with a B.")
+...
+'Bird (at position 1) starts with a B.'
+'Chicken (at position 2) doesn\'t start with a B.'
+'Barrel (at position 3) starts with a B.'
+'Apple (at position 4) doesn\'t start with a B.'
+```
+
+
+## Altering Loop Behavior
+
+The [`continue`][continue statement] keyword can be used to skip forward to the next iteration cycle:
+
+```python
+word_list = ["bird", "chicken", "barrel", "bongo", "sliver", "apple", "bear"]
+
+# This will skip *bird*, at index 0
+for index, word in enumerate(word_list):
+ if index == 0:
+ continue
+ if word.startswith("b"):
+ print(f"{word.title()} (at index {index}) starts with a b.")
+
+'Barrel (at index 2) starts with a b.'
+'Bongo (at index 3) starts with a b.'
+'Bear (at index 6) starts with a b.'
+```
+
+
+The [`break`][break statement] (_like in many C-related languages_) keyword can be used to stop the iteration and "break out" of the innermost enclosing `loop`:
+
+```python
+>>> word_list = ["bird", "chicken", "barrel", "bongo", "sliver", "apple"]
+
+>>> for index, word in enumerate(word_list):
+... if word.startswith("b"):
+... print(f"{word.title()} (at index {index}) starts with a B.")
+... elif word == "sliver":
+... break
+... else:
+... print(f"{word.title()} doesn't start with a B.")
+... print("loop broken.")
+...
+'Bird (at index 0) starts with a B.'
+'Chicken doesn\'t start with a B.'
+'Barrel (at index 2) starts with a B.'
+'Bongo (at index 3) starts with a B.'
+'loop broken.'
+```
+
+
+The loop [`else` clause][loop else] is unique to Python and can be used for "wrap up" or "concluding" actions when iteration has been exhausted.
+ Statements following the `else` keyword will not execute if iteration terminates via `break`:
+
+
+```python
+>>> word_list = ["bird", "chicken", "barrel", "bongo", "sliver", "apple"]
+
+# Using enumerate to get both an index and a value.
+
+>>> for index, word in enumerate(word_list):
+... word = word.title()
+... if word.startswith("B"):
+... print(f"{word} (at index {index}) starts with a B.")
+
+...# This executes once *StopIteration* is raised and
+...# there are no more items to iterate through.
+...# Note the indentation, which lines up with the for keyword.
+...else:
+... print(f"Found the above b-words, out of {len(word_list)} words in the word list.")
+...
+'Bird (at index 0) starts with a B.'
+'Barrel (at index 2) starts with a B.'
+'Bongo (at index 3) starts with a B.'
+'Found the above b-words, out of 6 words in the word list.'
+
+
+# Terminating a loop via *break* will bypass the loop *else* clause
+
+>>> for index, word in enumerate(word_list):
+... word = word.title()
+... if word.startswith("B"):
+... print(f"{word} (at index {index}) starts with a B.")
+... if word.startswith("S"):
+... print("Found an S, stopping iteration.")
+... break
+
+... # This statement does not run, because a *break* was triggered.
+... else:
+... print(f"Found the above b-words, out of {len(word_list)} words in the word list.")
+...
+'Bird (at index 0) starts with a B.'
+'Barrel (at index 2) starts with a B.'
+'Bongo (at index 3) starts with a B.'
+'Found an S, stopping iteration.'
+```
+
+[break statement]: https://docs.python.org/3/reference/simple_stmts.html#the-break-statement
+[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations
+[continue statement]: https://docs.python.org/3/reference/simple_stmts.html#the-continue-statement
+[enumerate]: https://docs.python.org/3/library/functions.html#enumerate
+[for statement]: https://docs.python.org/3/reference/compound_stmts.html#for
+[iterable]: https://docs.python.org/3/glossary.html#term-iterable
+[iterator]: https://docs.python.org/3/glossary.html#term-iterator
+[loop else]: https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops
+[next built-in]: https://docs.python.org/3/library/functions.html#next
+[range is not an iterator]: https://treyhunner.com/2018/02/python-range-is-not-an-iterator/
+[range]: https://docs.python.org/3/library/stdtypes.html#range
+[stopiteration]: https://docs.python.org/3/library/exceptions.html#StopIteration
+[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
+[while statement]: https://docs.python.org/3/reference/compound_stmts.html#the-while-statement
diff --git a/concepts/loops/introduction.md b/concepts/loops/introduction.md
new file mode 100644
index 00000000000..4f82503edd1
--- /dev/null
+++ b/concepts/loops/introduction.md
@@ -0,0 +1,31 @@
+# Introduction
+
+Python has two looping constructs.
+`while` loops for _indefinite_ (uncounted) iteration and `for` loops for _definite_, (counted) iteration.
+The keywords `break`, `continue`, and `else` help customize loop behavior.
+The `range()` and `enumerate()` functions help with loop counting and indexing.
+
+
+`while` loops do not have a number of loops to execute specified, and will continue as long as the `loop` expression or "test" evaluates to `True`.
+The loop will terminate when the loop expression evaluates to `False`.
+
+```python
+while expression:
+ set_of_statements
+```
+
+`for` loops cycle through the values of any sequence, terminating when there are no more values returned.
+The `range()` function provides a `loop` counter or sequence when there is no data structure or other iterable to loop over.
+
+```python
+# Python will keep track of the loop count internally.
+for item in sequence:
+ set_of_statements
+
+# `range()` is used as an explicit loop counter.
+# This will loop 12 times, from index 0 to index 11.
+for item in range(12):
+ set_of_statements
+```
+
+
diff --git a/concepts/loops/links.json b/concepts/loops/links.json
new file mode 100644
index 00000000000..4a5c845447e
--- /dev/null
+++ b/concepts/loops/links.json
@@ -0,0 +1,42 @@
+[
+ {
+ "url": "https://nedbatchelder.com/text/iter.html",
+ "description": "Ned Batchelder: Loop Like a Native."
+ },
+ {
+ "url": "https://docs.python.org/3/tutorial/controlflow.html#for-statements",
+ "description": "Python Docs Tutorial: for statements"
+ },
+ {
+ "url": "https://realpython.com/python-for-loop/",
+ "description": "Real Python: For Loops"
+ },
+ {
+ "url": "https://realpython.com/python-while-loop/",
+ "description": "Real Python: While Loops"
+ },
+ {
+ "url": "https://realpython.com/python-enumerate/",
+ "description": "Real Python: enumerate()"
+ },
+ {
+ "url": "https://docs.python.org/3/tutorial/controlflow.html#the-range-function/",
+ "description": "Python Docs: range() function."
+ },
+ {
+ "url": "https://realpython.com/python-for-loop/#the-range-function",
+ "description": "Real Python: range()"
+ },
+ {
+ "url": "https://treyhunner.com/2018/02/python-range-is-not-an-iterator/",
+ "description": "Trey Hunner: range() is not an iterator"
+ },
+ {
+ "url": "https://docs.python.org/3/library/exceptions.html#StopIteration",
+ "description": "Python Docs: StopIteration Error"
+ },
+ {
+ "url": "https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops",
+ "description": "Python Docs: Control Flow for Loops"
+ }
+]
diff --git a/concepts/memoryview/.meta/config.json b/concepts/memoryview/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/memoryview/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/memoryview/about.md b/concepts/memoryview/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/memoryview/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/memoryview/introduction.md b/concepts/memoryview/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/memoryview/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/memoryview/links.json b/concepts/memoryview/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/memoryview/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/none/.meta/config.json b/concepts/none/.meta/config.json
new file mode 100644
index 00000000000..83b11709199
--- /dev/null
+++ b/concepts/none/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "None is a singleton object frequently used to represent the *absence* of a value -- a placeholder to define a nil (empty, undefined, or null) variable, object, or argument. This \"placeholder\" can then be replaced by specific values or types later as needed. None is also automatically returned from functions that do not have a return specified.",
+ "authors": ["mohanrajanr", "BethanyG"],
+ "contributors": []
+}
diff --git a/concepts/none/about.md b/concepts/none/about.md
new file mode 100644
index 00000000000..79ac4c2a08d
--- /dev/null
+++ b/concepts/none/about.md
@@ -0,0 +1,2 @@
+# About
+
diff --git a/concepts/none/introduction.md b/concepts/none/introduction.md
new file mode 100644
index 00000000000..724413a1118
--- /dev/null
+++ b/concepts/none/introduction.md
@@ -0,0 +1,29 @@
+# Introduction
+
+In Python, `None` is frequently used to represent the absence of a value -- a placeholder to define a `null` (empty) variable, object, or argument.
+
+If you've heard about or used a `NULL` or `nil` type in another programming language, then this usage of `None` in Python will be familiar to you. `None` helps you to declare variables or function arguments that you don't yet have values for. These can then be re-assigned to specific values later as needed.
+
+```python
+a = None
+print(a)
+#=> None
+type(a)
+#=>
+
+# Adding a Default Argument with `None`
+def add_to_todos(new_task, todo_list=None):
+ if todo_list is None:
+ todo_list = []
+ todo_list.append(new_task)
+ return todo_list
+
+```
+
+`None` will evaluate to `False` when used in a conditional check, so it is useful for validating the "presence of" or "absence of" a value - _any_ value -- a pattern frequently used when a function or process might hand back an error object or message.
+
+```python
+a = None
+if a: #=> a will be evaluated to False when its used in a conditional check.
+ print("This will not be printed")
+```
diff --git a/concepts/none/links.json b/concepts/none/links.json
new file mode 100644
index 00000000000..fe51488c706
--- /dev/null
+++ b/concepts/none/links.json
@@ -0,0 +1 @@
+[]
diff --git a/concepts/number-variations/.meta/config.json b/concepts/number-variations/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/number-variations/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/number-variations/about.md b/concepts/number-variations/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/number-variations/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/number-variations/introduction.md b/concepts/number-variations/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/number-variations/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/number-variations/links.json b/concepts/number-variations/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/number-variations/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/numbers/.meta/config.json b/concepts/numbers/.meta/config.json
new file mode 100644
index 00000000000..7898f6099aa
--- /dev/null
+++ b/concepts/numbers/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "There are three different types of built-in numbers: integers (\"int\"), floating-point (\"float\"), and complex (\"complex\"). Ints have arbitrary precision and floats typically have 15 decimal places of precision -- but both int and float precision vary by host system. Complex numbers have a real and an imaginary part - each represented by floats.",
+ "authors": ["Ticktakto", "Yabby1997", "limm-jk", "OMEGA-Y", "wnstj2007"],
+ "contributors": ["BethanyG", "KaiAragaki", "meatball133"]
+}
diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md
new file mode 100644
index 00000000000..1155bcf7a5c
--- /dev/null
+++ b/concepts/numbers/about.md
@@ -0,0 +1,202 @@
+# About
+
+Python has three different types of built-in numbers: integers ([`int`][int]), floating-point ([`float`][float]), and complex ([`complex`][complex]).
+Fractions ([`fractions.Fraction`][fractions]) and Decimals ([`decimal.Decimal`][decimals]) are also available via import from the standard library.
+
+Whole numbers including hexadecimal ([_`hex()`_][hex]), octal ([_`oct()`_][oct]) and binary ([_`bin()`_][bin]) numbers **without** decimal places are also identified as `ints`:
+
+```python
+# Ints are whole numbers.
+>>> 1234
+1234
+>>> type(1234)
+
+
+>>> -12
+-12
+```
+
+Numbers containing a decimal point (with or without fractional parts) are identified as `floats`:
+
+```python
+>>> 3.45
+3.45
+>>> type(3.45)
+
+```
+
+## Arithmetic
+
+Python fully supports arithmetic between these different number types, and will convert narrower numbers to match their less narrow counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`).
+
+All numbers (except complex) support all [arithmetic operations][arithmetic-operations], evaluated according to [operator precedence][operator precedence].
+Support for mathematical functions (beyond `+` and `-`) for complex numbers can be found in the [cmath][cmath] module.
+
+### Addition and subtraction
+
+Addition and subtraction operators behave as they do in normal math.
+If one or more of the operands is a `float`, the remaining `int`s will be converted to `float`s as well:
+
+```python
+>>> 5 - 3
+2
+# The int is widened to a float here, and a float is returned.
+>>> 3 + 4.0
+7.0
+```
+
+### Multiplication
+
+As with addition and subtraction, multiplication will convert narrower numbers to match their less narrow counterparts:
+
+```python
+>>> 3 * 2
+6
+
+>>> 3 * 2.0
+6.0
+```
+
+### Division
+
+Division always returns a `float`, even if the result is a whole number:
+
+```python
+>>> 6/5
+1.2
+
+>>> 6/2
+3.0
+```
+
+### Floor division
+
+If an `int` result is needed, you can use floor division to truncate the result.
+Floor division is performed using the `//` operator:
+
+```python
+>>> 6//5
+1
+
+>>> 6//2
+3
+```
+
+### Modulo
+
+The modulo operator (`%`) returns the remainder of the division of the two operands:
+
+```python
+# The result of % is zero here, because dividing 8 by 2 leaves no remainder
+>>> 8 % 2
+0
+
+# The result of % is 2 here, because 3 only goes into 5 once, with 2 left over
+>>> 5 % 3
+2
+```
+
+Another way to look at 5 % 3:
+
+```python
+>>> whole_part = int(5/3)
+1
+
+>>> decimal_part = 5/3 - whole_part
+0.6666666666666667
+
+>>> whole_remainder = decimal_part * 3
+2.0
+```
+
+## Exponentiation
+
+Exponentiation is performed using the `**` operator:
+
+```python
+>>> 2 ** 3
+8
+
+>>> 4 ** 0.5
+2
+```
+
+## Conversions
+
+Numbers can be converted from `int` to `floats` and `floats` to `int` using the built-in functions `int()` and `float()`:
+
+```python
+>>> int(3.45)
+3
+
+>>> float(3)
+3.0
+```
+
+## Round
+
+Python provides a built-in function [`round(number, )`][round] to round off a floating point number to a given number of decimal places.
+If no number of decimal places is specified, the number is rounded off to the nearest integer and will return an `int`:
+
+```python
+>>> round(3.1415926535, 2)
+3.14
+
+>>> round(3.1415926535)
+3
+```
+
+## Priority and parentheses
+
+Python allows you to use parentheses to group expressions.
+This is useful when you want to override the default order of operations.
+
+```python
+>>> 2 + 3 * 4
+14
+
+>>> (2 + 3) * 4
+20
+```
+
+Python follows the [PEMDAS][pemdas] rule for operator precedence.
+This means calculations within `()` have the highest priority, followed by `**`, then `*`, `/`, `//`, `%`, `+`, and `-`:
+
+```python
+>>> 2 + 3 - 4 * 4
+-11
+
+>>> (2 + 3 - 4) * 4
+4
+
+# In the following example, the `**` operator has the highest priority, then `*`, then `+`
+# Meaning we first do 4 ** 4, then 3 * 256, then 2 + 768
+>>> 2 + 3 * 4 ** 4
+770
+```
+
+## Precision & Representation
+
+Integers in Python have [arbitrary precision][arbitrary-precision] -- the number of digits is limited only by the available memory of the host system.
+
+Floating point numbers are usually implemented using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system.
+Complex numbers have a `real` and an `imaginary` part, both of which are represented by floating point numbers.
+
+For a more detailed discussions of the issues and limitations of floating point arithmetic across programming languages, take a look at [0.30000000000000004.com][0.30000000000000004.com] and [The Python Tutorial][floating point math].
+
+[0.30000000000000004.com]: https://0.30000000000000004.com/
+[arbitrary-precision]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic
+[arithmetic-operations]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex
+[bin]: https://docs.python.org/3/library/functions.html#bin
+[cmath]: https://docs.python.org/3.9/library/cmath.html
+[complex]: https://docs.python.org/3/library/functions.html#complex
+[decimals]: https://docs.python.org/3/library/decimal.html#module-decimal
+[float]: https://docs.python.org/3/library/functions.html#float
+[floating point math]: https://docs.python.org/3.9/tutorial/floatingpoint.html
+[fractions]: https://docs.python.org/3/library/fractions.html
+[hex]: https://docs.python.org/3/library/functions.html#hex
+[int]: https://docs.python.org/3/library/functions.html#int
+[oct]: https://docs.python.org/3/library/functions.html#oct
+[operator precedence]: https://docs.python.org/3/reference/expressions.html#operator-precedence
+[pemdas]: https://mathworld.wolfram.com/PEMDAS.html
+[round]: https://docs.python.org/3/library/functions.html#round
diff --git a/concepts/numbers/introduction.md b/concepts/numbers/introduction.md
new file mode 100644
index 00000000000..3491bc20a3c
--- /dev/null
+++ b/concepts/numbers/introduction.md
@@ -0,0 +1,18 @@
+# Introduction
+
+Python has three different types of built-in numbers: integers ([`int`][int]), floating-point ([`float`][float]), and complex ([`complex`][complex]).
+Fractions ([`fractions.Fraction`][fractions]) and Decimals ([`decimal.Decimal`][decimals]) are also available via import from the standard library.
+
+Whole numbers including hexadecimal ([_`hex()`_][hex]), octal ([_`oct()`_][oct]) and binary ([_`bin()`_][bin]) numbers **without** decimal places are also identified as `ints`.
+
+Python fully supports arithmetic between these different number types, and will convert narrower numbers to match their less narrow counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`).
+
+
+[bin]: https://docs.python.org/3/library/functions.html#bin
+[complex]: https://docs.python.org/3/library/functions.html#complex
+[decimals]: https://docs.python.org/3/library/decimal.html#module-decimal
+[float]: https://docs.python.org/3/library/functions.html#float
+[fractions]: https://docs.python.org/3/library/fractions.html
+[hex]: https://docs.python.org/3/library/functions.html#hex
+[int]: https://docs.python.org/3/library/functions.html#int
+[oct]: https://docs.python.org/3/library/functions.html#oct
diff --git a/concepts/numbers/links.json b/concepts/numbers/links.json
new file mode 100644
index 00000000000..1082bd965ab
--- /dev/null
+++ b/concepts/numbers/links.json
@@ -0,0 +1,38 @@
+[
+ {
+ "url": "https://docs.python.org/3/library/functions.html#int",
+ "description": "integers"
+ },
+ {
+ "url": "https://docs.python.org/3/library/functions.html#float",
+ "description": "floats"
+ },
+ {
+ "url": "https://docs.python.org/3/library/functions.html#complexh",
+ "description": "complex numbers"
+ },
+ {
+ "url": "https://docs.python.org/3/library/fractions.html",
+ "description": "fractions"
+ },
+ {
+ "url": "https://docs.python.org/3/library/decimal.html#module-decimal",
+ "description": "Decimals"
+ },
+ {
+ "url": "https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex",
+ "description": "Arithmetic Operations"
+ },
+ {
+ "url": "https://docs.python.org/3/reference/expressions.html#operator-precedence",
+ "description": "Operator Precedence"
+ },
+ {
+ "url": "https://docs.python.org/3.9/library/cmath.html",
+ "description": "cmath: mathematical operations for complex numbers"
+ },
+ {
+ "url": "https://docs.python.org/3/library/numeric.html",
+ "description": "Pythons Numerical and Mathematical Modules"
+ }
+]
diff --git a/concepts/operator-overloading/.meta/config.json b/concepts/operator-overloading/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/operator-overloading/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/operator-overloading/about.md b/concepts/operator-overloading/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/operator-overloading/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/operator-overloading/introduction.md b/concepts/operator-overloading/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/operator-overloading/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/operator-overloading/links.json b/concepts/operator-overloading/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/operator-overloading/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/other-comprehensions/.meta/config.json b/concepts/other-comprehensions/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/other-comprehensions/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/other-comprehensions/about.md b/concepts/other-comprehensions/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/other-comprehensions/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/other-comprehensions/introduction.md b/concepts/other-comprehensions/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/other-comprehensions/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/other-comprehensions/links.json b/concepts/other-comprehensions/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/other-comprehensions/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/raising-and-handling-errors/.meta/config.json b/concepts/raising-and-handling-errors/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/raising-and-handling-errors/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/raising-and-handling-errors/about.md b/concepts/raising-and-handling-errors/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/raising-and-handling-errors/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/raising-and-handling-errors/introduction.md b/concepts/raising-and-handling-errors/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/raising-and-handling-errors/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/raising-and-handling-errors/links.json b/concepts/raising-and-handling-errors/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/raising-and-handling-errors/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/random/.meta/config.json b/concepts/random/.meta/config.json
new file mode 100644
index 00000000000..7319e329bad
--- /dev/null
+++ b/concepts/random/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "The random module contains functionality to generate random values for modelling, simulations and games. It should not be used for security or cryptographic applications.",
+ "authors": ["BethanyG", "colinleach"],
+ "contributors": []
+}
diff --git a/concepts/random/about.md b/concepts/random/about.md
new file mode 100644
index 00000000000..9ed984179d3
--- /dev/null
+++ b/concepts/random/about.md
@@ -0,0 +1,245 @@
+# About
+
+Many programs need (apparently) random values to simulate real-world events.
+
+Common, familiar examples include:
+- A coin toss: a random value from `('H', 'T')`.
+- The roll of a die: a random integer from 1 to 6.
+- Shuffling a deck of cards: a random ordering of a card list.
+
+Generating truly random values with a computer is a [surprisingly difficult technical challenge][truly-random], so you may see these results referred to as "pseudorandom".
+
+In practice, a well-designed library like the [`random`][random] module in the Python standard library is fast, flexible, and gives results that are amply good enough for most applications in modelling, simulation and games.
+
+The rest of this page will list a few of the most common functions in `random`.
+We encourage you to explore the full `random` documentation, as there are many more options than what we cover here.
+
+
+
+~~~~exercism/caution
+
+The `random` module should __NOT__ be used for security and cryptographic applications.
+
+Instead, Python provides the [`secrets`][secrets] module.
+This is specially optimized for cryptographic security.
+Some of the prior issues and reasons for creating the secrets module can be found in [PEP 506][PEP 506].
+
+[secrets]: https://docs.python.org/3.11/library/secrets.html#module-secrets
+[PEP 506]: https://peps.python.org/pep-0506/
+~~~~
+
+
+
+## Importing
+
+Before you can utilize the tools in the `random` module, you must first import it:
+
+```python
+>>> import random
+
+# Choose random integer from a range
+>>> random.randrange(1000)
+360
+
+>>> random.randrange(-1, 500)
+228
+
+>>> random.randrange(-10, 11, 2)
+-8
+
+# Choose random integer between two values (inclusive)
+>>> random.randint(5, 25)
+22
+
+```
+
+To avoid typing the name of the module, you can import specific functions by name:
+
+```python
+>>> from random import choice, choices
+
+# Using choice() to pick Heads or Tails 10 times
+>>> tosses = []
+>>> for side in range(10):
+>>> tosses.append(choice(['H', 'T']))
+
+>>> print(tosses)
+['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H']
+
+
+# Using choices() to pick Heads or Tails 8 times
+>>> picks = []
+>>> picks.extend(choices(['H', 'T'], k=8))
+>>> print(picks)
+['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T']
+```
+
+
+## Creating random integers
+
+The `randrange()` function has three forms, to select a random value from `range(start, stop, step)`:
+ 1. `randrange(stop)` gives an integer `n` such that `0 <= n < stop`
+ 2. `randrange(start, stop)` gives an integer `n` such that `start <= n < stop`
+ 3. `randrange(start, stop, step)` gives an integer `n` such that `start <= n < stop` and `n` is in the sequence `start, start + step, start + 2*step...`
+
+For the common case where `step == 1`, the `randint(a, b)` function may be more convenient and readable.
+Possible results from `randint()` _include_ the upper bound, so `randint(a, b)` is the same as using `randrange(a, b+1)`:
+
+```python
+>>> import random
+
+# Select one number at random from the range 0, 499
+>>> random.randrange(500)
+219
+
+# Select 10 numbers at random between 0 and 9 two steps apart.
+>>> numbers = []
+>>> for integer in range(10):
+>>> numbers.append(random.randrange(0, 10, 2))
+>>> print(numbers)
+[2, 8, 4, 0, 4, 2, 6, 6, 8, 8]
+
+# roll a die
+>>> random.randint(1, 6)
+4
+```
+
+
+
+## Working with sequences
+
+The functions in this section assume that you are starting from some [sequence][sequence-types], or other container.
+
+
+This will typically be a `list`, or with some limitations a `tuple` or a `set` (_a `tuple` is immutable, and `set` is unordered_).
+
+
+
+### `choice()` and `choices()`
+
+The `choice()` function will return one entry chosen at random from a given sequence.
+At its simplest, this might be a coin-flip:
+
+```python
+# This will pick one of the two values in the list at random 5 separate times
+>>> [random.choice(['H', 'T']) for _ in range(5)]
+['T', 'H', 'H', 'T', 'H']
+
+We could accomplish essentially the same thing using the `choices()` function, supplying a keyword argument with the list length:
+
+
+```python
+>>> random.choices(['H', 'T'], k=5)
+['T', 'H', 'T', 'H', 'H']
+```
+
+
+In the examples above, we assumed a fair coin with equal probability of heads or tails, but weights can also be specified.
+For example, if a bag contains 10 red balls and 15 green balls, and we would like to pull one out at random:
+
+```python
+>>> random.choices(['red', 'green'], [10, 15])
+['red']
+```
+
+
+
+### `sample()`
+
+The `choices()` example above assumes what statisticians call ["sampling with replacement"][sampling-with-replacement].
+Each pick or choice has **no effect** on the probability of future choices, and the distribution of potential choices remains the same from pick to pick.
+
+
+In the example with red and green balls: after each choice, we _return_ the ball to the bag and shake well before the next pick.
+This is in contrast to a situation where we pull out a red ball and _it stays out_.
+Not returning the ball means there are now fewer red balls in the bag, and the next choice is now _less likely_ to be red.
+
+To simulate this "sampling without replacement", the random module provides the `sample()` function.
+The syntax of `sample()` is similar to `choices()`, except it adds a `counts` keyword parameter:
+
+
+```python
+>>> random.sample(['red', 'green'], counts=[10, 15], k=10)
+['green', 'green', 'green', 'green', 'green', 'red', 'red', 'red', 'red', 'green']
+```
+
+Samples are returned in the order they were chosen.
+
+
+
+### `shuffle()`
+
+Both `choices()` and `sample()` return new lists when `k > 1`.
+In contrast, `shuffle()` randomizes the order of a list _**in place**_, and the original ordering is lost:
+
+```python
+>>> my_list = [1, 2, 3, 4, 5]
+>>> random.shuffle(my_list)
+>>> my_list
+[4, 1, 5, 2, 3]
+```
+
+
+## Working with Distributions
+
+Until now, we have concentrated on cases where all outcomes are equally likely.
+For example, `random.randrange(100)` is equally likely to give any integer from 0 to 99.
+
+Many real-world situations are far less simple than this.
+As a result, statisticians have created a wide variety of [`distributions`][probability-distribution] to describe "real world" results mathematically.
+
+
+
+### Uniform distributions
+
+For integers, `randrange()` and `randint()` are used when all probabilities are equal.
+This is called a [`uniform`][uniform-distribution] distribution.
+
+
+There are floating-point equivalents to `randrange()` and `randint()`.
+
+__`random()`__ gives a `float` value `x` such that `0.0 <= x < 1.0`.
+
+__`uniform(a, b)`__ gives `x` such that `a <= x <= b`.
+
+```python
+>>> [round(random.random(), 3) for _ in range(5)]
+[0.876, 0.084, 0.483, 0.22, 0.863]
+
+>>> [round(random.uniform(2, 5), 3) for _ in range(5)]
+[2.798, 2.539, 3.779, 3.363, 4.33]
+```
+
+
+
+### Gaussian distribution
+
+Also called the "normal" distribution or the "bell-shaped" curve, this is a very common way to describe imprecision in measured values.
+
+For example, suppose the factory where you work has just bought 10,000 bolts which should be identical.
+You want to set up the factory robot to handle them, so you weigh a sample of 100 and find that they have an average (or `mean`) weight of 4.731g.
+This is extremely unlikely to mean that they all weigh exactly 4.731g.
+Perhaps you find that values range from 4.627 to 4.794g but cluster around 4.731g.
+
+This is the [`Gaussian distribution`][gaussian-distribution], for which probabilities peak at the mean and tails off symmetrically on both sides (hence "bell-shaped").
+To simulate this in software, we need some way to specify the width of the curve (_typically, expensive bolts will cluster more tightly around the mean than cheap bolts!_).
+
+By convention, this is done with the [`standard deviation`][standard-deviation]: small values for a sharp, narrow curve, large for a low, broad curve.
+Mathematicians love Greek letters, so we use `μ` ('mu') to represent the mean and `σ` ('sigma') to represent the standard deviation.
+Thus, if you read that "95% of values are within 2σ of μ" or "the Higgs boson has been detected with 5-sigma confidence", such comments relate to the standard deviation.
+
+```python
+>>> mu = 4.731
+>>> sigma = 0.316
+>>> [round(random.gauss(mu, sigma), 3) for _ in range(5)]
+[4.72, 4.957, 4.64, 4.556, 4.968]
+```
+
+[gaussian-distribution]: https://simple.wikipedia.org/wiki/Normal_distribution
+[probability-distribution]: https://simple.wikipedia.org/wiki/Probability_distribution
+[random]: https://docs.python.org/3/library/random.html
+[sampling-with-replacement]: https://www.youtube.com/watch?v=LnGFL_A6A6A
+[sequence-types]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range
+[standard-deviation]: https://simple.wikipedia.org/wiki/Standard_deviation
+[truly-random]: https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random
+[uniform-distribution]: https://www.investopedia.com/terms/u/uniform-distribution.asp#:~:text=In%20statistics%2C%20uniform%20distribution%20refers,a%20spade%20is%20equally%20likely.
diff --git a/concepts/random/introduction.md b/concepts/random/introduction.md
new file mode 100644
index 00000000000..6bf880be57f
--- /dev/null
+++ b/concepts/random/introduction.md
@@ -0,0 +1,108 @@
+# Introduction
+
+Many programs need (apparently) random values to simulate real-world events.
+
+Common, familiar examples include:
+- A coin toss: a random value from `('H', 'T')`.
+- The roll of a die: a random integer from 1 to 6.
+- Shuffling a deck of cards: a random ordering of a card list.
+- The creation of trees and bushes in a 3-D graphics simulation.
+
+Generating _truly_ random values with a computer is a [surprisingly difficult technical challenge][truly-random], so you may see these results referred to as "pseudorandom".
+
+In practice, a well-designed library like the [`random`][random] module in the Python standard library is fast, flexible, and gives results that are amply good enough for most applications in modelling, simulation and games.
+
+For this brief introduction, we show the four most commonly used functions from the module.
+We encourage you to explore the full [`random`][random] documentation, as there are many tools and options.
+
+
+~~~~exercism/caution
+
+The `random` module should __NOT__ be used for security and cryptographic applications!!
+
+Instead, Python provides the [`secrets`][secrets] module.
+This is specially optimized for cryptographic security.
+Some of the prior issues and reasons for creating the secrets module can be found in [PEP 506][PEP 506].
+
+[secrets]: https://docs.python.org/3.11/library/secrets.html#module-secrets
+[PEP 506]: https://peps.python.org/pep-0506/
+~~~~
+
+
+Before you can utilize the tools in the `random` module, you must first import it:
+
+```python
+>>> import random
+
+# Choose random integer from a range
+>>> random.randrange(1000)
+360
+
+>>> random.randrange(-1, 500)
+228
+
+>>> random.randrange(-10, 11, 2)
+-8
+
+# Choose random integer between two values (inclusive)
+>>> random.randint(5, 25)
+22
+
+```
+
+To avoid typing the name of the module, you can import specific functions by name:
+
+```python
+>>> from random import choice, choices
+
+# Using choice() to pick Heads or Tails 10 times
+>>> tosses = []
+>>> for side in range(10):
+>>> tosses.append(choice(['H', 'T']))
+
+>>> print(tosses)
+['H', 'H', 'H', 'H', 'H', 'H', 'H', 'T', 'T', 'H']
+
+
+# Using choices() to pick Heads or Tails 8 times
+>>> picks = []
+>>> picks.extend(choices(['H', 'T'], k=8))
+>>> print(picks)
+['T', 'H', 'H', 'T', 'H', 'H', 'T', 'T']
+```
+
+
+
+## `randrange()` and `randint()`
+
+Shown in the first example above, the `randrange()` function has three forms:
+
+1. `randrange(stop)` gives an integer `n` such that `0 <= n < stop`
+2. `randrange(start, stop)` gives an integer `n` such that `start <= n < stop`
+3. `randrange(start, stop, step)` gives an integer `n` such that `start <= n < stop`
+ and `n` is in the sequence `start, start + step, start + 2*step...`
+
+For the most common case where `step == 1`, `randint(a, b)` may be more convenient and readable.
+Possible results from `randint()` _include_ the upper bound, so `randint(a, b)` is the same as using `randrange(a, b+1)`.
+
+
+
+## `choice()` and `choices()`
+
+These two functions assume that you are starting from some [sequence][sequence-types], or other container.
+This will typically be a `list`, or with some limitations a `tuple` or a `set` (_a `tuple` is immutable, and `set` is unordered_).
+
+The `choice()` function will return one entry chosen at random from a given sequence, and `choices()` will return `k` number of entries chosen at random from a given sequence.
+In the examples shown above, we assumed a fair coin with equal probability of heads or tails, but weights can also be specified.
+
+For example, if a bag contains 10 red balls and 15 green balls, and we would like to pull one out at random:
+
+
+```python
+>>> random.choices(['red', 'green'], [10, 15])
+['red']
+```
+
+[random]: https://docs.python.org/3/library/random.html
+[sequence-types]: https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range
+[truly-random]: https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random
diff --git a/concepts/random/links.json b/concepts/random/links.json
new file mode 100644
index 00000000000..22f60dbfb46
--- /dev/null
+++ b/concepts/random/links.json
@@ -0,0 +1,14 @@
+[
+ {
+ "url": "https://docs.python.org/3/library/random.html/",
+ "description": "Official documentation for the random module."
+ },
+ {
+ "url": "https://engineering.mit.edu/engage/ask-an-engineer/can-a-computer-generate-a-truly-random-number/",
+ "description": "MIT Engineering: Can a computer generate a truly random number?"
+ },
+ {
+ "url": "https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random",
+ "description": "Are Random Numbers Really Random?"
+ }
+]
diff --git a/concepts/recursion/.meta/config.json b/concepts/recursion/.meta/config.json
new file mode 100644
index 00000000000..131a285bb22
--- /dev/null
+++ b/concepts/recursion/.meta/config.json
@@ -0,0 +1,7 @@
+{
+ "blurb": "Recursion repeats code in a function by having the function call itself.",
+ "authors": [
+ "bobahop"
+ ],
+ "contributors": ["bethanyg"]
+}
diff --git a/concepts/recursion/about.md b/concepts/recursion/about.md
new file mode 100644
index 00000000000..1cf24388269
--- /dev/null
+++ b/concepts/recursion/about.md
@@ -0,0 +1,194 @@
+# About
+
+Recursion is a way to repeatedly execute code inside a function through the function calling itself.
+Functions that call themselves are know as _recursive_ functions.
+Recursion can be viewed as another way to loop/iterate.
+And like looping, a Boolean expression or `True/False` test is used to know when to stop the recursive execution.
+_Unlike_ looping, recursion without termination in Python cannot not run infinitely.
+Values used in each function call are placed in their own frame on the Python interpreter stack.
+If the total number of function calls takes up more space than the stack has room for, it will result in an error.
+
+## Looping vs Recursive Implementation
+
+Looping and recursion may _feel_ similar in that they are both iterative.
+However, they _look_ different, both at the code level and at the implementation level.
+Looping can take place within the same frame on the call stack.
+This is usually managed by updating one or more variable values to progressively maintain state for each iteration.
+This is an efficient implementation, but it can be somewhat cluttered when looking at the code.
+
+Recursion, rather than updating _variable state_, can pass _updated values_ directly as arguments to the next call (iteration) of the same function.
+This declutters the body of the function and can clarify how each update happens.
+However, it is also a less efficient implementation, as each call to the same function adds another frame to the stack.
+
+## Recursion: Why and Why Not?
+
+If there is risk of causing a stack error or overflow, why would anyone use a recursive strategy to solve a problem?
+_Readability, traceability, and intent._
+There may be situations where a solution is more readable and/or easier to reason through when expressed through recursion than when expressed through looping.
+There may also be program constraints with using/mutating data, managing complexity, delegating responsibility, or organizing workloads.
+Problems that lend themselves to recursion include complex but repetitive problems that grow smaller over time, particularly [divide and conquer][divide and conquer] algorithms and [cumulative][cumulative] algorithms.
+However, due to Python's limit for how many frames are allowed on the stack, not all problems will benefit from a fully recursive strategy.
+Problems less naturally suited to recursion include ones that have a steady state, but need to repeat for a certain number of cycles, problems that need to execute asynchronously, and situations calling for a great number of iterations.
+
+## Looping vs Recursive Strategy: Indira's Insecurity
+
+Indira has her monthly social security auto-deposited in her bank account on the **_second Wednesday_** of every month.
+Indira is concerned about balancing her check book.
+She is afraid she will write checks before her money is deposited.
+She asks her granddaughter Adya to give her a list of dates her money will appear in her account.
+
+Adya, who is just learning how to program in Python, writes a program based on her first thoughts.
+She wants to return a `list` of the deposit dates so they can be printed.
+She wants to write a function that will work for _any year_.
+In case the schedule changes (_or in case other relatives want Adya to calculate their deposit schedules_), she decides the function needs to take an additional parameter for the _weekday_.
+Finally, Adya decides that the function needs a parameter for _which weekday_ of the month it is: the first, second, etc.
+For all these requirements, she decides to use the `date` class imported from `datetime`.
+Putting all of that together, Adya comes up with:
+
+```
+from datetime import date
+
+
+def paydates_for_year(year, weekday, ordinal):
+ """Returns a list of the matching weekday dates.
+
+ Keyword arguments:
+ year -- the year, e.g. 2022
+ weekday -- the weekday, e.g. 3 (for Wednesday)
+ ordinal -- which weekday of the month, e.g. 2 (for the second)
+ """
+ output = []
+
+ for month in range(1, 13):
+ for day_num in range(1, 8):
+ if date(year, month, day_num).isoweekday() == weekday:
+ output.append(date(year, month, day_num + (ordinal - 1) * 7))
+ break
+ return output
+
+# find the second Wednesday of the month for all the months in 2022
+print(paydates_for_year(2022, 3, 2))
+```
+
+This first iteration works, but Adya wonders if she can refactor the code to use fewer lines with less nested looping.
+She's also read that it is good to minimize mutating state, so she'd like to see if she can avoid mutating some of her variables such as `output`, `month`, and `day_num` .
+
+She's read about recursion, and thinks about how she might change her program to use a recursive approach.
+The variables that are created and mutated in her looping function could be passed in as arguments instead.
+Rather than mutating the variables _inside_ her function, she could pass _updated values as arguments_ to the next function call.
+With those intentions she arrives at this recursive approach:
+
+```
+from datetime import date
+
+
+
+def paydates_for_year_rec(year, weekday, ordinal, month, day_num, output):
+ """Returns a list of the matching weekday dates
+
+ Keyword arguments:
+ year -- the year, e.g. 2022
+ weekday -- the weekday, e.g. 3 (for Wednesday)
+ ordinal -- which weekday of the month, e.g. 2 (for the second)
+ month -- the month currently being processed
+ day_num -- the day of the month currently being processed
+ output -- the list to be returned
+ """
+ if month == 13:
+ return output
+ if date(year, month, day_num).isoweekday() == weekday:
+ return paydates_for_year_rec(year, weekday, ordinal, month + 1, 1, output
+ + [date(year, month, day_num + (ordinal - 1) * 7)])
+ return paydates_for_year_rec(year, weekday, ordinal, month, day_num + 1, output)
+
+ # find the second Wednesday of the month for all the months in 2022
+ print(paydates_for_year_rec(2022, 3, 2, 1, 1, []))
+
+```
+
+Adya is happy that there are no more nested loops, no mutated state, and 2 fewer lines of code!
+
+She is a little concerned that the recursive approach uses more steps than the looping approach, and so is less "performant".
+But re-writing the problem using recursion has definitely helped her deal with ugly nested looping (_a performance hazard_), extensive state mutation, and confusion around complex conditional logic.
+It also feels more "readable" - she is sure that when she comes back to this code after a break, she will be able to read through and remember what it does more easily.
+
+In the future, Adya may try to work through problems recursively first.
+She may find it easier to initially walk through the problem in clear steps when nesting, mutation, and complexity are minimized.
+After working out the basic logic, she can then focus on optimizing her initial recursive steps into a more performant looping approach.
+
+Even later, when she learns about `tuples`, Adya could consider further "optimizing" approaches, such as using a `list comprehension` with `Calendar.itermonthdates`, or memoizing certain values.
+
+## Recursive Variation: The Tail Call
+
+A tail call is when the last statement of a function only calls itself and nothing more.
+This example is not a tail call, as the function adds 1 to the result of calling itself
+
+```python
+def print_increment(step, max_value):
+ if step > max_value:
+ return 1
+ print(f'The step is {step}')
+ return 1 + print_increment(step + 1, max_value)
+
+
+def main():
+ retval = print_increment(1, 2)
+ print(f'retval is {retval} after recursion')
+
+if __name__ == "__main__":
+ main()
+
+```
+
+This will print
+
+```
+The step is 1
+The step is 2
+retval is 3 after recursion
+```
+
+To refactor it to a tail call, make `retval` a parameter of `print_increment`
+
+```python
+def print_increment(step, max_value, retval):
+ if step > max_value:
+ return retval
+ print(f'The step is {step}')
+ return print_increment(step + 1, max_value, retval + 1)
+
+
+def main():
+ retval = print_increment(1, 2, 1)
+ print(f'retval is {retval} after recursion')
+
+if __name__ == "__main__":
+ main()
+
+```
+
+You may find a tail call even easier to reason through than a recursive call that is not a tail call.
+However, it is always important when using recursion to know that there will not be so many iterations that the stack will overflow.
+
+## Recursion Limits in Python
+
+Some languages are able to optimize tail calls so that each recursive call reuses the stack frame of the first call to the function (_similar to the way a loop reuses a frame_), instead of adding an additional frame to the stack.
+Python is not one of those languages.
+To guard against stack overflow, Python has a recursion limit that defaults to one thousand frames.
+A [RecursionError](https://docs.python.org/3.8/library/exceptions.html#RecursionError) exception is raised when the interpreter detects that the recursion limit has been exceeded.
+It is possible to use the [sys.setrecursionlimit](https://docs.python.org/3.8/library/sys.html#sys.setrecursionlimit) method to increase the recursion limit, but doing so runs the risk of having a runtime segmentation fault that will crash the program, and possibly the operating system.
+
+## Resources
+
+To learn more about using recursion in Python you can start with
+- [python-programming: recursion][python-programming: recursion]
+- [Real Python: python-recursion][Real Python: python-recursion]
+- [Real Python: python-thinking-recursively][Real Python: python-thinking-recursively]
+
+[python-programming: recursion]: https://www.programiz.com/python-programming/recursion
+[Real Python: python-recursion]: https://realpython.com/python-recursion/
+[Real Python: python-thinking-recursively]: https://realpython.com/python-thinking-recursively/
+[RecursionError]: https://docs.python.org/3.8/library/exceptions.html#RecursionError
+[setrecursionlimit]: https://docs.python.org/3.8/library/sys.html#sys.setrecursionlimit
+[divide and conquer]: https://afteracademy.com/blog/divide-and-conquer-approach-in-programming
+[cumulative]: https://www.geeksforgeeks.org/sum-of-natural-numbers-using-recursion/
diff --git a/concepts/recursion/introduction.md b/concepts/recursion/introduction.md
new file mode 100644
index 00000000000..fb7e1970705
--- /dev/null
+++ b/concepts/recursion/introduction.md
@@ -0,0 +1,35 @@
+# Introduction
+
+Recursion is a way to repeat code in a function by the function calling itself.
+It can be viewed as another way to loop/iterate.
+Like looping, a Boolean expression or `True/False` test is used to know when to stop the recursive execution.
+_Unlike_ looping, recursion without termination in Python cannot not run infinitely.
+Values used in each function call are placed in their own frame on the Python interpreter stack.
+If the total number of function calls takes up more space than the stack has room for, it will result in an error.
+
+```python
+def print_increment(step, max_value):
+ if step > max_value:
+ return
+ print(f'The step is {step}')
+ print_increment(step + 1, max_value)
+
+
+def main():
+ print_increment(1, 2)
+ print("After recursion")
+
+if __name__ == "__main__":
+ main()
+
+```
+
+This will print
+
+```
+The step is 1
+The step is 2
+After recursion
+```
+
+There may be some situations that are more readable and/or easier to reason through when expressed through recursion than when expressed through looping.
diff --git a/concepts/recursion/links.json b/concepts/recursion/links.json
new file mode 100644
index 00000000000..02ba406996b
--- /dev/null
+++ b/concepts/recursion/links.json
@@ -0,0 +1,34 @@
+[
+ {
+ "url": "https://hackernoon.com/recursion-vs-looping-in-python-9261442f70a5",
+ "description": "Hackernoon: Recursion vs Looping in Python"
+ },
+ {
+ "url": "https://towardsdatascience.com/a-friendly-guide-for-writing-recursive-functions-with-python-52441bd7ff5f",
+ "description": "Towards Data Science: A Friendly Guide to Writing Recursive Functions"
+ },
+ {
+ "url": "https://stackabuse.com/understanding-recursive-functions-with-python/",
+ "description": "Stack Abuse: Understanding Recursive Functions"
+ },
+ {
+ "url": "https://inventwithpython.com/blog/2022/03/20/how-many-recursive-cases-and-base-cases-does-a-recursive-function-need/",
+ "description": "Al Sweigart: How Many Recursive Cases and Base Cases Does a Recursive Function Need?"
+ },
+ {
+ "url": "https://realpython.com/python-thinking-recursively/",
+ "description": "Real Python: Thinking Recursively"
+ },
+ {
+ "url": "https://inventwithpython.com/blog/2018/08/24/pyohio-2018-recursion-tutorial/",
+ "description": "Al Sweigart: PyOhio 2018 Tutorial on Recursion."
+ },
+ {
+ "url": "https://inventwithpython.com/recursion/",
+ "description": "Invent With Python: The Recursive Book of Recursion"
+ },
+ {
+ "url": "https://understanding-recursion.readthedocs.io/en/latest/index.html",
+ "description": "Understanding Recursion Using Python"
+ }
+]
diff --git a/concepts/regular-expressions/.meta/config.json b/concepts/regular-expressions/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/regular-expressions/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/regular-expressions/about.md b/concepts/regular-expressions/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/regular-expressions/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/regular-expressions/introduction.md b/concepts/regular-expressions/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/regular-expressions/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/regular-expressions/links.json b/concepts/regular-expressions/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/regular-expressions/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/rich-comparisons/.meta/config.json b/concepts/rich-comparisons/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/rich-comparisons/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/rich-comparisons/about.md b/concepts/rich-comparisons/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/rich-comparisons/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/rich-comparisons/introduction.md b/concepts/rich-comparisons/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/rich-comparisons/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/rich-comparisons/links.json b/concepts/rich-comparisons/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/rich-comparisons/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/secrets/.meta/config.json b/concepts/secrets/.meta/config.json
new file mode 100644
index 00000000000..152aa0eb3ba
--- /dev/null
+++ b/concepts/secrets/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "The secrets module is a cryptographically-secure alternative to the random module, intended for security-critical uses.",
+ "authors": ["BethanyG", "colinleach"],
+ "contributors": []
+}
diff --git a/concepts/secrets/about.md b/concepts/secrets/about.md
new file mode 100644
index 00000000000..5987ab37e91
--- /dev/null
+++ b/concepts/secrets/about.md
@@ -0,0 +1,51 @@
+# About
+
+A previous concept discussed the [concept:python/random]() module, which produces [pseudo-random numbers][pseudo-random-numbers] and pseudo-random list orderings.
+
+The [`secrets`][secrets] module overlaps with `random` in some of its functionality, but the two modules are designed with very different priorities.
+
+- `random` is optimized for high performance in modelling and simulation, with "good enough" pseudo-random number generation.
+- `secrets` is designed to be crytographically secure for applications such as password hashing, security token generation, and account authentication.
+
+
+Further details on why the addition of the `secrets` module proved necessary are given in [PEP 506][PEP506].
+
+The `secrets` is relatively small and straightforward, with methods for generating random integers, bits, bytes or tokens, or a random entry from a given sequence.
+
+To use `scerets`, you mush first `import` it:
+
+
+```python
+>>> import secrets
+
+#Returns n, where 0 <= n < 1000.
+>>> secrets.randbelow(1000)
+577
+
+#32-bit integers.
+>>> secrets.randbits(32)
+3028709440
+
+>>> bin(secrets.randbits(32))
+'0b11111000101100101111110011110100'
+
+#Pick at random from a sequence.
+>>> secrets.choice(['my', 'secret', 'thing'])
+'thing'
+
+#Generate a token made up of random hexadecimal digits.
+>>> secrets.token_hex()
+'f683d093ea9aa1f2607497c837cf11d7afaefa903c5805f94b64f068e2b9e621'
+
+#Generate a URL-safe token of random alphanumeric characters.
+>>> secrets.token_urlsafe(16)
+'gkSUKRdiPDHqmImPi2HMnw'
+```
+
+
+If you are writing security-sensitive applications, you will certainly want to read the [full documentation][secrets], which gives further advice and examples.
+
+
+[PEP506]: https://peps.python.org/pep-0506/
+[pseudo-random-numbers]: https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators
+[secrets]: https://docs.python.org/3/library/secrets.html
diff --git a/concepts/secrets/introduction.md b/concepts/secrets/introduction.md
new file mode 100644
index 00000000000..04308ed0a2a
--- /dev/null
+++ b/concepts/secrets/introduction.md
@@ -0,0 +1,17 @@
+# Introduction
+
+A previous concept discussed the [concept:python/random]() module, which produces [pseudo-random numbers][pseudo-random-numbers] and pseudo-random list orderings.
+
+The [`secrets`][secrets] module overlaps with `random` in some of its functionality, but the two modules are designed with very different priorities.
+
+- `random` is optimized for high performance in modelling and simulation, with "good enough" pseudo-random number generation.
+- `secrets` is designed to be crytographically secure for applications such as password hashing, security token generation, and account authentication.
+
+
+Further details on why the addition of the `secrets` module proved necessary are given in [PEP 506][PEP506].
+
+If you are writing security-sensitive applications, you will certainly want to read the [full documentation][secrets], which gives further advice and examples.
+
+[PEP506]: https://peps.python.org/pep-0506/
+[pseudo-random-numbers]: https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators
+[secrets]: https://docs.python.org/3/library/secrets.html
diff --git a/concepts/secrets/links.json b/concepts/secrets/links.json
new file mode 100644
index 00000000000..ccf44e6fe59
--- /dev/null
+++ b/concepts/secrets/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "https://docs.python.org/3/library/secrets.html/",
+ "description": "The secrets module."
+ },
+ {
+ "url": "https://peps.python.org/pep-0506/",
+ "description": "PEP 506, giving reasons why the secrets module is necessary."
+ },
+ {
+ "url": "https://en.wikipedia.org/wiki/Pseudorandomness",
+ "description": "Wikipedia: Pseudorandomness."
+ },
+ {
+ "url": "https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/random-vs-pseudorandom-number-generators",
+ "description": "Khan Academy: Pseudorandom Number Generators."
+ }
+]
diff --git a/concepts/sequences/.meta/config.json b/concepts/sequences/.meta/config.json
new file mode 100644
index 00000000000..9b9e8da5a9b
--- /dev/null
+++ b/concepts/sequences/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "TODO: add blurb for this concept",
+ "authors": ["bethanyg", "cmccandless"],
+ "contributors": []
+}
diff --git a/concepts/sequences/about.md b/concepts/sequences/about.md
new file mode 100644
index 00000000000..c628150d565
--- /dev/null
+++ b/concepts/sequences/about.md
@@ -0,0 +1,2 @@
+#TODO: Add about for this concept.
+
diff --git a/concepts/sequences/introduction.md b/concepts/sequences/introduction.md
new file mode 100644
index 00000000000..fcde74642ca
--- /dev/null
+++ b/concepts/sequences/introduction.md
@@ -0,0 +1,2 @@
+#TODO: Add introduction for this concept.
+
diff --git a/concepts/sequences/links.json b/concepts/sequences/links.json
new file mode 100644
index 00000000000..eb5fb7c38a5
--- /dev/null
+++ b/concepts/sequences/links.json
@@ -0,0 +1,18 @@
+[
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ },
+ {
+ "url": "http://example.com/",
+ "description": "TODO: add new link (above) and write a short description here of the resource."
+ }
+]
diff --git a/concepts/sets/.meta/config.json b/concepts/sets/.meta/config.json
new file mode 100644
index 00000000000..7e4b5b00cc3
--- /dev/null
+++ b/concepts/sets/.meta/config.json
@@ -0,0 +1,5 @@
+{
+ "blurb": "A set is a mutable & unordered collection of hashable objects. Members must be unique, and sets are often used to quickly dedupe groups of items. They can be iterated over via \"for item in \" or \"for index, item in enumerate()\", but do not support sequence-type behaviors like indexing, slicing, or sorting.",
+ "authors": ["bethanyg"],
+ "contributors": []
+}
\ No newline at end of file
diff --git a/concepts/sets/about.md b/concepts/sets/about.md
new file mode 100644
index 00000000000..204df380577
--- /dev/null
+++ b/concepts/sets/about.md
@@ -0,0 +1,485 @@
+# Sets
+
+A [`set`][type-set] is a _mutable_ and _unordered_ collection of [_hashable_][hashable] objects.
+Set members must be distinct — duplicate items are not allowed.
+They can hold multiple different data types and even nested structures like a `tuple` of `tuples` — as long as all elements can be _hashed_.
+Sets also come in an immutable [`frozensets`][type-frozenset] flavor.
+
+Sets are most commonly used to quickly remove duplicates from other data structures or item groupings.
+They are also used for efficient comparisons when sequencing and duplicate tracking are not needed.
+
+Like other collection types (_dictionaries, lists, tuples_), `sets` support:
+- Iteration via `for item in `
+- Membership checking via `in` and `not in`,
+- Length calculation through `len()`, and
+- Shallow copies through `copy()`
+
+`sets` do not support:
+- Indexing of any kind
+- Ordering via sorting or insertion
+- Slicing
+- Concatenation via `+`
+
+
+Checking membership in a `set` has constant time complexity (on average) versus checking membership in a `list` or `string`, where the time complexity grows as the length of the data increases.
+Methods such as `.union()`, `.intersection()`, or `.difference()` also have constant time complexity (on average).
+
+
+## Set Construction
+
+While sets can be created in many different ways, the most straightforward construction methods are declaring a _set literal_, using the `set` class constructor (`set()`), and using a _set comprehension_.
+
+### Set Literals
+
+A `set` can be directly entered as a _set literal_ with curly `{}` brackets and commas between elements.
+Duplicates are silently omitted:
+
+
+```python
+>>> one_element = {'➕'}
+{'➕'}
+
+>>> multiple_elements = {'➕', '🔻', '🔹', '🔆'}
+{'➕', '🔻', '🔹', '🔆'}
+
+>>> multiple_duplicates = {'Hello!', 'Hello!', 'Hello!',
+ '¡Hola!','Привіт!', 'こんにちは!',
+ '¡Hola!','Привіт!', 'こんにちは!'}
+{'こんにちは!', '¡Hola!', 'Hello!', 'Привіт!'}
+```
+
+Set literals use the same curly braces as `dict` literals, which means you need to use `set()` to create an empty `set`.
+
+### The Set Constructor
+
+`set()` (_the constructor for the `set` class_) can be used with any `iterable` passed as an argument.
+Elements of the `iterable` are cycled through and added to the `set` individually.
+Element order is not preserved and duplicates are silently omitted:
+
+
+```python
+# To create an empty set, the constructor must be used.
+>>> no_elements = set()
+set()
+
+# The tuple is unpacked & each element is added.
+# Duplicates are removed.
+>>> elements_from_tuple = set(("Parrot", "Bird",
+ 334782, "Bird", "Parrot"))
+{334782, 'Bird', 'Parrot'}
+
+# The list is unpacked & each element is added.
+# Duplicates are removed.
+>>> elements_from_list = set([2, 3, 2, 3, 3, 3, 5,
+ 7, 11, 7, 11, 13, 13])
+{2, 3, 5, 7, 11, 13}
+```
+
+### Set Comprehensions
+
+Like `lists` and `dicts`, sets can be created via _comprehension_:
+
+```python
+# First, a list with duplicates
+>>> numbers = [1,2,3,4,5,6,6,5,4,8,9,9,9,2,3,12,18]
+
+# This set comprehension squares the numbers divisible by 3
+# Duplicates are removed.
+>>> calculated = {item**2 for item in numbers if item % 3 == 0}
+{9, 36, 81, 144, 324}
+```
+
+### Gotchas when Creating Sets
+
+Due to its "unpacking" behavior, using the `set` constructor with a string might be surprising:
+
+```python
+# String elements (Unicode code points) are
+# iterated through and added *individually*.
+>>> elements_string = set("Timbuktu")
+{'T', 'b', 'i', 'k', 'm', 't', 'u'}
+
+# Unicode separators and positioning code points
+# are also added *individually*.
+>>> multiple_code_points_string = set('अभ्यास')
+{'अ', 'भ', 'य', 'स', 'ा', '्'}
+```
+
+Remember: sets can hold different datatypes and _nested_ datatypes, but all `set` elements must be _hashable_:
+
+```python
+# Attempting to use a list for a set member throws a TypeError
+>>> lists_as_elements = {['🌈','💦'],
+ ['☁️','⭐️','🌍'],
+ ['⛵️', '🚲', '🚀']}
+
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: unhashable type: 'list'
+
+
+# Standard sets are mutable, so they cannot be hashed.
+>>> sets_as_elements = {{'🌈','💦'},
+ {'☁️','⭐️','🌍'},
+ {'⛵️', '🚲', '🚀'}}
+
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: unhashable type: 'set'
+```
+
+However, a `set` of `sets` can be created via type `frozenset()`:
+
+```python
+# Frozensets don't have a literal form.
+>>> set_1 = frozenset({'🌈','💦'})
+>>> set_2 = frozenset({'☁️','⭐️','🌍'})
+>>> set_3 = frozenset({'⛵️', '🚲', '🚀'})
+
+>>> frozen_sets_as_elements = {set_1, set_2, set_3}
+>>> frozen_sets_as_elements
+{frozenset({'⛵️', '🚀', '🚲'}),
+ frozenset({'🌈', '💦'}),
+ frozenset({'☁️', '⭐️', '🌍'})}
+```
+
+
+## Adding and Removing Set Members
+
+Elements can be added or removed from a `set` using the methods `.add(- )` and `
.remove(- )`.
+The `.remove(
- )` method will raise a `KeyError` if the item is not present in the `set`:
+
+```python
+>>> creatures = {'crow', 'duck', 'fish', 'monkey', 'elephant'}
+>>> creatures.add('beaver')
+>>> creatures.remove('duck')
+>>> creatures
+{'beaver', 'crow', 'elephant', 'fish', 'monkey'}
+
+# Trying to remove an item that is not present raises a KeyError
+>>> creatures.remove('bear')
+Traceback (most recent call last):
+ File "
", line 1, in
+ KeyError: 'bear'
+```
+
+### Additional Strategies for Removing Set Members
+
+- `.discard(- )` will remove an item from the `set`, but will **not** raise a `KeyError` if the item is not present.
+- `