7
7
# For the licensing terms see $ROOTSYS/LICENSE. #
8
8
# For the list of contributors see $ROOTSYS/README/CREDITS. #
9
9
################################################################################
10
-
11
10
import importlib
12
11
import inspect
13
12
import pkgutil
14
- import re
15
- import sys
16
13
import traceback
17
14
18
15
import cppyy
16
+
17
+ from ._generic import pythonize_generic
18
+
19
19
# \cond INTERNALS
20
20
gbl_namespace = cppyy .gbl
21
21
# \endcond
22
22
23
- from ._generic import pythonize_generic
24
23
25
-
26
- def pythonization (class_name , ns = '::' , is_prefix = False ):
27
- r'''
24
+ def pythonization (class_name , ns = "::" , is_prefix = False ):
25
+ r"""
28
26
\ingroup Pythonizations
29
27
Decorator that allows to pythonize C++ classes. To pythonize means to add
30
28
some extra behaviour to a C++ class that is used from Python via PyROOT,
@@ -56,28 +54,29 @@ def pythonization(class_name, ns='::', is_prefix=False):
56
54
function: function that receives the user-defined function and
57
55
decorates it.
58
56
59
- '''
57
+ """
60
58
61
59
# Type check and parsing of target argument.
62
60
# Retrieve the scope(s) of the class(es)/prefix(es) to register the
63
61
# pythonizor in the right scope(s)
64
62
target = _check_target (class_name )
65
63
66
64
# Remove trailing '::' from namespace
67
- if ns .endswith ('::' ):
65
+ if ns .endswith ("::" ):
68
66
ns = ns [:- 2 ]
69
67
70
68
# Create a filter lambda for the target class(es)/prefix(es)
71
69
if is_prefix :
70
+
72
71
def passes_filter (class_name ):
73
- return any (class_name .startswith (prefix )
74
- for prefix in target )
72
+ return any (class_name .startswith (prefix ) for prefix in target )
75
73
else :
74
+
76
75
def passes_filter (class_name ):
77
76
return class_name in target
78
77
79
78
def pythonization_impl (user_pythonizor ):
80
- '''
79
+ """
81
80
The real decorator. Accepts a user-provided function and decorates it.
82
81
An inner function - a wrapper of the user function - is registered in
83
82
cppyy as a pythonizor.
@@ -93,7 +92,7 @@ def pythonization_impl(user_pythonizor):
93
92
Returns:
94
93
function: the user function, after being registered as a
95
94
pythonizor.
96
- '''
95
+ """
97
96
98
97
npars = _check_num_pars (user_pythonizor )
99
98
@@ -103,7 +102,7 @@ def pythonization_impl(user_pythonizor):
103
102
_find_used_classes (ns , passes_filter , user_pythonizor , npars )
104
103
105
104
def cppyy_pythonizor (klass , name ):
106
- '''
105
+ """
107
106
Wrapper function with the parameters that cppyy requires for a
108
107
pythonizor function (class proxy and class name). It invokes the
109
108
user function only if the current class - a candidate for being
@@ -114,7 +113,7 @@ def cppyy_pythonizor(klass, name):
114
113
current candidate to be pythonized.
115
114
name (string): name of the class that is the current candidate
116
115
to be pythonized.
117
- '''
116
+ """
118
117
119
118
fqn = klass .__cpp_name__
120
119
@@ -136,10 +135,12 @@ def cppyy_pythonizor(klass, name):
136
135
137
136
return pythonization_impl
138
137
138
+
139
139
# \cond INTERNALS
140
140
141
+
141
142
def _check_target (target ):
142
- '''
143
+ """
143
144
Helper function to check the type of the `class name` argument specified by
144
145
the user in a @pythonization decorator.
145
146
@@ -148,83 +149,90 @@ def _check_target(target):
148
149
149
150
Returns:
150
151
list[string]: class name(s)/prefix(es) in `target`, with no repetitions.
151
- '''
152
+ """
152
153
153
154
if isinstance (target , str ):
154
155
_check_no_namespace (target )
155
- target = [ target ]
156
+ target = [target ]
156
157
else :
157
158
for name in target :
158
159
if isinstance (name , str ):
159
160
_check_no_namespace (name )
160
161
else :
161
- raise TypeError ('Invalid type of "target" argument in '
162
- ' @pythonization: must be string or iterable of '
163
- 'strings' )
162
+ raise TypeError (
163
+ 'Invalid type of "target" argument in @pythonization: must be string or iterable of strings '
164
+ )
164
165
# Remove possible duplicates
165
166
target = list (set (target ))
166
167
167
168
return target
168
169
170
+
169
171
def _check_no_namespace (target ):
170
- '''
172
+ """
171
173
Checks that a given target of a pythonizor does not specify a namespace
172
174
(only the class name / prefix of a class name should be present).
173
175
174
176
Args:
175
177
target (string): class name/prefix.
176
- '''
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
+ )
177
187
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
188
184
189
def _check_num_pars (f ):
185
- '''
190
+ """
186
191
Checks the number of parameters of the `f` function.
187
192
188
193
Args:
189
194
f (function): user pythonizor function.
190
195
191
196
Returns:
192
197
int: number of positional parameters of `f`.
193
- '''
198
+ """
194
199
npars = len (inspect .getfullargspec (f ).args )
195
200
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 ))
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
+ )
200
206
201
207
return npars
202
208
209
+
203
210
def _invoke (user_pythonizor , npars , klass , fqn ):
204
- '''
211
+ """
205
212
Invokes the given user pythonizor function with the right arguments.
206
213
207
214
Args:
208
215
user_pythonizor (function): user pythonizor function.
209
216
npars (int): number of parameters of the user pythonizor function.
210
217
klass (class type): cppyy proxy of the class to be pythonized.
211
218
fqn (string): fully-qualified name of the class to be pythonized.
212
- '''
219
+ """
213
220
214
221
try :
215
222
if npars == 1 :
216
223
user_pythonizor (klass )
217
224
else :
218
225
user_pythonizor (klass , fqn )
219
- except Exception as e :
220
- print (' Error pythonizing class {}:' .format (fqn ))
226
+ except Exception :
227
+ print (" Error pythonizing class {}:" .format (fqn ))
221
228
traceback .print_exc ()
222
229
# Propagate the error so that the class lookup that triggered this
223
230
# pythonization fails too and the application stops
224
231
raise RuntimeError
225
232
233
+
226
234
def _find_used_classes (ns , passes_filter , user_pythonizor , npars ):
227
- '''
235
+ """
228
236
Finds already instantiated classes in namespace `ns` that pass the filter
229
237
of `passes_filter`. Every matching class is pythonized with the
230
238
`user_pythonizor` function.
@@ -237,7 +245,7 @@ def _find_used_classes(ns, passes_filter, user_pythonizor, npars):
237
245
is the target of `user_pythonizor`.
238
246
user_pythonizor (function): user pythonizor function.
239
247
npars (int): number of parameters of the user pythonizor function.
240
- '''
248
+ """
241
249
242
250
ns_obj = _find_namespace (ns )
243
251
if ns_obj is None :
@@ -266,15 +274,16 @@ def get_class_name(instantiation):
266
274
return instantiation .__name__
267
275
268
276
raise RuntimeError (
269
- f"The template instantiation '{ instantiation } ' cannot be properly pythonized. Please report this as a bug." )
277
+ f"The template instantiation '{ instantiation } ' cannot be properly pythonized. Please report this as a bug."
278
+ )
270
279
271
280
ns_vars = vars (ns_obj )
272
281
for var_name , var_value in ns_vars .items ():
273
- if str (var_value ).startswith (' <class cppyy.gbl.' ):
282
+ if str (var_value ).startswith (" <class cppyy.gbl." ):
274
283
# It's a class proxy
275
284
pythonize_if_match (var_name , var_value )
276
285
277
- if str (var_value ).startswith (' <cppyy.Template' ):
286
+ if str (var_value ).startswith (" <cppyy.Template" ):
278
287
# If this is a template, pythonize the instances. Note that in
279
288
# older cppyy, template instantiations are cached by
280
289
# fully-qualified name directly in the namespace, so they are
@@ -284,12 +293,13 @@ def get_class_name(instantiation):
284
293
# Make sure we don't do any redundant pythonization, e.g. if we
285
294
# use a version of cppyy that caches both in the namespace and
286
295
# in the _instantiations attribute.
287
- if not instance in ns_vars :
296
+ if instance not in ns_vars :
288
297
instance_name = var_name + "<" + "," .join (map (get_class_name , args )) + ">"
289
298
pythonize_if_match (instance_name , instance )
290
299
300
+
291
301
def _find_namespace (ns ):
292
- '''
302
+ """
293
303
Finds and returns the proxy object of the `ns` namespace, if it has already
294
304
been accessed.
295
305
@@ -299,29 +309,32 @@ def _find_namespace(ns):
299
309
Returns:
300
310
namespace proxy object, if the namespace has already been accessed,
301
311
otherwise None.
302
- '''
312
+ """
303
313
304
- if ns == '' :
314
+ if ns == "" :
305
315
return gbl_namespace
306
316
307
317
ns_obj = gbl_namespace
308
318
# Get all namespaces in a list
309
- every_ns = ns .split ('::' )
319
+ every_ns = ns .split ("::" )
310
320
for ns in every_ns :
311
321
ns_vars = vars (ns_obj )
312
- if not ns in ns_vars :
322
+ if ns not in ns_vars :
313
323
return None
314
324
ns_obj = getattr (ns_obj , ns )
315
325
316
326
return ns_obj
317
327
328
+
318
329
def _register_pythonizations ():
319
- '''
330
+ """
320
331
Registers the ROOT pythonizations with cppyy for lazy injection.
321
- '''
332
+ """
322
333
323
- exclude = [ ' _rdf_utils' , ' _rdf_pyz' , ' _rdf_conversion_maps' ]
324
- for _ , module_name , _ in pkgutil .walk_packages (__path__ ):
334
+ exclude = [" _rdf_utils" , " _rdf_pyz" , " _rdf_conversion_maps" ]
335
+ for _ , module_name , _ in pkgutil .walk_packages (__path__ ):
325
336
if module_name not in exclude :
326
- importlib .import_module (__name__ + '.' + module_name )
337
+ importlib .import_module (__name__ + "." + module_name )
338
+
339
+
327
340
# \endcond
0 commit comments