= 1
x = x + 1
x = 2*x
x x
4
Introduction to Software Engineering (CSSE 1001)
An (imperative) programming language must provide three constructs:
Code order is relevant. Executing the same commands in a different order will (probably) change the output.
Example 1 Here is a simple sequence of code.
= 1
x = x + 1
x = 2*x
x x
4
Let us now swap two lines and observe there is a difference in output.
= 1
x = 2*x # swapped lines
x = x + 1 #
x x
3
Example 2 An example with multiple variables.
= 1
x = 2
y = y
x = x
y x, y
(2, 2)
= 1
x = 2
y = x # swapped lines
y = y #
x x, y
(1, 1)
Functions perhaps are the single most important feature of programming languages. Functions enable us to abstract away complexity by bundling lines of code together. Functions are the building blocks of more sophisticated functions.
We have already used functions: *
and max
for example. Each of these takes input and return output:
max
takes two or more numbers and returns a number.Functions are analogous to that of mathematics and can also be named.
For instance, a parabola in mathematics is given by the function
\[f(x) = x^2 + x + 1\] and in Python we write
def f(x):
return x**2 + x + 1
which loads f
into memory for invocation like
3) f(
13
In expressions like
2) + f(3) f(
20
we can regard f(1)
as being replaced by its return value.
7 + 13
20
Notice that if we do not invoke (by using brackets) the function Python will not return an error
f
<function __main__.f(x)>
because the function is itself an object with
type(f)
function
living in memory at location
id(f)
140598370846176
The Python visualizer illustrates the loading of f
and subsequent invocation in the following way.
Here is another example with two inputs.
def f(x, y):
return x*y
3, 2) f(
6
Which can be called in a more sophisticated way to demonstrate how multiple frames will be created (and deleted) to resolve the outtermost expression.
-f(5, 2) + 12, f(2, 3) ) f(
12
A function need not take input — though brackets must still be used to invoke it.
def foo():
return 905
foo
<function __main__.foo()>
foo()
905
In Python, four spaces/indents are significant and are used to associate lines of code with control structures (in this case function definition).
def f(x):
**2 + x + 1 ␣␣␣␣return x
If you get errors along the lines of:
IndentationError: unexpected indent
check your formatting.
return
is a reserved word (a name that cannot be assigned by us). It is used to designate what value the function should return while also terminating the function itself. The return
statement causes a function to exit and hand back a value to its caller.
print
is a function that returns nothing whose side effect is that it displays a string.
= print("Hello World")
x type(x)
Hello World
NoneType
This distinction is important because return
and print
can look like they are doing the same thing.
Consider the following REPL interaction.
def f(x, y):
print(x+y)
= f(2, 3) x
5
= f(3, 4) y
7
This seemingly looks like it is working but the astute reader will notice 5
printed whereas we normally expect no output from an assignment imperative. More problematic is that x
and y
currently store None
, the default return value so we cannot add x
and y
which we thought had numbers in them.
+ y x
TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'
If a function does not have a return statement it is presumed return None
is the last line.
def f(x, y):
print(x+y)
# return None implicitly
Compare this with the use of return.
def f(x, y):
return x + y
= f(2, 3)
x = f(3, 4)
y
+ y x
12
Notice return
is not a function but a reserved word.
That is, we do
def f(x, y):
return x + y
and not
def f(x, y):
return(x + y)
Notice the difference in output in the following functions. Note that each differs only by where the return
statement was placed.
def example(x):
print(1*x)
print(2*x)
return 3*x
= example(1) # prints 1, 2
a a
1
2
3
def example(x):
print(1*x)
return 3*x
print(2*x) # This line won't execute
= example(1) # prints 1
a a
1
3
def example(x):
return 3*x
return 2*x
print(1*x) # This line won't execute
= example(1)
a a
3
def example(x):
print(2*x)
= example(1) # prints 2
a # is None a
2
print(type(a)) # <class 'NoneType'>
<class 'NoneType'>
The area of \(\triangle ABC\) given by
is \[\sqrt{s(s-a)(s-b)(s-c)}\] where \(s = \frac{1}{2}(a+b+c)\).
Let us verify Heron’s formula with an easy example. The “3 4 5” triangle (a right-angle triangle with side lengths 3, 4, and 5) has area half its base time height \(\frac 1 2 \cdot 3 \cdot 4 = 6\).
0.5*(3+4+5)
(*(0.5*(3+4+5)-3)
*(0.5*(3+4+5)-4)
*(0.5*(3+4+5)-5))**0.5
6.0
But suppose we want to verify more, we have to type a lot and tt would be hard to repeat.
If we utilize sequencing we can simplify a bit.
= 3, 4, 5
a, b, c = (a + b + c)/2
s *(s-a)*(s-b)*(s-c)**0.5 s
36.0
Now we just have to change the values of a
, b
, and c
and the result will be printed by the repl. However we would have to copy this whenever and new calculation was required, and if we made a mistake we would have to make a correction in many places.
Better yet we want to bundle the sequence into a function. This streamlines obtaining the area and also isolates problems to a single place in the code.
def triangle_area(a, b, c):
= (a + b + c)/2
s return (s*(s-a)*(s-b)*(s-c))**0.5
3, 4, 5) triangle_area(
6.0
In mathematics, we would define a mapping: \[ \mbox{triangle-area} : \mathbb{R} \times \mathbb{R} \times \mathbb{R} \to \mathbb{R} . \] to specify what the domain and range of our function is.
In Python we can specify the expected type of each input variable and the type of the return value…
def triangle_area(a: float, b: float, c: float) -> float:
= (a+b+c)/2
s return (s*(s-a)*(s-b)*(s-c))**0.5
2.2, 3.3, 4.4) triangle_area(
3.5147323866832307
This is called a type annotation.
Type hints are not enforced but are rather documentation for making the code more readable.
Let us change the all the types in triangular_area
to str
and see what happens when we invoke it with the “wrong” input.
def triangle_area(a: str, b: str, c: str) -> str:
= (a+b+c)/2
s return (s*(s-a)*(s-b)*(s-c))**0.5
2.2, 3.3, 4.4) triangle_area(
3.5147323866832307
We get the correct answer (for floats) and no error.
def triangle_area(a: float, b: float, c: float) -> float:
"""
Return the area of the triangle with sides length a, b, and c.
Preconditions:
a, b, c are all nonzero positive floats.
>>> triangle_area(3, 4, 5)
6.0
"""
= (a+b+c)/2
s return (s*(s-a)*(s-b)*(s-c))**0.5
The general framework follows.
def function_name(arg0: type, arg1: type, ... ) -> type:
"""
Short description of function for documentation.
Preconditions (if any).
>>> function_name(x, y, ...)
expected output
"""
# function body
return
PEP stands for Python Enhancement Proposal. It is a design document providing information on how to style our code.
In particular, follow Google’s PEP which is a variant of PEP8.
We discuss the relevant part of the PEP now.
Names must start with a letter and not include special characters excepting underscore “_’’. Function and variable names should be lowercase, with words separated by underscores as necessary to improve readability.
Yes | No |
---|---|
descriptive_variable_name | DescriptiveVariableName |
According to PEP8 all lines must be strictly less than 80 characters wide.
Redundant bracket that encloses an expression enable us to break expressions across multiple lines.
= (gross_wages
income + taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest)
We can also use the line continuation character \
= gross_wages \
income + taxable_interest \
+ (dividends - qualified_dividends) \
- ira_deduction \
- student_loan_interest
But otherwise we would generate an error.
= gross_wages
income + taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction
- student_loan_interest
ERROR FILL THIS IN
These are not hard rules but guidelines. Generally speaking you want to optimize for readability by grouping things with higher operational precedence.
Yes | No |
---|---|
i = i + 1 |
i=i+1 |
x = x*2 - 1 |
x = x * 2 - 1 |
hypot2 = x*x + y*y |
hypot2 = x * x + y * y |
c = (a+b) * (a-b) |
c = (a + b) * (a - b) |
Exercise 1 Write a function that takes three integers and returns the number which is not the largest or the smallest. Do not use any Python features (like if-statements) that we have not yet introduced.
Exercise 2 Consider the function foo
defined below that computes the area of a circle with integer radius. What is the type of its return value?
def foo(radius: int):
"""
Precondition: radius > 0
"""
= 3.14159 * radius**2 area
Exercise 3 Is this valid Python code?
def foo(x: int, y: int) -> int:
"""
>>> foo(2, 3)
6
"""
return x
= foo("oi", 3) ans
Comments
Anything following a
#
will be ignored by Python. In addition to docstrings, you can (and should) include comments in your code to explain something not obvious in your design.Strive to include useful comments. For example, the following is not helpful.
It is explaining something evident from the code itself. The pertinent information is why
x
is being incremented (if it is not otherwise obvious).This has a better comment because it gives context to the code.