© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
G. GutaPragmatic Python Programminghttps://doi.org/10.1007/978-1-4842-8152-9_4

4. The Control Structure: How to Describe the Workflow

Gabor Guta1  
(1)
Budapest, Hungary
 

“Features of a programming language, whether syntactic or semantic, are all part of the language’s user interface. And a user interface can handle only so much complexity or it becomes unusable.”

Guido van Rossum

If different statements have to be executed based on various conditions or if statements need to be executed repeatedly, then so-called control structures can be used. They allow different statements to be executed depending, for example, on the value of a Boolean expression. These control structures are another important tool to enable programs to express complex behavior. In this chapter, you will learn the four main kinds of control structures of the Python language : the if statement, the match statement, the while statement, and the for statement. All these statements can be embedded into each other arbitrarily.

if Statement

The simplest control structure is the if statement . The if statement allows a series of statements to be executed depending on a condition formulated in a Boolean expression. This control structure is an if statement followed by statements depending on the condition with indentation (in the form of a block). The if statement begins with an if keyword followed by the Boolean expression and closed by a colon.

Optionally, another branch can be connected to this control structure, which runs if the condition is not satisfied. This is denoted by an else keyword located in the same indentation level as the if statement, with a succeeding colon; the statements that follow are executed if the condition is not satisfied.

You’ll recall the Product class was defined in Listing 3-20. If the value of the discount is between the acceptable range (i.e., it is higher than 0 and lower than 99), the price of the product will be reduced. Otherwise, an error message is displayed. Figure 4-1 shows the flow.

An algorithm of a simple if statement. The process starts with defining product and reducing the amount of discounts. If the discount value is between 0 and 99, reduce the price, and if anything else, message, the discount value is too low or too high. And the process ends by reduction of price, from both the discounted values.

Figure 4-1

Simple if statement

In the first two lines of Listing 4-1, a product object is instantiated, and the extent of the discount will be asked for.
product = Product('K01', 'cube', 1000)
discount_value = int(input('Amount of the discount (in %)?'))
if discount_value > 0 and discount_value <= 99:
    product.reduce_price(discount_value)
else:
    print('Discount value is too low or too high')
Listing 4-1

Simple if Statement

The if statement may contain multiple branches guarded by different conditions. The branch following the first one starts with the elif keyword, followed by a Boolean expression and a colon with indented statements that run if the condition is satisfied. This can be repeated any number of times. With many statements guarded by different conditions, the one satisfied first will run. An else branch can be defined in this case as well to the very end, which will be executed if none of the conditions got satisfied.

Figure 4-2 shows that all kinds of invalid values are handled as separate branches, and a corresponding error message is displayed.

An algorithm for a statement with multiple branches. The process starts with defining the product and reading the amount of discount. The components are differentiated and branched according to the different discount rates. All the branches converge at reducing the price, and the process ends.

Figure 4-2

if statement with multiple branches

Listing 4-2 shows the corresponding Python code .
product = Product('K01', 'cube', 1000)
discount_value = int(input('Amount of the discount (in %)?'))
if 0 < discount_value <= 99:
    product.reduce_price(discount_value)
elif discount_value < 0:
    print('Discount value is negative')
elif discount_value == 0:
    print('No discount')
elif 99 < discount_value <= 100:
    print('Discount value is too high')
else:
    print('Price will be negative')
Listing 4-2

If Statement with Multiple Branches

As mentioned earlier, control structures can be embedded into each other. In Listing 4-3, the top-level if statement has a branch corresponding to the case when discount_value is not in the expected range. This branch contains an if statement that checks whether the value is too low or too high.
product = Product('K01', 'cube', 1000)
discount_value = int(input('Amount of the discount (in %)?'))
if discount_value > 0 and discount_value <= 99:
    product.reduce_price(discount_value)
else:
    if discount_value <= 0:
        print('Discount value is too low')
    else:
        print('Discount value is too high')
