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}