-
-
Notifications
You must be signed in to change notification settings - Fork 81
SDC Tools sprints 3 and 4 #1276
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 32 commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
9751f76
SDC Tools sprints 3 and 4
illicitonion e16d252
Add module success criteria
illicitonion a5c60c6
Simplify themes
illicitonion 3a37c85
Update common-content/en/module/tools/comparing-javascript-and-python…
illicitonion 706d7a2
Update common-content/en/module/tools/comparing-javascript-and-python…
illicitonion beffbc7
Update common-content/en/module/tools/comparing-javascript-and-python…
illicitonion 9f63e52
Update common-content/en/module/tools/comparing-javascript-and-python…
illicitonion 964da99
Update common-content/en/module/tools/comparing-javascript-and-python…
illicitonion 6f09666
Update common-content/en/module/tools/comparing-javascript-and-python…
illicitonion 165b2d6
Update common-content/en/module/tools/converted-program/index.md
illicitonion bd744cb
Update common-content/en/module/tools/converted-program/index.md
illicitonion d59e0ac
Update common-content/en/module/tools/converting-javascript-to-python…
illicitonion 8670f27
Update common-content/en/module/tools/counting-words/index.md
illicitonion 83b06ba
Update common-content/en/module/tools/first-nodejs-program/index.md
illicitonion ffbf68c
Update common-content/en/module/tools/first-nodejs-program/index.md
illicitonion 246882f
Update common-content/en/module/tools/single-use-data-analysis/index.md
illicitonion 8de321f
Update common-content/en/module/tools/using-python-dependencies/index.md
illicitonion 5e78448
Update common-content/en/module/tools/using-python-dependencies/index.md
illicitonion 9c2e465
Update common-content/en/module/tools/using-python-dependencies/index.md
illicitonion b3dca09
Tooltip convention
illicitonion 1891137
Review feedback
illicitonion 72b11df
Review feedback
illicitonion d6b8570
Fix typo
illicitonion c771e22
Feedback on converting exercise
illicitonion ee3ede4
Much of Sally's first round of feedback
illicitonion febb904
Most of Ali's review comments
illicitonion 0baae4b
Clarify the fs.promises import
illicitonion 3b3d403
Add prompt to think about why fs is async
illicitonion bc3246f
Add link to docs
illicitonion 3dfc99e
Compare virtual environments to node_modules
illicitonion 70abc83
Convert filter+lambda to an exploration exercise
illicitonion ab96743
Merge branch 'main' into sdc-tools-sprint3and4
SallyMcGrath 4b8ddf7
Process tooltip
illicitonion File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
95 changes: 95 additions & 0 deletions
95
common-content/en/module/tools/comparing-javascript-and-python/index.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
+++ | ||
title = "Comparing JavaScript and Python" | ||
headless = true | ||
time = 40 | ||
facilitation = false | ||
emoji= "📖" | ||
objectives = [ | ||
"Identify and explain equivalences between JavaScript and Python", | ||
"Compare and contrast differences between JavaScript and Python", | ||
"Distinguish between essential and accidental complexity" | ||
] | ||
+++ | ||
|
||
JavaScript and Python have many things in common. | ||
|
||
Most differences are "cosmetic". Here are some examples of cosmetic differnces: | ||
* Some functions and operators have different names. But often there are functions/operators which do exactly the same thing. | ||
* JavaScript uses `{}` around blocks of code and we _choose_ if we indent code. Python uses `:` and indentation is required. | ||
* In JavaScript we choose to name variables in `camelCase`, whereas in Python we choose to name variables in `snake_case`. In both langues we _could_ do either; this is called a {{<tooltip title="convention">}}A convention is something a group (maybe a team, or a company, or most users of a programming language) agree to do. It's not quite a rule - things _could_ work another way. But we agree one way we'll all do it anyway.<br /><br />e.g. in Python you could name one variable `firstName` and another `middle_name` and another `LASTname`, but if everyone agrees to use `first_name` and `middle_name` and `last_name` it makes it a bit easier for everyone to read because they know what to expect.{{</tooltip>}}. | ||
|
||
Recall our "count containing words" JavaScript code. Now think about what it would look like in Python. | ||
|
||
```js | ||
import { program } from "commander"; | ||
import { promises as fs } from "node:fs"; | ||
import process from "node:process"; | ||
|
||
program | ||
.name("count-containing-words") | ||
.description("Counts words in a file that contain a particular character") | ||
.option("-c, --char <char>", "The character to search for", "e"); | ||
|
||
program.parse(); | ||
|
||
const argv = program.args; | ||
if (argv.length != 1) { | ||
console.error(`Expected exactly 1 argument (a path) to be passed but got ${argv.length}.`); | ||
process.exit(1); | ||
} | ||
const path = argv[0]; | ||
const char = program.opts().char; | ||
|
||
const content = await fs.readFile(path, "utf-8"); | ||
const countOfWordsContainingChar = content | ||
.split(" ") | ||
.filter((word) => word.includes(char)) | ||
.length; | ||
console.log(countOfWordsContainingChar); | ||
``` | ||
|
||
{{<tabs name="Exercise">}} | ||
===[[Exercise]]=== | ||
Think about what we're doing in this code. | ||
|
||
Try to list the high-level ideas. This means describing in English what we're achieving, using sentences like like "Reading a file". | ||
|
||
We're not trying to think about the programming concepts we're doing here (we aren't talking about things like "Assigning a variable" or "An if statement"). Think about what a non-programmer would want to understand about our program. | ||
|
||
===[[Answer]]=== | ||
|
||
You may have slightly different answers, but the programme is doing roughly the following things: | ||
|
||
* Parsing command line flags - writing down what flags we expect to be passed, and reading values for them based on the actual command line. | ||
* Validating the flags (i.e. checking that exactly one path was passed). | ||
* Reading a file. | ||
* Splitting the content of the file up into words. | ||
* Counting how many of the words contained a particular character. | ||
* Printing the count. | ||
|
||
{{</tabs>}} | ||
|
||
These are the meaningful things we needed to do. To solve the same problem with Python, we'd still do all of these things. | ||
|
||
We did some other things in our code to make it work. For example, we imported some modules. To write this code in Python, we might need modules or we might not. Importing modules isn't one of our _goals_, it was just something we needed to do to help us. | ||
|
||
We split up things we need to do into two categories: essential and accidental. | ||
|
||
**Essential** means it is a core part of the problem. e.g. in order to count how many words are in a file, it is _essential_ that we read the file. | ||
|
||
**Accidental** means it isn't what we _care_ about doing, but we may need to do it anyway. e.g. importing the `process` module isn't _essential_ to our problem, but we needed to do it anyway so we could report errors. | ||
|
||
{{<note type="Think about real life">}} | ||
Imagine we want to post a parcel, so we take the bus to the post office. | ||
|
||
_Essential_ to our goal is getting the parcel to someone who will deliver it. | ||
|
||
_Accidental_ to this, we took the bus. There may be ways we could achieve our essential goal without getting the bus. Maybe we could walk or cycle to the post office. Maybe we could arrange for someone from the post office to come to our home and collect the parcel. | ||
|
||
The accidental things we did were important - they helped us get our essential goal done. But we shouldn't get too attached to the accidental things - maybe we will replace them later. | ||
{{</note>}} | ||
|
||
|
||
When we're thinking about how we use different languages, it's useful to think about what parts of our problem are _essential_ (we'll need to do them in any language), and which parts are _accidental_ (it's just something we had to do on the way to achieve our aim). | ||
|
||
Whether we write the JavaScript `someArray.length` or the Python `len(some_array)` isn't a big difference. Both lines do the same thing, they just express it differently. |
11 changes: 0 additions & 11 deletions
11
common-content/en/module/tools/convert-script-between-languages/index.md
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
+++ | ||
title = "Putting it all together" | ||
headless = true | ||
time = 30 | ||
facilitation = false | ||
emoji= "📖" | ||
hide_from_overview = true | ||
objectives = [ | ||
] | ||
+++ | ||
|
||
Instead of calling `console.log`, in Python we call `print`. | ||
|
||
```python | ||
import argparse | ||
|
||
parser = argparse.ArgumentParser( | ||
prog="count-containing-words", | ||
description="Counts words in a file that contain a particular character", | ||
) | ||
|
||
parser.add_argument("-c", "--char", help="The character to search for", default="e") | ||
parser.add_argument("path", help="The file to search") | ||
|
||
args = parser.parse_args() | ||
|
||
with open(args.path, "r") as f: | ||
content = f.read() | ||
count_of_words_containing_char = len([word for word in content.split(" ") if args.char in word]) | ||
print(count_of_words_containing_char) | ||
``` | ||
|
||
This looks similar to the JavaScript version. The shape is the same, but every line is a little bit different. | ||
|
||
Some programming languages are very different, as different as Mandarin and English. But JavaScript and Python are, essentially, quite similar, like Spanish and Portuguese. |
83 changes: 83 additions & 0 deletions
83
common-content/en/module/tools/converting-javascript-to-python/index.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
+++ | ||
title = "Converting JavaScript to Python" | ||
headless = true | ||
time = 90 | ||
facilitation = false | ||
emoji= "📖" | ||
objectives = [ | ||
"Rewrite JavaScript code as Python" | ||
] | ||
+++ | ||
|
||
### Parsing command line flags | ||
|
||
In JavaScript, we wrote this code: | ||
|
||
```js | ||
import { program } from "commander"; | ||
|
||
program | ||
.name("count-containing-words") | ||
.description("Counts words in a file that contain a particular character") | ||
.option("-c, --char <char>", "The character to search for", "e"); | ||
|
||
program.parse(); | ||
|
||
const argv = program.args; | ||
const path = argv[0]; | ||
const char = program.opts().char; | ||
``` | ||
|
||
Which of the following are _essential_ goals in this code, and which are _accidental_ goals? | ||
|
||
{{<label-items heading="Drag essential/accidental from 👆🏾 onto each goal 👇🏽">}} | ||
[LABEL=Essential] Allow a user to pass a `-c` argument (defaulting to `e` if they don't). | ||
[LABEL=Accidental] Made a `const` variable called `argv`. | ||
[LABEL=Accidental] Import `program` from the `commander` library. | ||
[LABEL=Essential] Allow a user to pass a path as a positional argument. | ||
[LABEL=Accidental] Looked up element `0` in the `program.args` array. | ||
[LABEL=Essential] Supply a nice `--help` implementation to help a user if they don't know how to use our tool. | ||
[LABEL=Accidental] Use the commander library. | ||
[LABEL=Accidental] Called the function `program.name()`. | ||
{{</label-items>}} | ||
|
||
If we want to work out how to do this in Python, we should focus on the essential goals. We may want to search for things like "Parse command line flags Python" and "Default argument values Python" because they get to the essential problems we're trying to solve. | ||
illicitonion marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Searching Google for "Parse command line flags Python" brought us to [the Python argparse documentation](https://docs.python.org/3/library/argparse.html). The example code looks pretty similar to what we were doing in JavaScript. We can probably write something like: | ||
|
||
```python | ||
import argparse | ||
|
||
parser = argparse.ArgumentParser( | ||
prog="count-containing-words", | ||
description="Counts words in a file that contain a particular character", | ||
) | ||
|
||
parser.add_argument("-c", "--char", help="The character to search for", default="e") | ||
parser.add_argument("path", help="The file to search") | ||
|
||
args = parser.parse_args() | ||
``` | ||
|
||
There are some differences here. | ||
* With commander we were calling functions on a global `program`, whereas with argparse we construct a new `ArgumentParser` which we use. | ||
* `add_argument` takes separate parameters for the short (`-c`) and long (`--char`) forms of the option - `commander` expected them in one string. | ||
* The Python version uses a lot of named arguments (e.g. `add_argument(...)` took `help=`, `default=`), whereas the JavaScript version (`option(...)`) used a lot of positional ones. | ||
* The Python version handles positional arguments itself as arguments with names (`path`), whereas the JavaScript version just gives us an array of positional arguments and leaves us to understand them. | ||
|
||
### Validating command line flags | ||
|
||
In our JavaScript code, we needed to check that there was exactly one positional argument. | ||
|
||
We don't need to do this in our Python code. Because `argparse` treats positional arguments as arguments, it actually already errors if we pass no positional arguments, or more than one. | ||
|
||
So we can tick this essential requirement off our list. Different languages or libraries do things differently, and that's ok! | ||
|
||
> [!TIP] | ||
> We don't need to convert every line. | ||
> | ||
> We're trying to convert _essential requirements_. | ||
|
||
{{<note type="Exercise">}} | ||
Identify all of the essential requirements from our JavaScript program, and finish implementing the Python version. | ||
{{</note>}} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
+++ | ||
title = "Counting words containing a character" | ||
headless = true | ||
time = 45 | ||
facilitation = false | ||
emoji= "📖" | ||
hide_from_overview = true | ||
objectives = [ | ||
] | ||
+++ | ||
|
||
In JavaScript we wrote: | ||
|
||
```js | ||
content | ||
.split(" ") | ||
.filter((word) => word.includes(char)) | ||
.length | ||
``` | ||
|
||
What JavaScript calls arrays, Python calls lists. Arrays and lists are basically the same. | ||
|
||
Googling for "Python filter list" suggests there are two things we can use - a `filter` function, or something called a "list comprehension". Some people prefer one, other people prefer the other. | ||
|
||
Let's try out both approaches. We can do this in a standalone program, rather than in the whole word-counting program. This gives us a lot more control, and makes it easier for us to experiment. | ||
|
||
{{<note type="Exercise">}} | ||
Create a new file, `filter.py`. Start it with: | ||
|
||
```python | ||
content = "this is a list of words" | ||
char = "i" | ||
|
||
filtered = TODO | ||
|
||
print(filtered) | ||
``` | ||
|
||
Now fill in the TODO. First, use a list comprehension. Run the file and make sure you get the expected output. | ||
|
||
Next, replace your list comprehension with some code that calls the global function `filter`. (`filter` takes a function, and it may be useful to know that `lambda` is a keyword for making an anonymous function in Python, similar to arrow functions in JavaScript). Run the file and make sure you get the expected output. | ||
{{</note>}} | ||
|
||
Now that we've learnt how to do the filtering, we can apply what we've learnt to the program we're converting. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.