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/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml
index 01ddab0cac5..c2a5519234d 100644
--- a/.github/workflows/ci-workflow.yml
+++ b/.github/workflows/ci-workflow.yml
@@ -6,20 +6,19 @@ name: Exercises check
on:
push:
branches:
- - master
- main
pull_request:
jobs:
housekeeping:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
- python-version: 3.11.2
+ python-version: 3.13.5
- name: Download & Install dependencies
run: |
@@ -49,24 +48,20 @@ jobs:
./bin/template_status.py -v -p .problem-specifications
canonical_sync:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
needs: housekeeping
strategy:
matrix:
- python-version: [3.7, 3.8, 3.9, 3.10.6, 3.11.2]
+ python-version: [3.10.6, 3.11.2, 3.12, 3.13.5]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- - uses: actions/setup-python@v4
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: ${{ matrix.python-version }}
- - name: Install dataclasses package
- if: ${{ matrix.python-version == '3.6' }}
- run: pip install dataclasses
-
- name: Install pytest
- run: pip install pytest~=7.2.2
+ run: pip install pytest~=8.4.0
- name: Check exercises
run: |
diff --git a/.github/workflows/issue-commenter.yml b/.github/workflows/issue-commenter.yml
index 7bddcd077c0..9f7631e1465 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- name: Read issue-comment.md
id: issue-comment
- uses: juliangruber/read-file-action@v1
+ uses: juliangruber/read-file-action@271ff311a4947af354c6abcd696a306553b9ec18
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/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 13c0b62d44c..a70deb6b890 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.4.0
+ - uses: exercism/pr-commenter-action@f4a6aa5acc07742989788e70fd89cdc0980f0d1e
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 41156435d82..29b31a0401e 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@v8
+ - uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f
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..c9b0d21a713 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-24.04
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
- 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 df8e36761c1..3f7813de10a 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -90,4 +90,4 @@ This policy was initially adopted from the Front-end London Slack community and
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 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 0bac23e0af3..6ff557f7087 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,33 +4,37 @@
Contributing
[](https://forum.exercism.org)
- [](https://exercism.org)
+ [](https://exercism.org)
[](https://exercism.org/blog/freeing-our-maintainers)
[](https://github.com/exercism/python/actions?query=workflow%3A%22Exercises+check%22)
-
-
-Hi. ๐๐ฝ ๐ **We are happy you are here.** ๐ ๐
-
-
-
-
+> [!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.
+>
+>
+>
-We ๐ ๐ our community.
-**`But our maintainers are not accepting community contributions at this time.`**
-Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for details.
-
-
+Hi. ๐๐ฝ ๐ **We are happy you are here.** ๐ ๐
+
**`exercism/Python`** is one of many programming language tracks on [exercism(dot)org][exercism-website].
This repo holds all the instructions, tests, code, & support files for Python _exercises_ currently under development or implemented & available for students.
-๐ Track exercises support Python `3.7` - `3.11.2`.
+๐ Track exercises support Python `3.10` - `3.13.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`.
+๐ Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.13.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.
@@ -43,34 +47,38 @@ Practice exercises are open-ended, and can be used to practice concepts learned,
It is not uncommon to discover typos, confusing directions, or incorrect implementations of certain tests or code examples. Or you might have a great suggestion for a hint to aid students ( ๐ ), see optimizations for exemplar or test code, find missing test cases to add, or want to correct factual and/or logical errors. Or maybe you have a great idea ๐ก for an exercise or feature ( ๐ ).
_Our track is always a work in progress!_ ๐๐
-While contributions are paused, we ask that you [`open a thread in our community forum`](https://forum.exercism.org) to let us know what you have found/suggest.
+While contributions are paused, we ask that you [**open a thread in our community forum**](https://forum.exercism.org) to let us know what you have found/suggest.
## ๐ง **Did you write a patch that fixes a bug?**
-**`Our maintainers are not accepting community contributions at this time.`**
-Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for details.
+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.
-Once the pause ends, we will **happily** consider your PR.
-Until that time, all PRs from the larger community will be **automatically closed** with a note.
+Please read this [community blog post](https://exercism.org/blog/freeing-our-maintainers) for additional details.
+
-We're leaving the general contributing docs below for our long-term collaborators and maintainers.
+We're leaving the track contributing docs below for our long-term collaborators and maintainers.
+
+
+ Python Track Contributing Docs
In General
-- Maintainers are happy to review your work and help troubleshoot with you. ๐ ๐
+- Maintainers are happy to review your work and help troubleshoot with you. ๐ ๐ If you need help, comment in the Pull Request/issue. ๐๐ฝโโ๏ธ
+ - **Please wait at least 72 hours before pinging or `@`ing reviewers directly.**
- Requests are reviewed as soon as is practical/possible.
- - (โ ) Reviewers may be in a different timezone โ , or tied up ๐งถ with other tasks.
- - **Please wait at least 72 hours before pinging.**
-- If you need help, comment in the Pull Request/issue. ๐๐ฝโโ๏ธ
+ - (โ ) Keep in mind that reviewers may be in a different timezone โ , or tied up ๐งถ with other tasks.
- If you would like in-progress feedback/discussion, please mark your Pull Request as a **`[draft]`**
- Pull Requests should be focused around a single exercise, issue, or change.
- Pull Request titles and descriptions should make clear **what** has changed and **why**.
- - Please link ๐ to any related issues the PR addresses.
+ - Please link ๐ to any related forum discussions or issues the PR addresses.
- ๐ [ Open an issue ][open-an-issue]๐ and discuss it with ๐งฐ maintainers _**before**_:
- creating a Pull Request making significant or breaking changes.
- for changes across multiple exercises, even if they are typos or small.
@@ -170,7 +178,7 @@ Our documents use [Markdown][markdown-language], with certain [alterations][exer
- 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/ouputs are verbose.
+- We prefer **test data files** when test inputs/outputs are verbose.
- These should be named with `_data` or `_test_data` at the end of the filename, and saved alongside the test case file.
- See the [Cater-Waiter][cater-waiter] exercise directory for an example of this setup.
- **Test data files** need to be added under an `editor` key within [`config.json "files"`][exercise-config-json].
@@ -195,13 +203,13 @@ _We know it, and trust us, we are working on fixing it._ But if you see
-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`.
+This track officially supports Python `3.10 - 3.13.5` for students completing exercises.
+The track `test runner`, `analyzer`, and `representer` run in docker on `python:3.13.5-alpine3.22`.
Although the majority of test cases are written using `unittest.TestCase`,
-- All exercises should be written for compatibility with Python `3.7` - `3.11.2`.
-- Version backward _incompatibility_ (_e.g_ an exercise using features introduced in `3.8`, `3.9`, or `3.10`) should be clearly noted in any exercise hints, links, introductions or other notes.
+- All exercises should be written for compatibility with Python `3.10` - `3.13.5`.
+- Version backward _incompatibility_ (_e.g_ an exercise using features introduced in Python `3.10`+ that would not work in Python `3.10`) should be clearly noted in any exercise hints, links, introductions or other notes.
- Here is an example of how the Python documentation handles [version-tagged ๐ท ][version-tagged-language-features] feature introduction.
@@ -222,7 +230,7 @@ Although the majority of test cases are written using `unittest.TestCase`,
- For specifications, refer to [Concept Exercise Anatomy][concept-exercise-anatomy], or [Practice Exercise Anatomy][practice-exercise-anatomy] depending on which type of exercise you are contributing to.
-- **Practice exercise**, descriptions and instructions come from a centralized, cross-track [problem specifications][problem-specifications] repository.
+- **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.
@@ -270,9 +278,9 @@ Although the majority of test cases are written using `unittest.TestCase`,
- [ ] `.meta/config.json` (**required**)
- [ ] `.meta/example.py` (**required**)
- [ ] `.meta/design.md` (_optional_)
- - [ ] `.meta/template.j2` (_template for generating tests from cannonical data_)
- - [ ] `.meta/tests.toml` (_tests configuration from cannonical data_)
- - [ ] `_test.py` (_**auto-generated from cannonical data**_)
+ - [ ] `.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**)
@@ -369,45 +377,32 @@ configlet generate --spec-path path/to/problem/specifications --
configlet generate --spec-path path/to/problem/specifications
```
+
+
[.flake8]: https://github.com/exercism/python/blob/main/.flake8
[.style.yapf]: https://github.com/exercism/python/blob/main/.style.yapf
[american-english]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md
-[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
-[config-json]: https://github.com/exercism/python/blob/main/config.json
-[configlet-general]: https://github.com/exercism/configlet
[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
@@ -429,5 +424,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 1c82f33b9d0..20c3bd1ce0c 100644
--- a/README.md
+++ b/README.md
@@ -4,22 +4,39 @@
Exercism Python Track
[](https://forum.exercism.org)
- [](https://exercism.org)
+ [](https://exercism.org)
[](https://exercism.org/blog/freeing-our-maintainers)
[](https://github.com/exercism/python/actions?query=workflow%3A%22Exercises+check%22)
-Hi. ๐๐ฝ ๐ **We are happy you are here.** ๐ ๐
+> [!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.
-๐ Track exercises support Python `3.7` - `3.11.2`.
+๐ Track exercises support Python `3.10` - `3.13.13`.
Exceptions to this support are noted where they occur.
-๐ Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.11.2`.
+๐ Track tooling (_test-runner, representer, analyzer, and Continuous Integration_) runs on Python `3.13.13`.
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.
@@ -43,17 +60,17 @@ It might also be helpful to look at [Being a Good Community Member][being-a-good
-We ๐ ๐ our community.
-**`But our maintainers are not accepting community contributions at this time.`**
+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 [Exercism Community Forum](https://forum.exercism.org/).
+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 faster & more enthusiastic responses from volunteers._
+_Thoughtful suggestions will likely result in faster & more enthusiastic responses from volunteers._
@@ -67,7 +84,7 @@ _Thoughtful suggestions will likely result faster & more enthusiastic responses
## Python Software and Documentation
-**Copyright ยฉ 2001-2022 Python Software Foundation. All rights reserved.**
+**Copyright ยฉ 2001-2026 Python Software Foundation. All rights reserved.**
Python software and documentation are licensed under the [PSF License Agreement][psf-license].
@@ -99,7 +116,6 @@ This repository uses the [MIT License](/LICENSE).
[exercism-writing-style]: https://github.com/exercism/docs/blob/main/building/markdown/style-guide.md
[freeing-maintainers]: https://exercism.org/blog/freeing-our-maintainers
[practice-exercises]: https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md
-[prs]: https://github.com/exercism/docs/blob/main/community/good-member/pull-requests.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
diff --git a/bin/fetch-configlet b/bin/fetch-configlet
index 4800e150849..6bef43ab722 100755
--- a/bin/fetch-configlet
+++ b/bin/fetch-configlet
@@ -24,10 +24,11 @@ get_download_url() {
local latest='https://api.github.com/repos/exercism/configlet/releases/latest'
local arch
case "$(uname -m)" in
- x86_64) arch='x86-64' ;;
- *686*) arch='i386' ;;
- *386*) arch='i386' ;;
- *) arch='x86-64' ;;
+ 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}" |
@@ -47,7 +48,7 @@ main() {
fi
local os
- case "$(uname)" in
+ case "$(uname -s)" in
Darwin*) os='macos' ;;
Linux*) os='linux' ;;
Windows*) os='windows' ;;
@@ -58,8 +59,8 @@ main() {
local ext
case "${os}" in
- windows*) ext='zip' ;;
- *) ext='tar.gz' ;;
+ windows) ext='zip' ;;
+ *) ext='tar.gz' ;;
esac
echo "Fetching configlet..." >&2
@@ -69,16 +70,16 @@ main() {
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='' ;;
+ windows) executable_ext='.exe' ;;
+ *) executable_ext='' ;;
esac
local configlet_path="${output_dir}/configlet${executable_ext}"
diff --git a/bin/generate_tests.py b/bin/generate_tests.py
index 33580137058..2ad23a9b5f1 100755
--- a/bin/generate_tests.py
+++ b/bin/generate_tests.py
@@ -138,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
@@ -204,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]:
"""
@@ -261,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():
@@ -271,24 +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:
- for line in range(4):
- next(f)
- current_lines = f.readlines()
+ current_lines = f.readlines()[3:]
with tmpfile.open() as f:
- for line in range(4):
- next(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"
@@ -395,6 +411,7 @@ 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
diff --git a/concepts/basics/about.md b/concepts/basics/about.md
index 30ea083022f..6f932bfd16f 100644
--- a/concepts/basics/about.md
+++ b/concepts/basics/about.md
@@ -6,7 +6,8 @@ Imperative, declarative (e.g., functional), and object-oriented programming _sty
Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions.
-Python was created by Guido van Rossum and first released in 1991. The [Python Software Foundation][psf] manages and directs resources for Python and CPython development and receives proposals for changes to the language from [members][psf membership] of the community via [Python Enhancement Proposals or PEPs][peps].
+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].
@@ -18,8 +19,14 @@ 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.
-This concept introduces 4 major Python language features: Name Assignment (_variables and constants_), Functions (_and the return keyword_), Comments, and Docstrings.
~~~~exercism/note
@@ -32,9 +39,9 @@ On the Python track, [variables][variables] are always written in [`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
-[the zen of python]: https://www.python.org/dev/peps/pep-0020/
~~~~
@@ -57,20 +64,21 @@ For example, `my_first_variable` can be re-assigned many times using `=`, and ca
>>> 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.
+>>> 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))
+>>> my_first_variable = 'You can call me "str".' # <--Strings can be declared using single or double quote marks.
>>> print(my_first_variable)
-"Now, I'm a string." # Strings can be declared using single or double quote marks.
+You can call me "str".
-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.
+>>> import collections
+>>> my_first_variable = collections.Counter([1,1,2,3,3,3,4,5,6,7]) # <--Now my_first_variable has been re-bound to a Counter object.
>>> print(type(my_first_variable))
>>> print(my_first_variable)
->>> Counter({3: 3, 1: 2, 2: 1, 4: 1, 5: 1, 6: 1, 7: 1})
+Counter({3: 3, 1: 2, 2: 1, 4: 1, 5: 1, 6: 1, 7: 1})
```
@@ -94,19 +102,19 @@ 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]_).
+In Python, units of functionality are encapsulated in [_functions_][functions], which are themselves [objects][objects] (_it's [turtles all the way down][turtles all the way down]_).
Functions can be executed by themselves, passed as arguments to other functions, nested, or bound to a class.
When functions are bound to a [class][classes] name, they're referred to as [methods][method objects].
Related functions and classes (_with their methods_) can be grouped together in the same file or module, and imported in part or in whole for use in other programs.
The `def` keyword begins a [function definition][function definition].
-Each function can have zero or more formal [parameters][parameters] in `()` parenthesis, followed by a `:` colon.
-Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_.
+Each function can have zero or more formal [parameters][parameters] in `()` parentheses, followed by a `:` colon.
+Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_:
```python
-# The body of a function is indented by 2 spaces, & prints the sum of the numbers.
+# 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)
@@ -118,7 +126,7 @@ def add_two_numbers(number_one, number_two):
# Inconsistent indentation in your code blocks will raise an error.
>>> def add_three_numbers_misformatted(number_one, number_two, number_three):
... result = number_one + number_two + number_three # This was indented by 4 spaces.
-... print(result) #this was only indented by 3 spaces
+... print(result) # <--This was only indented by 3 spaces.
...
...
File "", line 3
@@ -127,24 +135,69 @@ def add_two_numbers(number_one, number_two):
IndentationError: unindent does not match any outer indentation level
```
-Functions explicitly return a value or object via the [`return`][return] keyword.
-Functions that do not have an explicit `return` expression will _implicitly_ return [`None`][none].
+
+Functions _explicitly_ return a value or object via the [`return`][return] keyword:
+
```python
-# Function definition on first line.
+# Function definition on first line, explicit return used on final line.
def add_two_numbers(number_one, number_two):
- result = number_one + number_two
- return result # Returns the sum of the numbers.
+ return number_one + number_two
+
+# Calling the function in the Python shell returns the sum of the numbers.
>>> add_two_numbers(3, 4)
7
-# This function will return None.
+# Assigning the function call to a variable and printing
+# the variable will also return the value.
+>>> sum_with_return = add_two_numbers(5, 6)
+>>> print(sum_with_return)
+11
+```
+
+Functions that do not have an _explicit_ expression following a `return` will _implicitly_ return the [`None`][none] object.
+The details of `None` will be covered in a later concept.
+For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null:
+
+
+```python
+
+# This function will return `None`
+def square_a_number(number):
+ square = number * number
+ return # <-- note that this return is not followed by an expression
+
+# Calling the function in the Python shell appears
+# to not return anything at all.
+>>> square_a_number(2)
+>>>
+
+
+# Using print() with the function call shows that
+# the function is actually returning the **None** object.
+>>> print(square_a_number(2))
+None
+```
+
+Functions that omit `return` will also _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.
+
+```python
+
+# This function omits a return keyword altogether
def add_two_numbers(number_one, number_two):
result = number_one + number_two
+>>> add_two_numbers(5, 7)
>>> 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
```
@@ -154,32 +207,41 @@ 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
+>>> def raise_to_power(number, power):
+... return number ** power
...
->>> number_to_the_power_of(3,3) # Invoking the function with the arguments 3 and 3.
+>>> raise_to_power(3,3) # Invoking the function with the arguments 3 and 3.
27
# A mis-match between the number of parameters and the number of arguments will raise an error.
->>> number_to_the_power_of(4,)
+>>> raise_to_power(4,)
...
Traceback (most recent call last):
File "", line 1, in
-TypeError: number_to_the_power_of() missing 1 required positional argument: 'number_two'
+TypeError: raise_to_power() missing 1 required positional argument: 'power'
# Calling methods or functions in classes and modules.
>>> start_text = "my silly sentence for examples."
->>> str.upper(start_text) # Calling the upper() method for the built-in str class.
-"MY SILLY SENTENCE FOR EXAMPLES."
+>>> str.upper(start_text) # <--Calling the upper() method from the built-in str class on start_text.
+'MY SILLY SENTENCE FOR EXAMPLES.'
-# Importing the math module
-import math
+# Because a string is an instance of the str class, methods can also be called on them "directly".
+>>> start_text = "my silly sentence for examples."
+>>> start_text.upper() # <--Calling the upper() method on start_text directly.
+'MY SILLY SENTENCE FOR EXAMPLES.'
+
+# Alternatively, we can skip the variable assignment (although this gets messy quick).
+>>> "my silly sentence for examples.".upper()
+'MY SILLY SENTENCE FOR EXAMPLES.'
->>> math.pow(2,4) # Calling the pow() function from the math module
->>> 16.0
+
+# Importing the math module
+>>> import math
+>>> math.pow(2,4) # <--Calling the pow() function from the math module.
+16.0
```
@@ -210,14 +272,18 @@ Docstrings are declared using triple double quotes (""") indented at the same le
```python
+# An example from PEP257 of a multi-line docstring
+# reformatted to use Google style non-type hinted docstrings.
+# Some additional details can be found in the Sphinx documentation:
+# https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html#getting-started
-# 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)
+ Keyword Arguments:
+ real (float): The real part of the number (default 0.0)
+ imag (float): The imaginary part of the number (default 0.0)
+
"""
if imag == 0.0 and real == 0.0:
@@ -234,33 +300,40 @@ 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.
+# An example on a user-defined function using a Google style docstring.
+>>> def raise_to_power(number, power):
+ """Raise a number to an arbitrary power.
+
+ Parameters:
+ number (int): The base number.
+ power (int): The power to raise the base number to.
+
+ Returns:
+ int: The number raised to the specified power.
+
+ Takes a number and raises it to the specified power, returning the result.
- :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
+ return number ** power
...
# Calling the .__doc__ attribute of the function and printing the result.
->>> print(number_to_the_power_of.__doc__)
+>>> print(raise_to_power.__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
+Parameters:
+ number (int): The base number.
+ power (int): The power to raise the base number to.
- Takes number_one and raises it to the power of number_two, returning the result.
+Returns:
+ int: The number raised to the specified power.
+Takes a number and raises it to the specified power, returning the result.
+...
-# Printing the __doc__ attribute for the built-in type: str.
+# Printing the __doc__ attribute of the built-in type: str.
>>> print(str.__doc__)
str(object='') -> str
str(bytes_or_buffer[, encoding[, errors]]) -> str
@@ -270,10 +343,11 @@ 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().
+encoding defaults to 'utf-8'.
errors defaults to 'strict'.
```
+
[PEP257]: https://www.python.org/dev/peps/pep-0257/
[calls]: https://docs.python.org/3/reference/expressions.html#calls
[classes]: https://docs.python.org/3/reference/datamodel.html#classes
diff --git a/concepts/basics/introduction.md b/concepts/basics/introduction.md
index 34e2a8804d7..cb61a0184ab 100644
--- a/concepts/basics/introduction.md
+++ b/concepts/basics/introduction.md
@@ -2,23 +2,31 @@
Python is a [dynamic and strongly typed][dynamic typing in python] programming language.
It employs both [duck typing][duck typing] and [gradual typing][gradual typing], via [type hints][type hints].
+Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions.
+
+Python was created by Guido van Rossum and first released in 1991.
Imperative, declarative (e.g., functional), and object-oriented programming _styles_ are all supported, but internally **[everything in Python is an object][everythings an object]**.
-Python puts a strong emphasis on code readability and (_similar to Haskell_) uses [significant indentation][significant indentation] to denote function, method, and class definitions.
+We'll dig more into what all of that means as we continue through the Python track concepts.
-Python was created by Guido van Rossum and first released in 1991.
+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.
+A name can be reassigned (or re-bound) to different values (different object types) over its lifetime:
```python
->>> my_first_variable = 1 # my_first_variable bound to an integer object of value one.
->>> my_first_variable = 2 # my_first_variable re-assigned to integer value 2.
+>>> 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))
@@ -26,31 +34,33 @@ A name can be reassigned (or re-bound) to different values (different object typ
>>> 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.
+>>> 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))
+>>> my_first_variable = 'You can call me "str".' # <--Strings can be declared using single or double quote marks.
>>> print(my_first_variable)
-"Now, I'm a string." # Strings can be declared using single or double quote marks.
+You can call me "str".
```
### 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.
+Constants are names meant to be assigned only once in a program โ although Python will not prevent re-assignment.
+Using `SCREAMING_SNAKE_CASE` signals to anyone reading the code that the name should **not** be re-assigned, or its value mutated.
+Constants should be defined at a [module][module] (file) level, and are typically visible to all functions and classes in a program.
+
## Functions
The `def` keyword begins a [function definition][function definition].
-Each function can have zero or more formal [parameters][parameters] in `()` parenthesis, followed by a `:` colon.
+Each function can have zero or more formal [parameters][parameters] in `()` parentheses, followed by a `:` colon.
Statements for the _body_ of the function begin on the line following `def` and must be _indented in a block_.
```python
-# The body of a function is indented by 2 spaces, & prints the sum of the numbers.
+# The body of this function is indented by 2 spaces & prints the sum of the numbers.
def add_two_numbers(number_one, number_two):
total = number_one + number_two
print(total)
@@ -62,7 +72,7 @@ def add_two_numbers(number_one, number_two):
# Inconsistent indentation in your code blocks will raise an error.
>>> def add_three_numbers_misformatted(number_one, number_two, number_three):
... result = number_one + number_two + number_three # This was indented by 4 spaces.
-... print(result) #this was only indented by 3 spaces
+... print(result) # <--This was only indented by 3 spaces.
...
...
File "", line 3
@@ -71,24 +81,68 @@ def add_two_numbers(number_one, number_two):
IndentationError: unindent does not match any outer indentation level
```
-Functions explicitly return a value or object via the [`return`][return] keyword.
-Functions that do not have an explicit `return` expression will _implicitly_ return [`None`][none].
+
+Functions _explicitly_ return a value or object via the [`return`][return] keyword:
+
```python
-# Function definition on first line.
+# Function definition on first line, explicit return used on final line.
def add_two_numbers(number_one, number_two):
- result = number_one + number_two
- return result # Returns the sum of the numbers.
+ return number_one + number_two
+
+# Calling the function in the Python shell returns the sum of the numbers.
>>> add_two_numbers(3, 4)
7
-# This function will return None.
+# Assigning the function call to a variable and printing
+# the variable will also return the value.
+>>> sum_with_return = add_two_numbers(5, 6)
+>>> print(sum_with_return)
+11
+```
+
+
+Functions that do not have an _explicit_ expression following a `return` will _implicitly_ return the [`None`][none] object.
+The details of `None` will be covered in a later concept.
+For the purposes of this exercise and explanation, `None` is a placeholder that represents nothing, or null:
+
+
+```python
+# This function will return `None`
+def square_a_number(number):
+ square = number * number
+ return # <-- note that this return is not followed by an expression
+
+# Calling the function in the Python shell appears
+# to not return anything at all.
+>>> square_a_number(2)
+>>>
+
+
+# Using print() with the function call shows that
+# the function is actually returning the **None** object.
+>>> print(square_a_number(2))
+None
+```
+
+Functions that omit `return` will also _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.
+
+```python
+# This function omits a return keyword altogether.
def add_two_numbers(number_one, number_two):
result = number_one + number_two
+>>> add_two_numbers(5, 7)
>>> 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
```
@@ -102,29 +156,35 @@ Each line of a comment block must start with the `#` character.
## Docstrings
The first statement of a function body can optionally be a [_docstring_][docstring], which concisely summarizes the function or object's purpose.
-Docstring conventions are laid out in [PEP257][pep257].
+Docstrings are read by automated documentation tools such as [Sphinx][sphinx] and are returned by calling the special attribute `.__doc__` on the function, method, or class name.
+General docstring conventions are laid out in [PEP257][pep257], but exact formats will vary by project and team.
Docstrings are declared using triple double quotes (""") indented at the same level as the code block:
```python
-
-# An example from PEP257 of a multi-line docstring.
+# An example from PEP257 of a multi-line docstring
+# reformatted to use Google style non-type hinted docstrings.
+# Some additional details can be found in the Sphinx documentation:
+# https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html#getting-started
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)
+ Keyword Arguments:
+ real (float): The real part of the number (default 0.0)
+ imag (float): The imaginary part of the number (default 0.0)
+
"""
if imag == 0.0 and real == 0.0:
return complex_zero
-
```
-[pep257]: https://www.python.org/dev/peps/pep-0257/
+Docstrings can also function as [lightweight unit tests][doctests], which will be covered in a later concept.
+
+
[comments]: https://realpython.com/python-comments-guide/#python-commenting-basics
[docstring]: https://docs.python.org/3/tutorial/controlflow.html#tut-docstrings
+[doctests]: https://docs.python.org/3/library/doctest.html
[duck typing]: https://en.wikipedia.org/wiki/Duck_typing
[dynamic typing in python]: https://stackoverflow.com/questions/11328920/is-python-strongly-typed
[everythings an object]: https://docs.python.org/3/reference/datamodel.html
@@ -134,6 +194,8 @@ def complex(real=0.0, imag=0.0):
[module]: https://docs.python.org/3/tutorial/modules.html
[none]: https://docs.python.org/3/library/constants.html
[parameters]: https://docs.python.org/3/glossary.html#term-parameter
+[pep257]: https://www.python.org/dev/peps/pep-0257/
[return]: https://docs.python.org/3/reference/simple_stmts.html#return
-[type hints]: https://docs.python.org/3/library/typing.html
[significant indentation]: https://docs.python.org/3/reference/lexical_analysis.html#indentation
+[sphinx]: https://www.sphinx-doc.org/en/master/usage/index.html
+[type hints]: https://docs.python.org/3/library/typing.html
diff --git a/concepts/basics/links.json b/concepts/basics/links.json
index 3e5561228ee..1d1d640c9e7 100644
--- a/concepts/basics/links.json
+++ b/concepts/basics/links.json
@@ -1,5 +1,6 @@
[
- {"url": "https://lerner.co.il/2019/06/18/understanding-python-assignment/",
+ {
+ "url": "https://lerner.co.il/2019/06/18/understanding-python-assignment/",
"description": "Reuven Lerner: Understanding Python Assignment"
},
{
@@ -14,6 +15,10 @@
"url": "https://www.pythonmorsels.com/everything-is-an-object/",
"description": "Python Morsels: Everything is an Object"
},
+ {
+ "url": "https://eli.thegreenplace.net/2012/03/23/python-internals-how-callables-work/",
+ "description": "Eli Bendersky: Python internals: how callables work"
+ },
{
"url": "https://stackoverflow.com/questions/11328920/is-python-strongly-typed",
"description": "dynamic typing and strong typing"
diff --git a/concepts/binary-octal-hexadecimal/about.md b/concepts/binary-octal-hexadecimal/about.md
index 667f3eb6cd7..67646aed2f2 100644
--- a/concepts/binary-octal-hexadecimal/about.md
+++ b/concepts/binary-octal-hexadecimal/about.md
@@ -18,7 +18,7 @@ A snippet from the base 2 system looks like this, although it continues infinite
| -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
| 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
+So if we want to represent the number 6 in binary, it would be 110.
| Place value | 4 | 2 | 1 |
| ------------- | --- | --- | --- |
@@ -41,7 +41,6 @@ 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
@@ -86,6 +85,8 @@ However, the usual mathematical operator rules apply: dividing two binary numbe
>>> 0b10011/3
6.333333333333333
+```
+
### Converting to and from Binary Representation
@@ -133,6 +134,9 @@ 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.
@@ -148,7 +152,6 @@ 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
```
@@ -157,7 +160,6 @@ 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:
@@ -165,6 +167,8 @@ 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:
@@ -175,23 +179,22 @@ To convert an octal number to an integer, we can use the `int()` function, passi
As with binary, giving the wrong base will raise a `ValueError`.
-### Hexadecimal
+## 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`.
+As with binary and octal, Python will automatically convert hexadecimal literals to `int`s.
```python
-# 0x123
>>> 0x123
291
```
-As with binary and octal - hexidecimal literals **are ints**, and you can perform all integer operations.
-Prefixing a non-hexidecimal number with `0x` will raise a `SyntaxError`.
+As with binary and octal โ hexadecimal literals **are ints**, and you can perform all integer operations with them.
+Prefixing a non-hexadecimal number with `0x` will raise a `SyntaxError`.
### Converting to and from Hexadecimal Representation
@@ -202,6 +205,8 @@ 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:
@@ -216,9 +221,6 @@ 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
-[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
-[methods-int]: https://docs.python.org/3/library/stdtypes.html#additional-methods-on-integer-types
[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
index 820aac33ee7..84ff634263d 100644
--- a/concepts/binary-octal-hexadecimal/introduction.md
+++ b/concepts/binary-octal-hexadecimal/introduction.md
@@ -1,11 +1,11 @@
-# binary, octal, hexadecimal
+# 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 hexidecimal numbers support all integer operations.
+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/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..1cd5a237c29 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 [`two's 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 `&`, `|`, `^`, and the shift operators described below 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` 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 them in Python.
+
+
+```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
+[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..07833339ff2 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][how-transistors-work]: 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.
+Python internals take care of (_eventually_) translating all the higher-level data 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
+[how-transistors-work]: https://www.build-electronic-circuits.com/how-transistors-work/
+[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..ed251fab33a 100644
--- a/concepts/bitwise-operators/links.json
+++ b/concepts/bitwise-operators/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://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": "http://example.com/",
- "description": "TODO: add new link (above) and write a short description here of the resource."
+ "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..7015fdfafa4 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 subclass 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`):
@@ -22,10 +22,10 @@ Each of the operators has a different precedence, where `not` is evaluated befor
Brackets can be used to evaluate one part of the expression before the others:
```python
->>>not True and True
+>>> not True and True
False
->>>not (True and False)
+>>> not (True and False)
True
```
@@ -45,25 +45,25 @@ A few `built-ins` are always considered `False` by definition:
```python
->>>bool(None)
+>>> bool(None)
False
->>>bool(1)
+>>> bool(1)
True
->>>bool(0)
+>>> bool(0)
False
->>>bool([1,2,3])
+>>> bool([1,2,3])
True
->>>bool([])
+>>> bool([])
False
->>>bool({"Pig" : 1, "Cow": 3})
+>>> bool({"Pig" : 1, "Cow": 3})
True
->>>bool({})
+>>> bool({})
False
```
@@ -95,10 +95,10 @@ The `bool` type is implemented as a _sub-type_ of _int_.
```python
->>>1 == True
+>>> 1 == True
True
->>>0 == False
+>>> 0 == False
True
```
@@ -106,14 +106,14 @@ However, `bools` are **still different** from `ints`, as noted when comparing th
```python
->>>1 is True
+>>> 1 is True
False
->>>0 is False
+>>> 0 is False
False
```
-> Note: in python >= 3.8, using a literal (such as 1, '', [], or {}) on the _left side_ of `is` will raise a warning.
+> Note: in python >= 3.8, using a literal (such as `1`, `''`, `[]`, or `{}`) on the _left side_ of `is` will raise a warning.
It is considered a [Python anti-pattern][comparing to true in the wrong way] to use the equality operator to compare a boolean variable to `True` or `False`.
@@ -134,8 +134,8 @@ It is considered a [Python anti-pattern][comparing to true in the wrong way] to
```
-[bool-function]: https://docs.python.org/3/library/functions.html#bool
-[bool]: https://docs.python.org/3/library/stdtypes.html#truth
[Boolean-operators]: https://docs.python.org/3/library/stdtypes.html#boolean-operations-and-or-not
+[bool-function]: https://docs.python.org/3/library/functions.html#bool
+[bools]: https://docs.python.org/3/library/stdtypes.html#typebool
[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
diff --git a/concepts/bools/introduction.md b/concepts/bools/introduction.md
index 535f90be07c..85eb032df25 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 subclass 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
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..9b6a8a0dfb7 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]
@@ -185,10 +185,10 @@ class Demo:
The moment that `