Listing 4-3

if Statement with an Embedded if Statement

match Statement

The match statement is similar to the if statement. The main difference is that it matches the resulting object of an expression to patterns to select the branch to be executed, instead of evaluating Boolean expressions. The match statement was introduced in Python 3.10. This statement starts with a match keyword followed by the expression to which the patterns are compared. After a colon, the block contains the list of the patterns. The pattern in the simplest case can be a string, integer, Boolean value, or None. Multiple patterns can be listed connected with the | symbol, which indicates an “or” connection (i.e., at least one of the patterns must match). The pattern is written between a case keyword and a colon, followed by the block that will be executed if there is a successful match.

The pattern can contain variable names and an if keyword followed by a guard condition (a Boolean expression), which can already reference the variable names. This notation of guard condition enables the practical combination of simple matching with the comparison, as shown in the if statement. What else can be a pattern will be detailed in the “Advanced Details” section.

You can also follow its functionality in Figure 4-3. In the first case, the discount_value must be equal to 0, and in this situation the “No discount” text will be displayed. In the second case, the discount_value must be equal to 1, and in this situation, the “Only 1%” text will be displayed, and the price will be reduced by 1 percent. The third case is similar to the second case with the difference that here the discount_value must be equal to 5 or 10 and the displayed text will be slightly different. The last case contains the underscore character, which means that it will match any value.

An algorithm for a statement with a match statement. The process starts with defining the product and reading the amount of discount. The components are differentiated and branched according to the different discount rates. All the branches converge at reducing the price, and the process ends.

Figure 4-3

match statement with literals

In Listing 4-4, you can see a match statement in which one of the four cases can be selected.
product = Product('K01', 'cube', 1000)
discount_value = int(input('Amount of the discount (in %)?'))
match discount_value:
    case 0:
        print('No discount')
    case 1:
        print('Only 1%')
        product.reduce_price(discount_value)
    case 5|10:
        print(f'{discount_value}% is reasonable')
        product.reduce_price(discount_value)
    case _:
        print('We allow only 0%, 1%, 5% or 10% discounts')
Listing 4-4

match Statement with Literals

Figure 4-4 shows a similar example with more complex guard conditions. This can be also realized with a match statement .

An algorithm for a statement with guard conditions. The process starts with defining the product and reading the amount of discount. The components are differentiated and branched according to the different discount rates. If the discount falls within the range, the price is reduced, while other branches do not get reduced, and the process ends.

Figure 4-4

match statement with guard conditions

In Listing 4-5, you can see the combination of fixed patterns with guard conditions, which enables the handling of more complex cases. In the second, third, and fourth cases, the variable name x is assigned to the integer expected to be evaluated in the guard conditions.
product = Product('K01', 'cube', 1000)
discount_value = int(input('Amount of the discount (in %)?'))
match discount_value:
    case 0:
        print('No discount')
    case x if 0 < x <= 99:
        print(f'Within allowed range: {x}%')
        product.reduce_price(discount_value)
    case x if x <= 0:
        print('Discount value is negative')
    case x if 99 < x <= 100:
        print('Discount value is too high')
    case _:
        print('Price will be negative')
Listing 4-5

match Statement with Guard Conditions

while Statement

The while statement is intended to repeat a series of statements until a condition is met. Such conditions can be, for example, reaching a value or a certain event occurring. The common property is that you do not know how many repetitions will lead to the desired result.

Figure 4-5 shows an example that can be implemented in Python with the while statement.

An algorithm for a simple statement. The process starts with defining the product and reading the amount of discount. If the discount value is between 0 and 99, display the message and reading amount of discount. And the step repeats. If the discount is something else, the price is reduced, and the process ends.

Figure 4-5

Simple while statement

