
Introduction
I often see myself exploring the repositories of Python's top Frameworks and Libraries repositories on GitHub. By doing so I can see how they structure big projects, and learn new things that I can implement on my projects and projects as a Solutions Engineer for my company.
In this article, I want to share five cool Python things I’ve learned in my not-so-long career — 5 to 6 years — that you can apply to your projects or at your job.
This is what we are going to cover in this article:
The walrus operator
Named parameters using *
Union type (not typing.Union)
Private methods and variables
Use a For-else instead of a flag
Learning these Python features will make you write better code and be a more proficient Python programmer. And you’ll look cool on your next interview code challenge which is always nice 🏂.
Hey there, lovely reader! 🌟 If you're enjoying this Python journey and want to be the first to dive into more code adventures with me, consider hitting that subscribe button so you’ll be the first to know about new articles, exclusive content, and ask me anything in a private chat!
And no worries, you don't need a premium pass (unless you want to boost my writing journey). Subscribing keeps the coding vibes alive, and I promise it's worth it! 🚀✨
The walrus Operator
The walrus operator was introduced in PEP 572 and it looks like this — :=. It’s a colon followed by the equal sign/operator.
This operator is only available in Python 3.8 or later.
We use it to assign value to a variable in an expression - NAME := expr
.
An expression can be any valid Python expression like the ones we use for comparison, except the “unparenthesized tuple”.
One of the most common use cases in my projects and my colleagues for walrus operator is when we have a function that can return some positive value, None, or False, and according to the output we have to perform some action. Take the code below as an example:
def get_user(username):
# This function fetch user info somewehere, if exists return User
# if not return None. Let's assume that the user does not exist
return None
def do_action(username):
if user := get_user(username):
pass
else:
print("User does not exist")
In our previous example, we have a function —get_user — that will fetch a User somewhere —cloud perhaps — and if it finds the User by username, it returns us a user object as output, otherwise, it returns None.
In our do_action function, we call the get_user function, and we know we can either get a User object or None. So instead of doing the following, which I did a lot in the past — nothing wrong with it — and many still do it:
def do_action(username):
user = get_user(username)
if user is not None:
pass
else:
print("User does not exist")
We simply used the NAME := expr
to check if we got a User or not — user := get_user(username)
. If we get a user, Python will assign the value to the user and we can use it inside our if statement scope to do our thing.
Another great example of where to use it, consider the following code:
n = len(my_list)
if n > 10:
print("We have {n} items in our list.")
Using the walrus operator we can transform our code to be like this:
if (n := len(my_list)) > 10:
print("We have {n} items in our list.")
Go to PEP 572 to get a much better understanding of various use cases and more.
Named parameters using *
Python gives us two ways of sending arguments to a function — position and keyword.
Positional arguments “force” us to know the order defined for each parameter in our function:
def double_n(n):
return n * 2
This is a straightforward example. We only have one parameter, so if we call our function Python will place the value we send in n.
def full_name(first_name, last_name):
return f"{first_name} {last_name}"
In this example, we now have two positional or keyword parameters — Python won’t complain if we decide to use any of these —, and the order we send our arguments will affect the output because values will be assigned by position.
But things get trickier as the complexity of our code grows and suddenly we have a function that receives a lot of parameters. When this happens, it gets harder to handle positional arguments, so we feel obligated to “enforce” the use of keyword-only arguments.
So how do we do this? How can we enforce keyword-only arguments in Python 🤔?
I’m glad you asked 🤓.
Simply by placing the * sign before any parameters in our function definition, we are enforcing keyword-only arguments:
def full_name(*, first_name, last_name):
return f"{first_name} {last_name}"
So whenever we want to call full_name we are obligated to send arguments as follows:
f_name = full_name(first_name="John", last_name="Doe")
By doing so, you are avoiding errors related to parameter order.
Union type (not typing.Union)
Python is not a static Programming Language but allows us to type function parameters and variables if we need to, but that doesn’t mean that it will prevent errors at runtime by sending the wrong type.
def double_n(n):
return n * 2
In our example, double_n is supposed to receive a parameter n and return the double of it:
res = double_n(n=2)
print(res) # 4
d_str = double_n(n="s")
print(d_str) # 'ss'
As we can see our function can return double pretty much anything we send as an argument. But for the sake of simplicity, let’s assume that we have this function and we expect it to double two specific types — string and integer.
Using type hints can be very useful when others are using your code. It makes explicit what your function expects to receive and what it returns as output. Additionally, it makes documenting the code easier and helps your IDE raise warnings.
So let’s type our double_n function, shall we? We said that our function can double two types — string and integer. So we need to explicitly say that we’re expecting one of these two types and return one of them as well, depending on the received type.
We could use the Union type from the built-in typing library:
from typing import Union
def double_n(n: Union[int, str]) -> Union[int, str]:
return n * 2
By doing so, whoever is using our function will know that they are supposed to send either str or int as an argument and the return will be the same type as the argument.
But there’s a “simpler” way of writing this code. Without the need to import anything.
Since Python 3.10, we can now use | as Union, so Union[int, str]
it can simply be int | str.
def double_n(n: int | str) -> int | str:
return n * 2
Python recommends using the shorthand version above 👆.
Private methods and variables
The concept of private in Python is not the same as in other programming languages.
In Java, we have the Private keyword that is used to signal that some attribute is not accessible outside the scope of a class:
public class Main {
private int doubler = 2;
private int double(int n) {
return n * this.doubler;
}
}
This might not be the best Java code you will see. But focus on the private keyword both on the doubler attribute and double method. This means that we cannot call them from any other place outside the scope of the Main class. Any attempt to do so will not work.
But in Python that’s not the case. We don’t have any private keywords. So how can we do the same thing we just did in Java?
In Python, when you see a method or a variable starting with two leading underscores, it means that they’re private.
class Main:
__doubler = 2
def __double_any(self, n: int | str) -> int | str:
return n * self.__doubler
def double_int(n: int) -> int:
return self.__double_any(n)
In our example, we have a private method __double_any that can only be called inside the scope of Main.
What happens if we try to call it outside the scope? Let’s see
main = Main()
main.__double_any(n=3)
If we try to call it like we are doing in the example above, we’ll get AttributeError.
__double_any does not exist in our Main class.
This is because in Python any “identifier” — the variable name of function name — that is of the form __spam (two leading underscore) is “textually replace with _classname__spam.
main = Main()
main._classname__double_any(n=3)
Another way that I’ve seen people doing and I particularly did it many times, is using single leading underscore to refer to a private attribute. Doing this, you’re still referring to your attributes as private but they can be accessed from anyplace in your code. But by convention, Python programmers know that they should not access or change attributes that are of the form _spam - meaning they are private.
class Main:
__doubler = 2
def _double_any(self, n: int | str) -> int | str:
return n * self.__doubler
def double_int(n: int) -> int:
return self._double_any(n)
Use a For-else instead of a flag
We’re all guilty of using flags to signal whether we found something inside a for loop. Nothing wrong with that.
Everything has its place and time, and sometimes we want to return a response whether we found the thing we’re looking for or not, as in the example below:
numbers = [1, 2, 3, 4, 5]
number_6_found = False
for n in numbers:
if n == 6:
number_6_found = True
break
if not number_6_found:
print("Number six not in our list")
What most probably don’t know, is that Python’s for loop has an else clause. This clause added to for will only be executed if our loop is completed normally — no break statement triggered inside our loop.
numbers = [1, 2, 3, 4, 6]
for n in numbers:
if n == 6:
# hypotetical function call
do_something()
break
else:
print("I will not be executed since number six exist in our list.")
# hypotetical function call
no_found_report()
The best usage of for-else and the most common, is to run a loop and search for an item in a collection — list, tuple, etc.
If we find the item we’re looking for, we break the loop and move forward
We don’t find the item and need to do something in regard
To avoid creating a flag variable — number_6_found — to map whether we found the item or not, we can transform our code to be like the example above. Simple and clean.
Conclusion
As you can see Python is full of cool features like these that we just covered. We could cover each of them in a single article but I tried to make it as simple as possible so you can understand and hopefully use them in your day-to-day as a Python Programmer.
If you enjoy this article maybe you will also enjoy my other articles on Python, my experience in IT as a programmer, and my journey.
Please like this post because it will help me more than you can imagine, and consider subscribing 🏂 so we can roll together in this writing journey.
If you want to explore more on each topic I put links this are the resources I used to help me write this article:
Private Attributes - https://docs.python.org/3/tutorial/classes.html#private-variables
Fluent Python book
For-Else - https://book.pythontips.com/en/latest/for_-_else.html
Walrus operator - https://realpython.com/python-walrus-operator/
Python Distilled book
Python official documentation on Devdocs
Exactly what I was looking for!