# About type-conversion special methods

**URL:** https://discuss.python.org/t/about-type-conversion-special-methods/94212
**Category:** Core Development
**Created:** [June 2, 2025, 2:57pm UTC](https://discuss.python.org/t/about-type-conversion-special-methods/94212 "2025-06-02T14:57:24Z")
**Posts on this page:** 10
**Page:** 1

<div class="post-metadata">

### Author: ![storchaka](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/storchaka/32/217_2.png) [@storchaka](https://discuss.python.org/u/storchaka)
#### Post date: [June 2, 2025, 2:57pm UTC](https://discuss.python.org/t/about-type-conversion-special-methods/94212/1 "2025-06-02T14:57:24Z")

</div>

There are special methods used in implicit and explicit type conversions. ` __str__ `, ` __bytes__ `, ` __int__ `, ` __index__ `, ` __float__ `, ` __complex__ `.

- ` __index__ `, ` __float__ ` and ` __complex__ ` – for implicit convertion to `int`, `float` and `complex`. Most of the C API which convert these Python types to corresponding C types accept also objects that implement these special methods and use them if necessary.
- ` __str__ `, ` __bytes__ ` and ` __int__ ` – only for explicit conversion by `str()`, `bytes()` and `int()`.

Now, there are some questions: when to the special method and how to handle its result.

Look, for example, at `PyFloat_AsDouble()`. If the argument is a Python `float`, it gets the C `double` value directly from the `ob_val` field of the `PyFloatObject` structure. This works also for `float` subclasses, becaus ethey have the same structure. If the argument if not a Python `float`, then the ` __float__ ` method is used, and the C `double` value is obtained from its result. It can also fall back to using ` __index__ `, but this is not relevant here.

If the result of ` __float__ ` is not a Python `float`, then this is error. ` __float__ ` is not called recursively, this could be unsafe.

Note two things:

1. ` __float__ ` is not called for `float` subclasses. It is not necessary, we already have `PyFloatObject`. Calling ` __float__ ` would be a waste of time. It is even more true for long integers, strings, etc.
2. Returning a `float` subclass from ` __float__ ` is not an error, fro the same reason. We only need to read `PyFloatObject.ob_val`, it is the same for subclasses as for exact `float`.

However, there was one problem. If `str()` just returns the result of ` __str__ `, it may return a subclass of `str`, and this is not whet we expect. On Python level we expect that `str()` always returns an exact `str`, this is the way to convert the object to exact `str`. There was a simiular issue with `operator.index()` and ` __index__ `.

The decision taken to solve this problem was two-step. If the result of the special methos is not an exact base type, but its subtype:

1. Convert it to the base type ignoring its type and only using its content.
2. Emit a deprecation warning.

BTW, ` __str__ ` and ` __bytes__ ` were [overlooked](https://github.com/python/cpython/issues/104231), so we still have a problem of returning an `str` subclass from `str()`, `bytes()` and `repr()`.

I think the deprecation was wrong. Silent conversion in step 1 was enough (it can be omitted if we need not a Python object, but just its content, as in `PyFloat_AsDouble()`). Deprecation just adds an inconvenience for users. For example, let we have an int-like object which implents ` __index__ ` that returns a calculated value. If the value happens to be not exact `int`, but `int` subclass (for example `bool` or `IntEnum`), the user is forced to convert it to `int` before returning from ` __index__ `. In worst case they will use `int()` for conversion, which can silently truncate the non-integer result, hiding the real bug. In any case, explicit conversion to `int` takes time and creates unnecesary object. Even if the exact `int` is needed (for example if we use `operator.index()`), it is faster to create in C than call `operator.index()` or `int()` in Python. And in most cases it should only be converted to C integer, so no need to create an intermediate Python object.

So my suggestion – just remove the deprecation. The code that already worked will continue to work. No new errors will occur. Most users will not notice anything. But the future user code could be cleaner and faster.

---

<div class="post-metadata">

### Author: ![skirpichev](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/skirpichev/32/10996_2.png) [@skirpichev](https://discuss.python.org/u/skirpichev)
#### Post date: [June 3, 2025, 7:14am UTC](https://discuss.python.org/t/about-type-conversion-special-methods/94212/2 "2025-06-03T07:14:49Z")

</div>

> [@storchaka](#):
>
> ` __float__ ` is not called for `float` subclasses.

This is not true in case of [`PyNumber_Float()`](https://github.com/python/cpython/blob/6e80f11eb5eba360334b4ace105eb7d73394baf7/Objects/abstract.c#L1591-L1649) (equivalent of `float(obj)`). Here we call the ` __float__ ` dunder for float subclasses too. Also, we have here a specific check for “broken” float subclasses:

```c
    /* A float subclass with nb_float == NULL */
    if (PyFloat_Check(o)) {
        return PyFloat_FromDouble(PyFloat_AS_DOUBLE(o));
    }

```

see also [gh-112636: remove check for float subclasses without nb\_float by skirpichev · Pull Request #112637 · python/cpython · GitHub](https://github.com/python/cpython/pull/112637)

> [@storchaka](#):
>
> So my suggestion – just remove the deprecation.

I would rather support such decision.

In principle, we can imagine that ` __float__ ` got overridden in the subclass and it returns something different from the `ob_val`, say:

```pycon
>>> class FloatSpam(float):
... def __float__ (self):
... return 42.
...         
>>> x = FloatSpam(1.25)
>>> float(x)
42.0
>>> float.from_number(x)
1.25

```

Though, it’s just a broken float subclass, isn’t?

Edit:  
I didn’t traced down all initial arguments for deprecation of subclasses. [Here is the discussion thread](https://mail.python.org/archives/list/python-dev@python.org/thread/DO7M52UNO4W522JBG2X52SNK3D7VM2IR/#OO6W4X3OOLPRN3Q3Z26CDQJBOBLN7GPR) that seems to be related. As far as I can understand, the main reason to reject subclasses is API. Docs says:

> object.` __float__ `(_self_ ) […] Called to implement the built-in functions [`complex()`](https://docs.python.org/3.14/library/functions.html#complex), [`int()`](https://docs.python.org/3.14/library/functions.html#int) and [`float()`](https://docs.python.org/3.14/library/functions.html#float). Should return a value of the appropriate type.

I.e. float() essentially just call the ` __float__ ()` dunder, it’s all (modulo type-checking of it’s output). In proposed version we have to document how the float constructor process the return value of the dunder. See [this Guido’s comment](https://mail.python.org/archives/list/python-dev@python.org/message/AF4WXBCH5BSSR5FWBSI2WPIM434DZPR3/).

CC @mdickinson

---

<div class="post-metadata">

### Author: ![efimov-mikhail](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/efimov-mikhail/32/23145_2.png) [@efimov-mikhail](https://discuss.python.org/u/efimov-mikhail)
#### Post date: [June 14, 2025, 7:18am UTC](https://discuss.python.org/t/about-type-conversion-special-methods/94212/3 "2025-06-14T07:18:38Z")

</div>

> [@skirpichev](#):
>
> Though, it’s just a broken float subclass, isn’t?

Maybe it will be useful to emit warnings/errors for those classes? One can use `_float_` dunder or float subclassing but not both.

---

<div class="post-metadata">

### Author: ![skirpichev](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/skirpichev/32/10996_2.png) [@skirpichev](https://discuss.python.org/u/skirpichev)
#### Post date: [June 14, 2025, 7:56am UTC](https://discuss.python.org/t/about-type-conversion-special-methods/94212/4 "2025-06-14T07:56:16Z")

</div>

Sorry, can you provide an example? Where you would like to emit errors/warnings?

---

<div class="post-metadata">

### Author: ![efimov-mikhail](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/efimov-mikhail/32/23145_2.png) [@efimov-mikhail](https://discuss.python.org/u/efimov-mikhail)
#### Post date: [June 14, 2025, 8:24am UTC](https://discuss.python.org/t/about-type-conversion-special-methods/94212/5 "2025-06-14T08:24:34Z")

</div>

I’m not sure about existing mechanisms on related topic. But basically defining this dunder method for the subclasses of `float` can emit warning/error.

---

<div class="post-metadata">

### Author: ![skirpichev](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/skirpichev/32/10996_2.png) [@skirpichev](https://discuss.python.org/u/skirpichev)
#### Post date: [June 14, 2025, 8:53am UTC](https://discuss.python.org/t/about-type-conversion-special-methods/94212/6 "2025-06-14T08:53:37Z")

</div>

This topic is not about deprecating using ` __float__ ()` for float’s subclasses, but rather about return value of such dunder methods.

---

<div class="post-metadata">

### Author: ![efimov-mikhail](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/efimov-mikhail/32/23145_2.png) [@efimov-mikhail](https://discuss.python.org/u/efimov-mikhail)
#### Post date: [June 15, 2025, 5:36am UTC](https://discuss.python.org/t/about-type-conversion-special-methods/94212/7 "2025-06-15T05:36:35Z")

</div>

I understand this. Yes, it’s a bit off-topic, but not very much, IMO.

I’m not sure about removing deprecation warning.  
In general, arguments look fine.  
But it feels a little awkward.

---

<div class="post-metadata">

### Author: ![vstinner](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/vstinner/32/15130_2.png) [@vstinner](https://discuss.python.org/u/vstinner)
#### Post date: [October 14, 2025, 12:45pm UTC](https://discuss.python.org/t/about-type-conversion-special-methods/94212/8 "2025-10-14T12:45:34Z")

</div>

Honestly, I don’t understand well this well. It’s quite subtle.

I’m more comfortable with examples (see below).

IMO ` __float__ ()` should only return exact `float` instance, so the `DeprecationWarning` makes sense and we should now convert it to an error. Otherwise, the result is unclear and there are bad surprises, especially if the `float` subclass has a ` __float__ ` method. If ` __float__ ()` returns a `float` subclass, ` __float__ ()` is not called again which gives surprising results: see example 1 and example 2.

Is there any project in the wild which is impacted by the `DeprecationWarning`?

numpy is not affected by this issue:

```python
$ python
>>> import numpy

>>> f64=numpy.float64(1.5)
>>> isinstance(f64, float)
True
>>> type(f64). __bases__
(<class 'numpy.floating'>, <class 'float'>)
>>> type(f64. __float__ ())
<class 'float'>

>>> f16=numpy.float16(1.5)
>>> isinstance(f16, float)
False
>>> type(f16. __float__ ())
<class 'float'>

```

* * *

Example 1:

```py
class MyFloat(float):
    def __float__ (self):
        return 42.

class NonFloat:
    def __init__ (self):
        self.value = MyFloat(1.5)

    def __float__ (self):
        return self.value

# (A) __float__ () returns float
print(float(MyFloat(1.5)))
print()

# (B) __float__ () returns MyFloat(float): emit DeprecationWarning
print(float(NonFloat()))

```

Output 1:

```python
42.0

x.py:16: DeprecationWarning: NonFloat. __float__ returned non-float (type MyFloat). The ability to return an instance of a strict subclass of float is deprecated, and may be removed in a future version of Python.
  print(float(NonFloat()))
1.5

```

It’s quite surprising that I get `42.0` in case (A) but `1.5` in case (B). It looks inconsistent to me.

Example 2:

```py
class MyFloat(float):
    def __float__ (self):
        return MyFloat(42)

# __float__ () returns MyFloat(float): emit a DeprecationWarning
print(float(MyFloat(1.5)))

```

Output 2:

```python
x.py:6: DeprecationWarning: MyFloat. __float__ returned non-float (type MyFloat). The ability to return an instance of a strict subclass of float is deprecated, and may be removed in a future version of Python.
  print(float(MyFloat(1.5)))
42.0

```

It works as expected but it’s a little bit strange that the `float()` calls ` __float__ ()` but then doesn’t call ` __float__ ()` again on the float subclass. I’m not sure if I expect `1.5` or `42` result in this case 🙂 The deprecation warning sounds like “hey, something is wrong”: I agree 🙂

* * *

If the float subclass has no ` __float__ ()` method, obviously, things are simpler.

Example 3:

```py
class MyFloat(float):
    pass

class NonFloat:
    def __init__ (self):
        self.value = MyFloat(1.5)

    def __float__ (self):
        return self.value

# (A) __float__ () returns float
print(float(MyFloat(1.5)))
print()

# (B) __float__ () returns MyFloat(float): emit DeprecationWarning
print(float(NonFloat()))

```

Output 3:

```python
1.5

x.py:16: DeprecationWarning: NonFloat. __float__ returned non-float (type MyFloat). The ability to return an instance of a strict subclass of float is deprecated, and may be removed in a future version of Python.
  print(float(NonFloat()))
1.5

```

@storchaka says that the DeprecationWarning doesn’t bring any value in this case. The conversion should be done silently.

Example 4:

```py
class MyFloat(float):
    pass

# __float__ () returns float
print(float(MyFloat(1.5)))

```

Output 4:

```python
1.5

```

---

<div class="post-metadata">

### Author: ![skirpichev](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/skirpichev/32/10996_2.png) [@skirpichev](https://discuss.python.org/u/skirpichev)
#### Post date: [October 15, 2025, 2:23am UTC](https://discuss.python.org/t/about-type-conversion-special-methods/94212/9 "2025-10-15T02:23:47Z")

</div>

> [@vstinner](#):
>
> IMO ` __float__ ()` should only return exact `float` instance

This proposal doesn’t change this.

> [@vstinner](#):
>
> so the `DeprecationWarning` makes sense and we should now convert it to an error

But _instead_ of issuing a warning or raising an exception — Serhiy proposed to use “known value” of the base class type, available for a subclass. I.e. just use `ob_val` for float or float subclasses. So, in your examples we get same values, but all of the float type. And no warnings.

In short, if you return a subclass instance in the ` __float__ ()` dunder — this value should be processed by consumers of this API (e.g. `float()` constructor) to get first the base class value.

My objections rather (1) explicit is better than implicit, (2) I’m not sure this will be easy to document, especially for alternative Python implementations.

> [@vstinner](#):
>
> Otherwise, the result is unclear and there are bad surprises, especially if the `float` subclass has a ` __float__ ` method.

This is not something, proposed to change.

On another hand, assuming this proposal, it might be then not clear why e.g. in your 1st example `PyNumber_Float()` shouldn’t ignore provided ` __float__ ()` method and just use `ob_val`…

---

<div class="post-metadata">

### Author: ![skirpichev](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/skirpichev/32/10996_2.png) [@skirpichev](https://discuss.python.org/u/skirpichev)
#### Post date: [April 3, 2026, 12:01am UTC](https://discuss.python.org/t/about-type-conversion-special-methods/94212/10 "2026-04-03T00:01:47Z")

</div>

Here Mark Dickinson objections against removal of the deprecation: [PyNumber\_Index() is not int-subclass friendly (or operator.index() docs lie) · Issue #61776 · python/cpython · GitHub](https://github.com/python/cpython/issues/61776#issuecomment-1093612163) Though, now we have float/complex.from\_number() methods and above argument sounds less strong.

BTW, the deprecation for ` __int__ ` dunder was added in the v3.3 (and in v3.10 — for ` __float__ `/` __complex__ `). In any case I see no good reason to complicate code (for CPython and it’s alternative implementations) with deprecations.

What we need to decide on this for v3.15?
