This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: C implementation of Decimal.from_float() bypasses __new__ and __init__
Type: behavior Stage: resolved
Components: Extension Modules Versions: Python 3.6, Python 3.5
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: facundobatista, mark.dickinson, python-dev, rhettinger, serhiy.storchaka, skrah
Priority: normal Keywords:

Created on 2016-05-12 07:15 by serhiy.storchaka, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Messages (6)
msg265368 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2016-05-12 07:15
Python implementation of Decimal.from_float() calls __new__ and __init__ methods of subclass.

>>> from _pydecimal import Decimal
>>> class D(Decimal):
...     def __new__(cls, *args, **kwargs):
...         print('__new__')
...         return Decimal.__new__(cls, *args, **kwargs)
...     def __init__(self, *args, **kwargs):
...         print('__init__')
... 
>>> print(type(D.from_float(42)))
__new__
__init__
<class '__main__.D'>
>>> print(type(D.from_float(42.0)))
__new__
__init__
<class '__main__.D'>

But C implementation doesn't.

>>> from decimal import Decimal
>>> class D(Decimal):
...     def __new__(cls, *args, **kwargs):
...         print('__new__')
...         return Decimal.__new__(cls, *args, **kwargs)
...     def __init__(self, *args, **kwargs):
...         print('__init__')
... 
>>> print(type(D.from_float(42)))
<class '__main__.D'>
>>> print(type(D.from_float(42.0)))
<class '__main__.D'>

This means that resulting instance of Decimal subclass can be in not valid state.

Example is Decimal enums (see also issue23640).

>>> from decimal import Decimal
>>> from enum import Enum
>>> class D(Decimal, Enum):
...     A = Decimal('3.25')
... 
>>> D(Decimal(3.25))
<D.A: Decimal('3.25')>
>>> D(3.25)
<D.A: Decimal('3.25')>
>>> D.from_float(3.25)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/serhiy/py/cpython/Lib/enum.py", line 486, in __repr__
    self.__class__.__name__, self._name_, self._value_)
AttributeError: 'D' object has no attribute '_name_'

A solution is to reproduce Python implementation in C code:

        result = ... # create exact Decimal
        if cls is not Decimal:
            result = cls(result)
        return result
msg268889 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2016-06-20 10:11
New changeset dd3f48f1df86 by Stefan Krah in branch '3.5':
Issue #27006: from_float(): call the subclass' __new__() and __init__().
https://hg.python.org/cpython/rev/dd3f48f1df86
msg268890 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2016-06-20 10:32
Thank you for the detailed summary!
msg268892 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2016-06-20 11:27
PyDec_CheckExact(type) always return 0. Should be PyDec_CheckExact(result).

And what about other calls of PyDecType_FromFloatExact()? Can they produce broken instance of Decimal subtype?
msg268896 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2016-06-20 12:13
New changeset 51a7a97c3ed4 by Stefan Krah in branch '3.5':
Issue #27006: Do not use PyDec_CheckExact() on a type.
https://hg.python.org/cpython/rev/51a7a97c3ed4
msg268898 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2016-06-20 12:22
> PyDec_CheckExact(type) always return 0. Should be PyDec_CheckExact(result).

'result' is always an exact decimal, because your enum example won't work otherwise.


> And what about other calls of PyDecType_FromFloatExact()? Can they produce broken instance of Decimal subtype?

The PyDecType* functions handle calling the correct allocator, they don't handle calling __new__().  Why should they? The regular non-class-method enum examples worked.
History
Date User Action Args
2022-04-11 14:58:30adminsetgithub: 71193
2016-06-20 12:58:32skrahsetstatus: open -> closed
2016-06-20 12:22:33skrahsetmessages: + msg268898
2016-06-20 12:13:58python-devsetmessages: + msg268896
2016-06-20 11:27:13serhiy.storchakasetstatus: closed -> open

messages: + msg268892
2016-06-20 10:32:21skrahsetstatus: open -> closed
resolution: fixed
messages: + msg268890

stage: resolved
2016-06-20 10:11:28python-devsetnosy: + python-dev
messages: + msg268889
2016-05-12 07:15:44serhiy.storchakacreate