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."):
278 # It's a class proxy
279 pythonize_if_match(var_name, var_value)
280
281 if str(var_value).startswith("<cppyy.Template"):
282 # If this is a template, pythonize the instances. Note that in
283 # older cppyy, template instantiations are cached by
284 # fully-qualified name directly in the namespace, so they are
285 # covered by the code branch above.
286 instantiations = getattr(var_value, "_instantiations", {})
287 for args, instance in instantiations.items():
288 # Make sure we don't do any redundant pythonization, e.g. if we
289 # use a version of cppyy that caches both in the namespace and
290 # in the _instantiations attribute.
291 if instance not in ns_vars:
292 instance_name = var_name + "<" + ",".join(map(get_class_name, args)) + ">"
293 pythonize_if_match(instance_name, instance)
294
295
296def _find_namespace(ns):
297 """
298 Finds and returns the proxy object of the `ns` namespace, if it has already
299 been accessed.
300
301 Args:
302 ns (string): a namespace.
303
304 Returns:
305 namespace proxy object, if the namespace has already been accessed,
306 otherwise None.
307 """
308 import ROOT
309
310 if ns == "":
311 return ROOT._cppyy.gbl
312
313 ns_obj = ROOT._cppyy.gbl
314 # Get all namespaces in a list
315 every_ns = ns.split("::")
316 for ns in every_ns:
317 ns_vars = vars(ns_obj)
318 if ns not in ns_vars:
319 return None
320 ns_obj = getattr(ns_obj, ns)
321
322 return ns_obj
323
324
326 """
327 Registers the ROOT pythonizations with cppyy for lazy injection.
328 """
329
330 exclude = ["_rdf_utils", "_rdf_pyz", "_rdf_conversion_maps"]
331 for _, module_name, _ in pkgutil.walk_packages(__path__):
332 if module_name not in exclude:
333 importlib.import_module(__name__ + "." + module_name)
334
335
337 import msvcrt
338 import time
339
340 from ROOT import gSystem
341
342 while not gSystem.ProcessEvents():
343 if msvcrt.kbhit():
344 k = msvcrt.getch()
345 if k[0] == 32:
346 break
347 else:
348 time.sleep(0.01)
349
350
352 import select
353 import sys
354 import termios
355 import time
356 import tty
357
358 from ROOT import gSystem
359
360 old_settings = termios.tcgetattr(sys.stdin)
361
363
364 try:
365
366 while not gSystem.ProcessEvents():
367 c = ''
368 if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
369 c = sys.stdin.read(1)
370 if (c == '\x20'):
371 break
372 time.sleep(0.01)
373
374 finally:
376
377
379 import os
380 import sys
381
382 from ROOT import gROOT
383
384 # no special handling in batch mode
385 if gROOT.IsBatch():
386 return
387
388 # no special handling in case of notebooks
389 if 'IPython' in sys.modules and sys.modules['IPython'].version_info[0] >= 5:
390 return
391
392 print("Press <space> key to continue")
393
394 if os.name == 'nt':
396 else:
398
399
400# \endcond
401
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