In Listing 4-6 reading the extent of the discount is repeated until the read value falls within the expected range. When this occurs, the repetition will end, and line 6 will be executed.
product = Product('K01', 'cube', 1000)
discount_value = int(input('Amount of the discount (in %)?'))
while not 0 < discount_value <= 99:
    print('Discount abount is too low or too high')
    discount_value = int(input('Amount of the discount (in %)?'))
product.reduce_price(discount_value)
Listing 4-6

Simple while Statement

There are two lines in the previous example where data is read: the value is read once always at the start, and second time it is read only when it was not possible to obtain a value meeting the conditions on the first try. If you wanted to read data only at one point, the read statement will have to be placed inside the loop. Additionally, the condition must be evaluated in a way that its value should be determined after the reading. Figure 4-6 represents this process visually.

An algorithm for a while statement with status variable. The process starts with defining products and needs to read the new value true. If there is a need to read new values, read the amount of discount, If the discount is between 0 and 99, the new value is read, and if the discount is something else, the message is displayed. For other prices, the price is reduced.

Figure 4-6

while statement with a status variable

The condition of the loop has been modified in Listing 4-7 so now it depends on a read_next variable . This is first true; then if the read value is found to be inside the range, it changes to false. If its value is false, the next repetition will not run. If it is not within range, its value will still be true; hence, the reading is repeated.
product = Product('K01', 'cube', 1000)
read_next = True
while read_next:
    discount_value = int(input('Amount of the discount (in %)?'))
    if 0 < discount_value <= 99:
        read_next = False
    else:
        print('Discount abount is too low or too high')
product.reduce_price(discount_value)
Listing 4-7

while statement with a Status Variable

The assignment expression (alias walrus expression) was introduced in Python 3.8, and it provides an elegant solution to the issue shown earlier. In Listing 4-8 the assignment of the level of discount located in the expression guards the while loop. The code will be more transparent; therefore, the same value giving the statement will not have to be repeated in two places or have not have to introduce a new variable to delay the evaluation of the condition.
product = Product('K01', 'cube', 1000)
while not 0 < (discount_value
        := int(input('Amount of the discount (in %)?'))) <= 99:
    print('Discount abount is too low or too high')
product.reduce_price(discount_value)
Listing 4-8

while Statement with Assignment Expression

Note that the assignment expression can be used in the while statement , if statement, and similar context and cannot be used in a stand-alone statement (as a trick it can be used in a parenthesized form, but it is not recommended).

The loop can be interrupted by a break statement . This is usually used when we want to discontinue the loop based on another condition or if it by default repeats infinitely many times. An infinitely repeating loop can be constructed by using a Boolean expression evaluated always as true (e.g., a constant of true) as a conditional expression instead of a real variable, which is changed to false at some point of the execution. Figure 4-7 shows an example.

An algorithm for a statement as an infinite loop. The process starts by defining the product and reading the amount of discount. If the amount is between 0 and 99, the process ends, and if the rate is something else message is displayed, and the process continues.

Figure 4-7

while statement as an infinite loop

Listing 4-9 shows the Python code for this example.
product = Product(‘K01’, ‘cube’, 1000)
while True:
    discount_value = int(input(‘Amount of the discount (in %)?’))
    if 0 < discount_value <= 99:
        break
    else:
        print(‘Discount abount is too low or too high’)
product.reduce_price(discount_value)
Listing 4-9

while Statement as an Infinite Loop

The execution of the statements within the loop can be interrupted by the continue statement too. This aborts the execution of the following statements and starts a new cycle of the loop. This causes the re-evaluation of the loop condition, and if it is still true, the statements start to be executed again. The continue statement is usually used when the rest of statements must be skipped; the loop condition must be evaluated based on the changed environment or to continue an infinite loop.

Figure 4-8 shows an example of the solution being implemented in this way.

An algorithm for a statement as an infinite loop. The process starts by defining the product and reading the amount of discount. If the amount is between 0 and 99, the process ends, and if the rate is something else message is displayed, and the process continues. The process ends by reducing the price.

