>>> abs(5)
5
>>> abs(-1.2)
1.2
>>> len('Hello')
5
>>> all([True, 'foo', 4, {'key': 0}])
True
>>> input()
hello
'hello'
>>> print("abc")
abc
>>> ret = print("abc")
>>> print(ret)
abc None
>>> divmod(7, 2)
(3, 1)
>>> a, b = divmod(7, 2)
>>> round(3.5) * 2 + abs(-5)
13
>>> import random
>>> random.randrange(10)
8
>>> it = iter(range(10))
>>> next(it)
3
defreturndef addition(a, b):
    return a + b
>>> addition(3, 5)
8
>>> int('123')
123
>>> str()
''
>>> range(10)
range(0, 10)
>>> "Hello".replace("l", "_")
'He__o'
>>> dict.fromkeys([1, 2, 3])
{1: None, 2: None, 3: None}
>>> f = lambda x: x+1
>>> f(5)
6
>>> (lambda i: i**2)(4)
16
functools.partial sont des appels partiels de fonctions>>> import functools
>>> f = functools.partial(max, 0)
>>> f(1, 2)
2
>>> f(-1, -2)
0
>>> addition(3, 5)
8
>>> addition(a=3, b=5)
8
>>> addition(3, b=5)
8
>>> addition(a=3, 5)
Cell In[31], line 1 addition(a=3, 5) ^ SyntaxError: positional argument follows keyword argument
a et b sont les paramètres de la fonction additiondef addition(a, b):
    return a + b
3 et 5 sont les arguments de l'appeladdition(3, b=5)
5 est un argument nommé associé au nom bdef multiplication(a, b=1):
    return a * b
>>> multiplication(3, 4)
12
>>> multiplication(5)
5
>>> def append(item, dest=[]):
...     dest.append(item)
...     return dest
>>> append(4, [1, 2, 3])
[1, 2, 3, 4]
>>> append('hello')
['hello']
Les paramètres peuvent être de plusieurs sortes :
Jusqu'ici nos paramètres étaient tous de type positional-or-keyword
/ et * de spécifier la sorte de nos paramètres :/ sont positional-only* sont keyword-onlydef function(first, /, second, third, *, fourth):
    ...
first est positional-onlysecond et third sont positional-or-keywordfourth est positional-onlyfirst ne peut pas recevoir d'argument nommé et fourth ne peut pas recevoir d'argument positionnel>>> function(1, 2, 3, fourth=4)
>>> function(1, second=2, third=3, fourth=4)
>>> function(1, 2, 3, 4)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[41], line 1 ----> 1 function(1, 2, 3, 4) TypeError: function() takes 3 positional arguments but 4 were given
>>> function(first=1, second=2, third=3, fourth=4)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[42], line 1 ----> 1 function(first=1, second=2, third=3, fourth=4) TypeError: function() got some positional-only arguments passed as keyword arguments: 'first'
def function(first, /, second, third=3):
    ...
def function(first, /, second=2, third):
    ...
Cell In[44], line 1 def function(first, /, second=2, third): ^ SyntaxError: invalid syntax
def function(first=1, /, second, third):
    ...
Cell In[45], line 1 def function(first=1, /, second, third): ^ SyntaxError: non-default argument follows default argument
def function(foo=None, *, bar=True, baz):
    return (foo, bar, baz)
>>> function(baz=False)
(None, True, False)
* récupère tous les arguments positionnels restants* dans la liste des paramètres*argsdef my_sum(*args):
    total = 0
    for item in args:
        total += item
    return total
>>> my_sum(1, 2, 3)
6
>>> my_sum()
0
def my_sum(first, /, *args):
    total = first
    for item in args:
        total += item
    return total
>>> my_sum(1, 2, 3)
6
>>> my_sum('a', 'b', 'c')
'abc'
>>> my_sum()
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[54], line 1 ----> 1 my_sum() TypeError: my_sum() missing 1 required positional argument: 'first'
print par exemple pour accepter un nombre arbitraire d'arguments>>> print(1, 'foo', ['hello', 'world'])
1 foo ['hello', 'world']
** permet de définir un paramètre qui récupère tous les arguments nommés restantskwargsdef make_dict(**kwargs):
    return kwargs
