Python features that will make you an expert.

Skyler Lewis
Level Up Coding
Published in
6 min readJul 9, 2020

--

Photo by George Becker from Pexels

Python has rich syntax, powerful language features, and some random fun, not so serious easter eggs. Here is just a handful of things I have found.

Fun things.

I am going to start with a few fun, esoteric things the language has.

braces

from __future__ import braces is a funny stab at other languages and some of the confusion users coming from those languages feel. Notice you will get an error. But the error message is in jest — “not a chance”. A less-than-subtle way our benevolent ex-BDFL, Guido Van Rossum, expressed his thoughts on adding braces to the language.

this

import this will get you the Zen of Python. These are a hierarchical set of guidelines that help you write clean/“Pythonic” code. And while written for Python, they are excellent for adapting to other programming languages too.

>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

antigravity

import antigravity Importing antigravity will pop open this python specific XKCD comic in your browser!

https://xkcd.com/353/

Loops

For/Else Statements.

For loops can have else statements! You have to be careful with this one. At first glance you might think it means that if there is nothing to iterate over, then it should do what is in the else block.

What it actually is for is catching if you don’t break out of the loop. For example if you are checking if a certain value exists in a list. If you find it, then break, if you do not find it, then your else function is there to handle that case.

# Example of using else statement with for loop
# List of numbers
numbers = [1, 2, 3, 4, 5]
# Value to search for
search_value = 6
# Check if search_value exists in the list
for num in numbers:
if num == search_value:
print("Found the search value!")
break
else:
print("Search value not found in the list.")

Dictionary Keys

In python you can use almost anything as a key.

“..a dictionary key must be of a type that is immutable. For example, you can use an integer, float, string, or Boolean as a dictionary key.” [ref] This means booleans and even tuples are usable as keys! Lists and dictionaries are not though.

# Example of using different types as dictionary keys
# Dictionary with various types of keys
my_dict = {
42: "integer key",
3.14: "float key",
"hello": "string key",
True: "boolean key",
(1, 2): "tuple key"
}
# Accessing values using different types of keys
print(my_dict[42]) # Access using integer key
print(my_dict[3.14]) # Access using float key
print(my_dict["hello"]) # Access using string key
print(my_dict[True]) # Access using boolean key
print(my_dict[(1, 2)]) # Access using tuple key

Switches

Python doesn’t have switches as you would see them in C or other languages. A switch is just a fancy if/elif/else statement, but maybe you want fancy. One way you can make Pythonic switches is with a map (using immutable keys like mentioned above) with the value as a lambda or a function.

# Example of creating a Pythonic "switch" using a dictionary mapping
# Define functions for different cases
def case1():
return "This is case 1."
def case2():
return "This is case 2."
def case3():
return "This is case 3."
# Create a dictionary mapping cases to functions
switch = {
'case1': case1,
'case2': case2,
'case3': case3
}
# Function to execute a case based on input
def execute_case(case):
return switch.get(case, lambda: "Invalid case")()
# Test cases
print(execute_case('case1')) # Output: This is case 1.
print(execute_case('case2')) # Output: This is case 2.
print(execute_case('case3')) # Output: This is case 3.
print(execute_case('case4')) # Output: Invalid case

2024 update: Python now has match statements, which is like a switch!

match term:
case pattern-1:
action-1
case pattern-2:
action-2
case pattern-3:
action-3
case _:
action-default

Dataclasses

Dataclasses are basically flexible interfaces. When you implement the interface, you get the added benefit of having an object with the coveted dot notation!

# Example of using dataclasses in Python
from dataclasses import dataclass
# Define a dataclass representing a Point
@dataclass
class Point:
x: float
y: float
# Create a Point object using dot notation
point = Point(3.5, 4.2)
# Access attributes using dot notation
print(point.x) # Output: 3.5
print(point.y) # Output: 4.2

You can also use dictionary decomposition when instantiating dataclass objects.

# Example of using dictionary decomposition with dataclasses in Python
from dataclasses import dataclass
# Define a dataclass representing a Point
@dataclass
class Point:
x: float
y: float
# Dictionary containing values for Point attributes
point_data = {'x': 3.5, 'y': 4.2}
# Instantiate a Point object using dictionary decomposition
point = Point(**point_data)
# Access attributes using dot notation
print(point.x) # Output: 3.5
print(point.y) # Output: 4.2

To get even more out of dataclasses use a library like Howard, which will recursively marshal your object into a rich object. (Do check it out!)

Default Values

The first time you run the test_default function, you get ['arst', 'A'], but the second time, you get ['arst', 'A', 'B']. That is because you are modifying a reference to bob not a new instance of bob like you would think. Instead, to avoid this, try setting your default argument as None and then change the value to your list.

Bad:

# Example demonstrating the default argument mutation bug and its fix

# Function with a default argument being a mutable list
def add_item_bug(item, my_list=[]):
my_list.append(item)
return my_list

# Test the function with the bug
print("Default argument mutation bug:")
print(add_item_bug('A')) # Output: ['A']
print(add_item_bug('B')) # Output: ['A', 'B']
print(add_item_bug('C')) # Output: ['A', 'B', 'C']

Good:

# Function with fixed default argument using None
def add_item_fixed(item, my_list=None):
if my_list is None:
my_list = []
my_list.append(item)
return my_list

# Test the function with the fix
print("\nFixed default argument:")
print(add_item_fixed('A')) # Output: ['A']
print(add_item_fixed('B')) # Output: ['B']
print(add_item_fixed('C')) # Output: ['C']

If you use an IDE like PyCharm, you will get a warning if you try and use a mutable default value.

The id function

for id in range(10):  # don't call stuff id
print(id)

This one is a bit unfortunate. id is a function that is built in. The actual purpose of it is to get the memory location of something. eg: id(my_var). Can you think of any time you might want to call a variable id? All the time? Same. You could just override it, but IDE’s worth their salt will complain about it.

Conclusion

Python (like any language) has some weird and cool features. Sometimes they are annoying. Sometimes they will make you feel l33t3r than Snowden. Knowing the ins-outs of the language you are working in will empower you to program like an artisan. These were just a handful of things I thought were worth sharing Stone-Soup style. Happy coding!

Skyler Lewis is a serial startup engineer from the Silicon Slopes area, Utah.

--

--