No history yet

Pythonic Structure and Functions

Beyond Simple Scripts

Writing code is a bit like building with LEGOs. At first, you follow the instructions, snapping pieces together one by one. Soon, you have a long, single-use script that does one thing. But what if you need to do that one thing, or a slight variation of it, over and over again? You could just copy and paste your code, but this quickly becomes a mess. If you find a mistake, you have to fix it in every single place you pasted it.

This is where the [{}] comes in: Don't Repeat Yourself. The goal is to reduce repetition of software patterns, replacing it with abstractions.

Instead of building a long, fragile chain of code, we can create reusable, modular blocks called functions. Think of them as custom LEGO bricks. You build a specialised brick once, and then you can use it anywhere you need it. This makes your code cleaner, easier to read, and much simpler to maintain.

Let's build a discount calculator for an e-commerce site. We'll need to calculate tax, apply discounts based on user status, and format the final price. Instead of writing one long script, we'll break the problem down into functions.

Building with Functions

A function is a named block of code that performs a specific task. You define it once and can call it whenever you need it. Functions are defined using the def keyword, followed by a name, parentheses for parameters, and a colon.

def calculate_tax(price, tax_rate):
    """Calculates the tax amount for a given price."""
    return price * tax_rate

Here, calculate_tax is the function's name. price and tax_rate are its parameters, which are placeholders for the values you'll provide when you use the function. The code inside the function is indented.

The return keyword is crucial. It sends a value back from the function. A function without a return statement implicitly returns None. Some functions don't need to return anything; their purpose might be to perform an action, like printing to the screen. This is known as a side effect.

To use, or call, the function, you write its name and provide the actual values, called arguments, in the parentheses.

# Let's say the item costs 100 and the tax rate is 20%
item_price = 100.0
vat_rate = 0.20

# Call the function with our arguments
tax_amount = calculate_tax(item_price, vat_rate)

final_price = item_price + tax_amount
print(f"The final price is: {final_price}")
# Output: The final price is: 120.0

Keeping Variables in Their Place

An important concept when working with functions is scope. Scope determines where a variable can be accessed. Variables created inside a function are local to that function. They exist only while the function is running and cannot be seen or used by code outside of it.

def apply_discount(price, discount_percentage):
    # discount_amount is a local variable
    discount_amount = price * (discount_percentage / 100)
    return price - discount_amount

# This will cause an error!
# print(discount_amount) 

If you try to print(discount_amount) outside the function, you'll get a NameError because that variable doesn't exist in the global scope.

Variables defined outside of any function are called global variables. They can be accessed from anywhere in your script, including inside functions.

While you can modify global variables from within a function using the global keyword, it's generally considered bad practice. It can make your code confusing and hard to debug, as it's not clear which functions are changing the state of your programme.

Documenting Your Code

As you write more functions, you'll need a way to remember what they do, what arguments they expect, and what they return. This is where documentation comes in.

Python has a built-in way to document functions called docstrings. A docstring is a string literal that occurs as the first statement in a module, function, class, or method definition. You create them using triple quotes.

def calculate_final_price(base_price: float, tax_rate: float, discount: float = 0.0) -> float:
    """Calculate the final price after tax and an optional discount.

    Args:
        base_price: The initial price of the item.
        tax_rate: The tax rate to apply (e.g., 0.20 for 20%).
        discount: An optional discount percentage (e.g., 10 for 10%).

    Returns:
        The final price, rounded to two decimal places.
    """
    price_after_discount = base_price * (1 - (discount / 100))
    price_with_tax = price_after_discount * (1 + tax_rate)
    return round(price_with_tax, 2)

Notice the text just after the function definition. That's the docstring. It explains the function's purpose, its arguments (Args), and what it returns. This is incredibly helpful for anyone (including your future self) who needs to use your code.

You can also see type hints in the function signature, like base_price: float and -> float. These are a modern Python feature that explicitly states the expected data type for parameters and the return value. They don't change how the code runs, but they make it much easier to understand and allow tools to catch potential errors before you even run the programme.

Quick, Anonymous Functions

Sometimes you need a simple, one-line function that you'll only use once. Defining it with def feels a bit heavy. For these situations, Python provides lambda functions. They are small, anonymous functions defined with the lambda keyword.

A lambda function can take any number of arguments, but can only have one expression.

Imagine we need a quick function to add a 5% service charge to our final price. Instead of a full def, we can use a lambda.

add_service_charge = lambda price: price * 1.05

final_price = 120.0
price_with_charge = add_service_charge(final_price)

print(f"Total with service charge: {price_with_charge:.2f}")
# Output: Total with service charge: 126.00

The syntax is lambda arguments: expression. The expression's result is automatically returned. Lambdas are most useful when passed as arguments to higher-order functions like map(), filter(), or for sorting keys, where defining a full function would be overkill.

Now, let's test your understanding of these structural concepts.

Quiz Questions 1/6

What is the primary benefit of using functions in programming?

Quiz Questions 2/6

Consider the following Python code:

def greet(name, message):
    print(f"{message}, {name}!")

greet("Alice", "Hello")

In the function call greet("Alice", "Hello"), what are "Alice" and "Hello" technically called?

By breaking problems into well-documented, reusable functions, you move from just writing code to engineering software. Your programmes become more organised, reliable, and easier to scale.