adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.08k stars 1.2k forks source link

CPython incompatibility for subclassed tuple #6677

Open FoamyGuy opened 2 years ago

FoamyGuy commented 2 years ago

CircuitPython version

Adafruit CircuitPython 7.3.1 on 2022-06-22; Adafruit PyPortal with samd51j20

Code/REPL

class SpecialTuple(tuple):
    def __init__(self, item1, item2):
        super().__init__((item1, item2))

if __name__ == '__main__':
    instance = SpecialTuple(1, 2)
    print(instance)

Behavior

On CPython this example raises this exception:

Traceback (most recent call last):
  File "/home/timc/repos/circuitpython/Adafruit_CircuitPython_turtle/tuple_api_difference.py", line 10, in <module>
    instance = SpecialTuple(1, 2)
TypeError: tuple expected at most 1 argument, got 2

But on CIrcuitPython this code does execute successfully without raising any exception:

code.py output:
(1, 2)

Description

No response

Additional information

This difference was first noticed in the Turtle library on this issue: https://github.com/adafruit/Adafruit_CircuitPython_turtle/issues/30

That library contains a Vec2D class that subclasses tuple. The person who made this issue is attempting to use the turtle library with CPython (using Blinka_DisplayIO I think, but it's not stated specifically.)

I found Stack Overflow Answers that indicate that maybe __new__ needs to be used instead of __init__ for tuples. But I am unsure if this applies to CircuitPython the same as CPython. If so it seems that maybe the difference in behavior is allowing the way the turtle library is currently coded to work under CircuitPython but not CPython even though it does currently implement __init__ not __new__

And either way it may be the case that we would want CircuitPython core to have the same behavior as CPython in this situation so I've created this issue to ask / track it if we do want the same behavior as CPython.

jepler commented 2 years ago

Based on a cursory test, the same behavior exists in micropython. Please check micropython's list of differences from standard python; if it's not listed there consider opening an issue with them. They may know if there's a "good reason" it's that way and if it's not documented they may wish to document it.

FoamyGuy commented 2 years ago

Thank you. I'll look into the micropython differences list.

Poking at this a bit more I did find that this variation using __new__ instead of __init__ does run successfully under CPython:

class SpecialTuple(tuple):
    def __new__(cls, item1, item2):
        self = super().__new__(cls, (item1, item2))
        return self

if __name__ == '__main__':
    instance = SpecialTuple(1, 2)
    print(instance)

CPYthon output:

(1, 2)

But this one raises an error on CircuitPython:

code.py output:
Traceback (most recent call last):
  File "code.py", line 13, in <module>
  File "code.py", line 9, in __new__
TypeError: function takes 1 positional arguments but 2 were given

Pretty much the same error message, but opposite way, this version has the error on CircuitPython instead of CPython.

tekktrik commented 2 years ago

I think this is related to other classes defined in C - I think I ran into this issue when working with super().__init__() when subclassing displayio.Group. I can do testing next week to confirm the specifics.