Figure 4-8

while statement as an infinite loop (second version)

Listing 4-10 shows the implementation of the example. This demonstrates the restart of the cycle after the execution of the if branches is implemented with the help of the continue statements .
product = Product('K01', 'cube', 1000)
while True:
    discount_value = int(input('Amount of the discount (in %)?'))
    if discount_value <= 0:
        print('Discount abount is too low')
        continue
    if discount_value > 99:
        print('Discount abount is too high')
        continue
    break
product.reduce_price(discount_value)
Listing 4-10

while Statement as an Infinite Loop (Second Version)

After the while statement , there can be an else keyword and a block that runs once if the condition is not met. The else branch is not executed if the loop is ended with the break statement. Listing 4-11 shows this.
product = Product('K01', 'cube', 1000)
tries = 0
while tries < 3:
    discount_value = int(input('Amount of the discount (in %)?'))
    if 0 < discount_value <= 99:
        break
    else:
        print('Discount abount is too low or too high')
    tries += 1
else:
    print('No more try')
    discount_value = 0
product.reduce_price(discount_value)
Listing 4-11

while Statement with an else Branch

for Statement

In a for loop , you can specify a repetition that corresponds to a range of values (the range can even be infinite). This statement is started with a for keyword followed by a target variable name; then an in keyword followed by an expression that generates values assigned to the variable; finally, a colon and the statements to be repeated with indentations appear. The simplest and most frequent iterator is the range function. This function makes the loop repeated by a fixed number, and the target variable name will take a value from a fixed range of numbers. The range function can have one, two, or three integer numbers as arguments.
  • In the case of a single argument, the number is the repetition number (from 0 to n-1).

  • In the case of two arguments, the start and end of the range are specified (from a to b-1).

  • In the case of three arguments, also the size of the steps can be specified.

Chapter 5 will cover what kind of expression can be used in the for statement instead of the range function. The for statement can contain an else branch that behaves similarly to those previously covered.

The upcoming examples will show that the price change of the product is calculated for each value of discount in a specific range. Figure 4-9 shows the essence of this behavior.

An algorithm for a statement with fixed loop numbers. The process starts with reading the next amount of discount. If the amount of discount is available, the product is defined, the price is reduced, and the new price is displayed. The process repeats. If the process is no more available, it ends.

Figure 4-9

A for statement with a fixed number of steps

Listing 4-12 shows an example of the value of the discount running between 0 and 9. This is achieved by a range function that has a single parameter, which represents the number of steps. In this case, the first value assigned to discount_value is 0.
for discount_value in range(10):
    product = Product('K01', 'cube', 1000)
    product.reduce_price(discount_value)
    print('Cost of the product:', product.price)
Listing 4-12

A for Statement with a Fixed Number of Steps

Listing 4-13 shows the value of the discount running between 1 and 10. This is done by providing two arguments to the range function: the first value and the one after the last value.
for discount_value in range(1,11):
    product = Product('K01', 'cube', 1000)
    product.reduce_price(discount_value)
    print('Cost of the product:', product.price)
Listing 4-13

A for statement with a Fixed Number of Steps with Range Arguments

It is important to emphasize that in the case of two arguments the second argument is not the number of steps, but the first number that is not included in the range.

Listing 4-14 shows the discounts of 1, 3, 5, 7, and 9 percent. In this case, the range function is called with three arguments: the first value, the one after the last value, and the step size.
for discount_value in range(1,11, 2):
    product = Product('K01', 'cube', 1000)
    product.reduce_price(discount_value)
    print('Cost of the product:', product.price)
Listing 4-14

A for Statement with a Fixed Number of Steps with Step Size

This is important to emphasize that in the case of two arguments, the second argument is not the number of steps, but the first number that is not included in the range.

