Skip to content

Commit 0e76592

Browse files
BCSharpslozier
andauthored
Update notes on upgrading from ipy2 to cover PEP 237 (#1423)
* Update notes on upgrading from ipy2 to cover PEP 237 * Update Documentation/upgrading-from-ipy2.md Co-authored-by: slozier <slozier@users.noreply.github.com> * Update after review Co-authored-by: slozier <slozier@users.noreply.github.com>
1 parent c3643c1 commit 0e76592

File tree

1 file changed

+176
-0
lines changed

1 file changed

+176
-0
lines changed

Documentation/upgrading-from-ipy2.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,179 @@ In an effort to improve compatibility, `sys.platform` no longer returns `cli`. I
1414
if sys.implementation.name == "ironpython":
1515
print("IronPython!")
1616
```
17+
18+
## `int` Type
19+
20+
One of the major backward incompatible changes in Python 3 is [PEP 237 – Unifying Long Integers and Integers][PEP 0237]: Essentially, `long` renamed to `int`. That is, there is only one built-in integral type, named `int`; but it behaves mostly like the old `long` type. From the pure Python perspective this means that `int` should be used wherever previously `long` was used. More consideration has to be applied in interop cases with .NET.
21+
22+
The Python `int` type in IronPython 3 is implemented as `System.Numerics.BigInteger` (and not as `System.Int32` as it was in IronPython 2). It can contain in theory an arbitrarily large integer (only limited by the 2 GByte memory boundary).
23+
24+
```pycon
25+
>>> import clr
26+
>>> clr.AddReference("System.Numerics")
27+
>>> import System
28+
>>> int is System.Numerics.BigInteger
29+
True
30+
>>> int is System.Int32
31+
False
32+
>>> clr.GetClrType(int).Name
33+
'BigInteger'
34+
```
35+
36+
This means that in interop cases, when the `int` type is used (think generics), it will mean `BigInteger` and not `Int32` (which was the case in IronPython 2). To retain IronPython 2 semantics, replace `int` with `System.Int32`.
37+
38+
Example:
39+
40+
```python
41+
# IronPython 2
42+
System.Collections.Generic.List[int]
43+
```
44+
45+
```python
46+
# IronPython 3
47+
System.Collections.Generic.List[System.Int32]
48+
```
49+
50+
Overview of `int` type equivalency:
51+
52+
| IronPython 2 | IronPython 3 | .NET |
53+
| ------------ | ------------ | ---------------------------- |
54+
| `long` | `int` | `System.Numerics.BigInteger` |
55+
| `int` | N/A | `System.Int32` |
56+
57+
### Instances of `int`
58+
59+
As for instances of `int`, mostly for performance reasons, IronPython may use instances of `System.Int32` to hold smaller integers, while `BigInteger` instances are used for large integers. This is done transparently from the Python side, but again the distinction may become relevant for interop cases. Examples:
60+
61+
```python
62+
i = 1 # instance of Int32
63+
j = 1 << 31 # instance of BigInteger
64+
k = j - 1 # still BigInteger, as one of the arguments makes the result type BigInteger
65+
```
66+
67+
This means that the type of `Int32` objects is always reported as `int` (which is the same as `BigInteger`). If it is important to check what is the actual type of a given integer object, test if the object is an instance of `System.Int32`. (An alternative way is a test for the presence of `MaxValue` or `MinValue`. For those properties to be visible, `System` has to be imported first.)
68+
69+
```pycon
70+
>>> import System
71+
>>> type(i)
72+
<class 'int'>
73+
>>> isinstance(i, System.Int32)
74+
True
75+
>>> type(j)
76+
<class 'int'>
77+
>>> isinstance(j, System.Int32)
78+
False
79+
>>> hex(i.MaxValue)
80+
'0x7fffffff'
81+
```
82+
83+
The creation of either `Int32` or `BigInteger` instances happens automatically by the `int` constructor. If for interop purposes it is important to create a `BigInteger` (despite the value fitting in 32 bits), use method `ToBigInteger`. It converts `Int32` values to `BigInteger` and leaves `BigInteger` values unaffected.
84+
85+
```pycon
86+
>>> bi = i.ToBigInteger()
87+
>>> isinstance(j, System.Int32)
88+
False
89+
```
90+
91+
In the opposite direction, if it is essential to create `Int32` objects, either use constructors for `int` or `Int32`. In the current implementation, the former converts an integer to `Int32` if the value fits in 32 bits, otherwise it leaves it as `BigInteger`. The latter throws an exception is the conversion is not possible. Although the behavior of the constructor `int` may or may not change in the future, it is always guaranteed to convert the value to the "canonical form" adopted for that version of IronPython.
92+
93+
```pycon
94+
>>> # k is a BigInteger that fits in 32 bits
95+
>>> isinstance(j, System.Int32)
96+
False
97+
>>> hex(k)
98+
'0x7fffffff'
99+
>>> ki = int(k) # converts k to Int32
100+
>>> isinstance(ki, System.Int32)
101+
True
102+
>>> ki = System.Int32(k) # also converts k to Int32
103+
>>> isinstance(ki, System.Int32)
104+
True
105+
>>> # j is a BigInteger that does not fit in 32 bits
106+
>>> isinstance(j, System.Int32)
107+
False
108+
>>> hex(j)
109+
'0x80000000'
110+
>>> j = int(j) # no type change, j stays BigInteger
111+
>>> isinstance(j, System.Int32)
112+
False
113+
>>> j = System.Int32(j) # conversion fails
114+
Traceback (most recent call last):
115+
File "<stdin>", line 1, in <module>
116+
OverflowError: Arithmetic operation resulted in an overflow.
117+
```
118+
119+
Such explicit conversions are in most cases unnecessary since the runtime recognizes `int`/`Int32` equivalence of instances and performs necessary conversions automatically.
120+
121+
```pycon
122+
>>> import System
123+
>>> int_list = System.Collections.Generic.List[int]()
124+
>>> int_list.Add(1) # Int32 instance converted to BigInteger
125+
>>> int32_list = System.Collections.Generic.List[System.Int32]()
126+
>>> int32_list.Add((1).ToBigInteger()) # BigInteger instance converted to Int32
127+
>>> int_list[0] == int32_list[0]
128+
True
129+
```
130+
131+
### Pickling and unpickling of `int`
132+
133+
When an `int` object is serialized using `pickle.dump(x, myfile)` and subsequently unpickled with `x = pickle.load(myfile)` (or `pickle.loads(pickle.dumps(x))`, this has the same effect as reconstructing the object using the `int` constructor, i.e. `x = int(x)`. In other words, if the `x` instance was `BigInteger` but the value fits in `Int32`, it will be reconstructed as `Int32`.
134+
135+
### BigIntegerV2 API
136+
137+
In IronPython 2, `long` type carries an obsolete `BigIntegerV2` API, accessible after importing `System`. In IronPython 3 this API is not available directly on `int` instances (regardless whether the instance is `Int32` or `BigInteger`), but is still accessible in some form through `Microsoft.Scripting.Utils.MathUtils` in `Microsoft.Dynamic.dll`.
138+
139+
```pycon
140+
>>> # IronPython 2
141+
>>> i = 1 # instance of Int32 (int)
142+
>>> j = 1 << 64 # instance of BigInteger (long)
143+
>>> import System
144+
>>> j.GetWords()
145+
Array[UInt32]((0, 0, 1))
146+
>>> i.GetWords()
147+
Traceback (most recent call last):
148+
File "<stdin>", line 1, in <module>
149+
AttributeError: 'int' object has no attribute 'GetWords'
150+
>>> long.GetWords(i)
151+
Array[UInt32]((1))
152+
```
153+
154+
```pycon
155+
>>> # IronPython 3
156+
>>> i = 1 # instance of Int32 (int)
157+
>>> j = 1 << 64 # instance of BigInteger (int)
158+
>>> import clr
159+
>>> clr.AddReference("Microsoft.Dynamic")
160+
>>> import Microsoft.Scripting.Utils.MathUtils
161+
>>> clr.ImportExtensions(Microsoft.Scripting.Utils.MathUtils)
162+
>>> j.GetWords()
163+
Array[UInt32]((0, 0, 1))
164+
>>> i.GetWords()
165+
Traceback (most recent call last):
166+
File "<stdin>", line 1, in <module>
167+
AttributeError: 'int' object has no attribute 'GetWords'
168+
>>> Microsoft.Scripting.Utils.MathUtils.GetWords(i)
169+
Array[UInt32]((1))
170+
```
171+
172+
Another set of Python-hidden methods on `long` in IronPython 2 that are not available on `int` in IronPython 3 are conversion methods with names like `ToXxx`. The recommended way to perform type conversions like those is to use type constructors. The exception is the conversion to `BigInteger` itself, for the reasons explained above.
173+
174+
```python
175+
# IronPython 2
176+
j = long(1)
177+
i64 = j.ToInt64()
178+
```
179+
180+
```python
181+
# IronPython 3
182+
import System
183+
j = (1).ToBigInteger()
184+
i64 = System.Int64(j)
185+
```
186+
187+
### `range`
188+
189+
IronPython's `range` is a generator that produces a sequence of `int` values. The values are instances of `Int32` or `BigInteger`, depending on the actual integer value they represent. When `range` is used in a LINQ context, it exposes interface `IEnumerable<Int32>` and all values generated are of type `Int32`. This limits the possible value to the range `Int32.MinValue` to `Int32.MaxValue`.
190+
191+
192+
[PEP 0237]: https://python.org/dev/peps/pep-0237

0 commit comments

Comments
 (0)