Control

# Control #

## Side Effects #

Side effects occur in functions when the function alters the global environment in some form. This could be in the form of altering a variable in the global scope, or using a `print` statement inside a function. One easy way to tell if a function contains side effects is that if a function acts like a mathematical function, it has no side effects.

A Mathematical function `f(x)` takes inputs and always provides the same outputs, without doing anything else. As a result, one input will never have two or more different outputs. If a programming function has the same property where certain outputs always provide the same inputs without doing anything else, it has no side effects

Here are a few examples:

### Example With No Side Effects #

``````def no_side_effects(x): # Squares x
return x*x

no_side_effects(2) # >>> 4
no_side_effects(2) # >>> 4
``````

In this example, every input will always map to the same output as the function does not do anything other than provide an output given an input

### Example With Side Effects #

``````ben = 1

def with_side_effects(x):
return x*ben

with_side_effects(2) # >>> 2
ben = 2
with_side_effects(2) # >>> 4
``````

While this example is not going to be used in a practical setting, it serves as a good example of what a side effect is. As you can see, even though we called the function `with_side_effects` twice with the same input, the output was different, which goes against the definition of a mathematical function. As a result, there is a side effect in that function.

A side note:

While the `print` function does not change the value of any variables, it does cause a side effect by outputting something to the console, which does go against how a mathematical function works. However, while side effects are usually better to avoid where possible, `print` could sometimes be useful for debugging.

An example of a side effect that can’t be avoided is writing to a file - this is necessary at times.

A function without side effects is also known as a pure function, while a function with side effects is also known as an impure function.

## The ‘None’ Value #

In Python, any function that does not `return` a value will return `None`, which when printed will render `None` to the console, and when called, will not render anything in the console.

For example:

``````def no_return(x):
x = 1

print(no_return(2))
# >>> None
``````

Note that the `print()` function has no return value, and thus will return `None` when called.

## Nested Print Statements #

A nested print function is somewhat weird, but worth it to learn.

Take this for example, what do you think will get outputted to the console? Try to think for yourself before opening the answer box below.

``````print(print(1))
``````
``````# >>> 1
# >>> None
``````

This occurs because `print(1)` is executed first (due to the order of operations with call functions), which outputs something to console but returns `None`, so this statement is essentially equal to

``````print(1)
print(None)
``````

For a harder example, try this one:

``````print(print("x"), print("y"))
``````
``````# >>> x
# >>> y
# >>> None None
``````

This occurs because the call function executes the operands from left to right, so `print("x")` is called, then `print(y)`, and because both these values return `None`, the outside print function will print `None, None`.

It executes the same as the sequence below:

``````print("x")
print("y")
print(None, None)
``````

## Default Arguments #

In the function signature, one of the inputs can have a default value. This is useful in situations where there is a most likely case for a function, but where it still makes sense for users to have some control.

For example, the default `round()` function in Python takes in 1 required parameter, with 1 optional parameter (which defaults to 0).

``````round(2.5342) # >>> 3
round(2.5342, 2) # >>> 2.53
``````

You can build your own function with default arguments by simply specifying it in the header.

For example:

``````def ben(baron, box="tao"):
return baron + box

ben("baron") # >>> 'barontao'
ben("baron", "hej") # >>> 'baronhej'
``````

Without the second optional parameter `hej`, the function defaulted to the value `tao`.

If you have multiple default arguments, you can also override them in this way:

``````def ben(baron, box="tao", foo="baz"):
return baron + box + foo

ben("baron", foo="yu") # >>> barontaoyu

ben("baron", box="yu") # >>> baronyubaz
``````

## Multiple Return Values #

One aspect of Python uncommon in other languages is the allowed use of multiple return values in functions. This can be done in a function by using multiple return values separated by a comma.

Any code that calls the function can either store it in a variable as a tuple (more on this later) or can be unpacked. For example:

``````def return_two_values():
return 1, 2

return_two_values() # >>> (1, 2) in tuple form

a, b = return_two_values()
a # >>> 1
b # >>> 2
``````

### Multiple Variable Assignment #

The values on the right side are evaluated first before being assigned, so you can swap the values of two variables in one line by simply doing the following:

``````x, y = y, x
``````

## Boolean #

A Boolean is a value that is either `True` or `False`, and is used frequently in many applications. For example, your mobile device would likely have a Boolean variable that stores whether your WiFi, flashlight, bluetooth etc. is turned on.

An expression can evaluate to a Boolean. For example:

``````passed_class = grade >= 70 # Will evaluate either true or false depending on the condition

take_shower = (not eecs_major) or did_sports
``````

### Comparison Operators #

Operator Meaning
`==` Equality
`!=` Inequality
`>` Greater Than
`<` Less Than
`>=` Greater Than or Equals
`<=` Less Than or Equals
Checking for Equality It is a common mistake to use `=` instead of `==` to check for equality. Please remember that `=` is for assigning a variable and cannot be used for checking for equality in a conditional statement. Python will throw a syntax error, but other languages may not, so not mixing these up is a good habit to get used to.

### Logical Operators #

Operator Meaning
`and` Evaluates to `True` if both values are `True`
`or` Evaluates to `True` if any of the values are `True`
`not` Evaluates to `True` if the value is `False`, else evaluates to `True`

