-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
CircuitPython version
Adafruit CircuitPython 8.2.9 on 2023-12-06; Adafruit PyPortal with samd51j20
Code/REPL
class ChildSingleton(TopSingleton):
"""Inherit from singleton (another singleton)"""
# do not explicitly override (shadow) class variable from parent class
def __new__(cls, *args, **kwargs):
if TopSingleton.instance is None:
if sys.implementation.name == 'cpython':
super().__new__(TopSingleton, *args, **kwargs)
else:
super().__new__(*args, **kwargs)
return TopSingleton.instance
Behavior
Trying to use the cpython version of code in circuitpython fails with
TypeError: function takes 1 positional arguments but 2 were given
Description
The use of (arguments for) super().new() is different when inheriting from object, or inheriting from a custom class that inherits from object.
When defining new(cls, *args, **kwargs) for a class that inherits from object,
x = super().__new__(cls, *args, **kwargs)
or
x = super().__new__(cls)
is needed. Leaving out the cls argument
x = super().__new__(*args, **kwargs)
or
x = super().__new__()
gives a runtime error of
TypeError: function takes 1 positional arguments but 0 were given
However, for new(cls, *args, **kwargs) in a (circuitpython) class that inherits from a custom class, this works:
x = super().__new__(*args, **kwargs)
or
x = super().__new__()
while this:
x = super().__new__(cls, *args, **kwargs)
or
x = super().__new__(cls)
fails with:
TypeError: function takes 1 positional arguments but 2 were given
For cpython (tested with 3.7 and 3.11),
x = super().__new__(cls, *args, **kwargs)
or
x = super().__new__(cls)
is correct for both cases. circuitpython "super().new()" acts like cpython does when cls is provided. However, that excludes the possibility of specifying something else.
For the case I was working on, I was trying to NOT create a shadow copy of a parent class, class attribute. That did not actually work (I am mocking the top singleton, without mock), but the testing involved showed up this difference.
I do not currently have a place to test this with micropython. I did look through the cpython to micropython differences documentation, but did not see anything related.
Research and symptoms seem to indicate that object.new is treated as static method, but TopSingleton.new is treated as a class method. I tried adding "@staticmethod", but that did not change the behaviour.
Additional information
Here is the output from my test code (included below)
% python bug_test_shadow_attributes.py
bug_main: start for namespace(_multiarch='x86_64-linux-gnu', cache_tag='cpython-37', hexversion=50794992, name='cpython', version=sys.version_info(major=3, minor=7, micro=17, releaselevel='final', serial=0))
TopSingleton.__new__: instance <class 'NoneType'>
TopSingleton.__new__: instance <class '__main__.TopSingleton'>
ChildSingleton.instance_check: TopSingleton.instance is TopSingleton
ChildSingleton.instance_check: shadowed instance is NoneType
ChildSingleton.instance_check: Child instance is NoneType
bug_main: child singleton created
TopSingleton.__new__: instance <class '__main__.TopSingleton'>
bug_main: top TopSingleton source is top
>>> from bug_test_shadow_attributes import bug_main; bug_main()
bug_main: start for (name='circuitpython', version=(8, 2, 9), mpy=517)
ChildSingleton.__new__: instance <class 'NoneType'>
ChildSingleton.__new__: instance <class 'ChildSingleton'>
ChildSingleton.instance_check: TopSingleton.instance is None
ChildSingleton.instance_check: cls.instance is ChildSingleton
ChildSingleton.instance_check: ChildSingleton.instance is ChildSingleton
bug_main: child singleton created
TopSingleton.__new__: instance <class 'NoneType'>
TopSingleton.__new__: instance <class 'TopSingleton'>
bug_main: top TopSingleton source is top
My test code:
# bug_test_shadow_attributes.py
"""
Check when attributes get shadowed with singleton class inheritance
"""
# pylint:disable=line-too-long
import sys
class TopSingleton:
"""
The base class defining attributes that might get shadowed
"""
instance = None
def __new__(cls, *args, **kwargs):
print(f'{cls.__name__}.__new__: instance {type(cls.instance)}')
if cls.instance is None:
cls.instance = super().__new__(cls, *args, **kwargs)
# cls.instance = super().__new__(cls)
# cls.instance = super().__new__(*args, **kwargs)
# cls.instance = super().__new__()
print(f'{cls.__name__}.__new__: instance {type(cls.instance)}')
return cls.instance
@property
def source(self):
return 'top'
class ChildSingleton(TopSingleton):
"""Inherit from singleton (another singleton)"""
# do not explicitly override (shadow) class variable from parent class
def __new__(cls, *args, **kwargs):
# cls.check_instance_shadow_state()
if TopSingleton.instance is None:
if sys.implementation.name == 'cpython':
# TopSingleton.instance = super().__new__(TopSingleton, *args, **kwargs)
# TopSingleton.instance = super().__new__(cls, *args, **kwargs) # correct for use case
super().__new__(TopSingleton, *args, **kwargs)
else:
# TopSingleton.instance = super().__new__(cls, *args, **kwargs)
# TopSingleton.instance = super().__new__(cls)
# TopSingleton.instance = super().__new__(*args, **kwargs) # correct for use case
# TopSingleton.instance = super().__new__()
super().__new__(*args, **kwargs)
cls.check_instance_shadow_state()
return TopSingleton.instance
@classmethod
def check_instance_shadow_state(cls):
"""report shadowed attribute state"""
cur_value = None
if TopSingleton.instance is None:
print(f'{cls.__name__}.instance_check: TopSingleton.instance is None')
print(f'{cls.__name__}.instance_check: cls.instance is {type(cls.instance).__name__}')
print(f'{cls.__name__}.instance_check: ChildSingleton.instance is {type(ChildSingleton.instance).__name__}')
else:
cur_value = TopSingleton.instance
print(f'{cls.__name__}.instance_check: TopSingleton.instance is {type(TopSingleton.instance).__name__}')
TopSingleton.instance = None
print(f'{cls.__name__}.instance_check: shadowed instance is {type(cls.instance).__name__}')
print(f'{cls.__name__}.instance_check: Child instance is {type(ChildSingleton.instance).__name__}')
TopSingleton.instance = cur_value
@property
def source(self):
return 'child'
def bug_main():
"""run the test"""
print(f'bug_main: start for {sys.implementation}')
ChildSingleton()
print('bug_main: child singleton created')
instance = TopSingleton()
print(f'bug_main: top {type(instance).__name__} source is {instance.source}')
if __name__ == '__main__':
bug_main()