>>> make_dict(foo=1, bar=2)
{'foo': 1, 'bar': 2}
def make_obj(id, **kwargs):
    return {'id': id} | kwargs
>>> make_obj('#1', foo='bar')
{'id': '#1', 'foo': 'bar'}
>>> make_obj(id='#1', foo='baz')
{'id': '#1', 'foo': 'baz'}
* et ** ne sont pas utilisables uniquement dans les listes de paramètres* appliqué à une liste (ou tout autre itérable) transforme ses éléments en arguments positionnels>>> addition(*[3, 5])
8
>>> print(*(i**2 for i in range(10)))
0 1 4 9 16 25 36 49 64 81
>>> my_sum(*range(5), 10, *range(3))
23
** il s'applique à un dictionnaire (ou similaire) et transforme les éléments en arguments nommés>>> addition(**{'a': 3, 'b': 5})
8
>>> first, *middle, last = *range(5), 8, *range(3)
>>> first
0
>>> middle
[1, 2, 3, 4, 8, 0, 1]
>>> last
2
signature du module inspect permet de récupérer la signature d'une fonction>>> import inspect
>>> inspect.signature(addition)
<Signature (a, b)>
addition attend deux paramètres a et binspect.signature permet d'explorer la signature de la fonction>>> sig = inspect.signature(addition)
>>> sig.parameters
mappingproxy({'a': <Parameter "a">, 'b': <Parameter "b">})
>>> sig.parameters.keys()
odict_keys(['a', 'b'])
>>> sig.parameters['a']
<Parameter "a">
>>> sig.parameters['a'].kind
<_ParameterKind.POSITIONAL_OR_KEYWORD: 1>
>>> sigmul = inspect.signature(multiplication)
>>> sigmul.parameters['b']
<Parameter "b=1">
>>> sigmul.parameters['b'].default
1
>>> binding = sig.bind(3, b=5)
>>> binding
<BoundArguments (a=3, b=5)>
>>> binding.arguments
{'a': 3, 'b': 5}
>>> binding.args, binding.kwargs
((3, 5), {})
args et les autres dans kwargs>>> sig.bind(5)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[79], line 1 ----> 1 sig.bind(5) File /usr/lib/python3.10/inspect.py:3182, in Signature.bind(self, *args, **kwargs) 3177 def bind(self, /, *args, **kwargs): 3178 """Get a BoundArguments object, that maps the passed `args` 3179 and `kwargs` to the function's signature. Raises `TypeError` 3180 if the passed arguments can not be bound. 3181 """ -> 3182 return self._bind(args, kwargs) File /usr/lib/python3.10/inspect.py:3097, in Signature._bind(self, args, kwargs, partial) 3095 msg = 'missing a required argument: {arg!r}' 3096 msg = msg.format(arg=param.name) -> 3097 raise TypeError(msg) from None 3098 else: 3099 # We have a positional argument to process 3100 try: TypeError: missing a required argument: 'b'
>>> binding = sigmul.bind(10)
>>> binding
<BoundArguments (a=10)>
>>> binding.apply_defaults()
>>> binding
<BoundArguments (a=10, b=1)>
replace pour en créer une copie modifiée>>> sig.replace(parameters=[])
<Signature ()>
>>> from inspect import Parameter
>>> sig.parameters['a'].replace(kind=Parameter.POSITIONAL_ONLY)
<Parameter "a">
>>> newsig = sig.replace(parameters=[p.replace(kind=Parameter.POSITIONAL_ONLY) for p in sig.parameters.values()])
>>> newsig
<Signature (a, b, /)>
>>> newsig.bind(1, 2)
<BoundArguments (a=1, b=2)>
>>> newsig.bind(a=1, b=2)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[86], line 1 ----> 1 newsig.bind(a=1, b=2) File /usr/lib/python3.10/inspect.py:3182, in Signature.bind(self, *args, **kwargs) 3177 def bind(self, /, *args, **kwargs): 3178 """Get a BoundArguments object, that maps the passed `args` 3179 and `kwargs` to the function's signature. Raises `TypeError` 3180 if the passed arguments can not be bound. 3181 """ -> 3182 return self._bind(args, kwargs) File /usr/lib/python3.10/inspect.py:3078, in Signature._bind(self, args, kwargs, partial) 3075 msg = '{arg!r} parameter is positional only, ' \ 3076 'but was passed as a keyword' 3077 msg = msg.format(arg=param.name) -> 3078 raise TypeError(msg) from None 3079 parameters_ex = (param,) 3080 break TypeError: 'a' parameter is positional only, but was passed as a keyword
__signature__ de la fonction>>> addition.__signature__ = newsig
inspect.signature>>> inspect.signature(addition)
<Signature (a, b, /)>
>>> addition(a=1, b=2)
3
def addition(a: int, b: int) -> int:
    return a + b
