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
15
16def pythonization(class_name, ns="::", is_prefix=False):
17 r"""
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.
26
27 Args:
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.
36 Default is False.
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"
44
45 Returns:
46 function: function that receives the user-defined function and
47 decorates it.
48
49 """
50
51 # Type check and parsing of target argument.
52 # Retrieve the scope(s) of the class(es)/prefix(es) to register the
53 # pythonizor in the right scope(s)
54 target = _check_target(class_name)
55
56 # Remove trailing '::' from namespace
57 if ns.endswith("::"):
58 ns = ns[:-2]
59
60 # Create a filter lambda for the target class(es)/prefix(es)
61 if is_prefix:
62
63 def passes_filter(class_name):
64 return any(class_name.startswith(prefix) for prefix in target)
65
66 else:
67
68 def passes_filter(class_name):
69 return class_name in target
70
71 def pythonization_impl(user_pythonizor):
72 """
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.
76
77 Args:
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
83 filtering).
84
85 Returns:
86 function: the user function, after being registered as a
87 pythonizor.
88 """
89 import ROOT
90
91 npars = _check_num_pars(user_pythonizor)
92
93 # Check whether any of the target classes has already been used.
94 # If so, the class proxy has to be immediately pythonized - even if we
95 # registered a pythonizor for it, the pythonizor would never be executed
96 _find_used_classes(ns, passes_filter, user_pythonizor, npars)
97
98 def cppyy_pythonizor(klass, name):
99 """
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.
104
105 Args:
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
109 to be pythonized.
110 """
111 from ._generic import pythonize_generic
112
114
115 # Add pretty printing (done on all classes)
116 pythonize_generic(klass, fqn)
117
118 if passes_filter(name):
119 _invoke(user_pythonizor, npars, klass, fqn)
120
121 # Register pythonizor in its namespace
122 ROOT._cppyy.py.add_pythonization(cppyy_pythonizor, ns)
123
124 # Return the original user function.
125 # We don't want to modify the user function, we just use the decorator
126 # to register the function as a pythonizor.
127 # This allows for correct chaining of multiple @pythonization decorators
128 # for a single function
129 return user_pythonizor
130
131 return pythonization_impl
132
133
134# \cond INTERNALS
135
136
137def _check_target(target):
138 """
139 Helper function to check the type of the `class name` argument specified by
140 the user in a @pythonization decorator.
141
142 Args:
143 target (string/iterable[string]): class name(s)/prefix(es).
144
145 Returns:
146 list[string]: class name(s)/prefix(es) in `target`, with no repetitions.
147 """
148
149 if isinstance(target, str):
150 _check_no_namespace(target)
151 target = [target]
152 else:
153 for name in target:
154 if isinstance(name, str):
156 else:
157 raise TypeError(
158 'Invalid type of "target" argument in @pythonization: must be string or iterable of strings'
159 )
160 # Remove possible duplicates
161 target = list(set(target))
162
163 return target
164
165
166def _check_no_namespace(target):
167 """
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).
170
171 Args:
172 target (string): class name/prefix.
173 """
174
175 if target.find("::") >= 0:
176 raise ValueError(
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)
181 )
182
183
184def _check_num_pars(f):
185 """
186 Checks the number of parameters of the `f` function.
187
188 Args:
189 f (function): user pythonizor function.
190
191 Returns:
192 int: number of positional parameters of `f`.
193 """
194 npars = len(inspect.getfullargspec(f).args)
195 if npars == 0 or npars > 2:
196 raise TypeError(
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)
200 )
201
202 return npars
203
204
205def _invoke(user_pythonizor, npars, klass, fqn):
206 """
207 Invokes the given user pythonizor function with the right arguments.
208
209 Args:
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.
214 """
215
216 try:
217 if npars == 1:
218 user_pythonizor(klass)
219 else:
220 user_pythonizor(klass, fqn)
221 except Exception:
222 print("Error pythonizing class {}:".format(fqn))
224 # Propagate the error so that the class lookup that triggered this
225 # pythonization fails too and the application stops
226 raise RuntimeError
227
228
229def _find_used_classes(ns, passes_filter, user_pythonizor, npars):
230 """
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.
236
237 Args:
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.
243 """
244
245 ns_obj = _find_namespace(ns)
246 if ns_obj is None:
247 # Namespace has not been used yet, no need to inspect more
248 return
249
250 def pythonize_if_match(name, klass):
251 # Check if name matches, excluding the namespace
252 if passes_filter(name.split("::")[-1]):
253 # Pythonize right away!
254 _invoke(user_pythonizor, npars, klass, klass.__cpp_name__)
255
256 def get_class_name(instantiation):
257 # Get the right class name for the input instantiation
258
259 # Template instantiation such as cppyy.gbl.MyClass["SomeType"]
260 if isinstance(instantiation, str):
261 return instantiation
262
263 # Template instantiation such as cppyy.gbl.MyClass[cppyy.gbl.SomeType]
264 # use the more specialized attribute first, then a more generic one
265 if hasattr(instantiation, "__cpp_name__"):
267
268 if hasattr(instantiation, "__name__"):
270
271 raise RuntimeError(
272 f"The template instantiation '{instantiation}' cannot be properly pythonized. Please report this as a bug."
273 )
274
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__"):
278 # It's a class proxy. Exclude template instantiations, because they
279 # will be traversed in the next step.
280 pythonize_if_match(var_name, var_value)
281
282 if str(var_value).startswith("<cppyy.Template"):
283 # If this is a template, pythonize the instances. Note that in
284 # older cppyy, template instantiations are cached by
285 # fully-qualified name directly in the namespace, so they are
286 # covered by the code branch above.
287 instantiations = getattr(var_value, "_instantiations", {})
288 for args, instance in instantiations.items():
289 # Make sure we don't do any redundant pythonization, e.g. if we
290 # use a version of cppyy that caches both in the namespace and
291 # in the _instantiations attribute.
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)
295
296
297def _find_namespace(ns):
298 """
299 Finds and returns the proxy object of the `ns` namespace, if it has already
300 been accessed.
301
302 Args:
303 ns (string): a namespace.
304
305 Returns:
306 namespace proxy object, if the namespace has already been accessed,
307 otherwise None.
308 """
309 import ROOT
310
311 if ns == "":
312 return ROOT._cppyy.gbl
313
314 ns_obj = ROOT._cppyy.gbl
315 # Get all namespaces in a list
316 every_ns = ns.split("::")
317 for ns in every_ns:
318 ns_vars = vars(ns_obj)
319 if ns not in ns_vars:
320 return None
321 ns_obj = getattr(ns_obj, ns)
322
323 return ns_obj
324
325
327 """
328 Registers the ROOT pythonizations with cppyy for lazy injection.
329 """
330
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)
335
336
338 import msvcrt
339 import time
340
341 from ROOT import gSystem
342
343 while not gSystem.ProcessEvents():
344 if msvcrt.kbhit():
345 k = msvcrt.getch()
346 if k[0] == 32:
347 break
348 else:
349 time.sleep(0.01)
350
351
353 import select
354 import sys
355 import termios
356 import time
357 import tty
358
359 from ROOT import gSystem
360
361 old_settings = termios.tcgetattr(sys.stdin)
362
364
365 try:
366
367 while not gSystem.ProcessEvents():
368 c = ''
369 if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
370 c = sys.stdin.read(1)
371 if (c == '\x20'):
372 break
373 time.sleep(0.01)
374
375 finally:
377
378
380 import os
381 import sys
382
383 from ROOT import gROOT
384
385 # no special handling in batch mode
386 if gROOT.IsBatch():
387 return
388
389 # no special handling in case of notebooks
390 if 'IPython' in sys.modules and sys.modules['IPython'].version_info[0] >= 5:
391 return
392
393 print("Press <space> key to continue")
394
395 if os.name == 'nt':
397 else:
399
400
401# \endcond
402
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:16