18 \ingroup Pythonizations
19 Decorator that allows to pythonize C++ classes. To pythonize means to add
20 some extra behaviour to a C++ class that is used from Python via PyROOT,
21 so that such a class can be used in an easier / more "pythonic" way.
22 When a pythonization is registered with this decorator, the injection of
23 the new behaviour in the C++ class is done immediately, if the class has
24 already been used from the application, or lazily, i.e. only when the class
25 is first accessed from the application.
28 class_name (string/iterable[string]): specifies either a single string or
29 multiple strings, where each string can be either (i) the name of a
30 C++ class to be pythonized, or (ii) a prefix to match all classes
31 whose name starts with that prefix.
32 ns (string): namespace of the classes to be pythonized. Default is the
33 global namespace (`::`).
34 is_prefix (boolean): if True, `class_name` contains one or multiple
35 prefixes, each prefix potentially matching multiple classes.
37 These are examples of prefixes and namespace and what they match:
38 - class_name="", ns="::" : all classes in the global namespace.
39 - class_name="C", ns="::" : all classes in the global namespace
40 whose name starts with "C"
41 - class_name="", ns="NS1::NS2" : all classes in namespace "NS1::NS2"
42 - class_name="C", ns="NS1::NS2" : all classes in namespace
43 "NS1::NS2" whose name starts with "C"
46 function: function that receives the user-defined function and
54 target = _check_target(class_name)
63 def passes_filter(class_name):
64 return any(class_name.startswith(prefix)
for prefix
in target)
68 def passes_filter(class_name):
69 return class_name
in target
71 def pythonization_impl(user_pythonizor):
73 The real decorator. Accepts a user-provided function and decorates it.
74 An inner function - a wrapper of the user function - is registered in
75 cppyy as a pythonizor.
78 user_pythonizor (function): user-provided function to be decorated.
79 It implements some pythonization. It can accept two parameters:
80 the class to be pythonized, i.e. the Python proxy of the class
81 in which new behaviour can be injected, and optionally the name
82 of that class (can be used e.g. to do some more complex
86 function: the user function, after being registered as a
91 npars = _check_num_pars(user_pythonizor)
96 _find_used_classes(ns, passes_filter, user_pythonizor, npars)
98 def cppyy_pythonizor(klass, name):
100 Wrapper function with the parameters that cppyy requires for a
101 pythonizor function (class proxy and class name). It invokes the
102 user function only if the current class - a candidate for being
103 pythonized - matches the `target` argument of the decorator.
106 klass (class type): cppyy proxy of the class that is the
107 current candidate to be pythonized.
108 name (string): name of the class that is the current candidate
111 from ._generic
import pythonize_generic
113 fqn = klass.__cpp_name__
116 pythonize_generic(klass, fqn)
118 if passes_filter(name):
119 _invoke(user_pythonizor, npars, klass, fqn)
122 ROOT._cppyy.py.add_pythonization(cppyy_pythonizor, ns)
129 return user_pythonizor
131 return pythonization_impl
137def _check_target(target):
139 Helper function to check the type of the `class name` argument specified by
140 the user in a @pythonization decorator.
143 target (string/iterable[string]): class name(s)/prefix(es).
146 list[string]: class name(s)/prefix(es) in `target`, with no repetitions.
149 if isinstance(target, str):
150 _check_no_namespace(target)
154 if isinstance(name, str):
155 _check_no_namespace(name)
158 'Invalid type of "target" argument in @pythonization: must be string or iterable of strings'
161 target = list(
set(target))
166def _check_no_namespace(target):
168 Checks that a given target of a pythonizor does not specify a namespace
169 (only the class name / prefix of a class name should be present).
172 target (string): class name/prefix.
175 if target.find(
"::") >= 0:
177 'Invalid value of "class_name" argument in '
178 '@pythonization: namespace definition found ("{}"). '
179 'Please use the "ns" parameter to specify the '
180 "namespace".format(target)
184def _check_num_pars(f):
186 Checks the number of parameters of the `f` function.
189 f (function): user pythonizor function.
192 int: number of positional parameters of `f`.
194 npars = len(inspect.getfullargspec(f).args)
195 if npars == 0
or npars > 2:
197 "Pythonizor function {} has a wrong number of "
198 "parameters ({}). Allowed parameters are the class to "
199 "be pythonized and (optionally) its name.".format(f.__name__, npars)
205def _invoke(user_pythonizor, npars, klass, fqn):
207 Invokes the given user pythonizor function with the right arguments.
210 user_pythonizor (function): user pythonizor function.
211 npars (int): number of parameters of the user pythonizor function.
212 klass (class type): cppyy proxy of the class to be pythonized.
213 fqn (string): fully-qualified name of the class to be pythonized.
218 user_pythonizor(klass)
220 user_pythonizor(klass, fqn)
222 print(
"Error pythonizing class {}:".format(fqn))
223 traceback.print_exc()
229def _find_used_classes(ns, passes_filter, user_pythonizor, npars):
231 Finds already instantiated classes in namespace `ns` that pass the filter
232 of `passes_filter`. Every matching class is pythonized with the
233 `user_pythonizor` function.
234 This makes sure a pythonizor is also applied to classes that have already
235 been used at the time the pythonizor is registered.
238 ns (string): namespace of the class names of prefixes in `targets`.
239 passes_filter (function): function that determines if a given class
240 is the target of `user_pythonizor`.
241 user_pythonizor (function): user pythonizor function.
242 npars (int): number of parameters of the user pythonizor function.
245 ns_obj = _find_namespace(ns)
250 def pythonize_if_match(name, klass):
252 if passes_filter(name.split(
"::")[-1]):
254 _invoke(user_pythonizor, npars, klass, klass.__cpp_name__)
256 def get_class_name(instantiation):
260 if isinstance(instantiation, str):
265 if hasattr(instantiation,
"__cpp_name__"):
266 return instantiation.__cpp_name__
268 if hasattr(instantiation,
"__name__"):
269 return instantiation.__name__
272 f
"The template instantiation '{instantiation}' cannot be properly pythonized. Please report this as a bug."
275 ns_vars = vars(ns_obj)
276 for var_name, var_value
in ns_vars.items():
277 if str(var_value).startswith(
"<class cppyy.gbl.")
and not hasattr(var_value,
"__cpp_template__"):
280 pythonize_if_match(var_name, var_value)
282 if str(var_value).startswith(
"<cppyy.Template"):
287 instantiations = getattr(var_value,
"_instantiations", {})
288 for args, instance
in instantiations.items():
292 if instance
not in ns_vars:
293 instance_name = var_name +
"<" +
",".join(
map(get_class_name, args)) +
">"
294 pythonize_if_match(instance_name, instance)
297def _find_namespace(ns):
299 Finds and returns the proxy object of the `ns` namespace, if it has already
303 ns (string): a namespace.
306 namespace proxy object, if the namespace has already been accessed,
312 return ROOT._cppyy.gbl
314 ns_obj = ROOT._cppyy.gbl
316 every_ns = ns.split(
"::")
318 ns_vars = vars(ns_obj)
319 if ns
not in ns_vars:
321 ns_obj = getattr(ns_obj, ns)
326def _register_pythonizations():
328 Registers the ROOT pythonizations with cppyy for lazy injection.
331 exclude = [
"_rdf_utils",
"_rdf_pyz",
"_rdf_conversion_maps"]
332 for _, module_name, _
in pkgutil.walk_packages(__path__):
333 if module_name
not in exclude:
334 importlib.import_module(__name__ +
"." + module_name)
337def _wait_press_windows():
341 from ROOT
import gSystem
343 while not gSystem.ProcessEvents():
352def _wait_press_posix():
359 from ROOT
import gSystem
361 old_settings = termios.tcgetattr(sys.stdin)
363 tty.setcbreak(sys.stdin.fileno())
367 while not gSystem.ProcessEvents():
369 if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
370 c = sys.stdin.read(1)
376 termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
379def _run_root_event_loop():
383 from ROOT
import gROOT
390 if 'IPython' in sys.modules
and sys.modules[
'IPython'].version_info[0] >= 5:
393 print(
"Press <space> key to continue")
396 _wait_press_windows()