11"""Very high level benchmarking decorator."""
22
3- from typing import TYPE_CHECKING
3+ from collections .abc import Callable
4+ from inspect import Signature , signature
5+ from typing import TYPE_CHECKING , Any , Literal
46
57from stopuhr .stopuhr import StopUhr , stopuhr
68
79if TYPE_CHECKING :
810 import pandas as pd
911
1012
13+ def _get_bound_args (sig : Signature , * args , ** kwargs ) -> dict [str , Any ]:
14+ # Create a dictionary to store parameter bindings
15+ bound_args = {}
16+
17+ # Bind positional arguments
18+ for arg_name , arg_value in zip (sig .parameters .keys (), args ):
19+ bound_args [arg_name ] = arg_value
20+
21+ # Add keyword arguments
22+ for key , value in kwargs .items ():
23+ if key in sig .parameters :
24+ bound_args [key ] = value
25+
26+ # Fill in default values for missing parameters
27+ for param_name , param in sig .parameters .items ():
28+ if param_name not in bound_args and param .default != param .empty :
29+ bound_args [param_name ] = param .default
30+
31+ return bound_args
32+
33+
1134class FunkUhr :
1235 """Very high level benchmarking decorator.
1336
@@ -33,6 +56,40 @@ class FunkUhr:
3356 >>> funkuhr.summary()
3457 Busy Function took 0.10 ± 0.00 s (n=5 -> total=0.50s)
3558
59+ Like the stateless decorator, it is possible to add arguments to the message.
60+
61+ .. code-block:: python
62+ >>> from stopuhr import FunkUhr
63+
64+ >>> funkuhr = FunkUhr()
65+
66+ >>> @funkuhr("Busy Function", print_kwargs=["arg1", "arg2"])
67+ >>> def busy_function(arg1, arg2, arg3):
68+ >>> time.sleep(0.1)
69+
70+ >>> for i in range(5):
71+ >>> busy_function(1, 2, 3)
72+
73+ >>> funkuhr.summary()
74+ Busy Function took 0.10 ± 0.00 s (n=5 -> total=0.50s) (with arg1=1, arg2=2)
75+
76+ It is also possible to add all arguments to the message.
77+
78+ .. code-block:: python
79+ >>> from stopuhr import FunkUhr
80+
81+ >>> funkuhr = FunkUhr()
82+
83+ >>> @funkuhr("Busy Function", print_kwargs=True)
84+ >>> def busy_function(arg1, arg2):
85+ >>> time.sleep(0.1)
86+
87+ >>> for i in range(5):
88+ >>> busy_function(1, 2)
89+
90+ >>> funkuhr.summary()
91+ Busy Function took 0.10 ± 0.00 s (n=5 -> total=0.50s) (with arg1=1, arg2=2)
92+
3693 """
3794
3895 def __init__ (self , printer : callable = print ):
@@ -62,27 +119,69 @@ def summary(self, res: int = 2):
62119 """
63120 self .stopuhr .summary (res )
64121
65- def __call__ (self , key : str , res : int = 2 , log : bool = True ):
122+ def __call__ (
123+ self ,
124+ key : str ,
125+ res : int = 2 ,
126+ log : bool = True ,
127+ print_kwargs : list [str ] | Literal ["all" ] | None = None ,
128+ ):
66129 """Decorate a function to measure the time taken in a block.
67130
68131 Args:
69132 key (str): The key to store the duration under.
70133 res (int, optional): The number of decimal places to round to. Defaults to 2.
71134 log (bool, optional): Whether to log the duration. Defaults to True.
135+ print_kwargs (list[str] | bool, optional): The arguments to be added to the `key`.
136+ If a list, only the arguments in the list will be added to the message.
137+ If True, all arguments will added. If False, no arguments will be added.
138+ Additions to the message will have the form: f"{key} (with {arg1=val1, arg2=val2, ...})".
139+ Defaults to False.
140+
141+ Raises:
142+ ValueError: If any of the print_kwargs are not in the functions signature.
72143
73144 """
74145
75146 def _decorator (func ):
147+ func_signature = signature (func )
148+ # Check if any of the print_kwargs are not in the functions signature and raise an error if so
149+ if isinstance (print_kwargs , list ) and any (
150+ k not in func_signature .parameters for k in print_kwargs if isinstance (print_kwargs , list )
151+ ):
152+ raise ValueError (
153+ f"Not all { print_kwargs = } found in { func_signature .parameters = } of function { func .__name__ } "
154+ )
155+
76156 def _inner (* args , ** kwargs ):
77- with self .stopuhr (key , res = res , log = log ):
157+ _inner_key = key
158+
159+ if isinstance (print_kwargs , list ):
160+ bound_args = _get_bound_args (func_signature , * args , ** kwargs )
161+ # Check if any of the print_kwargs are not in bound_args and raise an error if so
162+ if any (k not in bound_args for k in print_kwargs ):
163+ raise ValueError (f"Not all { print_kwargs = } found in { bound_args = } of function { func .__name__ } " )
164+
165+ # Filter by print_kwargs
166+ bound_args = {k : bound_args [k ] for k in print_kwargs if k in bound_args }
167+ # Make a string of it and add it to the message
168+ bound_args_msg = ", " .join (f"{ k } ={ v } " for k , v in bound_args .items ())
169+ _inner_key += f" (with { bound_args_msg } )"
170+ elif print_kwargs :
171+ bound_args = _get_bound_args (func_signature , * args , ** kwargs )
172+ # Make a string of it and add it to the message
173+ bound_args_msg = ", " .join (f"{ k } ={ v } " for k , v in bound_args .items ())
174+ _inner_key += f" (with { bound_args_msg } )"
175+
176+ with self .stopuhr (_inner_key , res = res , log = log ):
78177 return func (* args , ** kwargs )
79178
80179 return _inner
81180
82181 return _decorator
83182
84183
85- def funkuhr (msg : str , printer : callable = print , res : int = 2 ):
184+ def funkuhr (msg : str , printer : callable = print , res : int = 2 , print_kwargs : list [ str ] | bool = False ):
86185 """Decorate a function to measure the time taken in a block.
87186
88187 Wraps the `stopuhr` function to provide a decorator for benchmarking functions.
@@ -99,17 +198,73 @@ def funkuhr(msg: str, printer: callable = print, res: int = 2):
99198 >>> busy_function()
100199 Busy Function took 0.20s
101200
201+ It is possible to add arguments to the message.
202+
203+ .. code-block:: python
204+ >>> from stopuhr import funkuhr
205+
206+ >>> @funkuhr("Busy Function", print_kwargs=["arg1", "arg2"])
207+ >>> def busy_function(arg1, arg2, arg3):
208+ >>> time.sleep(0.2)
209+
210+ >>> busy_function(1, 2, 3)
211+ Busy Function took 0.20s (with arg1=1, arg2=2)
212+
213+ It is also possible to add all arguments to the message.
214+
215+ .. code-block:: python
216+ >>> from stopuhr import funkuhr
217+
218+ >>> @funkuhr("Busy Function", print_kwargs=True)
219+ >>> def busy_function(arg1, arg2):
220+ >>> time.sleep(0.2)
221+
222+ >>> busy_function(1, 2)
223+ Busy Function took 0.20s (with arg1=1, arg2=2)
224+
102225 Args:
103226 func (callable): The function to decorate.
104227 msg (str): The message to print.
105228 printer (callable, optional): The function to print with. Defaults to print.
106229 res (int, optional): The number of decimal places to round to. Defaults to 2.
230+ print_kwargs (list[str] | bool, optional): The arguments to be added to the `msg`.
231+ If a list, only the arguments in the list will be added to the message.
232+ If True, all arguments will added. If False, no arguments will be added.
233+ Additions to the message will have the form: f"{msg} (with {arg1=val1, arg2=val2, ...})".
234+ Defaults to False.
235+
236+ Raises:
237+ ValueError: If any of the print_kwargs are not in the functions signature.
107238
108239 """
109240
110- def _decorator (func ):
241+ def _decorator (func : Callable ):
242+ func_signature = signature (func )
243+ # Check if any of the print_kwargs are not in the functions signature and raise an error if so
244+ if isinstance (print_kwargs , list ) and any (
245+ k not in func_signature .parameters for k in print_kwargs if isinstance (print_kwargs , list )
246+ ):
247+ raise ValueError (
248+ f"Not all { print_kwargs = } found in { func_signature .parameters = } of function { func .__name__ } "
249+ )
250+
111251 def _inner (* args , ** kwargs ):
112- with stopuhr (msg , printer , res ):
252+ _inner_msg = msg
253+
254+ if isinstance (print_kwargs , list ):
255+ bound_args = _get_bound_args (func_signature , * args , ** kwargs )
256+ # Filter by print_kwargs
257+ bound_args = {k : bound_args [k ] for k in print_kwargs if k in bound_args }
258+ # Make a string of it and add it to the message
259+ bound_args_msg = ", " .join (f"{ k } ={ v } " for k , v in bound_args .items ())
260+ _inner_msg += f" (with { bound_args_msg } )"
261+ elif print_kwargs : # True was passed
262+ bound_args = _get_bound_args (func_signature , * args , ** kwargs )
263+ # Make a string of it and add it to the message
264+ bound_args_msg = ", " .join (f"{ k } ={ v } " for k , v in bound_args .items ())
265+ _inner_msg += f" (with { bound_args_msg } )"
266+
267+ with stopuhr (_inner_msg , printer , res ):
113268 return func (* args , ** kwargs )
114269
115270 return _inner
0 commit comments