>>> sig = inspect.signature(addition)
>>> sig
<Signature (a: int, b: int) -> int>
>>> sig.return_annotation
int
>>> sig.parameters['a'].annotation
int
__annotations__ de la fonction>>> addition.__annotations__
{'a': int, 'b': int, 'return': int}
inspect.get_annotations>>> inspect.get_annotations(addition)
{'a': int, 'b': int, 'return': int}
inspect.get_annotations est préférable__annotations__ sur l'objet et différents problèmes potentielsdef addition(a: "int", b: "int") -> "int":
    return a + b
>>> addition.__annotations__
{'a': 'int', 'b': 'int', 'return': 'int'}
>>> inspect.get_annotations(addition)
{'a': 'int', 'b': 'int', 'return': 'int'}
>>> inspect.get_annotations(addition, eval_str=True)
{'a': int, 'b': int, 'return': int}
def addition(a: int, b: int) -> int:
    "Return the sum of two integers"
    return a + b
__doc__ de la fonction>>> addition.__doc__
'Return the sum of two integers'
inspect.getdoc>>> inspect.getdoc(addition)
'Return the sum of two integers'
def function():
    """
    Docstring of the function
    on multiple lines
    """
>>> function.__doc__
'\n Docstring of the function\n on multiple lines\n '
>>> inspect.getdoc(function)
'Docstring of the function\non multiple lines'
import functools
@functools.cache
def addition(a, b):
    print(f'Computing {a}+{b}')
    return a + b
>>> addition(3, 5)
Computing 3+5
8
>>> addition(1, 2)
3
functools.cache a remplacé addition par une nouvelle fonction avec un mécanisme de cachedef addition(a, b):
    print(f'Computing {a}+{b}')
    return a + b
addition = functools.cache(addition)
On peut aussi appliquer plusieurs décorateurs à la suite
@deco1
@deco2
def function():
    ...
Qui est équivalent à :
def function():
    ...
function = deco1(deco2(function))
def decorator(func):
    return func
@decorator
def addition(a, b):
    return a + b
>>> addition(3, 5)
8
def decorator(func):
    def wrapper(*args, **kwargs):
        print('Calling decorated function')
        return func(*args, **kwargs)
    return wrapper
@decorator
def addition(a, b):
    return a + b
>>> addition(3, 5)
Calling decorated function
8
functools.cache pourrait être réécrit ainsidef cache(func):
    func_cache = {}
    def wrapper(*args, **kwargs):
        # make an hashable key
        key = args, tuple(kwargs.items())
        if key not in func_cache:
            func_cache[key] = func(*args, **kwargs)
        return func_cache[key]
    return wrapper
@cache
def addition(a, b):
    print(f'Computing {a}+{b}')
    return a + b
>>> addition(3, 5)
Computing 3+5
8
@functools.lru_cache(maxsize=1)
def addition(a, b):
    print(f'Computing {a}+{b}')
    return a + b
>>> addition(3, 5)
Computing 3+5
8
>>> addition(1, 2)
3
def param_decorator(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f'Function decorated with {n}')
            return func(*args, **kwargs)
        return wrapper
    return decorator
