Things That I Can Do in Python, but Usually Don’t

Mostly unnecessary things.

Table of Contents

Semicolon


Yes, we can use semicolons in Python. But not braces.

def foo(x):
    increment = 2;
    print(x + increment);

foo(10); # 12
foo(5); # 7
>>> from __future__ import braces
SyntaxError: not a chance

The role of the semicolon here is the same as in other programming languages: to terminate a statement. While this option can be very useful in some cases, it does not apply to regular code. This is unnecessary.

In the following example, we intend to call a function with an argument. This is a typo, but it is perfectly valid in Python. No errors. No output. These two statements, separated by a semicolon, have no effect.

foo;(10);
>>> foo;(10);
<function foo at 0x0000021812E0CB80>
10

What is a (10)? Just an integer. Parentheses are used in Python to enclose expressions or to create tuples. In this case, this expression does nothing.


Parentheses: if/while (condition)


This is what is used in other languages. Python allows it too. But you don't need to do this.

ext = "txt"
if (ext == "txt"):
    print(ext) # txt

The exception is the forced order of operations.

>>> (True or False) and (True and False)
False
>>> True or False and True and False
True

Using Reserved Names for Variables and Filenames


Python allows you to reuse names, including some reserved ones, like the names of built-ins and PSL modules. Problems arise when you want to use these names for their intended purpose.

>>> list = [1, 2, 3]
>>> list
[1, 2, 3]
>>> list("abcd")
Traceback (most recent call last):
...
    list("abcd")
TypeError: 'list' object is not callable

>>> import sys
>>> sys
<module 'sys' (built-in)>
>>> sys = "qwerty"
>>> sys
'qwerty'
>>> sys.path
Traceback (most recent call last):
...
    sys.path
AttributeError: 'str' object has no attribute 'path'

A special case is to name your file the same as the PSL module and try to import and use it.

Imagine we want to execute the following code.

import json

obj = json.dumps({'a': 2, 'b': 5}, indent=4)

1) If this code is in a file we named json.py (just like a standard module).

AttributeError: partially initialized module 'json' has no attribute 'dumps' (most likely due to a circular import)

2) If this code is in another file, for example, main.py. The file named json.py is in the same folder.

AttributeError: module 'json' has no attribute 'dumps'

The first place Python looks for modules to import, is the directory where the script we are running is located.


Changing Conventional Names


There are certain conventions for using certain names, such as self, cls, *args, *kwargs. The following example will work, but it is less familiar and readable.

class A:
    def __init__(inst, value):
        inst.value = value
         
    @classmethod
    def SomeMethod(cl, *a, **b):
        print(cl, a, b) 

a = A(12)
print(a.value)
# 12
A.SomeMethod()
# <class '__main__.A'> () {}


Creating Global Variables in Functions


You need to make sure you create this variable before using it in other parts of your code.

def use_global_variable():
    print("Use a new variable:", new_variable)
    
def create_global_variable():
    global new_variable  # this line is required here
    new_variable = "value"

# use_global_variable()
# NameError: name 'new_variable' is not defined

# creates a new global variable when the function is called
create_global_variable()

print(globals())
print("New variable:", new_variable)

use_global_variable()
Output:
{'__name__': '__main__', ...,
'create_global_variable': <function create_global_variable at 0x0000028D2E50CC20>,
'new_variable': 'value'}
New variable: value
Use new variable: value

range(len(iterable)) Pattern


This is an anti-pattern if you only need to loop over some iterable. Example:

text = "abcd"

for i in range(len(text)):
    print(text[i])

In Python you can do this more easily: for item in iterable.

text = "abcd"

for char in text:
    print(char)

# if you need index
for index, char in enumerate(text):
    print(index, char)

range-len can be used if you want to modify/delete list items in-place or loop through multiple lists (see on StackOverflow). Depending on the task, there may be more Pythonic alternatives.


Wildcard Import: from <module> import *


This import brings all names defined in the <module> to the current namespace. If the <module> also has imports, names from other unrelated modules will also be added.

If modules have classes or functions with the same names, this can lead to confusion. A notable example is the tkinter tk and ttk widgets with the same class names.

Let's say we have two files: utils.py and main.py.

# utils.py

def choice(x):
    if x in ["Y", "y"]:
        return True
    elif x in ["N", "n"]:
        return False
    else:
        return None
# main.py

from random import *
print(choice)
# <bound method Random.choice of <random.Random object at 0x000002D9D449C750>>

from utils import *   
print(choice)
# <function choice at 0x000002D9D682CCC0>


print(choice([1, 2, 3, 4, 5])) # None
print(choice("y")) # True

If we run main.py, we will see that the name "choice" refers to a function defined in utils.py. There are no errors here, as utils.choice accepts almost any argument.

If we switch imports, there will be no errors here either. random.choice takes sequence as argument. The string "y" is a sequence.

# main.py

from utils import * 
from random import *

print(choice)
# <bound method Random.choice of <random.Random object at 0x000001A5275BF1E0>>


print(choice([1, 2, 3, 4, 5])) # 5
print(choice("y")) # "y"

Your options are to rename utils.choice, or make sure the functions have different names in the current namespace, or be more explicit when using functions (module.funcname).

from random import choice as rchoice
from utils import choice as uchoice

print(rchoice([1, 2, 3, 4, 5])) # 3
print(uchoice("y")) # True

import random
import utils

print(random.choice([1, 2, 3, 4, 5])) # 1
print(utils.choice("n")) # False


Not Using Context Managers


Context managers are used to ensure proper resource management, for example, for working with file objects, thread lock objects, database transactions, etc. You can also write your context manager.

A basic example of working with a file object

f = open("test.txt", mode="r", encoding="utf-8")
print(type(f)) # <class '_io.TextIOWrapper'>

# try-except-finally block
try:
    data = f.read(500)
    print(data) # file content
    # raise Exception("Unknown error")
except Exception as e:
    print(e)
finally:
    f.close()
    
print(f.closed) # True

Example with a context manager

The base class of all io classes is the context manager, so it supports the with operator.

In this example, the file is closed after the with statement completes - even if an exception occurs. This is similar to the try-finally block above, but if an error occurs, an exception will be thrown.

# try-finally block
with open("test.txt", mode="r", encoding="utf-8") as f:
    data = f.read(500)

References:
  1. PEP 8: Naming Conventions
  2. Python Docs: import best practices
  3. Python Docs: range(len(mylist)) example
  4. Python Docs: Context Manager Types
  5. PEP 343 – The "with" Statement (and examples)
  6. Python Docs: contextlib - Utilities for with-statement contexts (and examples)

Popular posts from this blog