Register to get access to free programming courses with interactive exercises

Functional and procedural approach Python: Declarative programming

Python's creators wanted its users to actively and effectively use built-in collections.

Therefore, the language inclines the programmer toward a particular work style when choosing an approach. The language itself encourages the use of an imperative style combined with procedural and object-oriented programming.

In Python, collections are objects. We modify them mostly in place, which gives them their modifiable state. However, the language also has a place for elements of functional programming. Developers often consider it a subspecies of declarative programming because functional code looks like a data conversion pipeline.

In this lesson, we'll look at imperative and functional code in Python. In both cases, we'll try displaying a sorted list of unique elements from a set of numbers.

Procedural solution

When we use procedural solutions, we still split the program into subroutines that change the state instead of returning a value. The code will look like this:

INPUT = [1, 2, 5, 12, 3, 5, 2, 7, 12]


def main():
    numbers = INPUT[:]
    filter_and_sort(numbers)  # Sorting and filtering in place
    for number in numbers:
        print(number)         # Outputting element by element in a loop


def filter_and_sort(values):
    values.sort()  # The list is sorted in place
    previous_value = None
    index = 0
    while index < len(values):
        value = values[index]
        if value == previous_value and index > 0:
            # We remove the item from the list, modifying the list again
            values.pop(index)
        else:
            index += 1
            previous_value = value

This solution is quite efficient — it only consumes memory for a copy of the original list, then compresses it and sorts it in place. This solution has predictable memory consumption, independent of the input data. And after applying filter_and_sort(), we can further process the data in the same way but with other procedures.

This code is efficient but forces the programmer to remember a lot. It increases the likelihood of errors. For example, we may forget to get a copy of the INPUT list before we start processing — then we'll accidentally sort the original INPUT list itself. Incorrectly changing the wrong entities like this can lead to unpleasant consequences. And such errors don't just float up to the surface. The program will still display the correct numbers, so we will not see the problem immediately.

Functional solution

Let's write a functional version with elements of declarative style:

INPUT = [1, 2, 5, 12, 3, 5, 2, 7, 12]


def main():
    print('\n'.join(map(str, sorted(set(INPUT)))))

The code describes what kind of result we want to get:

  • print() — printed
  • '\n'.join(...) — combined via line feed
  • map(str, ...) — sequence of strings
  • sorted(set(...))— obtained from a sorted set of initial numbers

Note that there are no variables in the code. You can work with much less code and don't have to copy anything securely.

Let's try to analyze how this code works:

  • Creates an intermediate set set()
  • Creates a copy list inside sorted()
  • Creates a line for each resulting number
  • Launches the '\n'.join() function
  • The function assembles a string of size equal to the sum of all string lengths plus the number of line feeds

As you can see, short code means more resources.

Even if the original list doesn't contain too many repetitions, the intermediate structures will occupy much more memory than the original list due to the many temporary strings. They are significantly larger than the initial numbers. It turns out that memory consumption strongly depends on the composition of the input data.

The truth is somewhere in the middle

Functional solutions read well if the developer is familiar with this style. Code readability is an important characteristic, so you can choose this option if you have a small amount of input data.

The procedural solution is quite hard to read: you have to execute the code in your mind. It is more challenging to change this code because it is too specific and tied to the task at hand. Also, we should remember that the procedure filter_and_sort() modifies its argument. However, procedural code works efficiently and predictably. This option may be preferable if you have a lot of data with little repetition. However, we can improve both solutions with a bit of a departure from the paradigm.

For example, in a functional solution, you can apply a loop:

def main():
    for number in sorted(set(INPUT)):
        print(number)

This code doesn't create intermediate lines because print() already knows how to print numbers. We do not put the big string together before output. It is the most noticeable saving.

Let's complicate the situation and imagine that all input elements are unique. The set will be a copy of the original list in this case. There'll be another copy inside sorted(). We'll end up with two copies. Let's see how else we can improve the imperative version:

def main():
    previous_value = None
    for value in sorted(INPUT):
        if previous_value is None or value != previous_value:
            previous_value = value
            print(value)

This version reads much better because no procedure modifies a copy of the original list. There's no need to copy the input list explicitly — there'll be an implicit copy in sorted(). But now, if you need to work with the result of sorting before printing, you'll have to aggregate the values in an intermediate list.

In most cases, Python programmers choose the functional approach and refine it. They end with a balance — the code is still compact and works effectively.


Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

About Hexlet learning process

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

Bookmate
Health Samurai
Dualboot
ABBYY
Suggested learning programs
profession
new
Developing web applications with Django
10 months
from scratch
under development
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.