@param_decorator(42)
def function():
    ...
>>> function()
Function decorated with 42
callable permet de savoir si un objet est callable>>> callable(len)
True
>>> callable(str)
True
>>> callable(str.replace)
True
>>> callable(lambda: True)
True
>>> callable(5)
False
functools.partial pour l'application partielleimport functools
debug = functools.partial(print, '[DEBUG]', sep=' - ')
debug(1, 2, 3)
[DEBUG] - 1 - 2 - 3
@decorator
def addition(a: int, b: int) -> int:
    "Return the sum of two integers"
    return a + b
>>> inspect.signature(addition)
<Signature (*args, **kwargs)>
>>> inspect.getdoc(addition)
__doc__, __signature__ et autresfunctools fournit une fonction update_wrapper pour faire cela plus simplementdef decorator(func):
    def wrapper(*args, **kwargs):
        print('Calling decorated function')
        return func(*args, **kwargs)
    functools.update_wrapper(wrapper, func)
    return wrapper
@decorator
def addition(a: int, b: int) -> int:
    "Return the sum of two integers"
    return a + b
>>> inspect.signature(addition)
<Signature (a: int, b: int) -> int>
>>> inspect.getdoc(addition)
'Return the sum of two integers'
__wrapped__ à notre fonction>>> addition.__wrapped__
<function __main__.addition(a: int, b: int) -> int>
functools possède aussi un décorateur wraps pour faciliter celadef decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('Calling decorated function')
        return func(*args, **kwargs)
    return wrapper
operator expose les différents opérateurs du langageoperator.call permet notamment d'appeler un callable (Python 3.11)>>> import operator
>>> operator.call(addition, 1, 2)
itemgetter renvoie un callable pour récupérer un élément d'un conteneur donné>>> get_name = operator.itemgetter('name')
>>> get_name({'name': 'John'})
'John'
>>> get_fullname = operator.itemgetter('firstname', 'lastname')
>>> get_fullname({'firstname': 'Jude', 'lastname': 'Doe'})
('Jude', 'Doe')
attrgetter pour récupérer un attribut>>> get_module = operator.attrgetter('__module__')
>>> get_module(int)
'builtins'
methodcaller pour appeler une méthode sur un objet donné>>> replace = operator.methodcaller('replace', 'o', 'a')
>>> replace('toto')
'tata'
inspect dédié à l'introspectioninspect.isfunctioninspect.getsourcecallable permet de tester si un objet est callable__call__class Adder:
    def __init__(self, add):
        self.add = add
    def __call__(self, x):
        return self.add + x
>>> add_5 = Adder(5)
>>> callable(add_5)
True
>>> add_5(3)
8
class Expr:
    def __call__(self, **env):
        raise NotImplementedError
class Scalar(Expr):
    def __init__(self, value):
        self.value = value
    def __repr__(self):
        return repr(self.value)
    def __call__(self, **env):
        return self.value
class Operation(Expr):
    def __init__(self, op_func, op_repr, *args):
        self.func = op_func
        self.repr = op_repr
        self.args = args
    def __repr__(self):
        return self.repr(*self.args)
    def __call__(self, **env):
        values = (arg(**env) for arg in self.args)
        return self.func(*values)
class Symbol(Expr):
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return self.name
    def __call__(self, **env):
        if self.name in env:
            return env[self.name]
        return self
def make_op(op_func, op_fmt):
    return functools.partial(Operation, op_func, op_fmt)
add = make_op(operator.add, '{} + {}'.format)
sub = make_op(operator.sub, '{} - {}'.format)
mul = make_op(operator.mul, '{} * {}'.format)
div = make_op(operator.truediv, '{} / {}'.format)
fdiv = make_op(operator.floordiv, '{} // {}'.format)
mod = make_op(operator.mod, '{} % {}'.format)
pow = make_op(operator.pow, '{} ** {}'.format)
>>> expr = add(pow(Symbol('x'), Scalar(2)), Scalar(5))
>>> expr
x ** 2 + 5
>>> expr(x=3)
14
def function(func):
    def op_repr(*args):
        return f"{func.__name__}({', '.join(repr(arg) for arg in args)})"
    return functools.partial(Operation, func, op_repr)