Listing 4-15 shows an example of what the else branch of a for statement looks like. It will be executed after the for loop successfully iterates over each value in the specified range. If the inferred price is considered too low, the if statement will break the loop, and the else branch will not be executed.
TOO_LOW_PRICE = 900
for discount_value in range(10):
    product = Product('K01', 'cube', 1000)
    product.reduce_price(discount_value)
    if product.price < TOO_LOW_PRICE:
        break
    print('Cost of the product:', product.price)
else:
    print('All discount values are acceptable')
Listing 4-15

A for Statement with the else Branch

Exception Handling

Exception handling helps to manage errors or exceptional cases separately. Thus, you do not have to insert error handling sections between statements executed during the normal executions. This helps to separate clearly which statements belong to the normal operation and which belong to the handling of exceptional or faulty operation. Errors can range from “division by zero” to “file not found.” When handling an exception, the type of error or problem is signaled by the system with the instantiation of an exception object (or alternatively such an object created by the program itself as well).

The exception handling is built around four keywords: in the block after the try keyword, there is the code, which is attempted to be executed; in the blocks after the except keywords comes the statements managing the exceptional cases; the block after the else keyword contains the statements managing the nonexceptional cases; and statements in the block after the finally keyword always run. The except keyword can be followed by a class determining the type of the exception. Then it will be executed only when the exception belongs to the particular class; otherwise, it can be executed for any exception. Let’s start with the simplest case in which only a try and an except branch exists.

Figure 4-10 illustrates this concept.

An algorithm for exception handling. The process starts with reading the amount of discount and ends after that. If the value is invalid, the discount value is 0, and then the process ends.

Figure 4-10

Exception handling

In Listing 4-16, the string read is converted to an integer. If this conversion is unsuccessful as the string contains characters other than numbers, the exception handling block is executed to set the discount value to 0.
try:
    discount_value = int(input('Amount of the discount (in %)?'))
except ValueError as e:
    print(f'Error: {e}')
    discount_value = 0
Listing 4-16

A Simple Way to Handle Exceptions

The except and the following block can be repeated with different exception types, but then they have to be organized in a way that the more specific class would be the preceding one. The block after the else keyword is executed if no exception occurred. The finally block will run even when the block belongs to the try statement, which can be interrupted by break, continue, or return statements.

Listing 4-17 shows a complete try statement . It can have three different execution paths.
  • A valid integer is given as input, and the block corresponding to the try executes without interruption. Then the else and finally branches are executed too in this order.

  • A noninteger string is given as input, and the block corresponding to the try will be aborted with a ValueError exception . Then the first except and the finally branch are executed.

  • An internal error occurs in the interpreter (actually this scenario is close to impossible), and a SystemError exception is raised. Then the second except and the finally branch are executed.

try:
    discount_value = int(input('Amount of the discount (in %)?'))
except ValueError as e:
    print(f'Error converting the input: {e}')
    discount_value = 0
except Exception as e:
    print(f'Error: {e}')
    discount_value = 0
else:
    print(f'The input value is a valid integer')
finally:
    print(f'The amount of discount will be {discount_value}')
Listing 4-17

A Complete Way to Handle Exceptions

Triggering the exception can be achieved by the raise keyword followed by the instantiation of an exception type. This can be left out when in the exception handling block it is expected to reuse the currently handled exception object. The exception object can be instantiated from a class where the base class is the ExceptionBase . An exception can arise at any point of the program. This means that the program is stopped there and steps back to the outer block or block of the statements calling the function until it finds a point where handling of this exception took place. If there is no such point during the whole process, the program exits with an error message.

Listing 4-18 demonstrates how to raise an exception from a function. If due to any reason it cannot discount the product, the function raises an exception. To handle the conversion error of the string to a number, there is an exception handling block within the function itself. The outermost exception handling block is located around the invocation of the function.
def reduce_price(product):
    try:
        amount = input('Amount of the discount (in %)?')
        discount_value = int(amount)
    except ValueError as e:
        raise ValueError('Not an integer')
    if discount_value > 0 and discount_value <= 99:
        product.reduce_price(discount_value)
    else:
        raise ValueError('Discount abount is too low or too high')
