Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
__init__.py
Go to the documentation of this file.
1# Author: Enric Tejedor, Danilo Piparo CERN 06/2018
2
3################################################################################
4# Copyright (C) 1995-2018, Rene Brun and Fons Rademakers. #
5# All rights reserved. #
6# #
7# For the licensing terms see $ROOTSYS/LICENSE. #
8# For the list of contributors see $ROOTSYS/README/CREDITS. #
9################################################################################
10import importlib
11import inspect
12import pkgutil
13import traceback
14
15import cppyy
16
17from ._generic import pythonize_generic
18
19# \cond INTERNALS
20gbl_namespace = cppyy.gbl
21# \endcond
22
23
24def pythonization(class_name, ns="::", is_prefix=False):
25 r"""
26 \ingroup Pythonizations
27 Decorator that allows to pythonize C++ classes. To pythonize means to add
28 some extra behaviour to a C++ class that is used from Python via PyROOT,
29 so that such a class can be used in an easier / more "pythonic" way.
30 When a pythonization is registered with this decorator, the injection of
31 the new behaviour in the C++ class is done immediately, if the class has
32 already been used from the application, or lazily, i.e. only when the class
33 is first accessed from the application.
34
35 Args:
36 class_name (string/iterable[string]): specifies either a single string or
37 multiple strings, where each string can be either (i) the name of a
38 C++ class to be pythonized, or (ii) a prefix to match all classes
39 whose name starts with that prefix.
40 ns (string): namespace of the classes to be pythonized. Default is the
41 global namespace (`::`).
42 is_prefix (boolean): if True, `class_name` contains one or multiple
43 prefixes, each prefix potentially matching multiple classes.
44 Default is False.
45 These are examples of prefixes and namespace and what they match:
46 - class_name="", ns="::" : all classes in the global namespace.
47 - class_name="C", ns="::" : all classes in the global namespace
48 whose name starts with "C"
49 - class_name="", ns="NS1::NS2" : all classes in namespace "NS1::NS2"
50 - class_name="C", ns="NS1::NS2" : all classes in namespace
51 "NS1::NS2" whose name starts with "C"
52
53 Returns:
54 function: function that receives the user-defined function and
55 decorates it.
56
57 """
58
59 # Type check and parsing of target argument.
60 # Retrieve the scope(s) of the class(es)/prefix(es) to register the
61 # pythonizor in the right scope(s)
62 target = _check_target(class_name)
63
64 # Remove trailing '::' from namespace
65 if ns.endswith("::"):
66 ns = ns[:-2]
67
68 # Create a filter lambda for the target class(es)/prefix(es)
69 if is_prefix:
70
71 def passes_filter(class_name):
72 return any(class_name.startswith(prefix) for prefix in target)
73 else:
74
75 def passes_filter(class_name):
76 return class_name in target
77
78 def pythonization_impl(user_pythonizor):
79 """
80 The real decorator. Accepts a user-provided function and decorates it.
81 An inner function - a wrapper of the user function - is registered in
82 cppyy as a pythonizor.
83
84 Args:
85 user_pythonizor (function): user-provided function to be decorated.
86 It implements some pythonization. It can accept two parameters:
87 the class to be pythonized, i.e. the Python proxy of the class
88 in which new behaviour can be injected, and optionally the name
89 of that class (can be used e.g. to do some more complex
90 filtering).
91
92 Returns:
93 function: the user function, after being registered as a
94 pythonizor.
95 """
96
97 npars = _check_num_pars(user_pythonizor)
98
99 # Check whether any of the target classes has already been used.
100 # If so, the class proxy has to be immediately pythonized - even if we
101 # registered a pythonizor for it, the pythonizor would never be executed
102 _find_used_classes(ns, passes_filter, user_pythonizor, npars)
103
104 def cppyy_pythonizor(klass, name):
105 """
106 Wrapper function with the parameters that cppyy requires for a
107 pythonizor function (class proxy and class name). It invokes the
108 user function only if the current class - a candidate for being
109 pythonized - matches the `target` argument of the decorator.
110
111 Args:
112 klass (class type): cppyy proxy of the class that is the
113 current candidate to be pythonized.
114 name (string): name of the class that is the current candidate
115 to be pythonized.
116 """
117
119
120 # Add pretty printing (done on all classes)
121 pythonize_generic(klass, fqn)
122
123 if passes_filter(name):
124 _invoke(user_pythonizor, npars, klass, fqn)
125
126 # Register pythonizor in its namespace
127 cppyy.py.add_pythonization(cppyy_pythonizor, ns)
128
129 # Return the original user function.
130 # We don't want to modify the user function, we just use the decorator
131 # to register the function as a pythonizor.
132 # This allows for correct chaining of multiple @pythonization decorators
133 # for a single function
134 return user_pythonizor
135
136 return pythonization_impl
137
138
139# \cond INTERNALS
140
141
142def _check_target(target):
143 """
144 Helper function to check the type of the `class name` argument specified by
145 the user in a @pythonization decorator.
146
147 Args:
148 target (string/iterable[string]): class name(s)/prefix(es).
149
150 Returns:
151 list[string]: class name(s)/prefix(es) in `target`, with no repetitions.
152 """
153
154 if isinstance(target, str):
155 _check_no_namespace(target)
156 target = [target]
157 else:
158 for name in target:
159 if isinstance(name, str):
161 else:
162 raise TypeError(
163 'Invalid type of "target" argument in @pythonization: must be string or iterable of strings'
164 )
165 # Remove possible duplicates
166 target = list(set(target))
167
168 return target
169
170
171def _check_no_namespace(target):
172 """
173 Checks that a given target of a pythonizor does not specify a namespace
174 (only the class name / prefix of a class name should be present).
175
176 Args:
177 target (string): class name/prefix.
178 """
179
180 if target.find("::") >= 0:
181 raise ValueError(
182 'Invalid value of "class_name" argument in '
183 '@pythonization: namespace definition found ("{}"). '
184 'Please use the "ns" parameter to specify the '
185 "namespace".format(target)
186 )
187
188
189def _check_num_pars(f):
190 """
191 Checks the number of parameters of the `f` function.
192
193 Args:
194 f (function): user pythonizor function.
195
196 Returns:
197 int: number of positional parameters of `f`.
198 """
199 npars = len(inspect.getfullargspec(f).args)
200 if npars == 0 or npars > 2:
201 raise TypeError(
202 "Pythonizor function {} has a wrong number of "
203 "parameters ({}). Allowed parameters are the class to "
204 "be pythonized and (optionally) its name.".format(f.__name__, npars)
205 )
206
207 return npars
208
209
210def _invoke(user_pythonizor, npars, klass, fqn):
211 """
212 Invokes the given user pythonizor function with the right arguments.
213
214 Args:
215 user_pythonizor (function): user pythonizor function.
216 npars (int): number of parameters of the user pythonizor function.
217 klass (class type): cppyy proxy of the class to be pythonized.
218 fqn (string): fully-qualified name of the class to be pythonized.
219 """
220
221 try:
222 if npars == 1:
223 user_pythonizor(klass)
224 else:
225 user_pythonizor(klass, fqn)
226 except Exception:
227 print("Error pythonizing class {}:".format(fqn))
229 # Propagate the error so that the class lookup that triggered this
230 # pythonization fails too and the application stops
231 raise RuntimeError
232
233
234def _find_used_classes(ns, passes_filter, user_pythonizor, npars):
235 """
236 Finds already instantiated classes in namespace `ns` that pass the filter
237 of `passes_filter`. Every matching class is pythonized with the
238 `user_pythonizor` function.
239 This makes sure a pythonizor is also applied to classes that have already
240 been used at the time the pythonizor is registered.
241
242 Args:
243 ns (string): namespace of the class names of prefixes in `targets`.
244 passes_filter (function): function that determines if a given class
245 is the target of `user_pythonizor`.
246 user_pythonizor (function): user pythonizor function.
247 npars (int): number of parameters of the user pythonizor function.
248 """
249
250 ns_obj = _find_namespace(ns)
251 if ns_obj is None:
252 # Namespace has not been used yet, no need to inspect more
253 return
254
255 def pythonize_if_match(name, klass):
256 # Check if name matches, excluding the namespace
257 if passes_filter(name.split("::")[-1]):
258 # Pythonize right away!
259 _invoke(user_pythonizor, npars, klass, klass.__cpp_name__)
260
261 def get_class_name(instantiation):
262 # Get the right class name for the input instantiation
263
264 # Template instantiation such as cppyy.gbl.MyClass["SomeType"]
265 if isinstance(instantiation, str):
266 return instantiation
267
268 # Template instantiation such as cppyy.gbl.MyClass[cppyy.gbl.SomeType]
269 # use the more specialized attribute first, then a more generic one
270 if hasattr(instantiation, "__cpp_name__"):
272
273 if hasattr(instantiation, "__name__"):
275
276 raise RuntimeError(
277 f"The template instantiation '{instantiation}' cannot be properly pythonized. Please report this as a bug."
278 )
279
280 ns_vars = vars(ns_obj)
281 for var_name, var_value in ns_vars.items():
282 if str(var_value).startswith("<class cppyy.gbl."):
283 # It's a class proxy
284 pythonize_if_match(var_name, var_value)
285
286 if str(var_value).startswith("<cppyy.Template"):
287 # If this is a template, pythonize the instances. Note that in
288 # older cppyy, template instantiations are cached by
289 # fully-qualified name directly in the namespace, so they are
290 # covered by the code branch above.
291 instantiations = getattr(var_value, "_instantiations", {})
292 for args, instance in instantiations.items():
293 # Make sure we don't do any redundant pythonization, e.g. if we
294 # use a version of cppyy that caches both in the namespace and
295 # in the _instantiations attribute.
296 if instance not in ns_vars:
297 instance_name = var_name + "<" + ",".join(map(get_class_name, args)) + ">"
298 pythonize_if_match(instance_name, instance)
299
300
301def _find_namespace(ns):
302 """
303 Finds and returns the proxy object of the `ns` namespace, if it has already
304 been accessed.
305
306 Args:
307 ns (string): a namespace.
308
309 Returns:
310 namespace proxy object, if the namespace has already been accessed,
311 otherwise None.
312 """
313
314 if ns == "":
315 return gbl_namespace
316
317 ns_obj = gbl_namespace
318 # Get all namespaces in a list
319 every_ns = ns.split("::")
320 for ns in every_ns:
321 ns_vars = vars(ns_obj)
322 if ns not in ns_vars:
323 return None
324 ns_obj = getattr(ns_obj, ns)
325
326 return ns_obj
327
328
330 """
331 Registers the ROOT pythonizations with cppyy for lazy injection.
332 """
333
334 exclude = ["_rdf_utils", "_rdf_pyz", "_rdf_conversion_maps"]
335 for _, module_name, _ in pkgutil.walk_packages(__path__):
336 if module_name not in exclude:
337 importlib.import_module(__name__ + "." + module_name)
338
339
340# \endcond
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t UChar_t len
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t format
pythonization(class_name, ns="::", is_prefix=False)
Decorator that allows to pythonize C++ classes.
Definition __init__.py:24