diff --git a/python-program-structure/README.md b/python-program-structure/README.md new file mode 100644 index 0000000000..a371ad6033 --- /dev/null +++ b/python-program-structure/README.md @@ -0,0 +1,39 @@ +# Python Program Lexical Structure + +This folder provides the code examples for the Real Python tutorial [Python Program Lexical Structure](https://realpython.com/python-program-structure/). + +You can run all of the scripts directly by specifying their name: + +```sh +$ python .py +``` + +The scripts only use the standard library, so there's nothing to install. + +## The Scripts + +Each script collects one section of the tutorial, in the order the tutorial presents it: + +| Script | Tutorial section | +| --- | --- | +| [`statements.py`](./statements.py) | Python Statements | +| [`long_statements.py`](./long_statements.py) | Line Continuation | +| [`implicit_line_continuation.py`](./implicit_line_continuation.py) | Implicit Line Continuation | +| [`explicit_line_continuation.py`](./explicit_line_continuation.py) | Explicit Line Continuation | +| [`multiple_statements.py`](./multiple_statements.py) | Multiple Statements Per Line | +| [`comments.py`](./comments.py) | Comments | +| [`foo.py`](./foo.py) | Comments (the script file shown in the tutorial) | +| [`whitespace.py`](./whitespace.py) | Whitespace | + +## A Note on the Examples + +Nearly all of the tutorial's examples are REPL sessions. They've been turned into runnable scripts here, which means two small changes: + +- Where the REPL echoed a value, the script calls `print()` so that you see the same result. String values are printed with `repr()` so that the output matches the quoted form the REPL displays. +- The code is otherwise copied verbatim, including the tutorial's single quotes and its deliberately cramped or deliberately sprawling line layouts. + +That second point is why every script switches the formatter off with `# fmt: off`. This tutorial is *about* lexical structure, so line layout, whitespace, and quoting are the subject matter. Reformatting the examples would join the continued lines, split the semicolons, and pad out the whitespace, which would delete the very thing each example is demonstrating. For the same reason, the two semicolon examples carry a `# noqa: E702`. + +The tutorial also shows a number of deliberate errors: unterminated statements, a backslash followed by a space, `sin`, `is20`, `notin`, and an unexpected indent. Those blocks can't run, so they aren't reproduced as code. Each script's docstring names the ones its section skips. + +The tutorial's final code section, "Whitespace as Indentation," is not represented by a script. Its only Python example is a single `print('foo')` call and an `IndentationError`. diff --git a/python-program-structure/comments.py b/python-program-structure/comments.py new file mode 100644 index 0000000000..902bec0c9d --- /dev/null +++ b/python-program-structure/comments.py @@ -0,0 +1,58 @@ +"""Attaching explanatory detail to code with the hash character. + +Code from the "Comments" section of the tutorial. The interpreter ignores +everything from a '#' to the end of that line, unless the '#' is inside a +string literal. + +The tutorial's backslash-plus-comment example is not reproduced here: a +comment cannot follow an explicit line continuation, so it raises a +SyntaxError. +""" + +# fmt: off +a = ['foo', 'bar', 'baz'] # I am a comment. +print(a) + +# I am a comment. + # I am too. + +# A hash inside a string literal is protected and starts no comment. +a = 'foobar # I am *not* a comment.' +print(repr(a)) + +# Calculate and display the area of a circle. + +pi = 3.1415926536 +r = 12.35 + +area = pi * (r ** 2) + +print('The area of a circle with radius', r, 'is', area) + +# Comments can be included within implicit line continuation. +x = (1 + 2 # I am a comment. + + 3 + 4 # Me too. + + 5 + 6) +print(x) + +a = [ + 'foo', 'bar', # Me three. + 'baz', 'qux' +] +print(a) + +# Python has no block comment syntax, so a multiline comment is just a run +# of hash-prefixed lines: + +# Initialize value for radius of circle. +# +# Then calculate the area of the circle +# and display the result to the console. + +pi = 3.1415926536 +r = 12.35 + +area = pi * (r ** 2) + +print('The area of a circle with radius', r, 'is', area) +# fmt: on diff --git a/python-program-structure/explicit_line_continuation.py b/python-program-structure/explicit_line_continuation.py new file mode 100644 index 0000000000..fb5a5e8ce4 --- /dev/null +++ b/python-program-structure/explicit_line_continuation.py @@ -0,0 +1,22 @@ +"""Continuing a statement across lines with a backslash. + +Code from the "Explicit Line Continuation" section of the tutorial. A +backslash as the very last character on a line tells Python to ignore the +newline that follows it. PEP 8 recommends reaching for this only when +implicit line continuation is not practicable. + +The tutorial's three error examples are not reproduced here because they +cannot run: an unterminated 's =', an unterminated 'x = 1 + 2 +', and a +backslash followed by a stray space character. +""" + +# fmt: off +s = \ +'Hello, World!' +print(repr(s)) + +x = 1 + 2 \ + + 3 + 4 \ + + 5 + 6 +print(x) +# fmt: on diff --git a/python-program-structure/foo.py b/python-program-structure/foo.py new file mode 100644 index 0000000000..677bb9a8e7 --- /dev/null +++ b/python-program-structure/foo.py @@ -0,0 +1,14 @@ +# fmt: off +"""Initialize value for radius of circle. + +Then calculate the area of the circle +and display the result to the console. +""" + +pi = 3.1415926536 +r = 12.35 + +area = pi * (r ** 2) + +print('The area of a circle with radius', r, 'is', area) +# fmt: on diff --git a/python-program-structure/implicit_line_continuation.py b/python-program-structure/implicit_line_continuation.py new file mode 100644 index 0000000000..264038e2a9 --- /dev/null +++ b/python-program-structure/implicit_line_continuation.py @@ -0,0 +1,116 @@ +"""Continuing a statement across lines with parentheses, brackets, or braces. + +Code from the "Implicit Line Continuation" section of the tutorial, in the +order the tutorial presents it. Any statement with an open '(', '[', or '{' +is presumed incomplete until the matching closer is found, so it can run on +across as many lines as you like. + +The blocks that only define a value are given a print() here so that +running the script shows you the result. +""" + +# fmt: off +person1_age = 42 +person2_age = 16 +person3_age = 71 + +# The nested list from the previous section, made readable by the open +# brackets. +a = [ + [1, 2, 3, 4, 5], + [6, 7, 8, 9, 10], + [11, 12, 13, 14, 15], + [16, 17, 18, 19, 20], + [21, 22, 23, 24, 25] +] +print(a) + +# A long expression wrapped in grouping parentheses, as PEP 8 advocates. +someone_is_of_working_age = ( + (person1_age >= 18 and person1_age <= 65) + or (person2_age >= 18 and person2_age <= 65) + or (person3_age >= 18 and person3_age <= 65) +) +print(someone_is_of_working_age) + +# Parentheses: expression grouping +x = ( + 1 + 2 + + 3 + 4 + + 5 + 6 +) +print(x) + +# Parentheses: function call +print( + 'foo', + 'bar', + 'baz' +) + +# Parentheses: method call +print(repr('abc'.center( + 9, + '-' +))) + +# Parentheses: tuple definition +t = ( + 'a', 'b', + 'c', 'd' +) +print(t) + +# Curly braces: dictionary definition +d = { + 'a': 1, + 'b': 2 +} +print(d) + +# Curly braces: set definition +x1 = { + 'foo', + 'bar', + 'baz' +} +# Deliberately not printed: the display order of a set of strings varies +# between runs, and the tutorial shows no output for this block either. + +# Square brackets: list definition +a = [ + 'foo', 'bar', + 'baz', 'qux' +] +print(a) + +# Square brackets: indexing +print(repr(a[ + 1 + ])) + +# Square brackets: slicing +print(a[ + 1:2 + ]) + +# Square brackets: dictionary key reference +print(d[ + 'b' + ]) + +# Implicit continuation stays in effect until every parenthesis, bracket, +# and brace has been closed. Indentation clarifies the nested structure. +a = [ + [ + ['foo', 'bar'], + [1, 2, 3] + ], + {1, 3, 5}, + { + 'a': 1, + 'b': 2 + } +] +print(a) +# fmt: on diff --git a/python-program-structure/long_statements.py b/python-program-structure/long_statements.py new file mode 100644 index 0000000000..a698cb4479 --- /dev/null +++ b/python-program-structure/long_statements.py @@ -0,0 +1,21 @@ +"""Statements that grow too long to read comfortably on one line. + +Code from the "Line Continuation" section of the tutorial: these are the +"before" examples that motivate implicit and explicit line continuation. +The excessive line lengths are deliberate. + +The tutorial also shows an unterminated statement that raises a +SyntaxError. That block is not reproduced here because it cannot run. +""" + +# fmt: off +person1_age = 42 +person2_age = 16 +person3_age = 71 + +someone_is_of_working_age = (person1_age >= 18 and person1_age <= 65) or (person2_age >= 18 and person2_age <= 65) or (person3_age >= 18 and person3_age <= 65) +print(someone_is_of_working_age) + +a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15], [16, 17, 18, 19, 20], [21, 22, 23, 24, 25]] +print(a) +# fmt: on diff --git a/python-program-structure/multiple_statements.py b/python-program-structure/multiple_statements.py new file mode 100644 index 0000000000..4e0c13c3b7 --- /dev/null +++ b/python-program-structure/multiple_statements.py @@ -0,0 +1,16 @@ +"""Putting several statements on one line with semicolons. + +Code from the "Multiple Statements Per Line" section of the tutorial. +Python allows it, PEP 8 expressly discourages it, and Ruff flags it as +E702 -- which is exactly the tutorial's point, so the warnings are +suppressed rather than the examples rewritten. +""" + +# fmt: off +x = 1; y = 2; z = 3 # noqa: E702 +print(x); print(y); print(z) # noqa: E702 + +# The same result, written the way a Python programmer normally would. +x, y, z = 1, 2, 3 +print(x, y, z, sep='\n') +# fmt: on diff --git a/python-program-structure/statements.py b/python-program-structure/statements.py new file mode 100644 index 0000000000..72de77d55a --- /dev/null +++ b/python-program-structure/statements.py @@ -0,0 +1,19 @@ +"""Statements, the basic units of instruction in a Python program. + +Code from the "Python Statements" section of the tutorial. + +The formatter is switched off in this folder on purpose: this tutorial is +about lexical structure, so the exact line layout, whitespace, and quoting +of every example below is the lesson, not incidental style. +""" + +# fmt: off +print('Hello, World!') + +x = [1, 2, 3] +print(x[1:2]) + +# At the REPL a bare expression displays its value. In a script file it does +# nothing at all, so you need print() to see the result. +print(repr('foobar'[2:5])) +# fmt: on diff --git a/python-program-structure/whitespace.py b/python-program-structure/whitespace.py new file mode 100644 index 0000000000..c7d14b163e --- /dev/null +++ b/python-program-structure/whitespace.py @@ -0,0 +1,61 @@ +"""Where whitespace matters to the interpreter, and where it matters to you. + +Code from the "Whitespace" section of the tutorial. Whitespace is mostly +optional between tokens, but leaving it out hurts readability, and it +becomes mandatory when it is the only thing separating an identifier from +a keyword. + +The cramped examples below are copied from the tutorial verbatim. They are +not code you should write. + +Three error examples from the tutorial are not reproduced here because they +cannot run: 'sin [...]' raises a NameError, and 'y is20' and "'qux' notin +[...]" both raise a SyntaxError. +""" + +# fmt: off +# No whitespace at all -- the interpreter handles all of these fine. +x=3;y=12 # noqa: E702 +print(x+y) + +print((x==3)and(x=0)and(value1= 0) and (value1 < value2) +print(v) + +# String literals can be juxtaposed with or without whitespace. The effect +# is concatenation, exactly as though you had used the + operator. +s = "foo"'bar''''baz''' +print(repr(s)) + +s = 'foo' "bar" '''baz''' +print(repr(s)) + +# Whitespace is required here to separate the identifier s from the +# keyword in, and to separate the keywords not and in. +s = 'bar' + +print(s in ['foo', 'bar', 'baz']) + +print('qux' not in ['foo', 'bar', 'baz']) +# fmt: on