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################################################################################
10
11import importlib
12import inspect
13import pkgutil
14import re
15import sys
16import traceback
17
18import cppyy
19# \cond INTERNALS
20gbl_namespace = cppyy.gbl
21# \endcond
22
23from ._generic import pythonize_generic
24
25
26def pythonization(class_name, ns='::', is_prefix=False):
27 r'''
28 \ingroup Pythonizations
29 Decorator that allows to pythonize C++ classes. To pythonize means to add
30 some extra behaviour to a C++ class that is used from Python via PyROOT,
31 so that such a class can be used in an easier / more "pythonic" way.
32 When a pythonization is registered with this decorator, the injection of
33 the new behaviour in the C++ class is done immediately, if the class has
34 already been used from the application, or lazily, i.e. only when the class
35 is first accessed from the application.
36
37 Args:
38 class_name (string/iterable[string]): specifies either a single string or
39 multiple strings, where each string can be either (i) the name of a
40 C++ class to be pythonized, or (ii) a prefix to match all classes
41 whose name starts with that prefix.
42 ns (string): namespace of the classes to be pythonized. Default is the
43 global namespace (`::`).
44 is_prefix (boolean): if True, `class_name` contains one or multiple
45 prefixes, each prefix potentially matching multiple classes.
46 Default is False.
47 These are examples of prefixes and namespace and what they match:
48 - class_name="", ns="::" : all classes in the global namespace.
49 - class_name="C", ns="::" : all classes in the global namespace
50 whose name starts with "C"
51 - class_name="", ns="NS1::NS2" : all classes in namespace "NS1::NS2"
52 - class_name="C", ns="NS1::NS2" : all classes in namespace
53 "NS1::NS2" whose name starts with "C"
54
55 Returns:
56 function: function that receives the user-defined function and
57 decorates it.
58
59 '''
60
61 # Type check and parsing of target argument.
62 # Retrieve the scope(s) of the class(es)/prefix(es) to register the
63 # pythonizor in the right scope(s)
64 target = _check_target(class_name)
65
66 # Remove trailing '::' from namespace
67 if ns.endswith('::'):
68 ns = ns[:-2]
69
70 # Create a filter lambda for the target class(es)/prefix(es)
71 if is_prefix:
72 def passes_filter(class_name):
73 return any(class_name.startswith(prefix)
74 for prefix in target)
75 else:
76 def passes_filter(class_name):
77 return class_name in target
78
79 def pythonization_impl(user_pythonizor):
80 '''
81 The real decorator. Accepts a user-provided function and decorates it.
82 An inner function - a wrapper of the user function - is registered in
83 cppyy as a pythonizor.
84
85 Args:
86 user_pythonizor (function): user-provided function to be decorated.
87 It implements some pythonization. It can accept two parameters:
88 the class to be pythonized, i.e. the Python proxy of the class
89 in which new behaviour can be injected, and optionally the name
90 of that class (can be used e.g. to do some more complex
91 filtering).
92
93 Returns:
94 function: the user function, after being registered as a
95 pythonizor.
96 '''
97
98 npars = _check_num_pars(user_pythonizor)
99
100 # Check whether any of the target classes has already been used.
101 # If so, the class proxy has to be immediately pythonized - even if we
102 # registered a pythonizor for it, the pythonizor would never be executed
103 _find_used_classes(ns, passes_filter, user_pythonizor, npars)
104
105 def cppyy_pythonizor(klass, name):
106 '''
107 Wrapper function with the parameters that cppyy requires for a
108 pythonizor function (class proxy and class name). It invokes the
109 user function only if the current class - a candidate for being
110 pythonized - matches the `target` argument of the decorator.
111
112 Args:
113 klass (class type): cppyy proxy of the class that is the
114 current candidate to be pythonized.
115 name (string): name of the class that is the current candidate
116 to be pythonized.
117 '''
118
119 fqn = klass.__cpp_name__
120
121 # Add pretty printing (done on all classes)
122 pythonize_generic(klass, fqn)
123
124 if passes_filter(name):
125 _invoke(user_pythonizor, npars, klass, fqn)
126
127 # Register pythonizor in its namespace
128 cppyy.py.add_pythonization(cppyy_pythonizor, ns)
129
130 # Return the original user function.
131 # We don't want to modify the user function, we just use the decorator
132 # to register the function as a pythonizor.
133 # This allows for correct chaining of multiple @pythonization decorators
134 # for a single function
135 return user_pythonizor
136
137 return pythonization_impl
138
139# \cond INTERNALS
140
141def _check_target(target):
142 '''
143 Helper function to check the type of the `class name` argument specified by
144 the user in a @pythonization decorator.
145
146 Args:
147 target (string/iterable[string]): class name(s)/prefix(es).
148
149 Returns:
150 list[string]: class name(s)/prefix(es) in `target`, with no repetitions.
151 '''
152
153 if isinstance(target, str):
154 _check_no_namespace(target)
155 target = [ target ]
156 else:
157 for name in target:
158 if isinstance(name, str):
159 _check_no_namespace(name)
160 else:
161 raise TypeError('Invalid type of "target" argument in '
162 '@pythonization: must be string or iterable of '
163 'strings')
164 # Remove possible duplicates
165 target = list(set(target))
166
167 return target
168
169def _check_no_namespace(target):
170 '''
171 Checks that a given target of a pythonizor does not specify a namespace
172 (only the class name / prefix of a class name should be present).
173
174 Args:
175 target (string): class name/prefix.
176 '''
177
178 if target.find('::') >= 0:
179 raise ValueError('Invalid value of "class_name" argument in '
180 '@pythonization: namespace definition found ("{}"). '
181 'Please use the "ns" parameter to specify the '
182 'namespace'.format(target))
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('Pythonizor function {} has a wrong number of '
197 'parameters ({}). Allowed parameters are the class to '
198 'be pythonized and (optionally) its name.'
199 .format(f.__name__, npars))
200
201 return npars
202
203def _invoke(user_pythonizor, npars, klass, fqn):
204 '''
205 Invokes the given user pythonizor function with the right arguments.
206
207 Args:
208 user_pythonizor (function): user pythonizor function.
209 npars (int): number of parameters of the user pythonizor function.
210 klass (class type): cppyy proxy of the class to be pythonized.
211 fqn (string): fully-qualified name of the class to be pythonized.
212 '''
213
214 try:
215 if npars == 1:
216 user_pythonizor(klass)
217 else:
218 user_pythonizor(klass, fqn)
219 except Exception as e:
220 print('Error pythonizing class {}:'.format(fqn))
221 traceback.print_exc()
222 # Propagate the error so that the class lookup that triggered this
223 # pythonization fails too and the application stops
224 raise RuntimeError
225
226def _find_used_classes(ns, passes_filter, user_pythonizor, npars):
227 '''
228 Finds already instantiated classes in namespace `ns` that pass the filter
229 of `passes_filter`. Every matching class is pythonized with the
230 `user_pythonizor` function.
231 This makes sure a pythonizor is also applied to classes that have already
232 been used at the time the pythonizor is registered.
233
234 Args:
235 ns (string): namespace of the class names of prefixes in `targets`.
236 passes_filter (function): function that determines if a given class
237 is the target of `user_pythonizor`.
238 user_pythonizor (function): user pythonizor function.
239 npars (int): number of parameters of the user pythonizor function.
240 '''
241
242 ns_obj = _find_namespace(ns)
243 if ns_obj is None:
244 # Namespace has not been used yet, no need to inspect more
245 return
246
247 def pythonize_if_match(name, klass):
248 # Check if name matches, excluding the namespace
249 if passes_filter(name.split("::")[-1]):
250 # Pythonize right away!
251 _invoke(user_pythonizor, npars, klass, klass.__cpp_name__)
252
253 ns_vars = vars(ns_obj)
254 for var_name, var_value in ns_vars.items():
255 if str(var_value).startswith('<class cppyy.gbl.'):
256 # It's a class proxy
257 pythonize_if_match(var_name, var_value)
258
259 if str(var_value).startswith('<cppyy.Template'):
260 # If this is a template, pythonize the instances. Note that in
261 # older cppyy, template instantiations are cached by
262 # fully-qualified name directly in the namespace, so they are
263 # covered by the code branch above.
264 instantiations = getattr(var_value, "_instantiations", {})
265 for args, instance in instantiations.items():
266 # Make sure we don't do any redundant pythonization, e.g. if we
267 # use a version of cppyy that caches both in the namespace and
268 # in the _instantiations attribute.
269 if not instance in ns_vars:
270 instance_name = var_name + "<" + ",".join(args) + ">"
271 pythonize_if_match(instance_name, instance)
272
273def _find_namespace(ns):
274 '''
275 Finds and returns the proxy object of the `ns` namespace, if it has already
276 been accessed.
277
278 Args:
279 ns (string): a namespace.
280
281 Returns:
282 namespace proxy object, if the namespace has already been accessed,
283 otherwise None.
284 '''
285
286 if ns == '':
287 return gbl_namespace
288
289 ns_obj = gbl_namespace
290 # Get all namespaces in a list
291 every_ns = ns.split('::')
292 for ns in every_ns:
293 ns_vars = vars(ns_obj)
294 if not ns in ns_vars:
295 return None
296 ns_obj = getattr(ns_obj, ns)
297
298 return ns_obj
299
300def _register_pythonizations():
301 '''
302 Registers the ROOT pythonizations with cppyy for lazy injection.
303 '''
304
305 exclude = [ '_rdf_utils', '_rdf_pyz', '_rdf_conversion_maps' ]
306 for _, module_name, _ in pkgutil.walk_packages(__path__):
307 if module_name not in exclude:
308 importlib.import_module(__name__ + '.' + module_name)
309# \endcond
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:26