32

I'm having a really strange problem with Python super() and inheritance and properties. First, the code:

#!/usr/bin/env python3

import pyglet
import pygame

class Sprite(pyglet.sprite.Sprite):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.rect = pygame.Rect(0, 0, self.width, self.height)
        self.rect.center = self.x, self.y

    @property
    def x(self):
        return super().x

    @x.setter
    def x(self, value):
        super(Sprite, self.__class__).x.fset(self, value)
        self.rect.centerx = value

    @property
    def y(self):
        return super().y

    @y.setter
    def y(self, value):
        super(Sprite, self.__class__).y.fset(self, value)
        self.rect.centery = value

This works fine. However, what I want (what seems Pythonic to me)

#super(Sprite, self.__class__).x.fset(self, value)
super().x = value

doesn't work even though

super().x

gets the value fine. x in this case is a property of the superclass with both fset and fget defined. So why doesn't it work?

darkfeline
  • 9,404
  • 5
  • 31
  • 32

2 Answers2

27

I was trying to find the correct language to back up why this behavior is the way it is, so as not to give you a "because it just is" answer... But it seems this question has been asked more than once, and that it boils down to the behavior of super(). You can see a 2010 discussion about this exact behavior here: http://mail.python.org/pipermail/python-dev/2010-April/099672.html

Ultimately, it really does just come down to super() calls only letting you access getters directly, and not setters. Setters must be accessed via fset() or __set__(). It is probably easiest explained as "super() functionality just doesn't support it". It will resolve the property functionality of a "get" operation, not the setter in a left handed assignment, in the "set" operation (hence the fset() method call). As you can see from the date of this discussion thread, its obviously been this way since the introduction of super().

Maybe someone else has a more specifically technical reason, but frankly I'm not sure it even matters. If its not supported, thats pretty much a good enough reason.

jdi
  • 90,542
  • 19
  • 167
  • 203
  • 8
    I've filed a bug: http://bugs.python.org/issue14965 Hopefully this is resolved soon so this question is no longer relevant. – darkfeline Sep 01 '12 at 22:31
  • "its not supported ... a good enough reason" sounds like "because it just is". – simonzack Dec 10 '14 at 17:42
  • @simonzack - I did say I was *trying* not to give that kind of answer and explain the most I possibly could about its functionality. – jdi Dec 10 '14 at 21:59
  • One more reason to avoid `super` – dashesy Mar 04 '15 at 01:18
  • Can you provide an example that would be used in the child class? – nmz787 Mar 18 '16 at 00:23
  • @nmz787 what kind of example do you want? my answer wasn't about teaching how to use super(). it was just about why super behaves the way it does. The original question has the example of using fset – jdi Mar 18 '16 at 07:29
  • 1
    oh, I didn't see the OP say "This works fine. However", thanks! – nmz787 Mar 21 '16 at 17:47
  • This answer might be helpful if it actually showed how to access super using fset or __set__ – Erik Aronesty May 05 '20 at 15:42
  • 1
    @ErikAronesty: the OP already did: `super(self.__class__, self.__class__).x.fset(self, value)` – MestreLion Apr 14 '21 at 14:16
1

super(type(self), type(self)).setter.fset(self, value) is a common workaround; however it doesn't work adequately with multiple inheritance, which can change the MRO (Method Resolution Order).

Try using my duper class: duper(super()).setter = value

class duper:
    """Super wrapper which allows property setting & deletion.
    Super can't be subclassed with empty __init__ arguments.
    Works with multiple inheritance.
    
    References:
      https://mail.python.org/pipermail/python-dev/2010-April/099672.html
      https://bugs.python.org/issue14965
      https://bugs.python.org/file37546/superprop.py
    
    Usage: duper(super())
    """

    def __init__(self, osuper):
        object.__setattr__(self, 'osuper', osuper)

    def _find(self, name):
        osuper = object.__getattribute__(self, 'osuper')
        if name != '__class__':
            mro = iter(osuper.__self_class__.__mro__)
            for cls in mro:
                if cls == osuper.__thisclass__:
                    break
            for cls in mro:
                if isinstance(cls, type):
                    try:
                        return object.__getattribute__(cls, name)
                    except AttributeError:
                        pass
        return None
    
    def __getattr__(self, name):
        return getattr(object.__getattribute__(self, 'osuper'), name)

    def __setattr__(self, name, value):
        osuper = object.__getattribute__(self, 'osuper')
        desc = object.__getattribute__(self, '_find')(name)
        if hasattr(desc, '__set__'):
            return desc.__set__(osuper.__self__, value)
        return setattr(osuper, name, value)

    def __delattr__(self, name):
        osuper = object.__getattribute__(self, 'osuper')
        desc = object.__getattribute__(self, '_find')(name)
        if hasattr(desc, '__delete__'):
            return desc.__delete__(osuper.__self__)
        return delattr(osuper, name)

(full source https://gist.github.com/willrazen/bef3fcb26a83dffb6692e5e10d3e67ac)

SuperStormer
  • 4,997
  • 5
  • 25
  • 35
Will Razen
  • 307
  • 1
  • 11