>>> 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
def
return
def 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 addition
def 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 b
def 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*
args
def 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 restantskwargs
def 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 b
inspect.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.isfunction
inspect.getsource
callable
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