try:
    product = Product('K01', 'cube', 1000)
    reduce_price(product)
except ValueError as e:
    print('Modification is failed for the following reason:', str(e))
Listing 4-18

Exception Handling with Raising an Exception

Context Management

It occurs frequently in a program that some resources are needed to be reserved and then released again. Such resources can be files or network connections that are opened and then closed. As the resource reservation and releasing pairs are easily messed up, this can be provided automatically by the context management device. A with keyword is followed by the creation of the object representing the resource to be reserved and then released. In the block that follows, the resource can be used; then when leaving the block, this is automatically closed or released.

Listing 4-19 shows how to open a file that you write to; then after leaving the block, it will be automatically closed.
with open('orders.txt', 'wt') as orders_doc:
    orders_doc.write('Orders:')
Listing 4-19

Context Management

The open function takes two parameters: a filename and a file mode (wt means writing to a text file). It returns a file object that will be assigned to the orders_doc variable name. In the second line, the Orders: string will be written in the opened files. Without the context management statement, the write statement must be followed by the method call order_doc.close() to explicitly close the file.

Tip

File objects can be used to manipulate files. When opening a file by default, it is opened for reading in text mode. Files can be opened for reading (denoted with r), for reading and writing (denoted with +), or for writing (denoted with w to truncate an existing file, a to append to an existing file, or x to create a new file). The mode determines the type of the object expected by the file objects: in text mode (denoted with t) strings are read and written; in binary mode (denoted with b) bytes are expected.

The following are the five most important methods of the file object:

- The read(n) method reads n characters or bytes from the file and the entire file if the parameter is omitted.

- The readline() method reads a line from a file, but only works for the file opened in text mode.

- The write(v) method writes v into the file.

- The seek(n) method changes the position to n in the file.

- The tell() method returns the current position in the file.

Recursion

Recursion is being covered with the control structures since its behavior is similar to what can be achieved with a while statement; one has to use only a combination of a function call and an if statement. From a function, it is possible to invoke not only another function, but itself as well. This option is used typically in cases where the task can be described as an initial step and repetition of a step with changing parameters. Listing 4-20 solves recursively the following problem: how many times can a product be discounted by 10 percent to make its price lower than a given sum? A function calculating this must examine whether the current price of the product is lower than the expected price. If it is lower, it does not have to be discounted; if it is not true, these steps have to be repeated with the discounted price (i.e., the result will be that the price of the product will be reduced one more time). In this second case, the function calls itself with modified arguments. After some calls, these modifications in the arguments are expected to reach a value for which the price of the product will fall below the expected price; thus, the recursion will terminate. If the conditions are not properly specified or the step does not make the values converge fast enough to fulfill the conditions, the recursion will terminate after a certain number of calls (the default value of allowed function call depth is typically 1000).
def how_many(single_pass_value, total_value,
             actual_value=None, count=0):
    print(actual_value, count)
    if actual_value is None:
        actual_value = single_pass_value
    if actual_value>=total_value:
        return count
    else:
        return how_many(single_pass_value, total_value,
                        actual_value*single_pass_value, count+1)
print(how_many(1.1, 1.5))
Listing 4-20

Recursion

Loops in Practice

For loops , it is worthy to explain in the comments the reasoning behind their expected termination and what the invariant is (a Boolean expression that will always be true upon executing the loop). As shown in Listing 4-21, the loop will terminate once an applicable discount amount is specified. During the execution of the loop, you can be sure that if the loop does not repeat more times, the amount of the discount value will be inside the range. This invariant could be useful in understanding or debugging the loop.
product = Product('K01', 'cube', 1000)
read_next = True
attempts = 0
while read_next and attempts < 3:
    # Stop condition: will terminate after 3 attempts
    # Invariant: 0 < discount_value <= 99
    #            or read_next
    discount_value = int(input('Amount of the discount (in %)?'))
    if 0 < discount_value <= 99:
        read_next = False
    else:
        print('Discount value to high/low')
    attempts += 1