>>> import math
>>> expr = function(math.cos)(mul(Symbol('x'), Scalar(math.pi)))
>>> expr
cos(x * 3.141592653589793)
>>> expr(x=1)
-1.0
def ensure_expr(x):
    if isinstance(x, Expr):
        return x
    return Scalar(x)
def binop(op_func):
    return lambda lhs, rhs: op_func(ensure_expr(lhs), ensure_expr(rhs))
def rev_binop(op_func):
    return lambda rhs, lhs: op_func(ensure_expr(lhs), ensure_expr(rhs))
Expr.__add__ = binop(add)
Expr.__radd__ = rev_binop(add)
Expr.__sub__ = binop(sub)
Expr.__rsub__ = rev_binop(sub)
Expr.__mul__ = binop(mul)
Expr.__rmul__ = rev_binop(mul)
Expr.__truediv__ = binop(div)
Expr.__rtruediv__ = rev_binop(div)
Expr.__floordiv__ = binop(fdiv)
Expr.__rfloordiv__ = rev_binop(fdiv)
Expr.__mod__ = binop(mod)
Expr.__rmod__ = rev_binop(mod)
Expr.__pow__ = binop(pow)
Expr.__rpow__ = rev_binop(pow)
>>> expr = 3 * Symbol('x') ** 2 + 2
>>> expr
3 * x ** 2 + 2
>>> expr(x=10)
302
__call__>>> addition.__call__
<method-wrapper '__call__' of function object at 0x7f2c8ae545e0>
>>> addition.__call__(3, 5)
Calling decorated function
8
>>> addition.__call__.__call__.__call__(3, 5)
Calling decorated function
8
__code__def function():
    print('hello')
>>> function.__code__
<code object function at 0x7f2ca010dfd0, file "/tmp/ipykernel_12684/459794336.py", line 1>
>>> exec(function.__code__)
hello
FunctionType du module types>>> import types
>>> newfunc = types.FunctionType(function.__code__, globals())
>>> newfunc()
hello
compile permet celaast.FunctionDef est alors nécessaire pour construire une fonction>>> import ast
>>> func_body = ast.parse("print('hello')").body
>>> func_body
[<ast.Expr at 0x7f2ca03cc130>]
>>> fdef = ast.FunctionDef(
...     name='f',
...     args=ast.arguments(posonlyargs=[], args=[], kwonlyargs=[], kw_defaults=[], defaults=[]),
...     body=func_body,
...     lineno=0,
...     col_offset=0,
...     decorator_list=[],
... )
>>> code = compile(ast.Module(body=[fdef], type_ignores=[]), 'x', 'exec')
>>> func_code = code.co_consts[0]
>>> func_code
<code object f at 0x7f2ca010ddc0, file "x", line 1>
>>> f = types.FunctionType(func_code, {})
>>> f()
hello
def create_function(name, body, arg_names):
    function_body = ast.parse(body).body
    args = [ast.arg(arg=arg_name, lineno=0, col_offset=0) for arg_name in arg_names]
    function_def = ast.FunctionDef(
        name=name,
        args=ast.arguments(
            posonlyargs=[],
            args=args,
            kwonlyargs=[],
            defaults=[],
            kw_defaults=[]),
        body = function_body,
        decorator_list=[],
        lineno=0,
        col_offset=0,
    )
    module = compile(ast.Module(body=[function_def], type_ignores=[]), "<string>", "exec")
    function_code = next(c for c in module.co_consts if isinstance(c, types.CodeType))
    return types.FunctionType(function_code, globals())
>>> addition = create_function('addition', 'return a + b', ('a', 'b'))
>>> addition
<function __main__.addition(a, b)>
>>> addition(3, 5)
8
conclusion()
--------------------------------------------------------------------------- NameError Traceback (most recent call last) Cell In[181], line 1 ----> 1 conclusion() NameError: name 'conclusion' is not defined