19 posts about #python
The Zen of Python 🐍
$ python
>>> 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!
>>>
Gonna be pondering these for some time. Thanks Phil!
Python Help in REPL 🐍
Heading to the internet to get help with a Python function? Slow down! There's help right in your terminal.
With the Python executable installed, it's as easy as:
$ python
>>> help()
help> string.lower
Help on function lower in string:
string.lower = lower(s)
lower(s) -> string
Return a copy of the string s converted to lowercase.
help>
Decorator Factory vs Decorator
I've discovered decorator factories and they are cool!
Decorating a function in python is easy. Watch as I yell when a function gets called.
def yell(func):
def yeller(*args):
print("YARGH")
func(*args)
return yeller
@yell
def hi(name):
print(f'hi {name}')
hi("Bob")
# YARGH
# hi Bob
What if I always wanted to say hi to Bob, and wanted to configure that via a decorator. Could I do that?
@yell("Bob")
def hi(name):
print(f'hi {name}')
# TypeError: 'str' object is not callable
Instead of just passing an argument to a decorator, I need to create a function that will return a decorator, a decorator factory.
def yell(name):
def decorate(func):
def yeller():
print("YARGH")
func(name)
return yeller
return decorate
@yell("Bob")
def hi(name):
print(f'hi {name}')
hi()
# YARGH
# hi Bob
So this time, I created a function that returned a decorator which in turn returns a function that wraps the decorated function and calls the decorated function with the argument passed in to the decorator factory. Very russion doll. Fun.
Examining the closure
Python is the first language I've encountered that avails you the meta information about the defining closure of a function.
After you get the reference of a function, there is a lot of meta information available via the __code__
attribute. __code__
has many attributes and one of them is co_freevars
which is all the variables defined outside of the function, but available through the closure to the function. It returns a tuple. The order of the values in this tuple is important.
The values of those co_freevars
are in another dunder (__) method that is called __closure__
. It also returns a tuple. The order is the same order as the co_freevars
tuple. The tuple holds cell
objects with one attribute, cell_contents
. cell_contents
holds the current co_freevar
value.
def get_fn():
a = 1
b = 2
def get():
print(a)
print(b)
return (a, b)
return get
gfn = get_fn()
gfn.__code__.co_freevars
# ('a', 'b')
gfn.__closure__[0]
# <cell at 0x10849c4f8: int object at 0x1081907c0>
gfn.__closure__[0].cell_contents
# 1
gfn.__closure__[1].cell_contents
# 2
Python global closure scoping oddity
Beware closuring in global variables.
world = 'Endor'
def world_name():
def world_knower():
print(world)
world = 'Hoth'
return world_knower
knower = world_name()
knower()
# Hoth
In the above example, the reference the local world
was closured in to world_knower
, and the value was changed after the world_knower
declaration.
What if we use the global
keyword to let python know we want to use the global version of this variable?
world = 'Endor'
def world_name():
global world
def world_knower():
print(world)
world = 'Hoth'
return world_knower
knower = world_name()
knower()
# Hoth
Yikes, the inner function still uses the outer functions local reference. I guess if we truly want to ignore the local reference, we need to declare that world
is global in the inner function.
world = 'Endor'
def world_name():
def world_knower():
global world
print(world)
world = 'Hoth'
return world_knower
knower = world_name()
knower()
# Endor
Lambdas can only be one line
Unfortunately, lambdas in Python are limited. They can be only one expression.
This works:
print_twice = lambda word: print(word * 2)
# prints: "wordword"
But you can't assign the intermediate value to a variable.
print_twice = lambda word: result = word * 2; print result;
# Syntax error
White space is significant is this language, so there are no ()
{}
or
keywords that would help you out here.
lambda
s are the only way to define anonymous functions in python, but you can still define a named function in any context.
def build_fn(a):
def adder(b):
return b + a
return adder
add3 = build_fn(3)
add3(4)
# 7
Annotate Args with Anything
Python annotations don't do anything. Annotations are just meta information about the arguments of your method. You can apply this metadata in any way that you see fit.
Let's check out a function with no annotations:
def talk(a, b, c):
pass
talk.__annotations__
# {}
Makes sense, an empty dictionary. Now lets say that a
should be an str
.
def talk(a: str, b, c):
pass
talk.__annotations__
# {'a': <class 'str'>}
What's unique about python is that its primitive types are objects/classes. This allows for annotations to be accomodating of any object/class.
def talk(a: str, b: 'hi', c):
pass
talk.__annotations__
{'a': <class 'str'>, 'b': 'hi'}
Any type or instance? What about declaring a variable outside of the method and using that as an annotation.
birds = {'corvids': True}
def talk(a: str, b: 'hi', c: birds):
pass
talk.__annotations__
# {'a': <class 'str'>, 'b': 'hi', 'c': {'corvids': True}}
Yeah you can do that!! Crzy!!
Check out how argument annotations can be used as a type system with mypy
Call an object like a function with `__call__`
Using __call__
you can make an object behave like a function.
class HiyaPerson:
def __init__(self, person_name):
self.person_name = person_name
def __call__(self):
print("Hiya there, " + self.person_name)
hiya = HiyaPerson("Bob")
hiya()
# Hiya there, Bob
A side effect is that now a "function" can have state. Interesting!
class Counter:
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
count = Counter()
count()
import inspect
dict(inspect.getmembers(count))['count']
# 1
count()
dict(inspect.getmembers(count))['count']
# 2
range() v slice()
A range is not a slice and a slice is not a range. But they look the same.
slice(1, 10)
# slice(1, 10, None)
range(1, 10)
# range(1, 10)
They both take step as a third argument.
slice(1, 10, 3)
# slice(1, 10, 3)
range(1, 10, 3)
# range(1, 10, 3)
But one is iterable and the other is not.
list(slice(1, 10))
# TypeError: 'slice' object is not iterable
list(range(1, 10))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
One can be used as a list indices, one cannot.
[1, 2, 3, 4, 5][range(1, 2)]
# TypeError: list indices must be integers or slices, not range
>>> [1, 2, 3, 4, 5][slice(1, 2)]
# [2]
They both conform to the start
, stop
, step
interface.
s = slice(1, 10)
s.start, s.stop, s.step
# (1, 10, None)
r = range(1, 10)
r.start, r.stop, r.step
# (1, 10, 1)
You can slice a range but you can't range a slice.
range(1, 10)[slice(2, 8)]
# range(3, 9)
slice(1, 10)[range(2, 8)]
# TypeError: 'slice' object is not subscriptable
Counting is as easy as...
Python has a specialized dict
data structure for a common task, counting. Counter
can process and store the counts of items you pass it.
Initialized like this with chars, and then keys:
from collections import Counter
char_count = Counter('xyxxyxyy')
# Counter({'x': 4, 'y': 4})
keys_count = Counter({'apple': 2, 'banana': 5, 'pear': 3})
# Counter({'banana': 5, 'pear': 3, 'apple': 2})
Then updating the Counter is easy with update
:
chars_count.update('abxyabxy')
# Counter({'x': 6, 'y': 6, 'a': 2, 'b': 2})
keys_count.update({'apple': 1, 'banana': 2, 'fruit': 3})
# Counter({'banana': 7, 'apple': 3, 'pear': 3, 'fruit': 3})
Then figure out which items are the most common:
chars_count.most_common(2)
# [('x', 6), ('y', 6)]
keys_count.most_common(2)
# [('banana', 7), ('apple', 3)]
Read more about some really useful things you can do with counter objects in the Python docs
Keep your lists sorted on every insert
Python is a language that provides you the tools of efficiency. The bisect module has a series of functions that work with a bisecttion algorithm. An inserting function in this grouping is the insort
function which will insert an element into a sequence in the right spot according to the sort order.
from bisect import insort
sorted_list = [0, 2, 4, 6, 8, 10]
insort(sorted_list, 5)
# [0, 2, 4, 5, 6, 8, 10]
The above example uses a sorted list, what if the list is not sorted?
list = [0, 4, 2, 6]
insort(list, 5)
# [0, 4, 2, 5, 6]
It will still insert in the first place that it can find that makes sense. But what if there are two places that make sense? The algo seems to give up and just tack it on at the end.
list = [0, 4, 2, 6, 2]
>>> insort(list, 5)
# [0, 4, 2, 6, 2, 5]
insort
works left to right but you can also work right to left with insert_right
. In some cases this will be more efficient.
Steppin' and Slicin' through lists
Python allows you to slice a list with a range, which is common. But what is less common is adding a step variable to the same syntax construct.
A range takes on the form of start:end
in python, but it allows you to default to the start and end of the data with just :
.
[0, 1, 2, 3, 4, 5, 6][:]
# [0, 1, 2, 3, 4, 5, 6]
Add some numbers in there and it looks like this:
[0, 1, 2, 3, 4, 5, 6][2:5]
# [2, 3, 4]
But bolted on syntax allows you to step as well.
[0, 1, 2, 3, 4, 5, 6][2:5:2]
# [2, 4]
The form is actually start:end:step
. Leaving out the start and end looks weird but allows you to get all even indexed elements:
[0, 1, 2, 3, 4, 5, 6][::2]
# [0, 2, 4, 6]
Named arguments by default
This feature is fantastic and I haven't seen it in any other language.
You don't have to declare named arguments, all arguments are named with the argument names by default.
>>> def add(a, b, c):
... return a + b + c
...
>>> add(1, 2, 3)
6
>>> add(c=1, b=2, a=3)
6
What happens if you mix in named args with positional args
>>> add(1, b=2, c=3)
6
That works, but if you change the order?
>>> add(a=1, b=2, 3)
File "<stdin>", line 1
SyntaxError: non-keyword arg after keyword arg
That error is definitive which is great. What about using a named arg for an already declared positional argument? Another definitive error:
>>> add(1, a=2, c=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: add() got multiple values for keyword argument 'a'
There are so many times in Ruby and in JavaScript(es6 w/destructuring) where I debate whether the arguments should be named or not. I don't really have a good rhyme or reason to it other than just feel and percieved readability. To not have to think about it seems wonderful.
Creating named tuples in Python
Named tuples are an interesting, flexible and fun data structure in Python.
Sorta like an openstruct in Ruby, it's a quick way to create a finite data structure that has named properties. The properties of a namedtuple are also accessible via index.
from collections import namedtuple
Thing = namedTuple('Thing', ['a', 'b'])
x = Thing(1, 2)
x.a
# 1
x[0]
# 1
You can create an instance of a named tuple with named arguments as well.
x = Thing(b=3, a=4)
# Thing(a=4, b=3)
The properties of a namedtuple can also be declared with a string rather than a list. That looks like this:
Row = namedtuple('Row', 'a b c d')
or comma separated:
Row = namedtuple('Row', 'a, b, c, d')
String Interpolation in python??
String interpolation is something that I've come to expect from a modern language. Does python have it?
Well, sorta. From this article python has a couple different ways of getting data into a string.
'Old Style'
'Old Style is a cheap beer from %' % 'Chicago'
'New Style'
'New Style seems a bit less {}'.format('obtuse')
'f-Strings' (from python 3.6)
adejective = 'better'
f'This is a bit {adjective}'
Template Strings
from string import Template
t = Template('This is maybe more like Ruby\'s $tech?')
t.substitute(tech='ERB')
Where is that python function defined?
If you want to look at the definition of a function in Python but you don't know where it's defined, you can access a special attribute of the function __globals__
.
In the following case I have a function imported from nltk
called bigram
and I want to see what file it's defined in:
>>> bigram.__globals__['__file__']
'.../python3.7/site-packages/nltk/util.py'
Ah, just as I suspected, it is defined in nltk/util.py
__file__
is an attribute of the globals dictionary.
The special attribute __globals__
is only available for user-defined functions. If you try to access it for a python defined function you'll get an error:
>>> len.__globals__['__file__']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'builtin_function_or_method' object has no attribute '__globals__'
Python has tuples!
I love a good tuple and in JavaScript or Ruby I sometimes use arrays as tuples.
[thing, 1, "Description"]
In those languages however, this tuple isn't finite. Wikipedia defines tuple thusly.
a tuple is a finite ordered list (sequence) of elements.
Python tuples look like this:
mytuple = (thing, 1, "Description")
And is it finite?
>>> mytuple.append("c")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'tuple' object has no attribute 'append'
No append
attribute, ok. But can you add tuples together?
>>> a = ('a', 'b')
>>> a + ('c', 'd')
('a', 'b', 'c', 'd')
>>> a
('a', 'b')
You can add tuples together but it's not mutative.
A syntax quirk is the one element tuple.
>>> type(('c'))
<class 'str'>
>>> type(('c',))
<class 'tuple'>
Include a comma after the only tuple element to ensure that the tuple is not tokenized as a string.
Get the ancestors of a python class
Python has inheritance, but if you encounter a Python object in a program, how can you tell what it's superclasses are?
mro
is an abbreviation for method resolution. Use the mro
method on class
.
>>> something = object() type(something).mro()
[<class 'object'>]
This is just an object of type object, it's only class is object. Let's get a bit more complicated.
>>> class Fruit:
... pass ...
>>> class Apple(Fruit):
... pass ...
>>> Apple.mro()
[<class '__main__.Apple'>, <class '__main__.Fruit'>, <class 'object'>]
OK, Apple inherits from Fruit which inherits from object. Makes sense!