The world’s leading publication for data science, AI, and ML professionals.

Two Powerful Python Features to Streamline Your Code and Make It More Readable

Enhance your code quality with the beauty of match statements and object slicing.

Photo by Kevin Ku on Unsplash
Photo by Kevin Ku on Unsplash

There is a reason Python’s popularity has spread far and wide in the current tech landscape. Among modern programming languages, it is perhaps the most accessible for novices. And with that accessibility, it also offers plenty of power. Web development, Data Science, scientific computing – you can accomplish many a task with Python.

As Python has advanced over the years, its developers have put great amounts of effort into maintaining its readability and conciseness. Though many of its features may require a bit of extra effort to learn, the return on clarity and beauty in your code is beyond worth it.

In this article, we’ll look at two such features: match statements and string/list slicing. We’ll go over how each one works in detail, as well as consider some examples to build familiarity with the syntax and semantics.

Now then, let’s get into it.

Match Statements

Match statements – available in Python as of version 3.10 – are a way of checking equality of conditions and performing some action based on the conditions [1]. If you are coming from another language such as C or JavaScript, you might already be familiar with the concept as switch statements.

In principle, match statements are similar to conditional statements, but they do provide a couple of useful advantages. Let’s start by looking at the basic structure via a comparison with conditionals; then, we’ll talk about the advantages.

You might write the following conditional statement to check someone’s name for a bank account:

name = "Yen"

if name == "Yen":
    print("This is your account.")
elif name == "Ben":
    print("This is your sister's account.")
else:
    print("Fraud attempt.")

Translated to a match statement, this would look like the following:

name = "Yen"

match name:
    case "Yen":
        print("This is your account.")
    case "Ben":
        print("This is your sister's account.")
    case _:
        print("Fraud attempt.")

Let’s break this down line by line:

  • The first line is the same – we just define the name variable.
  • The keyword match is used to start a match statement.
  • Then, for the individual conditions, rather than explicitly checking equality, we use the case statement to effectively pattern match. Thus, you can think of case "Yen" as checking for the case that name, which we are matching, is equal to "Yen".
  • Finally, the last case is the wildcard case. This is specified by an underscore (_) and is effectively the else case.

Now, you might ask – why use this over a traditional conditional statement? I initially had the same question, and would even become annoyed when people used match instead of standard if-else statements. However, there are advantages.

The first one is simply that it is a cleaner way to achieve the same goal. This may seem like a cop-out reason, but it’s actually fairly important. The entire spirit of Python lies in writing clean, concise code (if you don’t believe me, type import this into your Python interpreter and hit enter).

Especially with a large number of conditions, it can be cumbersome to parse a long chain of if and elif statements. Using match statements cleans up the code and makes it easier for a fellow programmer to read – a worthy achievement for any Python programmer.

Beyond this, match statements can also deconstruct certain objects directly, removing the need to do so manually with conditional statements. In practice, this means two things:

  • You can automatically check types (removing the need for manual checks).
  • You can automatically access attributes of an object within each case.

Let’s take a look at an example. Say we have the following code, which defines two classes for different types of cars:

# Online Python compiler (interpreter) to run Python online.
# Write Python 3 code in this online editor and run it.
class Honda:
    # See below for explanation of __match_args__
    __match_args__ = ("year", "model", "cost")
    def __init__(self, year, model, cost):
        self.year = year
        self.model = model
        self.cost = cost

class Subaru:
    __match_args__ = ("year", "model", "cost")
    def __init__(self, year, model, cost):
        self.year = year
        self.model = model
        self.cost = cost

car = Subaru(2021, "Outback", 18000)

We have defined an instance of Subaru above. Now, we want to write code that checks what type a car is and prints out some attribute of it. Using traditional conditional statements, we could do so as follows:

if isinstance(car, Honda):
    print("Honda " + car.model)
elif isinstance(car, Subaru):
    print("Subaru " + car.model)
else:
    print("Failure :(")

For our car variable above, this will print out "Subaru Outback". If we translate this to a match statement, we get the following, simplified code:

match car:
    case Honda(year, model, cost):
        print("Honda " + model)
    case Subaru(year, model, cost):
        print("Subaru " + model)
    case _:
        print("Failure")

Match’s pattern-matching functionality enables Python to automatically check the type within the case statement, and further makes it so that the attributes of the object can be accessed directly. Note that this is made possible by the inclusion of the __match_args__ attribute in the class definition, as it names the positional arguments for Python. The recommendation in the Python documentation is for the pattern here to mimic the one used in the __init__ constructor when assigning attributes to self.

The match version of the code is both easier to read and less cumbersome to write. This is a fairly small example, but as situations get more complex, strings of conditional statements can get increasingly more convoluted [2].

All of that said, do keep in mind that this feature is only available starting in Python version 3.10. As such, you should encode that whatever system, application, or project you are writing code for does not exist in a code base that must be compatible with an older version of Python.

As long as that condition is met, consider using match statements. It might require a bit of effort, but your code will be better off for it in the long run.

String and List Slicing

You may already be somewhat familiar with this feature, but I’m willing to bet you aren’t using it to its full potential. Let’s start with a quick review, and then look into some more complex uses.