#### Execution rules of logical operators #

The statements are evaluated from left to right, but sometimes, these statements do not all need to be evaluated.

`and` statement procedure:

1. Evaluate the left statement.
2. If it evaluates to a `False` value `x`, the expression evaluates to `x`.
3. Else, the expression evaluates to the value of the expression on the right.

`or` statement procedure:

1. Evaluate the left statement.
2. If it evaluates to a `True` value `x` the expression evaluates to `x`.
3. Else, the expression evaluates to the value of the expression on the right.

This procedure functions using just Booleans, but strange things occur when you use other values instead.

For example:

``````5 and 2 # >>> 2
5 or 2 # >>> 5
not 5 # >>> False
not 0 # >>> True
0 and False # >>> 0
``````

For the `and` and `or` operators, numbers were returned rather than Booleans due to the procedure of evaluating these logical statements.

There is an order of operations for Booleans (`not``and``or`), but generally, use brackets to make your statements clearer.

You can use these expressions in functions as the return value. For example:

``````def boolean_example():
return is_ben or is_tao # This will return either True or False depending on the Boolean expression.
``````

## Statements #

A statement is executed to perform an action

### Compound Statements #

A compound statement is a statement that contains groups of other statements.

One example of which are conditional statements, which give your code a way to execute a different suite of statements based on whether conditions are met

``````if <condition>:
this_may_be_executed(1)
elif <condition_2>:
this_may_be_executed(2)
else:
this_may_be_executed(3)
``````

An `if` statement looks like the code above. The block indented after the `if`, `elif`, and `else` statements only get executed if the code directs it to.

For instance, if `<condition>` were `True`, then `this_may_be_executed(1)` is the only statement that gets evaluated, and the ones in the other blocks are skipped over.

If `<condition_2>` were `True`, then `this_may_be_executed(2)` is the only statement that gets evaluated.

If both `<condition>` and `<condition_2>` are `False`, then `this_may_be_executed(3)` is evaluated.

This means that the code does not get executed unless certain conditions are met, which is different from call expressions where every operand gets evaluated. (This property is important for some questions)

Additionally, this also allows for multiple return statements in functions because only that specific block gets executed, rather than every block.

``````def returning_conditional(x):
if x > 0:
return "positive"
if x < 0:
return "negative"
if x == 0:
return "neutral"
``````

## While Loop #

A `while` loop in Python executes a block of code as long as a condition is true. This loop keeps on getting checked after each iteration.

One problem of a `while` loop is that an infinite loop can easily occur if you aren’t careful.

``````counter = 1
while counter < 5:
print(counter)
counter += 1
'''
> 1
> 2
> 3
> 4
'''
``````

In the example above, `5` does not get printed because during that iteration, the `counter` variable is already `5`, and the conditional `5` < `5` returns `False`.

A `while` loop is very useful if you do not know how many repeats of the code you need to do, while a `for` loop (explained on another page) is better if you know how many loops to do.

### Break Statement #

If you ever want to prematurely leave a code block, you can use the `break` keyword.

``````while True:
print(1)
break
# >>> 1
``````

The above code would usually give an infinite loop, but the break statement prevents that from happening.

## For Loop #

A `for` loop in Python executes a block of code for a set number of times. It provides a cleaner way to write `while` loops as long as they are iterating over some sort of sequence, for instance, the `range()` function.

The `for` loop syntax is as follows:

``````for <name> in <expression>:
<suite>
``````
1. Evaluate `<expression>` — this must evaluate to an iterable value (strings, lists (more on this later), `range()`) etc
2. For each element in that `<expression>` (in order), bind `<name>` to the element in the current frame
1. Execute the suite, with `<name>` bound to a new value.

That might be slightly confusing for now, but just know that you can do everything a `for` loop can do with a `while` loop. So, if you see an example that uses a `for` loop, you can re-imagine it as a `while` loop, and it would still act the same.

### The range() function #

The `range()` function is used quite often in conjunction with the `for` loop. It represents a sequence of integers. There are 3 arguments that `range()` takes, each of which will be explained, then demonstrated below:

1. If there is just one argument `x`, `range(x)` will start on `0`, then keep increasing the number by `1` until `x - 1` (`0 <= i < x` where `i` is the current value)
2. If there are two arguments `x, y`, `range(x, y)` will start on `x`, then keep increasing the number by `1` until `y - 1`.
3. If there are three arguments `x, y, z`, `range(x, y, z)` will start on `x`, then keep incrementing the number by `z` (this can be negative), and then end on `y - z`.
``````for n in range(5): # equivalent to range(0, 5)
print(n)
'''
> 0
> 1
> 2
> 3
> 4
'''

for n in range(1, 5):
print(n)
'''
> 1
> 2
> 3
> 4
'''

for n in range(5, 0, -1):
print(n)
'''
> 5
> 4
> 3
> 2
> 1
'''
``````

As can be seen in the 3 examples above, the `range()` function works well with the `for` loop.

When `range()` is passed only 1 parameter in a `for` loop, it starts off at `0`, then ends off at the integer before `n`. With 2 parameters, the for loop’s value starts off at the first parameter’s value, then ends off at the integer before the second parameter’s value. The last parameter specifies the amount `n` should be changed by each loop, whether it be negative or a value other than `1`.