Python Tricks Roundup 2
By: Cam Wohlfeil
Published: 2019-06-17 1045 EDT
Modified: 2019-06-17 1715 EDT
Category: Programming
Tags:
python
I subscribed to Dan Bader's mailing lists in January so you don't have to (you should though, it's decent). Here's the second set of tricks I got from it so far.
Get the name of an object's class as a string:
class MyClass: pass
obj = MyClass()
obj.__class__.__name__
# 'MyClass'
# Functions have a similar feature:
def myfunc(): pass
myfunc.__name__
# 'myfunc'
Check for class inheritance relationships with the "issubclass()" built-in:
class BaseClass: pass
class SubClass(BaseClass): pass
issubclass(SubClass, BaseClass)
# True
issubclass(SubClass, object)
# True
issubclass(BaseClass, SubClass)
# False
Python 3 allows unicode variable names:
π = math.pi
class Spin̈alTap: pass
Spin̈alTap()
# <Spin̈alTap object at 0x10e58d908>
Only letter-like characters work, however:
🍺 = "beer"
# SyntaxError:
# "invalid character in identifier"
"globals()" returns a dict with all global variables in the current scope:
globals()
# {...}
locals()
does the same but for all local variables in the current scope:
locals()
# {...}
Python 3.3+ has a stdlib module for displaying tracebacks, even when Python "dies":
import faulthandler
faulthandler.enable()
Can also be enabled with "python -X faulthandler" from the command line.
Learn more here: https://docs.python.org/3/library/faulthandler.html
Avoid version conflicts with Virtual Environments
Virtual Environments ("virtualenvs") keep your project dependencies separated. They help you avoid version conflicts between packages and different versions of the Python runtime.
Before creating & activating a virtualenv:python
and pip
map to the systemversion of the Python interpreter (e.g. Python 2.7)
$ which python
# /usr/local/bin/python
Let's create a fresh virtualenv using another version of Python (Python 3):
$ python3 -m venv ./venv
A virtualenv is just a "Python environment in a folder":
$ ls ./venv
# bin include lib pyvenv.cfg
Activating a virtualenv configures the current shell session to use the python (and pip) commands from the virtualenvfolder instead of the global environment:
$ source ./venv/bin/activate
Note how activating a virtualenv modifies your shell prompt with a little note showing the name of the virtualenv folder:
(venv) $ echo "wee!"
With an active virtualenv, the python
command maps to the interpreter binary inside the active virtualenv:
(venv) $ which python
# /Users/dan/my-project/venv/bin/python3
Installing new libraries and frameworks with pip
now installs them into the virtualenv sandbox, leaving your global environment (and any other virtualenvs) completely unmodified:
(venv) $ pip install requests
To get back to the global Python environment, run the following command:
(venv) $ deactivate
(See how the prompt changed back to "normal" again?)
$ echo "yay!"
Deactivating the virtualenv flipped the python
and pip
commands back to the global environment:
$ which python
# /usr/local/bin/python
Python's for
and while
loops support an else
clause that executes only if the loops terminates without hitting a break
statement:
def contains(haystack, needle):
"""
Throw a ValueError if `needle` not
in `haystack`.
"""
for item in haystack:
if item == needle:
break
else:
# The `else` here is a
# "completion clause" that runs
# only if the loop ran to completion
# without hitting a `break` statement.
raise ValueError('Needle not found')
contains([23, 'needle', 0xbadc0ffee], 'needle')
# None
contains([23, 42, 0xbadc0ffee], 'needle')
# ValueError: "Needle not found"
But I'd rather do something like this:
def better_contains(haystack, needle):
for item in haystack:
if item == needle:
return
raise ValueError('Needle not found')
Note: Typically you'd write something like this to do a membership test, which is much more Pythonic:
if needle not in haystack:
raise ValueError('Needle not found')
Pythonic ways of checking if all items in a list are equal:
lst = ['a', 'a', 'a']
len(set(lst)) == 1
# True
all(x == lst[0] for x in lst)
# True
lst.count(lst[0]) == len(lst)
# True
I ordered those from "most Pythonic" to "least Pythonic" and "least efficient" to "most efficient". The len(set()) solution is idiomatic, but constructing a set is less efficient memory and speed-wise.
In Python 3.4+ you can use contextlib.suppress() to selectively ignore exceptions:
import contextlib
with contextlib.suppress(FileNotFoundError):
os.remove('somefile.tmp')
# This is equivalent to:
try:
os.remove('somefile.tmp')
except FileNotFoundError:
pass
contextlib.suppress docstring:
"Return a context manager that suppresses any
of the specified exceptions if they occur in the body
of a with statement and then resumes execution with
the first statement following the end of
the with statement."
In Python 3 you can use an asterisk in function parameter lists to force the caller to use keyword arguments:
def f(a, b, *, c='x', d='y', e='z'):
return 'Hello'
To pass the value for c, d, and e you will need to explicitly pass it as "key=value" named arguments:
f(1, 2, 'p', 'q', 'v')
# TypeError:
# "f() takes 2 positional arguments but 5 were given"
f(1, 2, c='p', d='q',e='v')
# 'Hello'
Python 3.5+ allows passing multiple sets of kwargs to a function using the "**" syntax:
def process_data(a, b, c, d):
print(a, b, c, d)
x = {'a': 1, 'b': 2}
y = {'c': 3, 'd': 4}
process_data(**x, **y)
# 1 2 3 4
process_data(**x, c=23, d=42)
# 1 2 23 42