Checks¶
Variables and expressions¶
Create some variables in the Python shell. What happens if you add spaces, hyphens or other characters to the variable names?
>>> x = 3 >>> var fünf = 5 File "<stdin>", line 1 var fünf = 5 ^^^^ SyntaxError: invalid syntax >>> var_fünf = 5
Do the results change if you use brackets to group numbers in different ways?
>>> 2 + 3 * 4 - 5 / 6 13.166666666666666 >>> (2 + 3) * 4 - 5 / 6 19.166666666666668 >>> 2 + (3 * 4) - 5 / 6 13.166666666666666 >>> 2 + 3 * (4 - 5) / 6 1.5
Which of the following variable and function names do you think are not good Python style, and why?
var*
❌ contains an invalid character (
*
)varname
✅ ok, but easier to read with underscore
func_name()
✅
varName
❌ Mixed upper and lower case letters
VARNAME
❌ Capital letters only, difficult to read
very_very_long_var_name
✅ ok, but very long and therefore only recommended if you want to differentiate between many very similar variables
Numbers¶
Create some number variables (integers, floating point numbers and complex numbers). Experiment a little with what happens when you perform operations with them, even across types.
>>> x = 3 >>> import math >>> pi = math.pi >>> pi 3.141592653589793 >>> c = 3j4 File "<stdin>", line 1 c = 3j4 ^ SyntaxError: invalid imaginary literal >>> c = 3 +4j >>> c (3+4j) >>> x * c (9+12j) >>> x + c (6+4j)
Complex numbers¶
Load the
math
module and try out some of the functions. Then load thecmath
module and do the same.>>> from math import sqrt >>> sqrt(3) 1.7320508075688772 >>> from cmath import sqrt >>> sqrt(3) (1.7320508075688772+0j)
How can you restore the functions of the
math
module?>>> from math import sqrt >>> sqrt(3) 1.7320508075688772
Boolean values¶
Decide whether the following statements are true or false:
1
→ True0
→ False-1
→ True[0]
→ True (List with one item)1 and 0
→ False1 > 0 or []
→ True
Lists¶
What does
len()
return for each of the following cases:>>> len([3]) 1 >>> len([]) 0 >>> len([[1, [2, 3], 4], "5 6"]) 2
How would you use
len()
and slices to determine the second half of a list if you don’t know how long it is?>>> l = [[1, [2, 3], 4], "5 6"] >>> l[len(l) // 2 :] ['5 6']
How could you move the last two entries of a list to the beginning without changing the order of the two?
>>> l[-2:] + l[:2] ['5 6', 7, [1, [2, 3], 4], '5 6']
Which of the following cases triggers an exception?
min(["1", "2", "3"])
max([1, 2, "3"])
[1,2,3].count("1")
max([1, 2, "3"])
, as strings and integers cannot be compared; it is therefore impossible to obtain a maximum value.If you have a list
l
, how can you remove a certain valuei
from it?>>> if i in l: ... l.remove(i) ...
Note
This code only removes the first occurrence of
i
. To remove all occurrences ofi
from the list, the list could be converted to the set type, for example:>>> l = set(l) >>> if i in l: ... l.remove(i) ... >>> l = list(l)
If you have a nested list
ll
, how can you get a copynll
of this list in which you can change the elements without changing the contents ofll
?>>> import copy >>> nll = copy.deepcopy(ll)
Make sure that the object
my_collection
is a list before you try to append data to it.>>> my_collection = [] >>> if isinstance(my_collection, list): ... print(f"my_collection is a list") ... my_collection is a list
What other options could you have besides explicitly checking the type?
Tuples¶
Explain why the following operations cannot be applied to the tuple
t
:t.append(1)
t[2] = 2
del t[3]
All operations attempt to change the tuple
t
. However, tuples cannot be changed.How can you sort the elements of a tuple?
>>> sorted(t)
Sets¶
How many elements does a set have if it is formed from the following list
[4, 2, 3, 2, 1]
?Four different elements.
Dictionaries¶
Suppose you have the two dictionaries
x = {"a":1, "b":2, "c":3, "d":4}
andy = {"a":5, "e":6, "f":7}
. What would be the content ofx
after the following code snippets have been executed?>>> del x["b"] >>> z = x.setdefault("e", 8) >>> x.update(y)
>>> x = {"a": 1, "b": 2, "c": 3, "d": 4} >>> y = {"a": 5, "e": 6, "f": 7} >>> del x["b"] >>> z = x.setdefault("e", 8) >>> x.update(y) >>> x {'a': 5, 'c': 3, 'd': 4, 'e': 6, 'f': 7}
Which of the following expressions can be a key of a dictionary:
1
;"Veit"
;("Veit", [1])
;[("Veit", [1])]
;["Veit"]
;("Veit", "Tim", "Monique")
>>> d = {} >>> d[1] = None >>> d["Veit"] = None >>> d[("Veit", [1])] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' >>> d[["Veit"]] = None Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type: 'list' >>> d[("Veit", "Tim", "Monique")] = None
You can use a Dictionary like a spreadsheet table by using Tuples as key row and column values. Write sample code to add and retrieve values.
>>> sheet = {} >>> sheet[("A", 0)] = 1 >>> sheet[("A", 1)] = 2 >>> sheet[("B", 0)] = 3 >>> sheet[("B", 1)] = 4 >>> print(sheet[("A", 1)]) 2
Strings¶
For example, can you add or multiply a string with an integer, a floating point number or a complex number?
>>> x = 3 >>> c = 3 + 4j >>> snake = "🐍" >>> x + snake Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'str' >>> x * snake '🐍🐍🐍' >>> c + snake Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'complex' and 'str' >>> c * snake Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't multiply sequence by non-int of type 'complex'
Operators and functions¶
Which of the following strings cannot be converted into numbers and why?
>>> int("1e2") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: '1e2' >>> int(1e+2) 100 >>> int("1+2") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: '1+2' >>> int("+2") 2
How can you change a heading such as
variables and expressions
so that it contains hyphens instead of spaces and can therefore be better used as a file name?>>> ve = "variables and expressions" >>> "-".join(ve.split()) 'variables-and-expressions'
If you want to check whether a line begins with
.. note::
, which method would you use? Are there any other options?>>> x.startswith(".. note::") True >>> x[:9] == ".. note::" True
Suppose you have a string with exclamation marks, quotation marks and line breaks. How can these be removed from the string?
>>> hipy = "„Hello Pythonistas!“\n" >>> hipy.strip("„“!\n") 'Hello Pythonistas'
How can you change all spaces and punctuation marks from a string to a hyphen (
-
)?>>> from string import punctuation, whitespace >>> chars = punctuation + whitespace >>> subs = str.maketrans(chars, len(chars) * "-") >>> hipy = "Hello Pythonistas!\n" >>> hipy.translate(subs) 'Hello-Pythonistas--'
re¶
Which regular expression would you use to find strings that represent the numbers between -3 and +3?
r"-?[0-3]"
orr"-{0,1}[0-3]"
?
is a quantifier for one or no occurrence.
Which regular expression would you use to find hexadecimal values?
r"0[xX][0-9a-fA-F]+"
corresponds to an expression starting with
0
, followed by a lower or upper casex
, followed by one or more characters in the ranges0-9
,a-f
orA-F
.
input()¶
How can you get string and integer values with the
input()
function?>>> year_birth = input("Geburtsjahr: ") Geburtsjahr: 1964 >>> type(year_birth) <class 'str'> >>> year_birth = int(input("Geburtsjahr: ")) Geburtsjahr: 1964 >>> type(year_birth) <class 'int'>
What is the effect if you do not use
int()
to callinput()
for integer inputs?>>> import datetime >>> current = datetime.datetime.now() >>> year = current.year >>> year_birth = input("Geburtsjahr? ") Geburtsjahr? 1964 >>> age = year - year_birth Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for -: 'int' and 'str'
Can you change the code so that it accepts a floating point number?
>>> import datetime >>> current = datetime.datetime.now() >>> year = current.year >>> year_birth = float(input("Geburtsjahr: ")) Geburtsjahr: 1964 >>> type(year_birth) <class 'float'>
What happens if you enter an incorrect value type?
>>> import datetime >>> current = datetime.datetime.now() >>> year = current.year >>> year_birth = int(input("Geburtsjahr: ")) Geburtsjahr: Schaltjahr Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: 'Schaltjahr'
Write the code to ask for the names and ages of three users. After the values have been entered, ask for one of the names and output the corresponding age.
>>> personal_data = {} >>> for i in range(3): ... name = input("Name? ") ... age = int(input("Age? ")) ... personal_data[name] = age ... Name? Veit Age? 60 Name? Tim Age? 35 Name? Monique Age? 37 >>> who = input("Who? ") Who? Veit >>> print(personal_data[who]) 60
Loops¶
Removes all negative numbers from the list
x = [ -2, -1, 0, 1, 2, 3]
.>>> x = [-2, -1, 0, 1, 2, 3] >>> pos = [] >>> for i in x: ... if i >= 0: ... pos.append(i) ... >>> pos [0, 1, 2, 3]
Which list comprehension would you use to achieve the same result?
>>> x = [-2, -1, 0, 1, 2, 3] >>> pos = [i for i in x if i >= 0] >>> pos [0, 1, 2, 3]
How would you count the total number of negative numbers in the list
[-[1, 0, 1], [-1, 1, 3], [-2, 0, 2]]
?>>> x = [[-1, 0, 1], [-1, 1, 3], [-2, 0, 2]] >>> neg = 0 >>> for row in x: ... for col in row: ... if col < 0: ... neg += 1 ... >>> neg 3
Creates a generator that only returns odd numbers from 1 to 10.
Tip
A number is odd if there is a remainder when it is divided by 2, in other words if
% 2
is true.>>> x = (x for x in range(10) if x % 2) >>> for i in x: ... print(i) ... 1 3 5 7 9
Write a dict with the edge lengths and volumes of cubes.
>>> {x: x**3 for x in range(1, 5)} {1: 1, 2: 8, 3: 27, 4: 64}
Exceptions¶
Write code that receives two numbers and divides the first number by the second. Check if the
ZeroDivisionError
occurs when the second number is0
and catch it.>>> x = int(input("Please enter an integer: ")) Please enter an integer: 7 >>> y = int(input("Please enter an integer: ")) Please enter an integer: 6 >>> try: ... z = x / y ... except ZeroDivisionError as e: ... print("It cannot be divided by 0!") ... >>> z 1.1666666666666667 >>> y = int(input("Please enter an integer: ")) Please enter an integer: 0 >>> try: ... print("It cannot be divided by 0!") ... except ZeroDivisionError as e: ... print("It cannot be divided by 0!") ... It cannot be divided by 0!
If
MyError
inherits fromException
, what is the difference betweenexcept Exception as e
andexcept MyError as e
?The first catches every exception that inherits from
Exception
, while the second only catchesMyError
exceptions.Write a simple program that receives a number and then uses the
assert()
statement to throw anException
if the number is0
.>>> x = int(input("Please enter an integer that is not zero: ")) Please enter an integer that is not zero: 0 >>> assert x != 0, "The integer must not be zero." Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError: The integer must not be zero.
Write a user-defined exception
outliers
that throws anException
if the variablex
is greater or less than3
?>>> class Outliers(Exception): ... pass ... >>> x = -4 >>> if abs(x) > 3: ... raise Outliers(f"The value {x} is an outlier") ... Traceback (most recent call last): File "<stdin>", line 2, in <module> Outliers: The value -4 is an outlier
Is checking whether an object is a list (Check: Listen) programming in the style of LBYL or EAFP?
This is LBYL programming. Only when you add
append()
to atry... except
block and catchTypeError
exceptions does it become a bit more EAFP.
Parameters¶
Write a function that can take any number of unnamed arguments and output their values in reverse order?
>> def my_func(*params): ... for i in reversed(params): ... print(i) ... >>> my_func(1, 2, 3, 4) 4 3 2 1
Variables¶
Assuming
x = 1
,func()
sets the local variablex
to2
andgfunc()
sets the global variablex
to3
, what value doesx
assume afterfunc()
andgfunc()
have been run through?>>> x = 1 >>> def func(): ... x = 2 ... >>> def gfunc(): ... global x ... x = 3 ... >>> func() >>> x 1 >>> gfunc() >>> x 3
Modules¶
If you have created a
my_math
module that contains adivide()
function, what options are there for importing this function and then using it? What are the advantages and disadvantages of each option?>>> import my_math >>> my_math.divide(..., ...)
>>> from my_math import divide >>> divide(..., ...)
The first solution is often favoured as there will be no conflict between the identifiers in
my_math
and the importing namespace. However, this solution is a little more complex.A variable
min
is contained in thescope.py
module. In which of the following contexts canmin
be used?With the module itself
Within the
scope()
function of the moduleWithin a script that has imported the
scope.py
module
and 2. but not 3.
Pack the functions that you created at the end of Decorators as an independent module. The functions should initially only be fully usable from another script.
from functools import wraps def my_decorator(f): @wraps(f) def wrapper(*args, **kwargs): """Wrapper docstring""" print("Call decorated function") return f(*args, **kwargs) return wrapper @my_decorator def example(): """Example docstring""" print("Call example function")
from example_mod import example print(example.__name__) print(example.__doc__)
Make your module executable.
--- /home/runner/work/python-basics-tutorial/python-basics-tutorial/docs/appendix/example_mod.py +++ /home/runner/work/python-basics-tutorial/python-basics-tutorial/docs/appendix/example_mod2.py @@ -15,3 +15,8 @@ def example(): """Example docstring""" print("Call example function") + + +if __name__ == "__main__": + print(example.__name__) + print(example.__doc__)
Rewrite your version of the
wc
utility so that it implements both the distinction between bytes and characters and the ability to read from files and from standard input.--- /home/runner/work/python-basics-tutorial/python-basics-tutorial/docs/modules/wcargv.py +++ /home/runner/work/python-basics-tutorial/python-basics-tutorial/docs/modules/wcargv_stdin.py @@ -1,27 +1,50 @@ -"""wc module. Contains function: words_occur()""" +"""Reads a file or stdin and returns the number of lines, words and characters – + similar to the UNIX wc utility.""" import sys -def words_occur(): - """words_occur() - count the occurrences of words in a file.""" - # Prompt user for the name of the file to use. - file_name = sys.argv.pop() - # Open the file, read it and store its words in a list. - with open(file_name, "r") as f: - word_list = f.read().split() - # Count the number of occurrences of each word in the file. - occurs_dict = {} - for word in word_list: - # increment the occurrences count for this word - occurs_dict[word] = occurs_dict.get(word, 0) + 1 - # Print out the results. - print( - f"File {file_name} has {len(word_list)} words, " - f"{len(occurs_dict)} are unique:" - ) - print(occurs_dict) +def main(): + """Count the occurrences of lines, words and characters in a file or + stdin.""" + # initialize counts + line_count = 0 + word_count = 0 + char_count = 0 + filename = None + option = None + if len(sys.argv) > 1: + params = sys.argv[1:] + if params[0].startswith("-"): + # If there are several parameters, the first one is taken as an option + option = params.pop(0).lower().strip() + if params: + filename = params[0] + file_mode = "r" + if option == "-c": + file_mode = "rb" + if filename: + infile = open(filename, file_mode) + else: + infile = sys.stdin + with infile: + for line in infile: + line_count += 1 + char_count += len(line) + words = line.split() + word_count += len(words) + if option in ("-c", "-m"): + print(f"{filename} has {char_count} characters.") + elif option == "-w": + print(f"{filename} has {word_count} words.") + elif option == "-l": + print(f"{filename} has {line_count} lines.") + else: + # print the answers using the format() method + print( + f"{filename} has {line_count} lines, {word_count} words and {char_count} characters." + ) if __name__ == "__main__": - words_occur() + main()
Classes¶
Write a
Triangle
class that can also calculate the area.class Triangle: def __init__(self, width, height): self.width = width self.height = height def area(self): return 0.5 * self.width * self.height
Methods¶
Write a class method that is similar to
circumferences()
, but returns the total area of all circles.def area(self): return self.diameter**2 / 4 * self.__class__.pi @classmethod def areas(cls): """Class method to sum all areas.""" careasum = 0 for c in cls.circles: careasum = careasum + c.area() return careasum
Classes and inheritance¶
Rewrites the code for a
Triangle
class so that it inherits fromForm
.>>> class Form: ... def __init__(self, x=0, y=0): ... self.x = x ... self.y = y ... >>> class Triangle(Form): ... def __init__(self, width=1, height=1, x=0, y=0): ... super().__init__(x, y) ... self.length = length ... self.height = height ...
How would you write the code to add an
area()
method for theTriangle
class? Should thearea()
method be moved to theForm
base class and inherited byCircle
,Square
andTriangle
? What problems would this change cause?It makes sense to put the
area()
method in aTriangle
class; but putting it inForm
would not be very helpful because different types ofForm
have their own area calculations. Any derivedForm
would override the basearea()
method anyway.
Data types as objects¶
What would be the difference between using
type()
andisinstance()
in Check: Lists?With
type()
you would only get lists, but not instances of lists.
Private variables and methods¶
Modify the code of the
Triangle
class to make the dimension variables private. What restriction will this change impose on the use of the class?>>> class Triangle: ... def __init__(self, x, y): ... self.__x = x ... self.__y = y ...
The dimension variables are no longer available outside the class via
.x
and.y
.Update the dimensions of the
Triangle
class so that they are properties with getters and setters that do not allow negative values.>>> class Triangle: ... def __init__(self, x, y): ... self.__x = x ... self.__y = y ... @property ... def x(self): ... return self.__x ... @x.setter ... def x(self, new_x): ... if new_x >= 0: ... self.__x = new_x ... @property ... def y(self): ... return self.__y ... @y.setter ... def y(self, new_y): ... if new_y >= 0: ... self.__y = new_y ... >>> t1 = Triangle(-2, 2) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 6, in __init__ ValueError: The number must be greater or equal to zero. >>> t1 = Triangle(2, 2) >>> t1.x = -2 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 13, in x ValueError: The number must be greater or equal to zero. >>> t1.x = 3 >>> t1.x 3
Creating a distribution package¶
If you want to create a task management package that writes the tasks to a database and provides them via a Python API and a command line interface (CLI), how would you structure the files?
The package performs three types of actions:
Accessing the database
Providing a Python API
Providing a command line interface
├── README.rst ├── pyproject.toml └── src └── items ├── __init__.py ├── api.py ├── cli.py └── db.py
Think about how you want to fulfil the above tasks. Which libraries and modules can you think of that could fulfil this task? Sketch the code for the modules of the Python API, the command line interface and the database connection.
I would create a
DB
class insrc/items/db.py
for communication with the database, in the following example for tinydb:import tinydb class DB: def __init__(self, db_path, db_file_prefix): self._db = tinydb.TinyDB( db_path / f"{db_file_prefix}.json", create_dirs=True ) def create(self, item: dict): """Create an item Returns: id: The items id. """ return id def read(self, id: int): """Reads an item. Args: id (int): The item id of an item. Returns: item: The item object.""" return item def update(self, id: int, mods): """Update an item in the database. Args: id (int): The item id of an item. mods (Item): The modifications to be made to this item. """ self._db.update(changes, doc_ids=[id]) def delete(self, id: int): """Deletes an item in the database. Args: id (int): The item id of an item. """ self._db.remove(doc_ids=[id]) def close(self): """Closes the database connection.""" self._db.close()
Then I would use
dataclass()
insrc/items/api
to create anItem
class:from dataclasses import dataclass, field @dataclass class Item: summary: str = None owner: str = None state: str = "todo" id: int = field(default=None, compare=False) class ItemsException(Exception): pass class ItemsDB: def __init__(self, db_path): self._db_path = db_path self._db = DB(db_path, ".items_db") def add_item(self, item: Item): return def get_item(self, item: Item): return def update_item(self, item: Item): return def delete_item(self, item: Item): return def close(self): self._db.close() def path(self): return self._db_path
ItemsException
Item andItemsDB
are then provided insrc/items/__init__.py
:from .api import ItemsException, Item, ItemsDB
See also
You can find a complete example at github.com/veit/items.
Files¶
Uses the functions of the
os
module to take a path to a file namedexample.log
and create a new file path in the same directory for a file namedexample.log1
.>>> import os >>> path = os.path.abspath("example.log") >>> print(path) /Users/veit/python-basics-tutorial-de/example.log >>> new_path = f"{path}2" >>> print(new_path) /Users/veit/python-basics-tutorial-de/example.log2
What is the significance of adding
b
as a parameter toopen()
?This opens the file in binary mode, which means that bytes and not characters are read and written.
Open a file
my_file.txt
and insert additional text at the end of the file. Which command would you use to openmy_file.txt
? Which command would you use to reopen the file and read it from the beginning?>>> with open("my_file", "a") as f: ... f.write("Hi, Pythinistas!\n") ... 17 >>> with open("my_file") as f: ... print(f.readlines()) ... ['Hi, Pythinistas!\n', 'Hi, Pythinistas!\n']
What use cases can you imagine in which the
struct
module would be useful for reading or writing binary data?when reading and writing a binary file
when reading from an external interface, where the data should be stored exactly as it was transmitted
Why pickle may or may not be suitable for the following use cases:
Saving some state variables from one run to the next ✅
Storing evaluation results ❌, as pickle is dependent on the respective Python version
Saving user names and passwords ❌, as pickles are not secure
Saving a large dictionary with English terms ❌, as the entire pickle would have to be loaded into memory
If you look at the man page for the wc utility, you will see two command line options:
-c
counts the bytes in the file
-m
counts the characters, which in the case of some Unicode characters can be two or more bytes long
Also, if a file is specified, our module should read from and process that file, but if no file is specified, it should read from and process
stdin
.See also
If a context manager is used in a script that reads and/or writes multiple files, which of the following approaches do you think would be best?
Put the entire script in a block managed by a
with
statement.Use one
with
statement for all reads and another for all writes.Use a
with
statement every time you read or write a file, that is, for every line.Use a
with
statement for each file you read or write.
Probably 4. is the best approach as part of the context manager’s job when accessing files is to ensure that a file is closed.
Archive
*.txt
files from the current directory in thearchive
directory as*.zip
files with the current date as the file name.Which modules do you need for this?
Write a possible solution.
1>>> import datetime 2>>> import pathlib 3>>> import zipfile 4>>> file_pattern = "*.txt" 5>>> archive_path = "archive" 6>>> today = f"{datetime.date.today():%Y-%m-%d}" 7>>> cur_path = pathlib.Path(".") 8>>> paths = cur_path.glob(file_pattern) 9>>> zip_path = cur_path.joinpath(archive_path, today + ".zip") 10>>> zip_file = zipfile.ZipFile(str(zip_path), "w") 11>>> for path in paths: 12... zip_file.write(str(path)) 13... path.unlink() 14...
- Line 9
creates the path to the ZIP file in the archive directory.
- Line 10
opens the new ZIP file object for writing;
str()
is required to convert a path into a character string.- Line 12
writes the current file to the ZIP file.
- Line 13
removes the current file from the working directory.