You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Python] Document auto-downcasting and restructure pythonizations.md
This commit documents the auto-downcasting for return types of C++
functions, and also improves the structure of the pythonization docs as
follows:
* The term "pythonizations" is very overloaded, so expand the
explanation in the beginning a bit
* Cleanly separate into explanation of pythonization in ROOT, and
later pythonization of use classes. Before, the section on user
pythonization was just labeled "Pythonization example", which was
confusing to me. Will this be an example of a pythonization in ROOT?
* Move the explanation of the user pythonizations to the end, as this
is an advanced feature.
\brief Python-specific functionalities offered by ROOT
4
4
5
-
This page lists the so-called "pythonizations", that is those functionalities offered by ROOT for classes and functions which are specific to Python usage of the package and provide a more pythonic experience.
5
+
This page describes ROOT's *pythonizations*: functionality that makes ROOT's C++ classes and functions behave more naturally when used from Python. Rather than simply reproducing C++ behavior, a pythonization augments how you interact with a C++ type. For instance, letting you use NumPy arrays in place of C++ arrays in function calls, or automatic downcasting of C++ instances to their actual types.
6
6
7
-
### Pythonization example
7
+
Some pythonizations are general and applied automatically to most C++ types, while others apply only to specific ROOT types. Below we explain some of the automatic pythonizations in detail, then show how to implement custom pythonizations for your own C++ classes.
8
+
9
+
10
+
### Pretty-printing pythonization
11
+
This example illustrates the pretty printing feature of PyROOT, which reveals
12
+
the content of the object if a string representation is requested, e.g., by
13
+
Python's print statement. The printing behaves similar to the ROOT prompt
14
+
powered by the C++ interpreter cling.
15
+
Create an object with PyROOT
16
+
17
+
~~~{.py}
18
+
obj = ROOT.std.vector("int")(3)
19
+
for i in range(obj.size()):
20
+
obj[i] = i
21
+
~~~
22
+
23
+
Print the object, which reveals the content. Note that `print` calls the special
24
+
method `__str__` of the object internally.
25
+
26
+
~~~{.py}
27
+
print(obj)
28
+
~~~
29
+
30
+
The output can be retrieved as string by any function that triggers the `__str__`
31
+
special method of the object, e.g., `str` or `format`.
32
+
33
+
~~~{.py}
34
+
print(str(obj))
35
+
print("{}".format(obj))
36
+
~~~
37
+
38
+
Note that the interactive Python prompt does not call `__str__`, it calls
39
+
`__repr__`, which implements a formal and unique string representation of
40
+
the object.
41
+
42
+
~~~{.py}
43
+
print(repr(obj))
44
+
obj
45
+
~~~
46
+
47
+
The print output behaves similar to the ROOT prompt, e.g., here for a ROOT histogram.
48
+
49
+
~~~{.py}
50
+
hist = ROOT.TH1F("name", "title", 10, 0, 1)
51
+
print(hist)
52
+
~~~
53
+
54
+
If cling cannot produce any nice representation for the class, we fall back to a
55
+
"<ClassNameataddress>" format, which is what `__repr__` returns
56
+
57
+
~~~{.py}
58
+
ROOT.gInterpreter.Declare('class MyClass {};')
59
+
m = ROOT.MyClass()
60
+
print(m)
61
+
print(str(m) == repr(m))
62
+
~~~
63
+
64
+
### Automatic downcasting pythonization
65
+
66
+
In C++, it is possible to use a base-class pointer to refer to an instance of a
67
+
derived type.
68
+
```cpp
69
+
classBase {
70
+
public:
71
+
virtual ~Base() = default;
72
+
};
73
+
class Derived : public Base {};
74
+
75
+
Base *foo() {
76
+
static Derived obj;
77
+
return &obj;
78
+
}
79
+
```
80
+
The same is also possible for smart pointers, like `std::unique_ptr` or
81
+
`std::shared_ptr`. For example:
82
+
```cpp
83
+
std::unique_ptr<Base> foo_unique() {
84
+
return std::unique_ptr<Base>{new Derived};
85
+
}
86
+
```
87
+
Using the `Derived` interface on the return value is not possible in C++ without
88
+
type casting (e.g. a `dynamic_cast`). Since explicit type casting is not natural
89
+
in Python, ROOT attempts to automatically downcast raw pointer or smart pointer
90
+
return values to their actual type. Demonstrating this with the types above:
91
+
```python
92
+
p1 =ROOT.foo()
93
+
p2 =ROOT.foo_unique()
94
+
95
+
# if you absolutely need a base class proxy, there is a way:
96
+
p3 =ROOT.bind_object(p1, "Base")
97
+
98
+
print(p1)
99
+
print(p2)
100
+
print(p3)
101
+
```
102
+
will give you something like:
103
+
```txt
104
+
<cppyy.gbl.Derived object>
105
+
<cppyy.gbl.Derived object held by std::unique_ptr<Base>>
106
+
<cppyy.gbl.Base object>
107
+
```
108
+
109
+
**Note 1**: keep in mind that the auto downcasting also affects overload
110
+
resolution. For example, consider these two overloads:
111
+
```cpp
112
+
voidconsume(Base *) {} // overload 1
113
+
void consume(Derived *) {} // overload 2
114
+
```
115
+
In C++, `consume(foo())` will hit overload 1. In Python,
116
+
`ROOT.consume(ROOT.foo())` resolves to the second overload because the pointee
117
+
was automatically downcast. If you really need to hit the first `Base` overload, you'll have to explicitly cast back to the base class type with `ROOT.bind_object`, as shown before.
118
+
119
+
**Note 2**: while the type of the pointee gets downcast, smart pointer types
120
+
remain unchanged. That's because `std::unique_ptr<Base>` and
121
+
`std::unique_ptr<Derived>` are distinct, unrelated types. Template
122
+
instantiations don't inherit from one another even when their type arguments do.
123
+
124
+
**Note 3**: automatic downcasting is not enabled
125
+
unconditionally. It is only available for polymorphic base
126
+
classes, which is why the `Base` class in the example has a virtual destructor.
127
+
128
+
129
+
### Custom pythonizations for C++ user classes by example
8
130
9
131
This example shows how to use the `@pythonization` decorator to add extra
10
132
behaviour to C++ user classes that are used from Python via PyROOT.
@@ -24,17 +146,17 @@ class MyClass {};
24
146
~~~
25
147
26
148
Next, we define a pythonizor function: the function that will be responsible
27
-
for injecting new behaviour in our C++ class `MyClass`.
149
+
for injecting new behaviour in our C++ class `MyClass`.
28
150
To convert a given Python function into a pythonizor, we need to decorate it
29
151
with the @pythonization decorator. Such decorator allows us to define which
30
152
which class we want to pythonize by providing its class name and its
31
153
namespace (if the latter is not specified, it defaults to the global
32
-
namespace, i.e. '::').
154
+
namespace, i.e. '::').
33
155
The decorated function - the pythonizor - must accept either one or two
34
156
parameters:
35
157
1. The class to be pythonized (proxy object where new behaviour can be
36
158
injected)
37
-
2. The fully-qualified name of that class (optional).
159
+
2. The fully-qualified name of that class (optional).
38
160
Let's see all this with a simple example. Suppose I would like to define how
39
161
`MyClass` objects are represented as a string in Python (i.e. what would be
40
162
shown when I print that object). For that purpose, I can define the following
@@ -113,7 +235,7 @@ for o in o1, o2:
113
235
In addition, @pythonization also accepts prefixes of classes in a certain
114
236
namespace in order to match multiple classes in that namespace. To signal that
115
237
what we provide to @pythonization is a prefix, we need to set the `is_prefix`
116
-
argument to `True` (default is `False`).
238
+
argument to `True` (default is `False`).
117
239
A common case where matching prefixes is useful is when we have a templated
118
240
class and we want to pythonize all possible instantiations of that template.
119
241
For example, we can pythonize the `std::vector` (templated) class like so:
@@ -199,7 +321,7 @@ first time in the application.
199
321
However, it can also happen that our target class/es have already been
200
322
accessed by the time we register a pythonization. In such a scenario, the
201
323
pythonizor is applied immediately (at registration time) to the target
202
-
class/es
324
+
class/es
203
325
Let's see an example of what was just explained. We will define a new class
204
326
and immediately create an object of that class. We can check how the object
205
327
still does not have a new attribute `pythonized` that we are going to inject
@@ -230,57 +352,3 @@ Now our object does have the `pythonized` attribute:
230
352
~~~{.py}
231
353
print(o.pythonized) # prints True
232
354
~~~
233
-
234
-
### Pythonization printing example
235
-
This example illustrates the pretty printing feature of PyROOT, which reveals
236
-
the content of the object if a string representation is requested, e.g., by
237
-
Python's print statement. The printing behaves similar to the ROOT prompt
238
-
powered by the C++ interpreter cling.
239
-
Create an object with PyROOT
240
-
241
-
~~~{.py}
242
-
obj = ROOT.std.vector("int")(3)
243
-
for i in range(obj.size()):
244
-
obj[i] = i
245
-
~~~
246
-
247
-
Print the object, which reveals the content. Note that `print` calls the special
248
-
method `__str__` of the object internally.
249
-
250
-
~~~{.py}
251
-
print(obj)
252
-
~~~
253
-
254
-
The output can be retrieved as string by any function that triggers the `__str__`
255
-
special method of the object, e.g., `str` or `format`.
256
-
257
-
~~~{.py}
258
-
print(str(obj))
259
-
print("{}".format(obj))
260
-
~~~
261
-
262
-
Note that the interactive Python prompt does not call `__str__`, it calls
263
-
`__repr__`, which implements a formal and unique string representation of
264
-
the object.
265
-
266
-
~~~{.py}
267
-
print(repr(obj))
268
-
obj
269
-
~~~
270
-
271
-
The print output behaves similar to the ROOT prompt, e.g., here for a ROOT histogram.
272
-
273
-
~~~{.py}
274
-
hist = ROOT.TH1F("name", "title", 10, 0, 1)
275
-
print(hist)
276
-
~~~
277
-
278
-
If cling cannot produce any nice representation for the class, we fall back to a
279
-
"<ClassNameataddress>" format, which is what `__repr__` returns
0 commit comments