If you search course curriculum I've written, you'll often find phrases like zip
function, enumerate
function, and list
function.
Those terms are all technically misnomers.
When I use terms like "the bool
function" and "the str
function" I'm implying that bool
and str
are functions.
But these aren't functions: they're classes!
I'm going to explain why this confusion between classes and functions happens in Python and then explain why this distinction often doesn't matter.
If you'd rather hear a 2 minute summary of this topic, watch The meaning of "callable" in Python.
There's a group activity I often do when training new Python developers: the class or function game.
In the class or function game, we take something that we "call" (using parentheses: ()
) and we guess whether it's a class or a function.
For example:
zip
with a couple iterables and we get another iterable back, so is zip
a class or a function?len
, are we calling a class or a function?int
: when we write int('4')
are we calling a class or a function?Python's zip
, len
, and int
are all often guessed to be functions, but only one of these is really a function:
>>> zip
<class 'zip'>
>>> len
<built-in function len>
>>> int
<class 'int'>
While len
is a function, zip
and int
are classes.
The reversed
, enumerate
, range
, and filter
"functions" also aren't really functions:
>>> reversed
<class 'reversed'>
>>> enumerate
<class 'enumerate'>
>>> range
<class 'range'>
>>> filter
<class 'filter'>
After the class or function game, we often talk discuss:
A callable is anything you can call, using parentheses. Callables often accept arguments (which go inside the parentheses).
All three of these lines involve callables:
>>> something()
>>> x = AnotherThing()
>>> something_else(4, 8, *x)
We don't know what something
, AnotherThing
, and something_else
do: but we know they're callables.
These are all examples of possible callables in Python:
Callables are a very important concept in Python because they're all over the place.
Functions are the most obvious callable in Python. Functions can be "called" in every programming language. A class being callable is a bit more unique though.
In JavaScript we can make an "instance" of the Date
class like this:
> new Date(2020, 1, 1, 0, 0)
2020-02-01T08:00:00.000Z
In JavaScript the class instantiation syntax (the way we create an "instance" of a class) involves the new
keyword.
In Python we don't have a new
keyword.
In Python we can make an instance of the datetime
class (from datetime
) like this:
>>> datetime(2020, 1, 1, 0, 0)
datetime.datetime(2020, 1, 1, 0, 0)
In Python, the syntax for instantiating a new class instance is the same as the syntax for calling a function.
There's no new
needed: we just call the class.
When we call a function, we get its return value. When we call a class, we get an "instance" of that class.
We use the same syntax for constructing objects from classes and for calling functions: this fact is the main reason the word "callable" is such an important part of our Python vocabulary.
There are many classes-which-look-like-functions among the Python built-ins and in the Python standard library.
I sometimes explain decorators (an intermediate-level Python concept) as "functions which accept functions and return functions".
But that's not an entirely accurate explanation. There are also class decorators: functions which accept classes and return classes. And there are decorators which are implemented using classes: classes which accept functions and return objects.
A better explanation of the term decorators might be "callables which accept callables and return callables" (still not entirely accurate, but good enough for most purposes).
Python's property decorator seems like a function:
>>> class Circle:
... def __init__(self, radius):
... self.radius = radius
... @property
... def diameter(self):
... return self.radius * 2
...
>>> c = Circle(5)
>>> c.diameter
10
But it's actually a class:
>>> property
<class 'property'>
The classmethod
and staticmethod
decorators are also classes:
>>> classmethod
<class 'classmethod'>
>>> staticmethod
<class 'staticmethod'>
What about context managers, like suppress
and redirect_stdout
from the contextlib
module?
These both use the snake_case
naming convention, so they seem like functions:
>>> from contextlib import suppress
>>> from io import StringIO
>>> with suppress(ValueError):
... int('hello')
...
>>> with redirect_stdout(StringIO()) as fake_stdout:
... print('hello!')
...
>>> fake_stdout.getvalue()
'hello!\n'
But they're actually implemented using classes, despite the snake_case
naming convention:
>>> suppress
<class 'contextlib.suppress'>
>>> redirect_stdout
<class 'contextlib.redirect_stdout'>
Decorators and context managers are just two places in Python where you'll often see callables which look like functions but aren't. Whether a callable is a class or a function is often just an implementation detail.
It's not really a mistake to refer to property
or redirect_stdout
as functions because they may as well be functions.
We can call them to get back a useful object, and that's what we care about.
Python's "call" syntax, those (...)
parentheses, can create a class instance or call a function.
But this "call" syntax can also call an object.
Technically, everything in Python is an object:
>>> isinstance(len, object)
True
>>> isinstance(range, object)
True
>>> isinstance(range(5), object)
True
But we often use the term "object" to imply that we're working with an instance of a class (the thing you get back when you call a class).
There's a partial
function which lives in the functools
module, which can "partially evaluate" a function by storing arguments to be used when calling the function later.
This is often used to make Python look a bit more like a functional programming language:
>>> from functools import partial
>>> just_numbers = partial(filter, str.isdigit)
>>> list(just_numbers(['4', 'hello', '50']))
['4', '50']
I said above that Python has "a partial
function".
That's a bit of a lie.
The phrase "a partial
function" makes sense, but the partial
callable isn't implemented using a function.
>>> partial
<class '__main__.partial'>
The Python core developers could have implemented partial
as a function, like this:
def partial(func, *args, **kwargs):
"""Return "partially evaluated" version of given function/arguments."""
def wrapper(*more_args, **more_kwargs):
all_kwargs = {**kwargs, **more_kwargs}
return func(*args, *more_args, **all_kwargs)
return wrapper
But instead they chose to use a class, doing something more like this:
class partial:
"""Return "partially evaluated" version of given function/arguments."""
def __init__(self, func, *args, **kwargs):
self.func, self.args, self.kwargs = func, args, kwargs
def __call__(self, *more_args, **more_kwargs):
all_kwargs = {**self.kwargs, **more_kwargs}
return self.func(*self.args, *more_args, **all_kwargs)
That __call__
method allows us to call partial
objects.
So the partial
class makes a callable object.
Adding a __call__
method to any class will make instances of that class callable.
In fact, checking for a __call__
method is one way to ask the question "is this object callable?"
All functions, classes, and callable objects have a __call__
method:
>>> hasattr(open, '__call__')
True
>>> hasattr(dict, '__call__')
True
>>> hasattr({}, '__call__')
False
Though using the built-in callable
function is a better way to check for callability:
>>> callable(len)
True
>>> callable(list)
True
>>> callable([])
False
The callable
built-in function returns True
if the given argument is a callable and False
otherwise.
In Python, classes, functions, and instances of classes can all be used as "callables".
Only 44 of the 72 built-in functions in Python are actually implemented as functions:
26 are classes and 1 (help
) is an instance of a callable class.
Of the 26 classes among those built-in "functions", four were actually functions in Python 2 (the now-lazy map
, filter
, range
, and zip
) but have since become classes.
The Python built-ins and the standard library are both full of maybe-functions-maybe-classes.
This is largely due to the fact that in Python, we practice duck typing, meaning we care more about the behavior of an object than the type of that object. Whether an object is a class or a function usually matters much less than what that object can do.
operator.itemgetter
The operator
module has lots of callables:
>>> from operator import getitem, itemgetter
>>> get_a_and_b = itemgetter('a', 'b')
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> get_a_and_b(d)
(1, 2)
>>> getitem(d, 'a'), getitem(d, 'b')
(1, 2)
Some of these callables are classes while others are functions:
>>> itemgetter
<class 'operator.itemgetter'>
>>> get_a_and_b
operator.itemgetter('a', 'b')
>>> getitem
<built-in function getitem>
The itemgetter
class could have been implemented as "a function that returns a function".
Instead it's a class which implements a __call__
method, so its class instances are callable.
Generator functions are functions which return iterators when called:
def count(n=0):
"""Generator that counts upward forever."""
while True:
yield n
n += 1
And iterator classes are classes which return iterators when called:
class count:
"""Iterator that counts upward forever."""
def __init__(self, n=0):
self.n = n
def __iter__(self):
return self
def __next__(self):
n = self.n
self.n += 1
return n
Iterators can be defined using functions or using classes: whichever you choose is an implementation detail.
The built-in sorted
function has an optional key
argument, which is called to get "comparison keys" for sorting (min
and max
have a similar key
argument).
This key
argument can be a function:
>>> def digit_count(s): return len(s.replace('_', ''))
...
>>> numbers = ['400', '2_020', '800_000']
>>> sorted(numbers, key=digit_count)
['400', '2_020', '800_000']
But it can also be a class:
>>> numbers = ['400', '2_020', '800_000']
>>> sorted(numbers, key=int)
['400', '2_020', '800_000']
The Python documentation says "key specifies a function of one argument...". That's not technically correct because key can be any callable, not just a function. But we often use the words "function" and "callable" interchangeably in Python, and that's okay.
defaultdict
"factory function"The defaultdict
class in the collections
module accepts a "factory" callable, which is used to generate default values for missing dictionary items.
Usually we use a class as a defaultdict
factory:
>>> from collections import defaultdict
>>> counts = defaultdict(int)
>>> counts['snakes']
0
>>> things = defaultdict(list)
>>> things['newer'].append('Python 3')
>>> things['newer']
['Python 3']
But defaultdict
can also accept a function (or any other callable):
>>> import random
>>> colors = ['blue', 'yellow', 'purple', 'green']
>>> favorite_colors = defaultdict(lambda: random.choice(colors))
>>> favorite_colors['Kevin']
'yellow'
>>> favorite_colors['Stacy']
'green'
>>> probabilities = defaultdict(random.random)
>>> probabilities['having fun']
0.6714530824158086
>>> probabilities['seeing a snake']
0.07703364911089605
Pretty much anywhere a "callable" is accepted in Python, a function, a class, or some other callable object will work just fine.
In Python Morsels exercises, I often ask you to think in terms of a "callable". Often I'll say something like "this week I'd like you to make a callable which returns an iterator...". I say "callable" because I want an iterator back, but I really don't care whether the callable you create is a generator function, an iterator class, or a function that returns a generator expression. All of these things are callables which return the expected object type (an iterator). It's up to you (the implementer of this callable) to determine how you'd like to define it.
We practice duck typing in Python: if it looks like a duck and quacks like a duck, it's a duck. Due to duck typing we tend to use generic terms to describe specific things: lists are sequences, generators are iterators, dictionaries are mappings, and functions are callables.
If something looks like a callable and quacks (or rather, calls) like a callable, it's a callable. Likewise, if something looks like a function and quacks (calls) like a function, we can call it a function... even if it's actually implemented using a class or a callable object!
Callables accept arguments and return something useful to the caller. When we call classes we get instances of that class back. When we call functions we get the return value of that function back. The distinction between a class and a function is rarely important from the perspective of the caller.
When talking about passing functions or class objects around, try to think in terms of callables. What happens when you call something is often more important than what that thing actually is.
More importantly though, if someone mislabels a function as a class or a class as a function, don't correct them unless the distinction is actually relevant. A function is a callable and a class is a callable: the distinction between these two can often be disregarded.
Need to fill-in gaps in your Python skills?
Sign up for my Python newsletter where I share one of my favorite Python tips every week.
Need to fill-in gaps in your Python skills? I send weekly emails designed to do just that.