In its simplest form, slicing refers to a concise syntax that lets you extract part of a string or list in Python [3]. Here is a small example:

>>> my_str = "hello"
>>> my_str[1:3]
'el'

The syntax requires using square brackets which contain the start and stop index separated by a colon. Remember that Python uses 0-indexing, so 1 corresponds to 'e' here. Additionally, note that slicing is exclusive of the right index, so it goes up to 3 but does not include it, hence why the output is 'el' and not 'ell'.

If you want to just start from the beginning or go all the way to the end of a string or list, you can leave the corresponding index blank:

>>> my_lst = ['apple', 'orange', 'blackcurrant', 'mango', 'pineapple']
>>> my_lst[:3]
['apple', 'orange', 'blackcurrant']
>>> my_lst[2:]
['blackcurrant', 'mango', 'pineapple']

Leaving both indices blank gives you a copy of the entire object:

>>> my_str[:]
'hello'
>>> my_lst[:]
['apple', 'orange', 'blackcurrant', 'mango', 'pineapple']

Note that with both lists and strings, slicing defines and returns a brand new object distinct from the original:

>>> new_lst = my_lst[2:]
>>> new_lst
['blackcurrant', 'mango', 'pineapple']
>>> my_lst
['apple', 'orange', 'blackcurrant', 'mango', 'pineapple']

Now, let’s get to the good stuff. With slicing, you can also use negative indices. If you’re unfamiliar with negative indexing, it basically enables you to start counting from the end of a list or string. The last letter corresponds to -1, the second-to-last letter corresponds to -2, and so on.

This can simplify code by removing the need to manually compute lengths. To get everything but the last letter of a string, for instance, you can just do this:

>>> my_str[:-1]
'hell'

Finally, one of the most overlooked features of slicing is that you can also specify a third number – which specifies a "jump" of sorts. This is easiest to explain with an example:

>>> my_long_lst = ['apple', 'orange', 'blackcurrant', 'mango', 'pineapple', 'grapes', 'kiwi', 'papaya', 'coconut']
>>> my_long_lst[1:-1:2]
['orange', 'mango', 'grapes', 'papaya']

Let’s break down what’s happening above:

  • For clarity, we define a list with more elements than the original one we had.
  • In the list slice, the first two numbers are 1 and -1. As we saw above, this does away with the first and last elements of the object being sliced – my_long_list, in this case.
  • Finally, we put a 2 as the final number after an additional colon. This tells Python that we want to slice the list from the start to the end index, but only keep every other item. Putting a 3 would give us every third item, putting a 4 would give us every fourth item, and so on.

Combining the two things above, we can also slice lists to get the elements backward:

>>> my_long_lst[-1:1:-2]
['coconut', 'kiwi', 'pineapple', 'blackcurrant']

# To slice backwars successfully, the "jump" value must be negative
# Otherwise, we just get an empty list
>>> my_long_lst[-1:1:2]
[]

And there you have it – everything you need to know about list slicing. When you get creative with the syntax described above, you can achieve some really cool behavior. For example, the following is one of the slickest ways to reverse a list in Python, courtesy of list slicing:

>>> my_lst
['apple', 'orange', 'blackcurrant', 'mango', 'pineapple']
>>> my_lst[::-1]
['pineapple', 'mango', 'blackcurrant', 'orange', 'apple']

Do you see how it works? As an exercise, you should review each feature of list slicing above and try to break down the code yourself. Hint: Take a look at what it means when we leave the start and end index blank.

Now then, let’s talk about why you should learn this stuff at all.

As a data scientist, why is this useful?

Just generally speaking, it’s important to consider readability and cleanliness of code when writing in Python. Using the features above will go a long way in helping you achieve this. As we already discussed, match statements have a couple of meaningful advantages over conditional statements in this regard. As for list slicing, it is much, much cleaner than trying to achieve the same behavior using some kind of convoluted loop.

But going beyond these broader benefits, let’s talk data science specifically for a moment.

Practically speaking, if you’re working as a data scientist, there is a fair probability that your formal training was not in computer science, but rather in something like statistics, mathematics, or even just data science itself if you were lucky enough to find such a program. In such programs, computer science is generally taught as a tool.

The focus is on learning the basic principles of Programming in a way that teaches you enough to process data, run analyses, and build models at scale. As such, there is not a huge amount of time left to learn topics like "useful Python-specific syntactic features." Heck, such topics are often overlooked even in a pure computer science class.

However, using these features can take your code to the next level, helping you stand out among your brilliant colleagues and deliver better results to your clients. Match statements and object slicing are two powerful examples, but there are a host more that Python has to offer which I encourage you to explore.

May the code be ever in your favor – until next time, friends.


Want to excel at Python? Get exclusive, free access to my simple and easy-to-read guides here. Want to read unlimited stories on Medium? Sign up with my referral link below!

Join Medium with my referral link – Murtaza Ali

References

[1] https://docs.python.org/3.10/whatsnew/3.10.html#syntax-and-operations [2] https://peps.python.org/pep-0622/#rationale-and-goals [3] https://docs.python.org/3/c-api/slice.html


Related Articles