product.reduce_price(discount_value)
Listing 4-21

Comments at the Loop

If you are unsure about the termination of the loop, you can consider introducing a counter to specify an upper number of attempts, as shown in Listing 4-22.
product = Product('K01', 'cube', 1000)
read_next = True
attempts = 1
while read_next:
    # Stop condition: will terminate after 3 attempts in worst case
    # Invariant: 0 < discount_value <= 99
    #            or read_next
    discount_value = int(input('Amount of the discount (in %)?'))
    if 0 < discount_value <= 99:
        read_next = False
    else:
        print('Discount value to high/low')
        if attempts >= 3:
            raise ValueError('No valid discount value after 3 attempt')
        attempts += 1
product.reduce_price(discount_value)
Listing 4-22

Loop with a Counter

It is important to note that after the failed attempts the loop is left by raising an exception; this prevents the execution of the statement after the loop, which expects discount_value and does have a valid value. Naturally, a loop with a counter can be expressed with a for statement too.

Advanced Details

This section describes mostly technical details in reference manual style and some advanced concepts that may need more technical background.

Matching Classes and Other Kinds of Patterns

In the case of class patterns, you can specify patterns that can match to specific objects. The pattern contains the name of the class and in parentheses positional or keyword arguments. This syntax resembles the object instantiation syntax, but no object will be created in the current case. The sole purpose of this syntax is to describe that the expected object belongs to a certain class and to specify its expected data attribute values. The parameters are listed as keyword arguments, or in the case of positional arguments the class definition must contain a special __match_args__ class variable. This variable must contain the list attributes relevant during the matching and their expected order in the pattern.

Listing 4-23 shows that the Product class contains the __match_args__ variable to specify that the code, name, and price attributes can be matched and in this order. In the first case, you expect an object in which all three attributes match with values specified in the pattern. The second pattern matches an object with fixed code and value attributes, but any name. The third pattern matches an object with fixed code and name attributes, but any value. The fourth pattern matches if the object does meet any of the earlier criteria but has at least a K01 code.
class Product:
    __match_args__ = ("code", "name", "price")
    def __init__(self, code, name, price):
        self.code = code
        self.name = name
        self.price = price
        self.old_price = price
product = Product('K01', 'cube', 1000)
product.name = input('Default name?')
product.price = int(input('Default price?'))
match product:
    case Product('K01', 'cube', 1000):
        print('No changes')
    case Product('K01', name, 1000):
        print('Has new name')
    case Product('K01', 'cube', value):
        print('Same old name, but different price:', value)
    case Product('K01', name, value):
        print('Everything has changed')
Listing 4-23

match Statement with Class Patterns

Additional kinds of patterns can be used: lists and dictionaries (these types will be explained in the next chapter). Listing 4-24 and Listing 4-25, respectively, show examples analogous to Listing 4-23 but using lists and dictionaries.
default_product_values = ['K01', 'cube', 1000]
default_product_values[1] = input('Default name?')
default_product_values[2] = int(input('Default price?'))
match default_product_values:
    case ['K01', 'cube', 1000]:
        print('No changes')
    case ['K01', name, 1000]:
        print('Has new name')
    case ['K01', 'cube', value]:
        print('Same old name, but different price:', value)
    case ['K01', name, value]:
        print('everything is changed')
Listing 4-24

match Statement with List Patterns

