5 Python Features That I Was Unaware Even Existed

I’ve just discovered a new hobby: leisurely reading Python documentation.

5 Python Features That I Was Unaware Even Existed

Photo by Alex Chumak on Unsplash

When you read anything for pleasure, you often pick up on intriguing details that you may have otherwise overlooked. So, the following is a collection of facts that I found interesting. Can you perform that in Python?

1. Function Attributes

You may set characteristics for functions in a similar way that you can for classes and objects.

def func(x):
    intermediate_var = x**2 + x + 1

    if intermediate_var % 2:
        y = intermediate_var ** 3
    else:
        y = intermediate_var **3 + 1

    # setting attributes here
    func.optional_return = intermediate_var
    func.is_awesome = 'Yes, my function is awesome.'

    return y

y = func(3)
print('Final answer is', y)
# Accessing function attributes
print('Show calculations -->', func.optional_return)
print('Is my function awesome? -->', func.is_awesome)

In lines 10 and 11, respectively, the attributes “optional return” and “is awesome” have been set. Later, on lines 19 and 20, we made an external call to those attributes. The code’s output is going to be:

Final answer is 219
Show calculations --> 13
Is my function awesome --> Yes, my function is awesome

This is useful when you don’t want to explicitly return a variable using the return statement each time the function is run but still want the ability to recover a variable that was sent in as an intermediate. Also, take note that attributes may be set from either inside or outside the function declaration.

2. For-else loop

A for loop in Python can have an else clause added to it. Only if there was no break statement encountered within the body of the loop during execution will the else clause be activated.

my_list = ['some', 'list', 'containing', 'five', 'elements']

min_len = 3

for element in my_list:
    if len(element) < min_len:
        print(f'Caught an element shorter than {min_len} letters')
        break
else:
    print(f'All elements at least {min_len} letters long')

Keep in mind that the else is inserted at the level of for rather than if. No element in this has a length less than 3. Therefore, the break statement won’t ever be used. As a result, after the for loop completes, the else clause will be activated, printing the output displayed above.

One could argue that this could be accomplished by keeping note of whether the break statement was found in a different variable. Additionally, it might make the code easier to understand for the reader after you. The equivalent method of getting the same result is as follows:

my_list = ['some', 'list', 'containing', 'five', 'elements']

min_len = 3

no_break = True
for element in my_list:
    if len(element) < min_len:
        print(f'Caught an element shorter than {min_len} letters')
        no_break = False
        break

if no_break:
    print(f'All elements at least {min_len} letters long')

OUTPUT:

All elements at least 3 letters long

3. Separators for ‘int’

Now, this is one of my personal favourite.Integers like 10000000 and 100000000 (are they really different numbers?) are difficult to distinguish visually. Python will perceive commas in this context as a tuple of many integers, thus we are unable to use them as we would in English.

Python has a very practical solution for this: we can use underscore as a separator to make the code more readable. As a result, 1_000_000 will be considered to be a single int.

a = 3250
b = 67_543_423_778

print(type(a))
print(type(b))

OUTPUT:

<class 'int'>
<class 'int'>

4. eval() and exec()

Python has the ability to read a string dynamically and treat it as a line of code. The eval() and exec() functions (used to evaluate expressions and execute statements, respectively) are used to do this.

a = 3

b = eval('a+2')
print('b =', b)

exec('c = a**2')
print('c =', c)

OUTPUT:

b = 5
c = 9

On line 3, eval () function reads the input string as a Python expression, evaluates it, and assigns the result to variable ‘b’.

Line 6 exec() function reads the input string as a Python statement and executes it.

You can also pass dynamically constructed strings to these functions. For example, you can create 1000 variables named x_0, x_1, …, x_999 without manually writing these variable declarations in your code. This may seem like a completely pointless feature, but it’s not.

In the broader context of programming in general, not just Python, the use of eval/exec is incredibly powerful because it allows you to write dynamic code that uses information available at runtime to solve problems which cannot even be expressed at compile time. […] exec is literally a Python interpreter embedded inside Python, so if you have a particularly hard problem to solve, one of the ways you can solve it is to write a program to write a program to solve it, then use exec to run that second program.

You can read more of this beautiful explanation by Steven D’Aprano.

5. Ellipsis

Ellipse or “…” is a Python built-in constant, similar to a built-in constant such as None, True, or False. It can be used in a variety of ways, including, but not limited to:

5.1 Placeholders for unwritten code

Like pass, ellipsis can be used as wildcards when the code is not completely written but wildcards are required for syntactic accuracy.

def some_function():
  ...
def another_function():
  pass

5.2 Alternatives to “None”.

If you want to indicate an empty input or return value, you typically choose None. However, there are cases where None is one of the expected inputs or function return values. In this case, the ellipse acts as a placeholder.

# calculate nth odd number 
def nth_odd(n): 
  if isinstance(n, int): 
    return 2 * n - 1 
  else: 
    return None 
# calculate the original n of nth odd number 
def original_num(m=...): 
  if m is ...: 
    print('This function needs some input') 
  elif m is None: 
    print('Non integer input provided to nth_odd() function') 
elif isinstance(m, int): 
    if m % 2: 
      print(f'{m} is {int((m + 1)/2)}th odd number') 
    else: 
      print(f'fml is not an odd number')

original_num() 

a = nth_odd(n='some string') 
original_num(a)

b = nth_odd(5) 
original_num(b) 

original_num(16)

The nth_odd() function computes the nth odd number given n. The original_num() function calculates the original number n from the nth odd number.

Here, None cannot be used because it is one of the expected inputs of the original_num() function. They are used as the default placeholders for the m argument. The output of the code looks like this:

This function needs some input
Non integer input provided to nth_odd() function
9 is 5th odd number
16 is not an odd number

5.3 Array slicing in NumPy

NumPy uses Ellipsis to slice arrays. The following code shows two equivalent ways of splitting a NumPy array.


import numpy as np

a = np.arange(16).reshape(2,2,2,2)

print(a[..., 0].flatten())
print(a[:, :, :, 0].flatten())

OUTPUT:

[ 0  2  4  6  8 10 12 14]
[ 0  2  4  6  8 10 12 14]

Thus, ‘…’ signifies that there are as many ‘:’s as needed.

Ellipse Boolean

Unlike None (the Boolean value is False), the Ellipsis Boolean value is considered True.

Thank you for reading. If you find something wrong or better ways to do it, let me know in the comments below.

If you like the post, hit the 👏 button below so that others may find it useful. You can follow me on GitHub and connect with me on Linkedin.

You can read about some powerful python one-liners here