diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index ed8f4a432bc..234b07e766c 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,4 +6,4 @@ updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
- interval: 'daily'
+ interval: 'monthly'
diff --git a/.github/labels.yml b/.github/labels.yml
index c76eadfd19a..3f8780530db 100644
--- a/.github/labels.yml
+++ b/.github/labels.yml
@@ -157,16 +157,16 @@
description: "Work on Documentation"
color: "ffffff"
-# This label can be added to accept PRs as part of Hacktoberfest
-- name: "hacktoberfest-accepted"
- description: "Make this PR count for hacktoberfest"
- color: "ff7518"
-
# 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. #
diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml
index fe8ff089a67..e853469c6d0 100644
--- a/.github/workflows/ci-workflow.yml
+++ b/.github/workflows/ci-workflow.yml
@@ -12,14 +12,14 @@ on:
jobs:
housekeeping:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
with:
- python-version: 3.10.6
+ python-version: 3.11.2
- name: Download & Install dependencies
run: |
@@ -49,15 +49,15 @@ jobs:
./bin/template_status.py -v -p .problem-specifications
canonical_sync:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
needs: housekeeping
strategy:
matrix:
- python-version: [3.7, 3.8, 3.9, 3.10.6]
+ python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
with:
python-version: ${{ matrix.python-version }}
@@ -66,7 +66,7 @@ jobs:
run: pip install dataclasses
- name: Install pytest
- run: pip install pytest~=7.1.2
+ run: pip install pytest~=7.2.2
- name: Check exercises
run: |
diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml
index 7bddcd077c0..4f6bff60471 100644
--- a/.github/workflows/issue-commenter.yml
+++ b/.github/workflows/issue-commenter.yml
@@ -5,20 +5,20 @@ on:
jobs:
comment-on-new-issue:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
name: Comments for every NEW issue.
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
- name: Read issue-comment.md
id: issue-comment
- uses: juliangruber/read-file-action@v1
+ uses: juliangruber/read-file-action@b549046febe0fe86f8cb4f93c24e284433f9ab58
with:
path: .github/issue-comment.md
- name: Base comment
- uses: jd-0001/gh-action-comment-on-new-issue@v2.0.3
+ 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
index 8b895111279..f12714aec38 100644
--- a/.github/workflows/pr-commenter.yml
+++ b/.github/workflows/pr-commenter.yml
@@ -4,9 +4,9 @@ on:
jobs:
pr-comment:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- - uses: exercism/pr-commenter-action@v1.3.0
+ - 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
index 9e4ecd1d5c9..4a5a9a772f1 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -6,9 +6,9 @@ on:
jobs:
stale:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- - uses: actions/stale@v6
+ - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 21
diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml
index ecfc2a10fc8..97fcf6e5be3 100644
--- a/.github/workflows/test-runner.yml
+++ b/.github/workflows/test-runner.yml
@@ -8,8 +8,8 @@ on:
jobs:
test-runner:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3
- name: Run test-runner
- run: docker-compose run test-runner
+ run: docker compose run test-runner
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 9bb22baa715..3f7813de10a 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -2,17 +2,23 @@
## 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.
+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.
+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.
+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.
+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.
@@ -36,15 +42,16 @@ Exercism should be a safe place for everybody regardless of
- Race
- Age
- Religion
-- Anything else you can think of.
+- 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 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:
@@ -57,26 +64,30 @@ 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.
+- 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.
+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.
+- 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 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. Slack, Twitter, email) and any other Exercism entity or event._
+_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
index 816a44f6b95..d9c30d85e0a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -3,68 +3,74 @@
Contributing
-
-
-
-Hi. ππ½ π **We are happy you are here.** ππ
-
-Thank you so much for your interest in 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)
-**`exercsim/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.8`.
- π Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.9`.
+> [!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.
+>
+>
+>
-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.
-
+Hi. ππ½ π **We are happy you are here.** π π
-ππ If you have not already done so, 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], and [Pull Requests][prs].
+**`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.
-Some defined roles in our community: [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers] **|** [Admins][exercism-admins]
+π 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`.
-
-
-
-β¨ π¦ _**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] (_ β¨ versions available in [contributing][website-contributing-section] on [exercism(dot)org][exercism-website]._)
+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 (β ).
+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!_ ππ
-Please π [ Open an issue ][open-an-issue]π , and let us know what you have found/suggest.
+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?**
-_Before you get started, please review [Pull Requests][prs]._
+## π§ **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.
- π π **We Warmly Welcome Pull Requests that 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 (_and previously agreed-upon_) contributions from recent & regular (_within the last 6 months_) contributors.
+Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for additional details.
+
-When in doubt, π [ Open an issue ][open-an-issue]π . We will happily discuss your proposed change.
-π _But we should talk before you take a whole lot of time or energy implementing anything._
+We're leaving the track contributing docs below for our long-term collaborators and maintainers.
-
-In General
+
+ Python Track Contributing Docs
+
+
+In General
-- Please make sure to have a quick read-through of our Exercism [Pull Requests][prs] document before jumping in. π
- 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.
@@ -78,62 +84,47 @@ When in doubt, π [ Open an issue ][open-an-issue]π . We wil
- 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.
+- 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. Try to understand & fix any failures.
+- Continuous Integration is going to run **a lot** of checks. Pay attention to failures & try to understand and fix them.
+
β οΈ Pre-Commit Checklist β οΈ
-
-
-
- - [ ] 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.
-
- |
-
-
+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 and Standards
+
+
+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].
+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.
+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
+Coding Standards
-1. We follow [PEP8][PEP8] (["For Humans" version here][pep8-for-humans]).
+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.
@@ -153,7 +144,7 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer
- **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.
+- **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.
@@ -162,20 +153,20 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer
- 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.
+- 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)
+ 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.
+- 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].
@@ -184,8 +175,8 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer
- 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.
+- 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.
@@ -193,11 +184,10 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer
- 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()`.
+- 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.
+ - 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.
@@ -205,30 +195,28 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer
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 will motivate us to fix it! π
+_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! π
-Python Versions
+Language Versions
-This track officially supports Python = `3.8`
-The track `test runner`, `analyzer`, and `representer` run in docker on `python:3.9-slim`.
+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`.
-- All exercises should be written for compatibility with Python = `3.8` or `3.9`.
-- Version backward _incompatibility_ (*e.g* an exercise using a `3.8` or `3.9` **only** feature) should be clearly noted in any exercise hits, links, introductions or other notes.
+Although the majority of test cases are written using `unittest.TestCase`,
-- Here is an example of how the Python documentation handles [version-tagged π· ][version-tagged-language-features] feature introduction.
+- 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.
-- _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.
+- Here is an example of how the Python documentation handles [version-tagged π· ][version-tagged-language-features] feature introduction.
-- 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.
+- _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.
@@ -237,22 +225,19 @@ The track `test runner`, `analyzer`, and `representer` run in docker on `python:
-- 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 Command line Interface.
+- 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.
-- Each exercise/problem should include
- - a complete test suite,
- - an example/exemplar solution,
- - 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.
-- For specifications, refer to the links below, depending on which type of exercise you are contributing to.
- - [Concept Exercise Anatomy][concept-exercise-anatomy]
- - [Practice Exercise Anatomy][practice-exercise-anatomy]
+- **Practice exercise**, descriptions and instructions come from a centralized, cross-track [problem specifications][problem-specifications] repository.
-- **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.
+ - 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].
-- Practice Exercise **Test Suits** for many 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.
@@ -265,18 +250,18 @@ The track `test runner`, `analyzer`, and `representer` run in docker on `python:
- - [ ] `.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`
+ - [ ] `.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`
@@ -286,50 +271,46 @@ The track `test runner`, `analyzer`, and `representer` run in docker on `python:
- - [ ] `.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**)
+ - [ ] `.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, representers, and analyzers_) runs in isolated containers within the exercism website. Because of this isolation, exercises cannot rely on third-party or external libraries. Any library needed for an exercise or exercise tests must be incorporated as part of a tooling build, and noted for students who are using the CLI to solve problems locally.
+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.
+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.
+[**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.
+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.
+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_
-```Graphql
+```Bash
[/
βββ .docs
β βββ instructions.md
@@ -341,9 +322,10 @@ _Exercise Structure with Auto-Generated Test Files_
βββ .py #stub file
βββ
-Practice exercise `_test.py` files are generated/regenerated via the [Python Track Test Generator][python-track-test-generator].
+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.
@@ -361,6 +343,7 @@ If an unimplemented exercise does not have a `canonical-data.json` file, the tes
**Example solution files serve two purposes only:**
+
1. Verification of the tests
2. Example implementation for mentor/student reference
@@ -376,7 +359,7 @@ Implementing Track-specific Practice Exercises is similar to implementing a `can
-
Generating Practice Exercise Documents
+
Generating Practice Exercise Documents
You will need
@@ -388,61 +371,49 @@ Implementing Track-specific Practice Exercises is similar to implementing a `can
```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
-[EOL]: https://en.wikipedia.org/wiki/Newline
-[PEP8]: https://www.python.org/dev/peps/pep-0008/
[american-english]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md
-[being-a-good-community-member]: https://github.com/exercism/docs/tree/main/community/good-member
[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
-[concept-exercises]: 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
-[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-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-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
[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
-[help-wanted]: https://github.com/exercism/python/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22
[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
-[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.md
[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
@@ -454,5 +425,4 @@ configlet generate --spec-path path/to/problem/specifications
[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
-[website-contributing-section]: https://exercism.org/docs/building
[yapf]: https://github.com/google/yapf
diff --git a/README.md b/README.md
index 879557ac612..f3d083aab42 100644
--- a/README.md
+++ b/README.md
@@ -3,39 +3,74 @@
Exercism Python Track
+ [](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)
+
-
-[](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 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.
+>
+>
+>
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.
+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.8`.
- π Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.9`.
+π 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`.
-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.
+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.
-
+
-
+
+
+
+
+
-ππ 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], and [Pull Requests][prs].
+ππ 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].
-Some defined roles in our community: [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers] **|** [Admins][exercism-admins]
+ Some defined roles in our community: [Contributors][exercism-contributors] **|** [Mentors][exercism-mentors] **|** [Maintainers][exercism-track-maintainers] **|** [Admins][exercism-admins]
+
+
-
+
-We π π Pull Requests. **But our maintainers generally can't accept _unsolicited_ PRs.**
-Check our [help wanted][open-issues] list or [open an issue ][open-an-issue] for discussion first.
-We β¨π π π β¨ [PRs][prs] that follow our **[Contributing Guidelines][contributing-guidelines]**.
+We π π our community.
+**But our maintainers are not accepting community contributions at this time.**
+Please read this [community blog post][freeing-maintainers] for details.
+
+
+
+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._
@@ -45,24 +80,11 @@ We β¨π π π β¨ [PRs][prs] that follow our **[C
[Writing Style Guide][exercism-writing-style] **|** [Markdown Specification][exercism-markdown-specification] (_β¨ version in [contributing][website-contributing-section] on exercism.org_)
-
-
-If you are here to help out with [open issues][open-issues], you have our gratitude π ππ½.
-Anything with [`help wanted`] and without a [`Claimed`] tag is up for grabs.
-Comment on the issue and we will reserve it for you. π β¨
-
-
-
-
-Here to suggest a new 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._
-
## Python Software and Documentation
-**Copyright Β© 2001-2021 Python Software Foundation. All rights reserved.**
+**Copyright Β© 2001-2025 Python Software Foundation. All rights reserved.**
Python software and documentation are licensed under the [PSF License Agreement][psf-license].
@@ -70,17 +92,16 @@ Starting with `Python 3.8.6`, examples, recipes, and other code in the Python do
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.
+
## Exercism Python Track License
-This repository uses the [MIT License](/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
-[contributing-guidelines]: https://github.com/exercism/python/blob/main/CONTRIBUTING.md
[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
@@ -93,12 +114,11 @@ This repository uses the [MIT License](/LICENSE).
[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
-[open-an-issue]: https://github.com/exercism/python/issues/new/choose
-[open-issues]: https://github.com/exercism/python/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22
-[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.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/data.py b/bin/data.py
index 54dec7612b3..7fce51b9087 100644
--- a/bin/data.py
+++ b/bin/data.py
@@ -4,9 +4,16 @@
from itertools import chain
import json
from pathlib import Path
-import tomli
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__")
@@ -355,7 +362,7 @@ class TestsTOML:
@classmethod
def load(cls, toml_path: Path):
with toml_path.open("rb") as f:
- data = tomli.load(f)
+ data = tomllib.load(f)
return cls({uuid: TestCaseTOML(uuid, *opts) for
uuid, opts in
data.items() if
diff --git a/bin/fetch-configlet b/bin/fetch-configlet
index 827088ab17c..6bef43ab722 100755
--- a/bin/fetch-configlet
+++ b/bin/fetch-configlet
@@ -6,29 +6,6 @@
set -eo pipefail
-readonly LATEST='https://api.github.com/repos/exercism/configlet/releases/latest'
-
-case "$(uname)" in
- Darwin*) os='mac' ;;
- Linux*) os='linux' ;;
- Windows*) os='windows' ;;
- MINGW*) os='windows' ;;
- MSYS_NT-*) os='windows' ;;
- *) os='linux' ;;
-esac
-
-case "${os}" in
- windows*) ext='zip' ;;
- *) ext='tgz' ;;
-esac
-
-case "$(uname -m)" in
- *64*) arch='64bit' ;;
- *686*) arch='32bit' ;;
- *386*) arch='32bit' ;;
- *) arch='64bit' ;;
-esac
-
curlopts=(
--silent
--show-error
@@ -41,15 +18,26 @@ if [[ -n "${GITHUB_TOKEN}" ]]; then
curlopts+=(--header "authorization: Bearer ${GITHUB_TOKEN}")
fi
-suffix="${os}-${arch}.${ext}"
-
get_download_url() {
- curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${LATEST}" |
+ 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
@@ -59,16 +47,45 @@ main() {
return 1
fi
- download_url="$(get_download_url)"
- output_path="${output_dir}/latest-configlet.${ext}"
+ 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}" ;;
+ 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
index d0406aad5e2..2ad23a9b5f1 100755
--- a/bin/generate_tests.py
+++ b/bin/generate_tests.py
@@ -17,12 +17,13 @@
from githelp import Repo
_py = sys.version_info
-if _py.major < 3 or (_py.major == 3 and _py.minor < 6):
- print("Python version must be at least 3.6")
+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
@@ -34,11 +35,17 @@
from itertools import repeat
from string import punctuation, whitespace
from subprocess import check_call
-import tomli
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
@@ -131,7 +138,7 @@ def parse_datetime(string: str, strip_module: bool = False) -> datetime:
]| # OR
o(?:[0-8]{1,3}) # an octal value
| # OR
- x(?:[0-9A-Fa-f]{2}) # a hexidecimal value
+ x(?:[0-9A-Fa-f]{2}) # a hexadecimal value
| # OR
N # a unicode char name composed of
\{ # an opening brace
@@ -197,6 +204,8 @@ def regex_find(s: str, find: str) -> List[Any]:
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]:
"""
@@ -254,6 +263,19 @@ def format_file(path: Path) -> NoReturn:
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():
@@ -264,20 +286,25 @@ def check_template(slug: str, tests_path: Path, tmpfile: Path):
check_ok = False
if check_ok and not filecmp.cmp(tmpfile, tests_path):
with tests_path.open() as f:
- current_lines = f.readlines()
+ current_lines = f.readlines()[3:]
with tmpfile.open() as f:
- rendered_lines = f.readlines()
- diff = difflib.unified_diff(
+ 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}",
- )
- logger.debug(f"{slug}: ##### DIFF START #####")
- for line in diff:
- logger.debug(line.strip())
- logger.debug(f"{slug}: ##### DIFF END #####")
- check_ok = False
+ 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"
@@ -384,9 +411,11 @@ def generate(
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)):
diff --git a/concepts/basics/.meta/config.json b/concepts/basics/.meta/config.json
index 122161cc814..86bb653b158 100644
--- a/concepts/basics/.meta/config.json
+++ b/concepts/basics/.meta/config.json
@@ -1,5 +1,5 @@
{
- "blurb": "Python is a dynamic and strongly typed object-oriented 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.",
+ "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
index ddcc57790a0..ef873ce418f 100644
--- a/concepts/basics/about.md
+++ b/concepts/basics/about.md
@@ -1,14 +1,14 @@
# basics
-[Python][python docs] is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language.
+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].
-It supports multiple programming paradigms including both imperative (_object-oriented, procedural_) and declarative (_functional, concurrent_) flavors.
-But do not be fooled: while programming across paradigms is fully _supported_, [everything in Python is an object][everythings an object].
-
-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].
+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.
-The [zen of Python (PEP 20)][the zen of python] and [What is Pythonic?][what is pythonic] lay out additional philosophies.
+
+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].
@@ -19,158 +19,217 @@ Complete documentation for the current release can be found at [docs.python.org]
- [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)
-## Getting Started
+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.
-Objects are [assigned][assignment statements] to [names][naming and binding] in Python via the `=` or _assignment operator_. [Variables][variables] are written in [`snake_case`][snake case], and constants usually in `SCREAMING_SNAKE_CASE`.
+For example, `my_first_variable` can be re-assigned many times using `=`, and can refer to different object types with each re-assignment:
-A `name` (_variable or constant_) is not itself typed, and can be attached or re-attached to different objects or values over its lifetime.
-For extended naming conventions and formatting advice, see [PEP 8][pep8].
```python
->>> my_first_variable = 1
->>> my_first_variable = "Last one, I promise"
+>>> 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))
+
-"Last one, I promise"
+>>> print(my_first_variable)
+>>> Counter({3: 3, 1: 2, 2: 1, 4: 1, 5: 1, 6: 1, 7: 1})
```
-Constants are usually defined on a [module][module] or `global` level, and although they _can_ be changed, they are _intended_ to be assigned only once.
-Their `SCREAMING_SNAKE_CASE` is a message to other developers that the assignment should not be altered.
+### 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
+# All caps signal that this is intended as a constant.
MY_FIRST_CONSTANT = 16
# Re-assignment will be allowed by the compiler & interpreter,
-# but is VERY strongly discouraged.
-# Please don't do: MY_FIRST_CONSTANT = "Some other value"
+# 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 keyword `def` begins a [function definition][function definition].
-`def` must be followed by the function name and a parenthesized list of zero or more formal [parameters][parameters].
- Parameters can be of several different varieties, and can even [vary][more on functions] in length.
-The `def` line is terminated with a colon (`:`).
+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_:
-Statements for the `function body` begin on the line following `def`, and must be _indented in a block_.
-There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent for all indented statements_.
-Functions explicitly return a value or object via the [`return`][return] keyword.
```python
-# Function definition on first line.
->>> def add_two_numbers(number_one, number_two):
-... return number_one + number_two # Returns the sum of the numbers, and is indented by 2 spaces.
+# 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
-```
-
-Functions that do not have an explicit `return` expression will return [`None`][none].
-
-```python
-# This function will return None.
-def add_two_numbers(number_one, number_two):
- result = number_one + number_two
-
->>> print(add_two_numbers(5, 7))
-None
-```
-Inconsistent indentation will raise an error:
-```python
-# The return statement line does not match the first line indent.
+# 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 # Indented by 4 spaces.
-... return result #this was only indented by 3 spaces
+... 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
- return result
- ^
+ print(result)
+ ^
IndentationError: unindent does not match any outer indentation level
```
-Functions are [_called_][calls] using their name followed by `()`.
-The number of arguments passed in the parentheses must match the number of parameters in the original function definition unless [default arguments][default arguments] have been used.
+
+Functions _explicitly_ return a value or object via the [`return`][return] keyword:
+
```python
->>> 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.
- """
+# Function definition on first line, explicit return used on final line.
+def add_two_numbers(number_one, number_two):
+ return 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
->>> number_to_the_power_of(3,3)
-27
+# 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
```
-A mis-match between parameters and arguments will raise an error:
+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
->>> 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'
+# This function does not have an explicit return.
+def add_two_numbers(number_one, number_two):
+ result = number_one + number_two
-```
-Adding a [default value][default arguments] for a parameter can defend against such errors:
+# Calling the function in the Python terminal appears
+# to not return anything at all.
+>>> add_two_numbers(5, 7)
+>>>
-```python
-def number_to_the_power_of_default(number_one, number_two=2):
- """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
+# Using print() with the function call shows that
+# the function is actually returning the **None** object.
+>>> print(add_two_numbers(5, 7))
+None
+
->>> number_to_the_power_of_default(4)
-16
+# 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
```
-Methods bound to class names are invoked via dot notation (`.()`), as are functions, constants, or global names imported as part of a module.:
+
+### 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
+...
-import string
+>>> number_to_the_power_of(3,3) # Invoking the function with the arguments 3 and 3.
+27
-# This is a constant provided by the *string* module.
->>> print(string.ascii_lowercase)
-"abcdefghijklmnopqrstuvwxyz"
-# This is a method call of the str *class*.
+# 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)
+>>> str.upper(start_text) # Calling the upper() method for the built-in str class.
"MY SILLY SENTENCE FOR EXAMPLES."
-# This is a method call of an *instance* of the str *class*.
->>> start_text.upper()
-"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.
+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.
@@ -181,25 +240,53 @@ x = "foo" # This is an in-line comment.
# 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 read by automated documentation tools and are returned by calling `.__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 are declared using triple double quotes (""") indented at the same level as the code block:
```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.
+
+# 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)
"""
- return number_one ** number_two
+ 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.
@@ -209,7 +296,9 @@ Raise a number to an arbitrary power.
Takes number_one and raises it to the power of number_two, returning the result.
-# __doc__() for the built-in type: str.
+
+
+# Printing the __doc__ attribute for the built-in type: str.
>>> print(str.__doc__)
str(object='') -> str
str(bytes_or_buffer[, encoding[, errors]]) -> str
@@ -223,33 +312,24 @@ encoding defaults to sys.getdefaultencoding().
errors defaults to 'strict'.
```
-Docstrings can also include [doctests][doctests], which are interactive examples of how a method or function should work.
-Doctests can be read and run by PyTest, or by importing the `doctest` module.
-
[PEP257]: https://www.python.org/dev/peps/pep-0257/
-[assignment statements]: https://docs.python.org/3/reference/simple_stmts.html#assignment-statements
[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
-[default arguments]: https://docs.python.org/3/tutorial/controlflow.html#default-argument-values
[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
-[indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation
[method objects]: https://docs.python.org/3/c-api/method.html#method-objects
[module]: https://docs.python.org/3/tutorial/modules.html
-[more on functions]: https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions
-[naming and binding]: https://docs.python.org/3/reference/executionmodel.html#naming-and-binding
[none]: https://docs.python.org/3/library/constants.html
-[object oriented programming]: https://en.wikipedia.org/wiki/Object-oriented_programming
[objects]: https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy
[parameters]: https://docs.python.org/3/glossary.html#term-parameter
-[pep8]: https://www.python.org/dev/peps/pep-0008/
[peps]: https://www.python.org/dev/peps/
[psf membership]: https://www.python.org/psf/membership/
[psf]: https://www.python.org/psf/
@@ -262,9 +342,5 @@ Doctests can be read and run by PyTest, or by importing the `doctest` module.
[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
-[snake case]: https://en.wikipedia.org/wiki/Snake_case
-[the zen of python]: https://www.python.org/dev/peps/pep-0020/
[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
-[variables]: https://realpython.com/python-variables/
-[what is pythonic]: https://blog.startifact.com/posts/older/what-is-pythonic.html
diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md
index ca69d5af68b..818dd47deac 100644
--- a/concepts/basics/introduction.md
+++ b/concepts/basics/introduction.md
@@ -1,230 +1,181 @@
# Introduction
-[Python][python docs] is a [dynamic and strongly][dynamic typing in python] typed [object-oriented][object oriented programming] programming language.
-It employs both [duck typing][duck typing] and [gradual typing][gradual typing] via [type hints][type hints].
-It supports multiple programming paradigms including both imperative (_object-oriented, procedural_) and declarative (_functional, concurrent_) flavors.
+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 puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] for function, method, and class definitions.
-The [zen of Python (PEP 20)][the zen of python] and [What is Pythonic?][what is pythonic] lay out additional philosophies.
+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:
-Objects are [assigned][assignment statements] to [names][naming and binding] via the _assignment operator_, `=`.
-[Variables][variables] are written in [`snake_case`][snake case], and _constants_ usually in `SCREAMING_SNAKE_CASE`.
-A `name` (_variable or constant_) is not itself _typed_, and can be attached or re-attached to different objects over its lifetime.
-For extended naming conventions and advice, see [PEP 8][pep8].
```python
->>> my_first_variable = 1
->>> my_first_variable = "Last one, I promise"
+>>> 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))
+
-"Last one, I promise"
+>>> print(my_first_variable)
+"Now, I'm a string." # Strings can be declared using single or double quote marks.
```
-Constants are typically defined on a [module][module] or _global_ level, and although they _can_ be changed, they are _intended_ to be named only once.
-Their `SCREAMING_SNAKE_CASE` is a message to other developers that the assignment should not be altered:
-```python
-# All caps signal that this is intended as a constant.
-MY_FIRST_CONSTANT = 16
+### Constants
-# Re-assignment will be allowed by the compiler & interpreter,
-# but this is VERY strongly discouraged.
-# Please don't do: MY_FIRST_CONSTANT = "Some other value"
-```
+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.
-The keyword `def` begins a [function definition][function definition].
-It must be followed by the function name and a parenthesized list of zero or more formal [parameters][parameters].
- Parameters can be of several different varieties, and can even [vary][more on functions] in length.
-The `def` line is terminated with a colon.
-Statements for the _body_ of the function begin on the line following `def`, and must be _indented in a block_.
-There is no strict indentation amount (_either space **OR** [tab] characters are acceptable_), but [indentation][indentation] must be _consistent for all indented statements_.
-Functions explicitly return a value or object via the [`return`][return] keyword.
-```python
-# Function definition on first line.
-def add_two_numbers(number_one, number_two):
- return number_one + number_two # Returns the sum of the numbers, and is indented by 2 spaces.
+## Functions
->>> add_two_numbers(3, 4)
-7
-```
+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_.
-Functions that do not have an explicit `return` expression will return [`None`][none].
```python
-# This function will return None.
+# The body of this function is indented by 2 spaces,& prints the sum of the numbers.
def add_two_numbers(number_one, number_two):
- result = number_one + number_two
+ total = number_one + number_two
+ print(total)
->>> print(add_two_numbers(5, 7))
-None
-```
+>>> add_two_numbers(3, 4)
+7
-Inconsistent indentation will raise an error:
-```python
-# The return statement line does not match the first line indent.
+# 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 # Indented by 4 spaces.
-... return result #this was only indented by 3 spaces
+... 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
- return result
- ^
+ print(result)
+ ^
IndentationError: unindent does not match any outer indentation level
```
-Functions are [_called_][calls] using their name followed by `()`.
-The number of arguments passed in the parentheses must match the number of parameters in the original function definition unless [default arguments][default arguments] have been used:
-```python
-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.
- """
+Functions _explicitly_ return a value or object via the [`return`][return] keyword:
- return number_one ** number_two
->>> number_to_the_power_of(3,3)
-27
-```
+```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
-A mis-match between parameters and arguments will raise an error:
-```python
->>> 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 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
```
-Adding a [default value][default arguments] for a parameter can defend against such errors:
-
-```python
-def number_to_the_power_of_default(number_one, number_two=2):
- """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
->>> number_to_the_power_of_default(4)
-16
-```
+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:
-Methods bound to class names are invoked via dot notation (.), as are functions, constants, or global names imported as part of a module.:
```python
+# This function does not have an explicit return.
+def add_two_numbers(number_one, number_two):
+ result = number_one + number_two
-import string
-# This is a constant provided by the *string* module.
->>> print(string.ascii_lowercase)
-"abcdefghijklmnopqrstuvwxyz"
+# Calling the function in the Python terminal appears
+# to not return anything at all.
+>>> add_two_numbers(5, 7)
+>>>
-# This is a method call of the str *class*.
->>> start_text = "my silly sentence for examples."
->>> str.upper(start_text)
-"MY SILLY SENTENCE FOR EXAMPLES."
-# This is a method call of an *instance* of the str *class*.
->>> start_text.upper()
-"MY SILLY SENTENCE FOR EXAMPLES."
+# 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.
+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 read by automated documentation tools and are returned by calling `.__doc__()` on the function, method, or class name.
-They can also function as [lightweight unit tests][doctests], which will be covered in a later exercise.
-They are recommended for programs of any size where documentation is needed, and their conventions are laid out in [PEP257][PEP257]:
+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 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
-
->>> 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
+# An example from PEP257 of a multi-line docstring.
+def complex(real=0.0, imag=0.0):
+ """Form a complex number.
- Takes number_one and raises it to the power of number_two, returning the result.
+ Keyword arguments:
+ real -- the real part (default 0.0)
+ imag -- the imaginary part (default 0.0)
+ """
-# __doc__() for the built-in type: str.
->>> print(str.__doc__)
-str(object='') -> str
-str(bytes_or_buffer[, encoding[, errors]]) -> str
+ if imag == 0.0 and real == 0.0:
+ return complex_zero
-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/
-[assignment statements]: https://docs.python.org/3/reference/simple_stmts.html#assignment-statements
-[calls]: https://docs.python.org/3/reference/expressions.html#calls
+[pep257]: https://www.python.org/dev/peps/pep-0257/
[comments]: https://realpython.com/python-comments-guide/#python-commenting-basics
-[default arguments]: https://docs.python.org/3/tutorial/controlflow.html#default-argument-values
[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
[gradual typing]: https://en.wikipedia.org/wiki/Gradual_typing
-[indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation
[module]: https://docs.python.org/3/tutorial/modules.html
-[more on functions]: https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions
-[naming and binding]: https://docs.python.org/3/reference/executionmodel.html#naming-and-binding
[none]: https://docs.python.org/3/library/constants.html
-[object oriented programming]: https://en.wikipedia.org/wiki/Object-oriented_programming
[parameters]: https://docs.python.org/3/glossary.html#term-parameter
-[pep8]: https://www.python.org/dev/peps/pep-0008/
-[python docs]: https://docs.python.org/3/
[return]: https://docs.python.org/3/reference/simple_stmts.html#return
-[significant indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation
-[snake case]: https://en.wikipedia.org/wiki/Snake_case
-[the zen of python]: https://www.python.org/dev/peps/pep-0020/
[type hints]: https://docs.python.org/3/library/typing.html
-[variables]: https://realpython.com/python-variables/
-[what is pythonic]: https://blog.startifact.com/posts/older/what-is-pythonic.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
index 0c7752279da..1d1d640c9e7 100644
--- a/concepts/basics/links.json
+++ b/concepts/basics/links.json
@@ -1,27 +1,23 @@
[
{
- "url": "https://docs.python.org/3/",
- "description": "Python documentation"
+ "url": "https://lerner.co.il/2019/06/18/understanding-python-assignment/",
+ "description": "Reuven Lerner: Understanding Python Assignment"
},
{
- "url": "https://www.python.org/dev/peps/pep-0020/",
- "description": "The Zen of Python (PEP 20)"
+ "url": "https://www.youtube.com/watch?v=owglNL1KQf0",
+ "description": "Sentdex (YouTube): Python 3 Programming Tutorial - Functions"
},
{
- "url": "https://www.python.org/dev/peps/pep-0008/",
- "description": "PEP 8"
+ "url": "https://realpython.com/documenting-python-code/#commenting-vs-documenting-code",
+ "description": "Real Python: Commenting vs Documenting Code."
},
{
- "url": "https://www.python.org/psf-landing/",
- "description": "Python Software Foundation"
+ "url": "https://www.pythonmorsels.com/everything-is-an-object/",
+ "description": "Python Morsels: Everything is an Object"
},
{
- "url": "https://www.python.org/dev/peps/",
- "description": "Python Enhancement Proposals or PEPs"
- },
- {
- "url": "https://docs.python.org/3/reference/datamodel.html",
- "description": "everything in Python 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",
@@ -36,59 +32,11 @@
"description": "significant indentation"
},
{
- "url": "https://realpython.com/python-variables/",
- "description": "variables in Python"
- },
- {
- "url": "https://docs.python.org/3/reference/simple_stmts.html#assignment-statements",
- "description": "assignment statements in Python"
- },
- {
- "url": "https://docs.python.org/3/reference/executionmodel.html#naming-and-binding",
- "description": "naming and binding in Python"
- },
- {
- "url": "https://docs.python.org/3/tutorial/controlflow.html#defining-functions",
- "description": "function definition"
- },
- {
- "url": "https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions",
- "description": "more on defining functions"
- },
- {
- "url": "https://docs.python.org/3/reference/compound_stmts.html#function",
- "description": "functions in Python"
- },
- {
- "url": "https://docs.python.org/3/reference/datamodel.html#classes",
- "description": "class in Python"
- },
- {
- "url": "https://docs.python.org/3/c-api/method.html#method-objects",
- "description": "methods in Python"
- },
- {
- "url": "https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy",
- "description": "Pythons standard type hierarchy"
- },
- {
- "url": "https://docs.python.org/3/reference/expressions.html#calls",
- "description": "calls"
- },
- {
- "url": "https://docs.python.org/3/tutorial/controlflow.html#default-argument-values",
- "description": "default arguments"
- },
- {
- "url": "https://realpython.com/python-comments-guide/#python-commenting-basics",
- "description": "Comments"
- },
- {
- "url": "https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings",
- "description": "docstring"
+ "url": "https://www.digitalocean.com/community/tutorials/how-to-write-doctests-in-python",
+ "description": "DigitalOcean: How to Write Doctests in Python."
},
{
- "url": "https://docs.python.org/3/library/doctest.html",
- "description": "doctests"
+ "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-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/bitwise-operators/.meta/config.json b/concepts/bitwise-operators/.meta/config.json
index 9b9e8da5a9b..7767ff5d740 100644
--- a/concepts/bitwise-operators/.meta/config.json
+++ b/concepts/bitwise-operators/.meta/config.json
@@ -1,5 +1,5 @@
{
- "blurb": "TODO: add blurb for this concept",
- "authors": ["bethanyg", "cmccandless"],
+ "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
index c628150d565..a68e5378f12 100644
--- a/concepts/bitwise-operators/about.md
+++ b/concepts/bitwise-operators/about.md
@@ -1,2 +1,197 @@
-#TODO: Add about for this concept.
+# 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
index bbe12ffd5e9..88aba3a6a7b 100644
--- a/concepts/bitwise-operators/introduction.md
+++ b/concepts/bitwise-operators/introduction.md
@@ -1 +1,20 @@
-#TODO: Add introduction for this concept.
+# 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
index eb5fb7c38a5..7c103c84630 100644
--- a/concepts/bitwise-operators/links.json
+++ b/concepts/bitwise-operators/links.json
@@ -1,18 +1,22 @@
[
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://wiki.python.org/moin/BitwiseOperators/",
+ "description": "BitwiseOperators on the Python wiki."
},
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://realpython.com/python-bitwise-operators",
+ "description": "Real Python: Bitwise Operators in Python."
},
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://stackoverflow.com/a/20768199",
+ "description": "Stack Overflow: Convert a Python int to an unsigned int."
},
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "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/about.md b/concepts/bools/about.md
index 4b697270659..a2680fc06b3 100644
--- a/concepts/bools/about.md
+++ b/concepts/bools/about.md
@@ -1,6 +1,6 @@
# About
-Python represents True and False values with the [bool][bool] type.
+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`):
@@ -139,3 +139,5 @@ It is considered a [Python anti-pattern][comparing to true in the wrong way] to
[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
index 535f90be07c..af24137025e 100644
--- a/concepts/bools/introduction.md
+++ b/concepts/bools/introduction.md
@@ -1,6 +1,6 @@
# Introduction
-Python represents true and false values with the `bool` type.
+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:
@@ -21,3 +21,5 @@ We can evaluate Boolean expressions using the `and`, `or`, and `not` operators.
>>> 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/class-inheritance/about.md b/concepts/class-inheritance/about.md
index 5db7909e2c7..9f1bdf30cd9 100644
--- a/concepts/class-inheritance/about.md
+++ b/concepts/class-inheritance/about.md
@@ -7,7 +7,7 @@ In situations where only a small amount of functionality needs to be customized
`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 it's name.
+To create a child class, specify the parent class name inside the pair of parenthesis, followed by its name.
Example
```python
class Child(Parent):
diff --git a/concepts/classes/about.md b/concepts/classes/about.md
index f50af7321d3..11b03643543 100644
--- a/concepts/classes/about.md
+++ b/concepts/classes/about.md
@@ -118,7 +118,7 @@ class MyClass:
def __init__(self, location):
# This is an instance or object property, attribute, or variable.
- # Note that we are unpacking the tuple argument into two seperate instance variables.
+ # Note that we are unpacking the tuple argument into two separate instance variables.
self.location_x = location[0]
self.location_y = location[1]
@@ -314,12 +314,12 @@ class MyClass:
# This will compile and run without error, but has no current functionality.
def pending_functionality(self):
- # Stubbing or placholding the body of this method.
+ # 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://www.dataindependent.com/python/python-glossary/python-dunder/
+[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/links.json b/concepts/classes/links.json
index 5687b92a3d1..8cc9ba5926e 100644
--- a/concepts/classes/links.json
+++ b/concepts/classes/links.json
@@ -17,7 +17,7 @@
},
{
"url": "https://dbader.org/blog/6-things-youre-missing-out-on-by-never-using-classes-in-your-python-code",
- "description": "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",
diff --git a/concepts/comparisons/about.md b/concepts/comparisons/about.md
index 568b6603c1a..1d2c677d22a 100644
--- a/concepts/comparisons/about.md
+++ b/concepts/comparisons/about.md
@@ -29,7 +29,7 @@ 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.
+For more information on the rules that Python uses for numeric conversion, see [arithmetic conversions][arithmetic conversions] in the Python documentation.
```python
>>> import fractions
@@ -47,7 +47,8 @@ True
>>> 6/3 == 0b10
True
-# An int can be converted to a complex number with a 0 imaginary part.
+# An int can be converted to a complex
+# number with a 0 imaginary part.
>>> 17 == complex(17)
True
@@ -60,8 +61,8 @@ 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.
+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')
@@ -82,7 +83,7 @@ False
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.
-Unlike in Python 2.x, in Python 3.x, `str` and `bytes` cannot be directly coerced/compared.
+In Python 3.x, `str` and `bytes` cannot be directly coerced/compared.
```python
>>> 'Python' > 'Rust'
@@ -188,7 +189,7 @@ See the Python reference docs on [value comparisons][value comparisons none] and
>>> my_fav_numbers is your_fav_numbers
True
-# The returned id will differ by system and python version.
+# The returned id will differ by system and Python version.
>>> id(my_fav_numbers)
4517478208
diff --git a/concepts/comparisons/links.json b/concepts/comparisons/links.json
index ed61054b722..f16869e2b92 100644
--- a/concepts/comparisons/links.json
+++ b/concepts/comparisons/links.json
@@ -1,27 +1,19 @@
[
- {
- "url": "https://docs.python.org/3/reference/expressions.html#comparisons",
- "description": "Comparisons in Python (Python language reference)"
- },
{
"url": "https://www.tutorialspoint.com/python/python_basic_operators.htm",
"description": "Python basic operators on Tutorials Point"
},
{
- "url": "https://data-flair.training/blogs/python-comparison-operators/",
- "description": "Python comparison operators on Data Flair"
- },
- {
- "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/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/library/operator.html",
- "description": "Operators (Python Docs)"
+ "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",
@@ -44,11 +36,11 @@
"description": "Python Object Model (Python docs)"
},
{
- "url": "https://docs.python.org/3/reference/datamodel.html#customization",
- "description": "Basic Customization (Python language reference)"
+ "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/expressions.html#value-comparisons",
- "description": "Value comparisons in Python (Python language reference)"
+ "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
index 9b9e8da5a9b..ca6ccc8811d 100644
--- a/concepts/complex-numbers/.meta/config.json
+++ b/concepts/complex-numbers/.meta/config.json
@@ -1,5 +1,5 @@
{
- "blurb": "TODO: add blurb for this concept",
- "authors": ["bethanyg", "cmccandless"],
+ "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
index c628150d565..dfe067be4ee 100644
--- a/concepts/complex-numbers/about.md
+++ b/concepts/complex-numbers/about.md
@@ -1,2 +1,260 @@
-#TODO: Add about for this concept.
+# 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
index fcde74642ca..a82f47cb6cb 100644
--- a/concepts/complex-numbers/introduction.md
+++ b/concepts/complex-numbers/introduction.md
@@ -1,2 +1,93 @@
-#TODO: Add introduction for this concept.
+# 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
index eb5fb7c38a5..759ef1689ff 100644
--- a/concepts/complex-numbers/links.json
+++ b/concepts/complex-numbers/links.json
@@ -1,18 +1,18 @@
[
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex/",
+ "description": "Operations on numeric types."
},
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://docs.python.org/3/library/functions.html#complex/",
+ "description": "The complex class."
},
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://docs.python.org/3/library/cmath.html/",
+ "description": "Module documentation for cmath."
},
{
- "url": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "url": "https://docs.python.org/3/library/math.html/",
+ "description": "Module documentation for math."
}
]
diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md
index 3b3d5b1ba1d..2060905b335 100644
--- a/concepts/conditionals/about.md
+++ b/concepts/conditionals/about.md
@@ -8,7 +8,6 @@ Python 3.10 introduces a variant case-switch statement called `pattern matching`
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
@@ -55,19 +54,21 @@ else:
>>> z is greater than x and y
```
-[Boolen operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing:
+[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:
- return 'FizzBuzz!'
+ say = 'FizzBuzz!'
elif number % 5 == 0:
- return 'Buzz!'
+ say = 'Buzz!'
elif number % 3 == 0:
- return 'Fizz!'
+ say = 'Fizz!'
else:
- return str(number)
+ say = str(number)
+
+ return say
>>> classic_fizzbuzz(15)
'FizzBuzz!'
@@ -76,29 +77,54 @@ else:
'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:
- return "Student driver, needs supervision."
+ status = "Student driver, needs supervision."
elif driver_age == 18:
- return "Permitted driver, on probation."
+ status = "Permitted driver, on probation."
elif driver_age > 18:
- return "Fully licensed driver."
+ status = "Fully licensed driver."
else:
- return "Unlicensed!"
+ status = "Unlicensed!"
+
+ return status
>>> driving_status(63, 78)
-'Unlicsensed!'
+'Unlicensed!'
>>> driving_status(16, 81)
'Student driver, needs supervision.'
>>> driving_status(23, 80)
-'Fully licsensed driver.'
+'Fully licensed driver.'
```
## Conditional expressions or "ternary operators"
@@ -115,8 +141,8 @@ def just_the_buzz(number):
>>> just_the_buzz(15)
'Buzz!'
->>> just_the_buzz(10)
-'10'
+>>> just_the_buzz(7)
+'7'
```
## Truthy and Falsy
@@ -151,8 +177,9 @@ This is Truthy.
Nope. It's Falsey.
```
-[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement
-[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools
-[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
[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
index 29e3e635975..ee1d4336207 100644
--- a/concepts/conditionals/introduction.md
+++ b/concepts/conditionals/introduction.md
@@ -45,10 +45,8 @@ 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")
@@ -56,19 +54,20 @@ else:
>>> z is greater than x and y
```
-[Boolen operations][boolean operations] and [comparisons][comparisons] can be combined with conditionals for more complex testing:
+[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:
- return 'FizzBuzz!'
+ say = 'FizzBuzz!'
elif number % 5 == 0:
- return 'Buzz!'
+ say = 'Buzz!'
elif number % 3 == 0:
- return 'Fizz!'
+ say = 'Fizz!'
else:
- return str(number)
+ say = str(number)
+
+ return say
>>> classic_fizzbuzz(15)
'FizzBuzz!'
@@ -77,8 +76,8 @@ else:
'13'
```
-[if statement]: https://docs.python.org/3/reference/compound_stmts.html#the-if-statement
-[control flow tools]: https://docs.python.org/3/tutorial/controlflow.html#more-control-flow-tools
-[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
[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/dataclasses-and-namedtuples/.meta/config.json b/concepts/dataclasses/.meta/config.json
similarity index 100%
rename from concepts/dataclasses-and-namedtuples/.meta/config.json
rename to concepts/dataclasses/.meta/config.json
diff --git a/concepts/dataclasses-and-namedtuples/about.md b/concepts/dataclasses/about.md
similarity index 100%
rename from concepts/dataclasses-and-namedtuples/about.md
rename to concepts/dataclasses/about.md
diff --git a/concepts/dataclasses-and-namedtuples/introduction.md b/concepts/dataclasses/introduction.md
similarity index 100%
rename from concepts/dataclasses-and-namedtuples/introduction.md
rename to concepts/dataclasses/introduction.md
diff --git a/concepts/dataclasses-and-namedtuples/links.json b/concepts/dataclasses/links.json
similarity index 100%
rename from concepts/dataclasses-and-namedtuples/links.json
rename to concepts/dataclasses/links.json
diff --git a/concepts/dict-methods/about.md b/concepts/dict-methods/about.md
index e36fc7082e1..6dcf9b4ae7a 100644
--- a/concepts/dict-methods/about.md
+++ b/concepts/dict-methods/about.md
@@ -1,74 +1,75 @@
# Dictionary Methods in Python
-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 [hash table or hashmap][hashtable-wikipedia].
-In Python, it's considered a [mapping type][mapping-types-dict].
-`dicts` enable the retrieval of a value in constant time (on average), given the key.
-
-Compared to searching for a value within a list or array (_without knowing the index position_), a dictionary uses significantly more memory, but has very rapid retrieval.
-It's especially useful in scenarios where the collection of items is large and must be accessed/updated frequently.
-
-## Dictionary Methods
-
The `dict` class in Python provides many useful [methods][dict-methods] for working with dictionaries.
Some were introduced in the concept for `dicts`.
-Here are a few more - along with some techniques for iterating through and manipulating `dicts`.
+Here we cover a few more - along with some techniques for iterating through and manipulating dictionaries.
-To quickly populate a dictionary with various `keys` and default values, the _class method_ [`dict.fromkeys(iterable, )`][fromkeys] will iterate through the `keys` and create a new `dict`. All `values` will be set to the `default` value provided.
+- `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.
-```python
->>> new_dict = dict.fromkeys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'], 'fill in hex color here')
->>> new_dict
-{'Grassy Green': 'fill in hex color here',
- 'Purple Mountains Majesty': 'fill in hex color here',
- 'Misty Mountain Pink': 'fill in hex color here'}
-```
-`dict.clear()` will removed all `key:value` pairs from the dictionary, leaving it empty and ready for new entries.
+## `setdefault()` for Error-Free Insertion
-```python
->>> pallette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
->>> pallette_II.clear()
->>> pallette_II
-{}
-```
+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.
-`dict.get(key, )` works similarly to `dict[key]` -- but it will return the `default` if the `key` is not in the dictionary.
-If no `default` is given, the method will return `None`.
+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'}
->>> palette_I['Factory Stone Purple']
-Traceback (most recent call last):
- line 1, in
- palette_I['Factory Stone Purple']
+# 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'
-KeyError: 'Factory Stone Purple'
+# 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'}
+```
->>> palette_I.get('Factory Stone Purple', 'That color was not found.')
-'That color was not found.'
+## `fromkeys()` to Populate a Dictionary from an Iterable
->>> palette_I.get('Factory Stone Purple', False)
-False
+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:
->>> palette_I.get('Factory Stone Purple')
-None
+```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'}
```
-`dict.popitem()` removes & returns a single `key:value` pair from the `dict`.
-Pairs are returned in Last-in-First-out (LIFO) order.
-If the dictionary is empty, calling `.dict.popitem` will raise a `KeyError`.
+## 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 = {'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')
->>> palette_I.popitem()
+# All (key, value) pairs have been removed.
+>>> palette_I.popitem()
Traceback (most recent call last):
line 1, in
@@ -77,25 +78,38 @@ Traceback (most recent call last):
KeyError: 'popitem(): dictionary is empty'
```
-While `dict.clear()` and `dict.popitem()` are _destructive_ actions, the `.keys()`, `.values()`, and `.items()` methods return [_iterable views_][dict-views].
-These views can be used for looping over `dict` content without altering it and are _dynamic_ -- when underlying dictionary data changes, the associated view object will reflect the change.
+## 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'}
+>>> 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'])
+dict_values(['#9bc400', (128, 118, 163), '#f9c5bd', '#932432'])
->>> palette_I['Deep Red'] = '#932432'
>>> palette_I.keys()
dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink', 'Deep Red'])
@@ -103,32 +117,69 @@ dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink', 'D
dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', (128, 118, 163)), ('Misty Mountain Pink', '#f9c5bd'), ('Deep Red', '#932432')])
```
-`dict_one.update()` can be used to _combine_ two dictionaries.
-This method will take the `key:value` pairs of `dict_two` and write them into `dict_one`.
+## 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_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)
->>> palette_I
+>>> 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')
-{'Grassy Green': '#9bc400',
- 'Purple Mountains Majesty': '#8076a3',
- 'Misty Mountain Pink': '#f9c5bd',
- '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')
```
-Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be _overwritten_ by the corresponding `value` from `dict_two`.
+## 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',
- 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
->>> palette_III = {'Grassy Green': (155, 196, 0), 'Purple Mountains Majesty': (128, 118, 163),
+>>> 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),
@@ -137,14 +188,21 @@ Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be
'Green Treeline': '#478559', 'Purple baseline': '#161748'}
```
-Python 3.9 introduces a different means of merging `dicts`: the `union` operators.
-`dict | other_dict` will create a **new** `dict`, made up of the `key:value` pairs of `dict` and `other_dict`.
-When both dictionaries share keys, the `other_dict` values will take precedence.
-`dict |= other` will behave similar to `dict.update()`, but in this case, `other` can be either a `dict` or an iterable of `key:value` pairs.
+## 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'}
+>>> 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
...
@@ -154,44 +212,34 @@ When both dictionaries share keys, the `other_dict` values will take precedence.
'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)}
- >>> 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'}
```
-## Tips and Tricks
-
-As of Python 3.6, `dicts` preserve the order in which items are inserted, allowing ordered iteration using `.items()`. As of Python 3.8, `dict` _views_ are reversible, allowing keys, values or items to be iterated over reverse of insertion order by using `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())`.
+`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_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)
+>>> 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
...
-('Purple baseline', '#161748')
-('Green Treeline', '#478559')
-('Factory Stone Purple', '#7c677f')
-
+{'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'}
```
-While `dict` does not have a built-in sorting method, it is possible to sort a dictionary _view_ by keys or values using the built-in `sorted()` with `dict.items()`. The sorted view can then be used to create a new, sorted dictionary. Unless a _sort key_ is specified, the default sort is over dictionary keys.
+## 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',
@@ -200,6 +248,7 @@ While `dict` does not have a built-in sorting method, it is possible to sort a d
'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',
@@ -208,7 +257,10 @@ While `dict` does not have a built-in sorting method, it is possible to sort a d
'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',
@@ -217,110 +269,93 @@ While `dict` does not have a built-in sorting method, it is possible to sort a d
'Purple Mountains Majesty': '#8076a3',
'Grassy Green': '#9bc400',
'Misty Mountain Pink': '#f9c5bd'}
-
```
-Swapping keys and values reliably in a dictionary takes a little more work, but can be accomplished via a loop using `dict.items()`. But if the values stored in the `dict` are not unique, extra checks are required. Both methods assume that `dict` keys and values are _hashable_.
-
-```python
+## 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',
- 'Pink highlight': '#f95d9b',
- 'Bluewater lowlight': '#39a0ca',
- 'Bright Red': '#DE354C',
- 'Deep Red': '#932432',
- 'Pure Purple': '#3C1874',
- 'Purple Tinged Grey': '#283747',
- 'Cloud': '#F3F3F3'}
-
->>> reversed_color_reference = {}
+ '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():
-... reversed_color_reference[value] = key
+... 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'}
->>> reversed_color_reference
+
+# 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',
- '#f95d9b': 'Pink highlight',
- '#39a0ca': 'Bluewater lowlight',
- '#DE354C': 'Bright Red',
- '#932432': 'Deep Red',
- '#3C1874': 'Pure Purple',
- '#283747': 'Purple Tinged Grey',
- '#F3F3F3': 'Cloud'}
-
-
->>> extended_color_reference = {'#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',
- '#161748': 'Purple baseline',(22, 23, 72): 'Purple baseline',
- (69, 68, 0, 72): 'Purple baseline','#f95d9b': 'Pink highlight',
- (249, 93, 155): 'Pink highlight',(0, 63, 38, 2): 'Pink highlight',
- '#39a0ca': 'Bluewater lowlight',(57, 160, 202): 'Bluewater lowlight',
- (72, 21, 0, 21): 'Bluewater lowlight','#DE354C': 'Bright Red',
- (222, 53, 76): 'Bright Red',(0, 76, 66, 13): 'Bright Red',
- '#932432': 'Deep Red',(147, 36, 50): 'Deep Red',
- (0, 76, 66, 42): 'Deep Red','#3C1874': 'Pure Purple',
- (60, 24, 116): 'Pure Purple',(48, 79, 0, 55): 'Pure Purple',
- '#283747': 'Purple Tinged Grey',(40, 55, 71): 'Purple Tinged Grey',
- (44, 23, 0, 72): 'Purple Tinged Grey','#F3F3F3': 'Cloud',
- (243, 243, 243): 'Cloud',(0, 0, 0, 5): 'Cloud'}
+ '#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:
+... if value in consolidated_colors: #Check if key has already been created.
... consolidated_colors[value].append(key)
... else:
-... consolidated_colors[value] = [key]
+... 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)],
- 'Purple baseline': ['#161748', (22, 23, 72), (69, 68, 0, 72)],
- 'Pink highlight': ['#f95d9b', (249, 93, 155), (0, 63, 38, 2)],
- 'Bluewater lowlight': ['#39a0ca', (57, 160, 202), (72, 21, 0, 21)],
- 'Bright Red': ['#DE354C', (222, 53, 76), (0, 76, 66, 13)],
- 'Deep Red': ['#932432', (147, 36, 50), (0, 76, 66, 42)],
- 'Pure Purple': ['#3C1874', (60, 24, 116), (48, 79, 0, 55)],
- 'Purple Tinged Grey': ['#283747', (40, 55, 71), (44, 23, 0, 72)],
- 'Cloud': ['#F3F3F3', (243, 243, 243), (0, 0, 0, 5)]}
-
+ '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.
-For more on sorting, see the [Sorting HOW TO][sorting-howto] in the python docs.
- [Real Python][how-to-dicts] and [Finxter][fi-dict-guide] also have very thorough articles on Python dictionaries.
-## Extending Dictionaries: The collections module
+[Real Python][how-to-dicts] and [Finxter][fi-dict-guide] also have very thorough articles on Python dictionaries.
-The [`collections`][collections-docs] module adds more functionality to Python's standard collection-based datatypes (`dictionary`, `set`, `list`, `tuple`).
-A popular `dict`-oriented member of this module is the [`Counter`][counter-dicts], which automatically counts items and returns them a `dict` with the items as keys and their counts as values.
-There is also the [`OrderedDict`][ordered-dicts-docs], which has methods specialized for re-arranging the order of a dictionary.
-Finally, there is the [`defaultdict`][default-dicts], a subclass of the built-in `dict` module that, based on a factory method, sets a default value if a key is not found when trying to retrieve or assign the value.
+For more on sorting, see the [Sorting HOW TO][sorting-howto] in the Python docs.
-[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable
-[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table
-[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
-[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
-[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp
-[fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys
-[collections-docs]: https://docs.python.org/3/library/collections.html
-[counter-dicts]: https://docs.python.org/3/library/collections.html#collections.Counter
-[ordered-dicts-docs]: https://docs.python.org/3/library/collections.html#collections.OrderedDict
-[default-dicts]: https://docs.python.org/2/library/collections.html#collections.defaultdict
-[dict-views]: https://docs.python.org/3/library/stdtypes.html#dict-views
[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
index 52868299b9d..c15fbc113de 100644
--- a/concepts/dict-methods/introduction.md
+++ b/concepts/dict-methods/introduction.md
@@ -1,17 +1,16 @@
# Dictionary Methods in Python
-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 [hash table or hashmap][hashtable-wikipedia].
-In Python, it's considered a [mapping type][mapping-types-dict].
-`dicts` enable the retrieval of a value in constant time (on average), given the key.
+The `dict` class in Python provides many useful [methods][dict-methods], some of which are introduced in the concept exercise for dictionaries.
-Compared to searching for a value within a list or array (_without knowing the index position_), a dictionary uses significantly more memory, but has very rapid retrieval.
-It's especially useful in scenarios where the collection of items is large and must be accessed/updated frequently.
+This concept tackles a few more:
-The `dict` class in Python provides many useful [methods][dict-methods] for working with dictionaries.
-Some are introduced in the concept exercise for `dicts`.
-This concept tackles a few more - along with some techniques for iterating through and manipulating `dicts`.
+- `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.
-[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
-[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table
-[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable
[dict-methods]: https://docs.python.org/3/library/stdtypes.html#dict
diff --git a/concepts/dicts/about.md b/concepts/dicts/about.md
index fb43067efe2..72ea9079c6d 100644
--- a/concepts/dicts/about.md
+++ b/concepts/dicts/about.md
@@ -1,55 +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].
-A dictionary (`dict`) is a [mapping type][mapping-types-dict] data structure that associates [hashable][term-hashable] `keys` to `values` -- known in other programming languages as a resizable [hash table or hashmap][hashtable-wikipedia].
- `Keys` can include `numbers`, `str`, `tuples` (of _immutable_ values), or `frozensets`, but must be hashable and unique across the dictionary.
- `keys` are _immutable_ - once added to a `dict`, they can only be removed, they cannot be updated.
- `values` can be of any or multiple data type(s) or structures, including other dictionaries, built-in types, custom types, or even objects like functions or classes.
- `values` associated with any `key` are _mutable_, and can be replaced, updated or altered as long as the `key` entry exists.
- Dictionaries enable the retrieval of a `value` in (on average) constant O(1) time, given the `key`.
+`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.
- Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more space in memory, but has significantly more rapid retrieval.
- Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and/or updated frequently.
+`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.
-## Dictionary creation
+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.
-A simple `dict` can be declared using the literal form `{: , : }`:
+Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently.
- ```python
+## 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'
```
- The dictionary constructor `dict(=, =)`, but there are many more ways of creating and initializing dictionaries including the use of a _dict comprehension_ or passing additional constructor parameters as illustrated in the [Python docs][mapping-types-dict].
+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'
+```
-Inserting a new `key`:`value` pair can be done with `dict[key] = value` and the value can be retrieved by using `retrieved_value = dict[key]`.
+## Looping Through/Iterating over a Dictionary
-## Methods
+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_:
-`dicts` implement various methods to allow easy initialization, updating and viewing.
+```python
+>>> for key in bear:
+>>> print((key, bear[key])) #this prints a tuple of (key, value)
+('name', 'Black Bear')
+('speed', 40)
+('land_animal', True)
+```
-Some useful `dict` methods:
+You can also use the `.items()` method, which returns (`key`, `value`) tuples:
-- Retrieve a value "safely" from a dictionary by using the `.get(key, [default])` method. `.get(key, [default])` returns the value for the key **or** the _default value_ if the key is not found, instead of raising 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.
-- Retrieve a value "safely" or insert a default _value_ if the key is not found using the `.setdefault(key, [default])` method. `setdefault(key, [default])` will insert the default value in the dictionary **only** if the key is not found, then it will retrieve either the **newly inserted** default value if the key was not found or the **unchanged** existing value if the key was found.
-- Return various _iterable_ views of your `dict` with `.keys()`, `.values()`, `.items()` (_an iterable of (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 Dictionaries: The collections module
-The [`collections`][collections-docs] module adds more functionality to Python's standard collection-based datatypes (`dictionary`, `set`, `list`, `tuple`). A popular `dict`-oriented member of this module is the [`Counter`][counter-dicts], which automatically counts items and returns them a `dict` with the items as keys and their counts as values. There is also the [`OrderedDict`][ordered-dicts-docs], which has methods specialized for re-arranging the order of a dictionary. Finally, there is the [`defaultdict`][default-dicts], a subclass of the built-in `dict` module that, based on a factory method, sets a default value if a key is not found when trying to retrieve or assign the value.
+## Extending Dictionary Functionality: The Collections Module
-[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable
-[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table
-[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
-[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
-[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp
+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
-[ordered-dicts-docs]: https://docs.python.org/3/library/collections.html#collections.OrderedDict
[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
index 9a887ccfc65..5c8a772480b 100644
--- a/concepts/dicts/introduction.md
+++ b/concepts/dicts/introduction.md
@@ -1,15 +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].
-A dictionary (`dict`) is a [mapping type][mapping-types-dict] data structure that associates [hashable][term-hashable] `keys` to `values` -- known in other programming languages as a resizable [hash table or hashmap][hashtable-wikipedia].
- `Keys` can include `numbers`, `str`, `tuples` (of _immutable_ values), or `frozensets`, but must be hashable and unique across the dictionary.
- `values` can be of any or multiple data type(s) or structures, including other dictionaries, built-in types, custom types, or even objects like functions or classes.
- Dictionaries enable the retrieval of a `value` in (on average) constant O(1) time, given the `key`.
- Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more space in memory, but has significantly more rapid retrieval.
- Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and/or updated frequently.
+`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/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/about.md b/concepts/function-arguments/about.md
index 09b01b10862..0f2ab5dddda 100644
--- a/concepts/function-arguments/about.md
+++ b/concepts/function-arguments/about.md
@@ -4,7 +4,7 @@ For the basics on function arguments, please see the [function concept][function
## Parameter Names
-Paramater names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers.
+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
@@ -182,7 +182,7 @@ For instance, `*` is used for multiplication, it is used for unpacking, and it i
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 amount of values being passed to a function:
+Following is an example of an arbitrary number of values being passed to a function:
```python
@@ -196,7 +196,7 @@ Following is an example of an arbitrary amount of values being passed to a funct
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 amount of values being passed to a function after a positional argument:
+Following is an example of an arbitrary number of values being passed to a function after a positional argument:
```python
@@ -210,7 +210,7 @@ Following is an example of an arbitrary amount of values being passed to a funct
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 amount of values being passed to a function that also has a positional argument and a default argument:
+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
@@ -228,7 +228,7 @@ To put it all together is an example of an arbitrary amount of values being pass
```
-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 amount of separate arguments.
+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.
@@ -257,7 +257,7 @@ The `**` transforms the group of named arguments into a [`dictionary`][dictionar
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 amount of key-value pairs being passed to a function:
+Following is an example of an arbitrary number of key-value pairs being passed to a function:
```python
>>> def add(**kwargs):
@@ -271,7 +271,7 @@ Note that the `dict.values()` method is called to iterate through the `kwargs` d
When iterating a dictionary the default is to iterate the keys.
-Following is an example of an arbitrary amount of key-value pairs being passed to a function that then iterates over `kwargs.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):
diff --git a/concepts/function-arguments/introduction.md b/concepts/function-arguments/introduction.md
index 171675ce3c4..07b885f332e 100644
--- a/concepts/function-arguments/introduction.md
+++ b/concepts/function-arguments/introduction.md
@@ -4,7 +4,7 @@ For the basics on function arguments, please see the [function concept][function
## Parameter Names
-Paramater names, like variable names, must start with a letter or underscore and may contain letters, underscores, or numbers.
+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
diff --git a/concepts/functions/about.md b/concepts/functions/about.md
index 9d8fddfa956..f3630af763c 100644
--- a/concepts/functions/about.md
+++ b/concepts/functions/about.md
@@ -2,11 +2,11 @@
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_][build-in functions] ([`print`][print], [`map`][map], and so on) that are readily available.
+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 parenthesese [`()`].
+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.
@@ -376,7 +376,7 @@ The full list of function attributes can be found at [Python DataModel][attribut
[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
-[build-in functions]: https://docs.python.org/3/library/functions.html
+[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
diff --git a/concepts/functools/about.md b/concepts/functools/about.md
index 32748a45c23..e5afb577d39 100644
--- a/concepts/functools/about.md
+++ b/concepts/functools/about.md
@@ -12,7 +12,7 @@ The functools module is for higher-order functions: functions that act on or ret
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 lastest 128 function calls at max.
+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.
@@ -23,8 +23,8 @@ The lru_cache works the same way but it can cache at max maxsize calls and if ty
```python
>>> @cache
- def factorial(n):
- return n * factorial(n-1) if n else 1
+>>> def factorial(n):
+>>> return n * factorial(n-1) if n else 1
>>> factorial(10) # no previously cached result, makes 11 recursive calls
3628800
@@ -37,9 +37,10 @@ The lru_cache works the same way but it can cache at max maxsize calls and if ty
# 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
+>>> @lru_cache(maxsize = 128)
+>>> def factorial(n):
+>>> return n * factorial(n-1) if n else 1
+
>>> factorial(10)
3628800
@@ -50,7 +51,7 @@ CacheInfo(hits=0, misses=11, maxsize=128, currsize=11)
## 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.
+***[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.
@@ -193,11 +194,11 @@ True
The ```pow_2.func``` is same as the ```pow``` function.
-Here ```pow_2.args``` return an empty tuple becuse we does not pass any positional argument to out partial object call.
+Here ```pow_2.args``` returns an empty tuple because we do not pass any positional argument to our partial object call.
-```pow_2.keywords``` return a dictionary of keywords argument which will be supplied when the partial object is called.
+```pow_2.keywords``` returns a dictionary of keywords argument which will be supplied when the partial object is called.
-Here ```two_pow.args``` return an ```(2,)``` tuple because we passed 2 as an argument while creating the pratial object, which fixed the value of ```base``` argument as ```2```.
+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```
diff --git a/concepts/functools/introduction.md b/concepts/functools/introduction.md
index 15e83e3e61a..c91aedc81bd 100644
--- a/concepts/functools/introduction.md
+++ b/concepts/functools/introduction.md
@@ -12,7 +12,7 @@ The functools module is for higher-order functions: functions that act on or ret
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 lastest 128 function calls at max.
+Here ```maxsize = 128``` means that it is going to memoize latest 128 function calls at max.
### ```@functools.cache(user_function)```
diff --git a/concepts/generators/.meta/config.json b/concepts/generators/.meta/config.json
index 2204a700df6..3322727ef74 100644
--- a/concepts/generators/.meta/config.json
+++ b/concepts/generators/.meta/config.json
@@ -1,5 +1,9 @@
{
- "blurb": "Learn about generators by assigning seats to passengers.",
+ "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": []
+ "contributors": [
+ "BethanyG",
+ "kytrinyx",
+ "meatball133"
+ ]
}
diff --git a/concepts/generators/about.md b/concepts/generators/about.md
index f1b97291e4c..59b5035d6b9 100644
--- a/concepts/generators/about.md
+++ b/concepts/generators/about.md
@@ -1,68 +1,74 @@
# 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:
-
+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
+... 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(list_of_numbers):
- for number in list_of_number:
- yield number ** 2
+>>> 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.
+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:
+To use the `squares_generator()` generator:
```python
->>> squared_numbers = squares([1, 2, 3, 4])
+>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
->>> print(square)
+... print(square)
+...
1
4
9
16
```
-Values within a generator can also be produced/accessed via the `next()` function.
+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
-square_generator = squares([1, 2])
+>>> squared_numbers = squares_generator([1, 2])
->>> next(square_generator)
+>>> next(squared_numbers)
1
->>> next(square_generator)
+>>> next(squared_numbers)
4
```
When a `generator` is fully consumed and has no more values to return, it throws a `StopIteration` error.
```python
->>> next(square_generator)
+>>> next(squared_numbers)
Traceback (most recent call last):
File "", line 1, in
StopIteration
@@ -72,12 +78,12 @@ StopIteration
Generators are a special sub-set of _iterators_.
`Iterators` are the mechanism/protocol that enables looping over _iterables_.
-Generators and and the iterators returned by common Python (`iterables`)[https://wiki.python.org/moin/Iterator] act very similarly, but there are some important differences to note:
-
+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.
@@ -88,7 +94,7 @@ Generators and and the iterators returned by common Python (`iterables`)[https:/
## The yield expression
-The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` 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.
@@ -96,14 +102,16 @@ When `yield` is evaluated, it pauses the execution of the enclosing function and
The function then _stays in scope_, and when `__next__()` is called, execution resumes until `yield` is encountered again.
-Note: _Using `yield` expressions is prohibited outside of functions._
+~~~~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
+... current_number = 0
+... while True:
+... yield current_number
+... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
@@ -123,10 +131,17 @@ Generators are also very helpful when a process or calculation is _complex_, _ex
```python
>>> def infinite_sequence():
->>> current_number = 0
->>> while True:
->>> yield current_number
->>> current_number += 1
+... 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
index 2c148371330..ad1175ca0b6 100644
--- a/concepts/generators/introduction.md
+++ b/concepts/generators/introduction.md
@@ -1,5 +1,13 @@
# Introduction
-A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation).
+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.
-_Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: 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
index b8ae2f7b648..134a723c693 100644
--- a/concepts/generators/links.json
+++ b/concepts/generators/links.json
@@ -1,10 +1,18 @@
[
{
- "url": "https://docs.python.org/3.8/reference/expressions.html#yield-expressions",
- "description": "Official Python 3.8 docs for the yield expression."
+ "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/list-methods/about.md b/concepts/list-methods/about.md
index 3a5132fd0ad..1c9686360d4 100644
--- a/concepts/list-methods/about.md
+++ b/concepts/list-methods/about.md
@@ -136,10 +136,22 @@ The order of list elements can be reversed _**in place**_ with `.reverse(
[3, 2, 1]
```
-List elements can be sorted _**in place**_ using `.sort()`.
- Internally, Python uses [`Timsort`][timsort] to arrange the elements.
- The default order is _ascending_.
- The Python docs have [additional tips and techniques for sorting][sorting how to] `lists` effectively.
+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
@@ -254,7 +266,7 @@ For a detailed explanation of names, values, list, and nested list behavior, tak
[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
-[timsort]: https://en.wikipedia.org/wiki/Timsort
[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
diff --git a/concepts/lists/about.md b/concepts/lists/about.md
index 014a6d56725..f7d4054eef0 100644
--- a/concepts/lists/about.md
+++ b/concepts/lists/about.md
@@ -7,7 +7,7 @@ A [`list`][list] is a mutable collection of items in _sequence_.
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(` 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_).
@@ -18,7 +18,7 @@ Accessing elements, checking for membership via `in`, or appending items to the
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 objects, they also take up more space in memory than a fixed-size [`array.array`][array.array] type of the same apparent length.
+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.
@@ -135,7 +135,8 @@ TypeError: 'int' object is not iterable
## 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_).
+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_).
@@ -173,9 +174,11 @@ Items inside lists (_as well as elements in other sequence types such as [`str`]
'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 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 (`[start:stop:step]`) to "skip over" or filter the returned elements (_for example, a `step` of 2 will select every other element in the section_):
+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"]
@@ -269,7 +272,7 @@ Lists can also be combined via various techniques:
>>> first_one
['George', 5, 'cat', 'Tabby']
-# This loops through the first list and appends it's items to the end of the second list.
+# 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]
@@ -284,7 +287,7 @@ Lists can also be combined via various techniques:
## 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.
+`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.
@@ -293,7 +296,7 @@ Assigning a `list` object to a new variable _name_ **does not copy the `list` ob
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-leve referencing complication.
+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.
@@ -305,21 +308,22 @@ A `shallow_copy` will create a new `list` object, but **will not** create new ob
# Altering the list via the new name is the same as altering the list via the old name.
>>> same_list.append("Clarke")
->>> same_list
["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"
->>> same_list
['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"]
```
@@ -455,4 +459,4 @@ The collections module also provides a `UserList` type that can be customized to
[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
\ No newline at end of file
+[tuple]: https://docs.python.org/3/library/stdtypes.html#tuple
diff --git a/concepts/loops/about.md b/concepts/loops/about.md
index 88bb71e6194..0f39e733d0c 100644
--- a/concepts/loops/about.md
+++ b/concepts/loops/about.md
@@ -235,17 +235,17 @@ The loop [`else` clause][loop else] is unique to Python and can be used for "wra
'Found an S, stopping iteration.'
```
-[loop else]: https://docs.python.org/3/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops
-[range]: https://docs.python.org/3/library/stdtypes.html#range
[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
-[while statement]: https://docs.python.org/3/reference/compound_stmts.html#the-while-statement
-[truth value testing]: https://docs.python.org/3/library/stdtypes.html#truth-value-testing
[enumerate]: https://docs.python.org/3/library/functions.html#enumerate
-[iterator]: https://docs.python.org/3/glossary.html#term-iterator
-[common sequence operations]: https://docs.python.org/3/library/stdtypes.html#common-sequence-operations
-[range is not an iterator]: https://treyhunner.com/2018/02/python-range-is-not-an-iterator/
[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/numbers/.meta/config.json b/concepts/numbers/.meta/config.json
index 583a8284a81..7898f6099aa 100644
--- a/concepts/numbers/.meta/config.json
+++ b/concepts/numbers/.meta/config.json
@@ -1,5 +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.",
+ "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"]
+ "contributors": ["BethanyG", "KaiAragaki", "meatball133"]
}
diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md
index e746972d1fd..1155bcf7a5c 100644
--- a/concepts/numbers/about.md
+++ b/concepts/numbers/about.md
@@ -1,8 +1,9 @@
# 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.
+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 hex, octals and binary numbers_) **without** decimal places are identified as `ints`:
+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.
@@ -15,130 +16,187 @@ Whole numbers (_including hex, octals and binary numbers_) **without** decimal p
-12
```
-Hex numbers are prefixed with `0x`:
+Numbers containing a decimal point (with or without fractional parts) are identified as `floats`:
```python
-# Hex numbers start with 0x.
->>> 0x17
-23
->>> type(0x17)
-
+>>> 3.45
+3.45
+>>> type(3.45)
+
```
-Octals are prefixed with a `0o`:
+## 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
-# Octal numbers start with a 0o.
->>> 0o446
-294
->>> type(0o446)
-
+>>> 5 - 3
+2
+# The int is widened to a float here, and a float is returned.
+>>> 3 + 4.0
+7.0
```
-Binary numbers are prefixed with `0b`, and written with only zeros and ones:
+### Multiplication
+
+As with addition and subtraction, multiplication will convert narrower numbers to match their less narrow counterparts:
```python
-# Binary numbers are made up of 0s and 1s.
->>> 0b1100110
-102
->>> type(0b1100110)
-
+>>> 3 * 2
+6
+
+>>> 3 * 2.0
+6.0
```
-Each of these `int` displays can be converted into the other via constructor:
+### Division
-```python
+Division always returns a `float`, even if the result is a whole number:
->>> starting_number = 1234
+```python
+>>> 6/5
+1.2
->>> hex(starting_number)
-'0x4d2'
+>>> 6/2
+3.0
+```
->>> oct(starting_number)
-'0o2322'
+### Floor division
->>> bin(starting_number)
-'0b10011010010'
+If an `int` result is needed, you can use floor division to truncate the result.
+Floor division is performed using the `//` operator:
->>> hex(0b10011010010)
-'0x4d2'
+```python
+>>> 6//5
+1
->>> int(0x4d2)
-1234
+>>> 6//2
+3
```
-Numbers containing a decimal point (with or without fractional parts) are identified as `floats`:
+### Modulo
+
+The modulo operator (`%`) returns the remainder of the division of the two operands:
```python
->>> 3.45
-3.45
->>> type(3.45)
-
+# 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
```
-Appending `j` or `J` to a number creates an _imaginary number_ -- a `complex` number with a zero real part. `ints` or `floats` can then be added to an imaginary number to create a `complex` number with both real and imaginary parts:
+Another way to look at 5 % 3:
```python
->>> 3j
-3j
->>> type(3j)
-
+>>> whole_part = int(5/3)
+1
+
+>>> decimal_part = 5/3 - whole_part
+0.6666666666666667
->>> 3.5+4j
-(3.5+4j)
+>>> whole_remainder = decimal_part * 3
+2.0
```
-## Arithmetic
+## Exponentiation
-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 `%`).
-
-Python considers `ints` narrower than `floats`, which are considered narrower than `complex` numbers. Comparisons between different number types behave as if the _exact_ values of those numbers were being compared:
+Exponentiation is performed using the `**` operator:
```python
-# The int is widened to a float here, and a float is returned.
->>> 3 + 4.0
-7.0
+>>> 2 ** 3
+8
+
+>>> 4 ** 0.5
+2
+```
-# The int is widened to a complex number, and a complex number is returned.
->>> 6/(3+2j)
-(2+2j)
+## Conversions
-# Division always returns a float, even if integers are used.
->>> 6/2
+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
+```
-# If an int result is needed, you can use floor division to truncate the result.
->>> 6//2
+## 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
+```
-# When comparing, exact values are used.
->>> 23 == 0x17
-True
+## Priority and parentheses
->>> 0b10111 == 0x17
-True
+Python allows you to use parentheses to group expressions.
+This is useful when you want to override the default order of operations.
->>> 6 == (6+0j)
-True
+```python
+>>> 2 + 3 * 4
+14
+
+>>> (2 + 3) * 4
+20
```
-All numbers (except complex) support all [arithmetic operations][arethmetic-operations], evaluated according to [operator precedence][operator precedence]. Support for mathematical functions (beyond `+`, `-`, `/`) for complex numbers can be found in the [cmath][cmath] module.
+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](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) -- the amount of digits is limited only by the available memory of the host system.
+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.
+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 langages, take a look at [0.30000000000000004.com][0.30000000000000004.com] and [The Python Tutorial][floating point math].
+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].
-[int]: https://docs.python.org/3/library/functions.html#int
-[float]: https://docs.python.org/3/library/functions.html#float
-[complex]: https://docs.python.org/3/library/functions.html#complex
-[fractions]: https://docs.python.org/3/library/fractions.html
-[decimals]: https://docs.python.org/3/library/decimal.html#module-decimal
[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
-[arethmetic-operations]: https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex
-[operator precedence]: https://docs.python.org/3/reference/expressions.html#operator-precedence
+[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
index c72139289a7..3491bc20a3c 100644
--- a/concepts/numbers/introduction.md
+++ b/concepts/numbers/introduction.md
@@ -1,97 +1,18 @@
# Introduction
-## Numbers
+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.
-There are three different kinds of built-in numbers in Python : `ints`, `floats`, and `complex`. However, in this exercise you'll be dealing only with `ints` and `floats`.
+Whole numbers including hexadecimal ([_`hex()`_][hex]), octal ([_`oct()`_][oct]) and binary ([_`bin()`_][bin]) numbers **without** decimal places are also identified as `ints`.
-### 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 `%`).
-`ints` are whole numbers. e.g. `1234`, `-10`, `20201278`.
-Integers in Python have [arbitrary precision][arbitrary-precision] -- the amount of digits is limited only by the available memory of the host system.
-
-### floats
-
-`floats` or `floating point numbers` contain a decimal point. e.g. `0.0`,`3.14`,`-9.01`.
-
-Floating point numbers are usually implemented in Python using a `double` in C (_15 decimal places of precision_), but will vary in representation based on the host system and other implementation details. This can create some surprises when working with floats, but is "good enough" for most situations.
-
-You can see more details and discussions in the following resources:
-
-- [Python numeric type documentation][numeric-type-docs]
-- [The Python Tutorial][floating point math]
-- [Documentation for `int()` built in][`int()` built in]
-- [Documentation for `float()` built in][`float()` built in]
-- [0.30000000000000004.com][0.30000000000000004.com]
-
-### Precision
-
-Before diving into arithmetic, it is worth thinking about what precision means. Precision is the level of exactness at which a number can be represented. An `int` is less precise than a `float` in the same way that `1` is less precise than `1.125`.
-
-## Arithmetic
-
-Python fully supports arithmetic between `ints` and `floats`. It will convert narrower numbers to match their wider (or more precise) counterparts when used with the binary arithmetic operators (`+`, `-`, `*`, `/`, `//`, and `%`). When division with `/`, `//` returns the quotient and `%` returns the remainder.
-
-Python considers `ints` narrower than `floats`. So, using a float in an expression ensures the result will be a float too. However, when doing division, the result will always be a float, even if only integers are used.
-
-```python
-# The int is widened to a float here, and a float type is returned.
->>> 3 + 4.0
-7.0
->>> 3 * 4.0
-12.0
->>> 3 - 2.0
-1.0
-# Division always returns a float.
->>> 6 / 2
-3.0
->>> 7 / 4
-1.75
-# Calculating remainders.
->>> 7 % 4
-3
->>> 2 % 4
-2
->>> 12.75 % 3
-0.75
-```
-
-If an int result is needed, you can use `//` to truncate the result.
-
-```python
->>> 6 // 2
-3
->>> 7 // 4
-1
-```
-
-To convert a float to an integer, you can use `int()`. Also, to convert an integer to a float, you can use `float()`.
-
-```python
->>> int(6 / 2)
-3
->>> float(1 + 2)
-3.0
-```
-
-## Underscores in Numeric Literals
-
-As of version 3.6, Python supports the use of underscores in numerical literals to improve readability:
-```python
-# A float with underscores
->>> dollars = 35_000_000.0
->>> print(dollars)
-35000000.0
-```
-
-The rules for underscores are outline in [pep 515][pep 515] under 'Literal Grammar' are quite dense, but essentially boil down to:
-* Underscores can only be between two digits (not at beginning or ends of numbers, or next to signs (+/-) or decimals points)
-* No consecutive underscores
-
-[arbitrary-precision]: https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic#:~:text=In%20computer%20science%2C%20arbitrary%2Dprecision,memory%20of%20the%20host%20system.
-[numeric-type-docs]: https://docs.python.org/3/library/stdtypes.html#typesnumeric
-[`int()` built in]: https://docs.python.org/3/library/functions.html#int
-[`float()` built in]: https://docs.python.org/3/library/functions.html#float
-[0.30000000000000004.com]: https://0.30000000000000004.com/
-[floating point math]: https://docs.python.org/3.9/tutorial/floatingpoint.html
-[pep 515]: https://www.python.org/dev/peps/pep-0515/
+[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/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/about.md b/concepts/recursion/about.md
index 1c66756caf2..1cf24388269 100644
--- a/concepts/recursion/about.md
+++ b/concepts/recursion/about.md
@@ -6,7 +6,7 @@ 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 amount of function calls takes up more space than the stack has room for, it will result in an error.
+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
diff --git a/concepts/recursion/introduction.md b/concepts/recursion/introduction.md
index aebfd6596be..fb7e1970705 100644
--- a/concepts/recursion/introduction.md
+++ b/concepts/recursion/introduction.md
@@ -5,7 +5,7 @@ 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 amount of function calls takes up more space than the stack has room for, it will result in an error.
+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):
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/sets/about.md b/concepts/sets/about.md
index dd567f070f4..204df380577 100644
--- a/concepts/sets/about.md
+++ b/concepts/sets/about.md
@@ -1,119 +1,153 @@
# Sets
-A [`set`][type-set] is a mutable and _unordered_ collection of _hashable_ objects.
-Items within a `set` are distinct and duplicate members are not allowed.
-Like most collections, `sets` can hold any (or multiple) data type(s) -- as long as those types can be [hashed][hashable].
-Sets also come in an _immutable_ [`frozenset`][type-frozenset] flavor.
+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.
-Like other collection types, `sets` support membership testing through `in`, length calculation through `len()`, shallow copies through `copy()`, and iteration via `for item in `.
-_Unlike_ sequence types (_`string`, `list` & `tuple`_), `sets` are **neither ordered nor indexed**, and _do not support_ slicing, sorting, or other sequence-type behaviors.
+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.
-`sets` are most commonly used to quickly dedupe groups of items.
-They're also used for fast membership testing, finding supersets & subsets of items, and performing "set math" (_calculating union, intersection, difference & symmetric difference between groups of items._).
+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()`
-Checking membership in a `set` has only O(1) time complexity versus checking for membership in a `list` or `string`, which has worst-case O(n) time complexity.
-Operations such as `.union()`, `.intersection()`, or `.diference()` have an average O(n) time complexity.
+`sets` do not support:
+- Indexing of any kind
+- Ordering via sorting or insertion
+- Slicing
+- Concatenation via `+`
-## Construction
-A `set` can be declared as a _set literal_ with curly `{}` brackets and commas between elements.
+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 = {'π'}
->>> one_element
-{'π'}
+>>> one_element = {'β'}
+{'β'}
->>> multiple_elements = {'Hello!', 'Β‘Hola!', 'ΠΡΠΈΠ²Π΅Ρ!', 'γγγ«γ‘γ―οΌ'}
->>> multiple_elements
-{'γγγ«γ‘γ―οΌ', 'Β‘Hola!', 'Hello!', 'ΠΡΠΈΠ²Π΅Ρ!'}
+>>> multiple_elements = {'β', 'π»', 'πΉ', 'π'}
+{'β', 'π»', 'πΉ', 'π'}
->>> multiple_duplicates = {'Hello!', 'Β‘Hola!', 'ΠΡΠΈΠ²Π΅Ρ!', 'γγγ«γ‘γ―οΌ', 'Β‘Hola!', 'ΠΡΠΈΠ²Π΅Ρ!'}
->>> multiple_duplicates
-{'γγγ«γ‘γ―οΌ', 'Β‘Hola!', 'Hello!', 'ΠΡΠΈΠ²Π΅Ρ!'}
+>>> multiple_duplicates = {'Hello!', 'Hello!', 'Hello!',
+ 'Β‘Hola!','ΠΡΠΈΠ²ΡΡ!', 'γγγ«γ‘γ―οΌ',
+ 'Β‘Hola!','ΠΡΠΈΠ²ΡΡ!', 'γγγ«γ‘γ―οΌ'}
+{'γγγ«γ‘γ―οΌ', 'Β‘Hola!', 'Hello!', 'ΠΡΠΈΠ²ΡΡ!'}
```
-Set literals use the same curly braces as `dict` literals, so the `set()` constructor must be used to declare an empty `set`.
+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:
-The `set()` constructor can also be used with any _iterable_ passed as an argument.
-Elements are cycled through by the constructor and added to the `set` individually.
-Order is not preserved and duplicates are silently omitted:
```python
+# To create an empty set, the constructor must be used.
>>> no_elements = set()
->>> no_elements
set()
-# The tuple is unpacked and each distinct element is added. Duplicates are removed.
->>> multiple_elements_from_tuple = set(("Parrot", "Bird", 334782, "Bird", "Parrot"))
->>> multiple_elements_from_tuple
+# 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 and each distinct element is added.
->>> multiple_elements_from_list = set([2, 3, 2, 3, 3, 3, 5, 7, 11, 7, 11, 13, 13])
->>> multiple_elements_from_set
-{2, 3, 5, 7, 11}
+# 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}
```
-Results when using a set constructor with a string or dictionary may be surprising:
+### Set Comprehensions
+
+Like `lists` and `dicts`, sets can be created via _comprehension_:
```python
-# String elements (Unicode code points) are iterated through and added *individually*.
->>> multiple_elements_string = set("Timbuktu")
->>> multiple_elements_string
+# 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*.
+# Unicode separators and positioning code points
+# are also added *individually*.
>>> multiple_code_points_string = set('ΰ€
ΰ€ΰ₯ΰ€―ΰ€Ύΰ€Έ')
->>> multiple_code_points_string
{'ΰ€
', 'ΰ€', 'ΰ€―', 'ΰ€Έ', 'ΰ€Ύ', 'ΰ₯'}
-
-# The iteration default for dictionaries is over the keys.
->>> source_data = {"fish": "gold", "monkey": "brown", "duck" : "white", "crow": "black"}
->>> set(source_data)
-{'crow', 'duck', 'fish', 'monkey'}
```
-Sets can hold heterogeneous datatypes, but all `set` elements must be _hashable_:
+Remember: sets can hold different datatypes and _nested_ datatypes, but all `set` elements must be _hashable_:
```python
-
->>> lists_as_elements = {['π
','π€£'], ['π','π','π'], ['π', 'π€ͺ', 'π']}
+# Attempting to use a list for a set member throws a TypeError
+>>> lists_as_elements = {['π','π¦'],
+ ['βοΈ','βοΈ','π'],
+ ['β΅οΈ', 'π²', 'π']}
Traceback (most recent call last):
-
- File "", line 1, in
- lists_as_elements = {['π
','π€£'], ['π','π','π'], ['π', 'π€ͺ', 'π']}
-
+ 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
- sets_as_elements = {{'π
','π€£'}, {'π','π','π'}, {'π', 'π€ͺ', 'π'}}
+# 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'
```
-Therefore, to create a `set` of `sets`, the contained sets must be of type `frozenset()`
+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({'π', 'π', 'π'})
+# 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({'π', 'π', 'π'})}
+{frozenset({'β΅οΈ', 'π', 'π²'}),
+ frozenset({'π', 'π¦'}),
+ frozenset({'βοΈ', 'βοΈ', 'π'})}
```
-## Working with Sets
-Elements can be added/removed using `.add(- )` / `.remove(
- )`.
-`remove(
- )` will raise a `KeyError` if the item is not present in the `set`.
+## 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'}
@@ -122,100 +156,139 @@ Elements can be added/removed using `.add(
- )` / `.remove(
- )`
>>> creatures
{'beaver', 'crow', 'elephant', 'fish', 'monkey'}
-# Trying to remove an item that is not present will raise a KeyError
+# Trying to remove an item that is not present raises a KeyError
>>> creatures.remove('bear')
Traceback (most recent call last):
-
- File "", line 1, in
- creatures.remove('bear')
-
-KeyError: 'bear'
+ File "", line 1, in
+ KeyError: 'bear'
```
-`.discard(
- )` will also remove an item from the `set`, but will **not** raise a `KeyError` if the item is not present.
-`.clear()` will remove all items.
-`.pop()` will remove and _return_ an **arbitrary** item and raises a `KeyError` if the `set` is empty.
+### Additional Strategies for Removing Set Members
-## Set Methods
+- `.discard(
- )` will remove an item from the `set`, but will **not** raise a `KeyError` if the item is not present.
+- `.clear()` will remove all items from the set.
+- `.pop()` will remove and _return_ an **arbitrary** item, and raises a `KeyError` if the `set` is empty.
-Sets implement methods that generally mimic [mathematical set operations][mathematical-sets].
-Most (_though not all_) of these methods can be performed using either operator(s) or method call(s).
-Using operators requires that both inputs be `sets` or `frozensets`, while methods will generally take any iterable as an argument.
-### Fast Membership Testing Between Groups
+## Set Operations
-The `.isdisjoint()` method is used to test if a `set` has **no elements in common** with another set or iterable.
-It will accept any `iterable` or `set` as an arugment, returning `True` if they are **disjoint**, `False` otherwise.
-Note that for `dcts`, the iteration default is over`.keys()`.
+Sets have methods that generally mimic [mathematical set operations][mathematical-sets].
+Most (_not all_) of these methods have an [operator][operator] equivalent.
+Methods generally take any `iterable` as an argument, while operators require that both sides of the operation are `sets` or `frozensets`.
-```python
->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'}
->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}
-# Dictionary of animal names with colors
->>> animals = {'chicken': 'white','sparrow': 'grey','eagle': 'brown and white',
- 'albatross': 'grey and white','crow': 'black','elephant': 'grey',
- 'dog': 'rust','cow': 'black and white','tiger': 'orange and black',
- 'cat': 'grey','squirrel': 'black'}
+### Membership Testing Between Sets
-# List of additonal animals
->>> additional_animals = ['pangolin', 'panda', 'parrot', 'lemur', 'tiger', 'pangolin']
-...
+The `.isdisjoint()` method is used to test if a `sets` elements have any overlap with the elements of another.
+The method will accept any `iterable` or `set` as an argument.
+It will return `True` if the two sets have **no elements in common**, `False` if elements are **shared**.
->>> mammals.isdisjoint(birds)
+```python
+# Both mammals and additional_animals are lists.
+>>> mammals = ['squirrel','dog','cat','cow', 'tiger', 'elephant']
+>>> additional_animals = ['pangolin', 'panda', 'parrot',
+ 'lemur', 'tiger', 'pangolin']
+
+# Animals is a dict.
+>>> animals = {'chicken': 'white',
+ 'sparrow': 'grey',
+ 'eagle': 'brown and white',
+ 'albatross': 'grey and white',
+ 'crow': 'black',
+ 'elephant': 'grey',
+ 'dog': 'rust',
+ 'cow': 'black and white',
+ 'tiger': 'orange and black',
+ 'cat': 'grey',
+ 'squirrel': 'black'}
+
+# Birds is a set.
+>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}
+
+# Mammals and birds don't share any elements.
+>>> birds.isdisjoint(mammals)
True
->>> mammals.isdisjoint(animals)
-False
-
+# There are also no shared elements between
+# additional_animals and birds.
>>> birds.isdisjoint(additional_animals)
True
->>> set(additional_animals).isdisjoint(animals)
+# Animals and mammals have shared elements.
+# **Note** The first object needs to be a set or converted to a set
+# since .isdisjoint() is a set method.
+>>> set(animals).isdisjoint(mammals)
False
```
-`.issubset()` | ` <= ` are used to check if every element in `` is also in ``.
-`.issuperset()` | ` >= ` are used to check the inverse -- if every element in `` is also in ``.
+### Checking for Subsets and Supersets
-```python
->>> animals = {'chicken': 'white','sparrow': 'grey','eagle': 'brown and white',
- 'albatross': 'grey and white','crow': 'black','elephant': 'grey',
- 'dog': 'rust','cow': 'black and white','tiger': 'organge and black',
- 'cat': 'grey','squirrel': 'black'}
+`.issubset()` is used to check if every element in `` is also in ``.
+The operator form is ` <= `:
->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'}
->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}
+```python
+# Set methods will take any iterable as an argument.
+# All members of birds are also members of animals.
+>>> birds.issubset(animals)
+True
-# Methods will take any iterable as an argument
->>> mammals.issubset(animal_colors)
+# All members of mammals also appear in animals.
+# **Note** The first object needs to be a set or converted to a set
+# since .issubset() is a set method.
+>>> set(mammals).issubset(animals)
True
+# Both objects need to be sets to use a set operator
+>>> birds <= set(mammals)
+False
-# A set is always a loose subset of itself
->>> animals <= animals
+# A set is always a loose subset of itself.
+>>> set(additional_animals) <= set(additional_animals)
True
+```
+
+`.issuperset()` is the inverse of `.issubset()`.
+It is used to check if every element in `` is also in ``.
+The operator form is ` >= `:
->>> birds <= animals
+
+```python
+# All members of mammals also appear in animals.
+# **Note** The first object needs to be a set or converted to a set
+# since .issuperset() is a set method.
+>>> set(animals).issuperset(mammals)
True
->>> birds <= mammals
+# All members of animals do not show up as members of birds.
+>>> birds.issuperset(animals)
False
+
+# Both objects need to be sets to use a set operator
+>>> birds >= set(mammals)
+False
+
+# A set is always a loose superset of itself.
+>>> set(animals) >= set(animals)
+True
```
-` < ` and ` > ` are used to test for _proper subsets_:
-(`` <= ``) AND (`` != ``) for the `<` operator; (`` >= ``) AND (`` != ``) for the `>` operator.
-They have no method equivelent.
+### 'Proper' Subsets and Supersets
+
+` < ` and ` > ` are used to test for _proper subsets_.
+A `set` is a proper subset if (`` <= ``) **AND** (`` != ``) for the `<` operator.
+
+A `set is a proper superset if `(`` >= ``) **AND** (`` != ``) for the `>` operator.
+These operators have no method equivalent:
```python
->>> animal_names = {'albatross','cat','chicken','cow','crow','dog',
- 'eagle','elephant','sparrow','squirrel','tiger'}
+>>> animal_names = {'albatross','cat','chicken','cow','crow','dog',
+ 'eagle','elephant','sparrow','squirrel','tiger'}
->>> animal_names_also = {'albatross','cat','chicken','cow','crow','dog',
- 'eagle','elephant','sparrow','squirrel','tiger'}
+>>> animals_also = {'albatross','cat','chicken','cow','crow','dog',
+ 'eagle','elephant','sparrow','squirrel','tiger'}
->>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'}
->>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}
+>>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'}
+>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}
>>> mammals < animal_names
True
@@ -223,81 +296,114 @@ True
>>> animal_names > birds
True
-# A set is never a *proper subset* of itself
->>> animal_names_also < animal_names
+# A set is not a *proper subset* if set == other set.
+>>> animals_also < animal_names
False
-
->>> animals < animals
-
+# A set is never a *proper subset* of itself
+>>> animals_also < animals_also
+False
```
-### Set Operations
+### Set Unions
-`.union(*)` and ` | | | ... | ` return a new `set` with elements from `` and all ``.
+`.union(*)` returns a new `set` with elements from `` and all ``.
+The operator form of this method is ` | | | ... | `.
```python
->>> perennial_vegetables = {'Asparagus', 'Broccoli', 'Sweet Potatoe', 'Kale'}
->>> annual_vegetables = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'}
-
->>> more_perennials = ['Radicchio', 'Rhubarb', 'Spinach', 'Watercress']
+>>> perennials = {'Asparagus', 'Broccoli', 'Sweet Potato', 'Kale'}
+>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'}
+>>> more_perennials = ['Radicchio', 'Rhubarb',
+ 'Spinach', 'Watercress']
# Methods will take any iterable as an argument.
->>> perennial_vegetables.union(more_perennials)
-{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb','Spinach','Sweet Potatoe','Watercress'}
+>>> perennials.union(more_perennials)
+{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb',
+'Spinach','Sweet Potato','Watercress'}
# Operators require sets.
->>> perennial_vegetables | annual_vegetables
-{'Asparagus','Broccoli','Corn','Kale','Summer Squash','Sweet Peas','Sweet Potatoe','Zucchini'}
-
+>>> set(more_perennials) | perennials
+{'Asparagus',
+ 'Broccoli',
+ 'Kale',
+ 'Radicchio',
+ 'Rhubarb',
+ 'Spinach',
+ 'Sweet Potato',
+ 'Watercress'}
```
-`.difference(*)` and ` - - - ...` return a new `set` with elements from the original `