default_product_values = {'id': 'K01', 'name': 'cube', 'price': 1000}
default_product_values['id'] = input('Default name?')
default_product_values['name'] = int(input('Default price?'))
match default_product_values:
    case {'id': 'K01', 'name': 'cube', 'price': 1000}:
        print('No changes')
    case {'id': 'K01', 'name': name, 'price': 1000}:
        print('Has new name')
    case {'id': 'K01', 'name': 'cube', 'price': value}:
        print('Same old name, but different price:', value)
    case {'id': 'K01', 'name': name, 'price': value}:
        print('everything is changed')
Listing 4-25

match Statement with Dictionary Patterns

Exception Classes

As described earlier, exceptions are also objects. An exception will be instantiated from classes, the base class of which is the BaseException class or a derived class of it, the Exception. In Listing 4-26, a class named ProductcodeError is defined with two extra instance variables (the code and the message) storing error-specific information. The last line is necessary to make the message instance variable to the string representation of the exception.
class ProductCodeError(Exception):
    def __init__(self, code):
        self.code = code
        self.message = f'Code {code} does not exists'
        super().__init__(self.message)
Listing 4-26

Custom Exception Class

The exception class defined in Listing 4-26 can be raised as shown in Listing 4-27. The raised exception is also caught in the example. The last line of the listing demonstrates how the string representation of the exception can be printed.
try:
    raise ProductCodeError('B1')
except ProductCodeError as e:
    print(f'Error: {e}')
Listing 4-27

Raising a Custom Exception

Context Manager Classes

When a context is being created, an expression that yields a context manager class must be specified. The context manager classes contain two special methods: __entry__() and __exit__(). The former creates the context, while the latter destructs it.

In Listing 4-28, the DiscountAttempt class is defined that stores a reference in its instantiation to a Product type object. When entering the context managed block, it saves the instance variables of the product object, and when exiting the context, it restores the saved values.
class PriceReductionTry:
    def __init__(self, product):
        self.product = product
    def __enter__(self):
        self.price = self.product.price
        self.old_price = self.product.old_price
        return self.product
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.product.price = self.price
        self.product.old_price = self.old_price
sub_cube = Product('T01', 'Substitute cube', 300)
with PriceReductionTry(sub_cube):
    sub_cube.reduce_price(20)
    print(sub_cube)
print(sub_cube)
Listing 4-28

Context Manager

Evaluating Strings

The Python language contains a built-in tool for calculating an expression stored in the form of an arbitrary string. Listing 4-29 demonstrates the evaluation of the expression covered in Chapter 1. It can be used to store the expression to be calculated in a configuration file or in other form. As the eval() makes possible the calculation of an arbitrary expression, you should be careful regarding the source of the string to be calculated so as not to give an opportunity to run malicious code.
eval('5000 * 2 + 2000')
Listing 4-29

Evaluating a String

Activity Diagram

The activity diagram is the workflow diagram of the UML standard. The activity on the diagram—denoting typically a function call or execution of an operation—is denoted by rectangles with rounded edges. The beginning and end of the process are denoted by solid and empty circles, respectively. These elements can be connected by a solid line having an open arrowhead at one end, which denotes the succession of the elements. The arrow points at the element later in time. The branches are denoted by rectangles, and conditions are written between square brackets with arrows pointing outward.

Key Takeaways

  • if and match statements allow you to execute different parts of the program based on the state of the environment. If a statement evaluates a Boolean expression and the result is true, it executes a block of code. Additionally, any number of elif branches and a single else branch can be attached to the if statement. The match statement selects a branch based on the matching of a pattern to the specified object, and the branch containing the first matching pattern will be selected.

  • Other important control structures are loops, namely, the while statement and the for statement . They make possible the repetition of program steps. The while statement executes the next block based on the result of a Boolean expression. The for statement expects an expression that defines how many times and for which values the next block must be executed.

  • There are control structures serving special purposes. The exception handling helps to execute program blocks only in the case of errors or unexpected conditions. The objects triggering such blocks are called exceptions , and the developer can use the raise statement with an exception object to signal an error or unexpected condition.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.183.172