How does the super type present itself and do lookups?

Barry Scott barry at barrys-emacs.org
Tue Mar 10 11:28:40 EDT 2020



> On 4 Mar 2020, at 17:12, Adam Preble <adam.preble at gmail.com> wrote:
> 
> Months ago, I asked a bunch of stuff about super() and managed to fake it well enough to move on to other things for awhile. The day of reckoning came this week and I was forced to implement it better for my personal Python project. I have a hack in place that makes it work well-enough but I found myself frustrated with how shift the super type is. It's both the self and the parent class, but not.
> 
> If you don't know, you can trap what super() returns some time and poke it with a stick. If you print it you'll be able to tell it's definitely unique:
> <super: <class 'Child'>, <Child object>>
> 
> If you try to invoke methods on it, it'll invoke the superclass' methods. That's what is supposed to happen and basically what already happens when you do super().invoke_this_thing() anyways.
> 
> Okay, so how is it doing the lookup for that? The child instance and the super types' __dict__ are the same. The contents pass an equality comparison and are the same if you print them.

Do you know about the Method Resolution Order (MRO) that is in __class__.__mro__)?

I was very confused by super() until I found out about the MRO. When you call super() it finds
where the code is in the MRO and then starts looking for a method in classes before the calling
class.

The following code shows this:

class A:
    def __init__(self):
        super().__init__()

    def fn(self):
        return 'A.fn'

class B(A):
    def __init__(self):
        super().__init__()

class C(B):
    def __init__(self):
        super().__init__()

    def fn(self):
        return 'C.fn and super().fn is %r with value %r' % (super().fn, super().fn())

class D(C):
    def __init__(self):
        super().__init__()


print(D.__mro__)
obj = D()
print( obj.fn() )

When it is run it prints:

(<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
C.fn and super().fn is <bound method A.fn of <__main__.D object at 0x10e1728d0>> with value 'A.fn'

When fn() is called on obj the version of fn() in class C is called.
That fn() calls the super().fn and the second print shows that its the class A fn that is found called.
super starts with the next class after the one it is called in. It is in C so find C in the MRO and starts with
B. B does not have an fn(). Then it looks in A and finds fn().

Is this what you are looking for?

Barry


> 
> They have the same __getattribute__ method wrapper. However, if you dir() them you definitely get different stuff. For one, the super type has its special variables __self__, __self_class__, and __thisclass__. It's missing __dict__ from the dir output. But wait, I just looked at that!
> 
> So I'm thinking that __getattr__ is involved, but it's not listed in anything. If I use getattr on the super, I'll get the parent methods. If I use __getattribute__, I get the child's methods. I get errors every way I've conceived of trying to pull out a __getattr__ dunder. No love.
> 
> I guess the fundamental question is: what different stuff happens when LOAD_ATTR is performed on a super object versus a regular object?
> 
> If you are curious about what I'm doing right now, I overrode __getattribute__ since that's primarily what I use for attribute lookups right now. It defer to the superclass' __getattribute__. If a method pops out, it replaces the self with the super's __self__ before kicking it out. I feel kind of dirty doing it:
> 
> https://github.com/rockobonaparte/cloaca/blob/312758b2abb80320fb3bf344ba540a034875bc4b/LanguageImplementation/DataTypes/PySuperType.cs#L36
> 
> If you want to see how I was experimenting with super, here's the code and output:
> 
> class Parent:
>    def __init__(self):
>        self.a = 1
> 
>    def stuff(self):
>        print("Parent stuff!")
> 
> 
> class Child(Parent):
>    def __init__(self):
>        super().__init__()
>        self.b = 2
>        self.super_instance = super()
> 
>    def stuff(self):
>        print("Child stuff!")
> 
>    def only_in_child(self):
>        print("Only in child!")
> 
> 
> c = Child()
> c.super_instance.__init__()
> c.stuff()
> c.super_instance.stuff()
> print(c)
> print(c.super_instance)
> print(c.__init__)
> print(c.super_instance.__init__)
> print(c.stuff)
> print(c.super_instance.stuff)
> print(c.__getattribute__)
> print(c.super_instance.__getattribute__)
> print(dir(c))
> print(dir(c.super_instance))
> print(c.__dict__ == c.super_instance.__dict__)
> print(getattr(c, "__init__"))
> print(getattr(c.super_instance, "__init__"))
> print(c.__getattribute__("__init__"))
> print(c.super_instance.__getattribute__("__init__"))
> 
> 
> 
> Child stuff!
> Parent stuff!
> <__main__.Child object at 0x0000026854D99828>
> <super: <class 'Child'>, <Child object>>
> <bound method Child.__init__ of <__main__.Child object at 0x0000026854D99828>>
> <bound method Parent.__init__ of <__main__.Child object at 0x0000026854D99828>>
> <bound method Child.stuff of <__main__.Child object at 0x0000026854D99828>>
> <bound method Parent.stuff of <__main__.Child object at 0x0000026854D99828>>
> <method-wrapper '__getattribute__' of Child object at 0x0000026854D99828>
> <method-wrapper '__getattribute__' of Child object at 0x0000026854D99828>
> ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a', 'b', 'only_in_child', 'stuff', 'super_instance']
> ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__self_class__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__thisclass__', 'a', 'b', 'super_instance']
> True
> <bound method Child.__init__ of <__main__.Child object at 0x0000026854D99828>>
> <bound method Parent.__init__ of <__main__.Child object at 0x0000026854D99828>>
> <bound method Child.__init__ of <__main__.Child object at 0x0000026854D99828>>
> <bound method Child.__init__ of <__main__.Child object at 0x0000026854D99828>>
> -- 
> https://mail.python.org/mailman/listinfo/python-list
> 



More information about the Python-list mailing list