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: hex() and oct() use __index__ instead of __int__
Type: behavior Stage: resolved
Components: Versions: Python 3.4
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: gvanrossum Nosy List: ethan.furman, gvanrossum, mark.dickinson, pitrou, rhettinger, serhiy.storchaka, skrah
Priority: normal Keywords:

Created on 2013-12-15 16:27 by ethan.furman, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Messages (10)
msg206238 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2013-12-15 16:27
In Py3k __hex__ and __oct__ were removed, and hex() and oct() switched to use __index__.

hex() and oct() should be using __int__ instead.

Having read through PEP 357 [1] I see that __index__ is /primarily/ concerned with allowing arbitrary objects to be used in slices, but will also allow Python to convert an object to an int "whenever it needs one".

The problem is that my dbf [2] module has a couple custom types (Logical and Quantum) that allow for ternary logic (False/True/Unknown).  As much as possible Logical is intended to be a drop in replacement for the bool type, but of course there are some differences:

  - internally 'unknown' is represented as None

  - attempts to get an int, float, complex, etc., numeric value
    on Unknown raises an Exception

  - attempts to get the numeric string value via __hex__ and
    __oct__ on Unknown raises an Exception

  - when Unknown is used as an index (via __index__), 2 is returned

The problem, of course, is that in Python 3 calling hex() and oct() on Unknown now succeeds when it should be raising an exception, and would be if __int__ were used instead of __index__.

In summary, if Python just switches to using __index__, there would be little point in having separate __int__ and __index__ methods.


[1] http://www.python.org/dev/peps/pep-0357
[2] https://pypi.python.org/pypi/dbf
msg206241 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2013-12-15 17:05
__index__() is used because float has __int__ but not __index__.

>>> (42.0).__int__()
42
>>> (42.0).__index__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'float' object has no attribute '__index__'
msg206245 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-12-15 17:28
Indeed, the definition and use of __index__ has derived since PEP 357. Nowadays, __index__ means "can be converted to an int without loss".

In any case, I find the behaviour of your "logical type" a bit dubious. If it's like bool but ternary, it *should* convert to int (or perhaps be an int subclass like bool).
msg206255 - (view) Author: Mark Dickinson (mark.dickinson) * (Python committer) Date: 2013-12-15 20:45
> hex() and oct() should be using __int__ instead.

Strong -1 from me.  I wouldn't want `hex(45.3)` to work, and `hex(45.0)` working isn't much better.
msg206256 - (view) Author: Stefan Krah (skrah) * (Python committer) Date: 2013-12-15 20:57
-1 from me as well.  I would not want to audit a large program for
accidentally converted floats.
msg206261 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2013-12-15 21:25
Guido, I believe that __index__ was your initiative.  Do you care to opine on this one?
msg206263 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2013-12-15 22:50
I agree with Mark and Stafan.  Hex/oct/bin are only defined for integers.  __int__ is ambiguous -- it has the same problem as (int) in C in that it applies to floats and then loses the fraction.

I think the problem with Ethan's ternary logic is that it tries to act as an index and yet doesn't want to be an integer -- that doesn't make a lot of logical sense.  (You should use a dict to map from true/false/unknown, not a list of size three.)
msg206275 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2013-12-16 02:04
For the record, the true/false values of my Logical type do convert to int, just not the unknown value.

I agree using __int__ is dubious because of float (and Decimal, etc.), which means really the only clean way to solve the issue (definitely for me, and for any one else in a similar situation) is to bring back __hex__, __oct__, and, presumably, __bin__.

To make things even worse, there is a discrepancy between hex() and %x, oct() and %o:

  --> hex(Unknown)
  '0x2'

  --> oct(Unknown)
  '0o2'

  --> '%x' % Unknown
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: %x format: a number is required, not Logical

  --> '%o' % Unknown
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: %o format: a number is required, not Logical

Which is bizarre when one considers:

  --> '%o' % Truth
  '1'

So if '%o' fails, why doesn't oct()?

Do we reopen this issue, or start a new one?
msg206277 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2013-12-16 04:38
I still think the problem is with your class design.  You shouldn't want a hex representation for a value that's not an integer.

For the difference between %x and hex() please open another issue (you might want to track down the cause in the source first so you can add a patch or at least a suggested fix to the issue).
msg206291 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2013-12-16 10:33
Guido van Rossum opined:
------------------------
> I still think the problem is with your class design.
> You shouldn't want a hex representation for a value
> that's not an integer.

Well, in fairness I only supported it because bool does, and I was trying to have Logical match bool as closely as possible.  Which is also why, in Py2, attempting such operations on an Unknown value raises exceptions.

As an example:

  # bool
  --> True + True
  2

  # Logical
  --> Truth + Truth
  2

  --> Truth + Unknown
  Logical('?')

I can do that because I was able to override __add__ and __raddd__; however, if I _do not_ overide index:

  # bool
  --> ['no', 'yes'][True]
  'yes'

  # Logical
  --> ['no', 'yes', 'maybe'][Truth]
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
  TypeError: list indices must be integers, not Logical

So either I break compatibility with bools in a rather fundamental way, or I live with the eyesore of being able to use %x and %o on Unknown values.

Incidentally, bool is still not subclassable, and using int as a base class for Logical blew up over half my tests.

Perhaps not quite so incidentally, if __index__ is added to an Enum (not IntEnum) subclass, it will have the same wierdness.


Guido van Rossum stated:
------------------------
> For the difference between %x and hex() please open another
> issue (you might want to track down the cause in the source
> first so you can add a patch or at least a suggested fix to
> the issue).

Issue19995 is created, current participants nosied.  I'll track it down as soon as I can (may be a few days).
History
Date User Action Args
2022-04-11 14:57:55adminsetgithub: 64187
2013-12-16 10:33:23ethan.furmansetmessages: + msg206291
2013-12-16 04:38:51gvanrossumsetmessages: + msg206277
2013-12-16 02:04:56ethan.furmansetmessages: + msg206275
2013-12-15 22:50:45gvanrossumsetstatus: open -> closed
resolution: rejected
messages: + msg206263

stage: resolved
2013-12-15 21:25:09rhettingersetassignee: gvanrossum

messages: + msg206261
nosy: + rhettinger, gvanrossum
2013-12-15 20:57:26skrahsetnosy: + skrah
messages: + msg206256
2013-12-15 20:45:05mark.dickinsonsetmessages: + msg206255
2013-12-15 17:28:32pitrousetnosy: + pitrou
messages: + msg206245
2013-12-15 17:05:27serhiy.storchakasetnosy: + mark.dickinson, serhiy.storchaka
messages: + msg206241
2013-12-15 16:27:52ethan.furmancreate