10.11. Functional Function Object

>>> def add(a, b):
...     return a + b
>>>
>>>
>>> type(add)
<class 'function'>

10.11.1. Name

  • function.__name__

  • function.__qualname__

>>> def add(a, b):
...     return a + b
>>> add.__name__
'add'
>>> add.__qualname__
'add'

10.11.2. Type

  • type(function)

  • function.__class__

  • function.__class__.__name__

  • function.__class__.__qualname__

  • function.__class__.__text_signature__

>>> def add(a, b):
...     return a + b
>>> type(add)
<class 'function'>

add.__class__ <class 'function'>

>>> add.__class__.__name__
'function'
>>> add.__class__.__qualname__
'function'

10.11.3. Annotations

>>> def add(a: int, b: int) -> int:
...     return a + b
>>> add.__annotations__
{'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}

10.11.4. Signature

>>> from inspect import signature
>>>
>>>
>>> def add(a: int, b: int = 0) -> int:
...     return a + b
>>> signature(add)
<Signature (a: int, b: int = 0) -> int>
>>> signature(add).parameters
mappingproxy(OrderedDict({'a': <Parameter "a: int">, 'b': <Parameter "b: int = 0">}))
>>> signature(add).parameters['a']
<Parameter "a: int">
>>>
>>> signature(add).parameters['a'].annotation
<class 'int'>
>>>
>>> signature(add).parameters['a'].name
'a'
>>>
>>> signature(add).parameters['a'].default
<class 'inspect._empty'>
>>> signature(add).parameters['b']
<Parameter "b: int = 0">
>>>
>>> signature(add).parameters['b'].name
'b'
>>>
>>> signature(add).parameters['b'].annotation
<class 'int'>
>>>
>>> signature(add).parameters['b'].default
0

10.11.5. Doc

  • help(function)

  • function.__doc__

>>> def add(a: int, b: int) -> int:
...     """
...     Docstring for a function add
...     """
...     return a + b
>>> add.__doc__
'\nDocstring for a function add\n'
>>> help(add)
Help on function add in module __main__:

add(a: int, b: int) -> int
    Docstring for a function add

10.11.6. Call

  • callable(function)

  • function()

  • function.__call__()

>>> def add(a, b):
...     return a + b
>>> callable(add)
True
>>> add(1, 2)
3
>>> add.__call__(1,2)
3

10.11.7. Function Code

  • add.__code__

  • add.__code__.co_code

  • add.__code__.co_firstlineno

  • add.__code__.co_varnames

  • add.__code__.co_kwonlyargcount

  • add.__code__.co_posonlyargcount

  • add.__code__.co_argcount

>>> def add(a, b):
...     return a + b
>>>
>>>
>>> add.__code__.co_code
b'\x95\x00X\x01-\x00\x00\x00$\x00'
>>>
>>> add.__code__.co_filename  
'myfile.py'
>>>
>>> add.__code__.co_firstlineno
1
>>>
>>> add.__code__.co_varnames
('a', 'b')
>>>
>>> add.__code__.co_kwonlyargcount
0
>>>
>>> add.__code__.co_posonlyargcount
0
>>>
>>> add.__code__.co_argcount
2
>>>
>>>
>>> dir(add.__code__)  
[...,
 'co_argcount',
 'co_cellvars',
 'co_code',
 'co_consts',
 'co_exceptiontable',
 'co_filename',
 'co_firstlineno',
 'co_flags',
 'co_freevars',
 'co_kwonlyargcount',
 'co_lines',
 'co_linetable',
 'co_lnotab',
 'co_name',
 'co_names',
 'co_nlocals',
 'co_positions',
 'co_posonlyargcount',
 'co_qualname',
 'co_stacksize',
 'co_varnames',
 'replace']

10.11.8. Setattr, Getattr

>>> def add(a, b):
...     return a + b
>>>
>>>
>>> add.firstname = 'Mark'
>>> add.lastname = 'Watney'
>>> add.say_hello = lambda: print(f'Hello {add.firstname} {add.lastname}')
>>>
>>> add.say_hello()
Hello Mark Watney

10.11.9. Use Case - 1

>>> from functools import lru_cache
>>>
>>> @lru_cache
... def factorial(n):
...     return 1 if n==0 else n*factorial(n-1)
>>>
>>> factorial.cache_info()
CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)

10.11.10. Use Case - 2

>>> from abc import abstractmethod, ABC
>>>
>>> class Account(ABC):
...     @abstractmethod
...     def login(self):
...         raise NotImplementedError

Implementation:

>>> def abstractmethod(funcobj):
...     funcobj.__isabstractmethod__ = True
...     return funcobj

Rationale:

>>> def login(self):
...     raise NotImplementedError
>>>
>>> login.__isabstractmethod__ = True

10.11.11. Use Case - 3

>>> def typecheck(func):
...     annotations = func.__annotations__
...     argnames = func.__code__.co_varnames
...     def wrapper(*args, **kwargs):
...         arguments = dict(zip(argnames, args)) | kwargs
...         for argname, argvalue in arguments.items():
...             argtype = type(argvalue)
...             expected = annotations[argname]
...             if argtype is not expected:
...                 raise TypeError(f'"{argname}" is {argtype}, but {expected} was expected')
...         return func(*args, **kwargs)
...     return wrapper
>>>
>>>
>>> @typecheck
... def add(a: int, b: int) -> int:
...     return a + b

Usage:

>>> add(1, 2)
3
>>> add(1.0, 2)
Traceback (most recent call last):
TypeError: "a" is <class 'float'>, but <class 'int'> was expected
>>> add(1, 2.0)
Traceback (most recent call last):
TypeError: "b" is <class 'float'>, but <class 'int'> was expected

10.11.12. Use Case - 2

>>> def add(a, b):
...     return a + b
>>>
>>>
>>> add(1, 2)
3
>>>
>>> add(1, 2)
3
>>>
>>> add(1, 2)
3
>>> def add(a, b):
...     if not hasattr(add, '_cache'):
...         setattr(add, '_cache', {})
...     if (a,b) in add._cache:
...         print('Found in cache; fetching...')
...         return add._cache[a,b]
...     else:
...         print('Not in cache; computing and updating cache...')
...         add._cache[a,b] = result = a + b
...         return result
>>>
>>>
>>> add(1, 2)
Not in cache; computing and updating cache...
3
>>>
>>> add(1, 2)
Found in cache; fetching...
3
>>>
>>> add(1, 2)
Found in cache; fetching...
3

10.11.13. Use Case - 3

>>> def add(a, b):
...     cache = getattr(add, '__cache__', {})
...     if (a,b) not in cache:
...         cache[(a,b)] = a + b
...         setattr(add, '__cache__', cache)
...     return cache[(a,b)]
>>>
>>>
>>> add(1,2)
3
>>>
>>> add(3,2)
5
>>>
>>> add(3,5)
8
>>>
>>> add  
<function add at 0x...>
>>>
>>> add.__cache__
{(1, 2): 3, (3, 2): 5, (3, 5): 8}

10.11.14. Use Case - 4

>>> def factorial(n):
...     if not hasattr(factorial, 'cache'):
...         factorial.cache = {0: 1}
...     if n not in factorial.cache:
...         factorial.cache[n] = n * factorial(n-1)
...     return factorial.cache[n]
>>>
>>>
>>> factorial(10)
3628800
>>>
>>> factorial.cache  
{0: 1,
 1: 1,
 2: 2,
 3: 6,
 4: 24,
 5: 120,
 6: 720,
 7: 5040,
 8: 40320,
 9: 362880,
 10: 3628800}