-1

Can you set parent class attributes using super() in python 2.7? I would expect the following code to work, but it does not:

class A(object):
    val_a = 1

class B(A):
    val_b = 2

    def set_a(self, val):
        super(B,self).__class__.val_a = val

b = B()

b.set_a(3)

It gives the following error:

TypeError: can't set attributes of built-in/extension type 'super'

Is there a correct way to do this?

PProteus
  • 549
  • 1
  • 10
  • 23
  • 2
    you should go for `thisclass` instead of `class`. `super().__class__` is the class attrribute of super object, while `super().__thisclass__` is the class attribute of the parent class. – Faibbus Jan 31 '17 at 17:29
  • @Faibbus, I just replaced `__class__` with `__thisclass__` and it worked. Thanks! I wonder why this is the case? – PProteus Jan 31 '17 at 17:30
  • Why are you trying to do this though? B inherits from A, any instance of B has access to the attributes of A. You should be setting the value on `self.__class__.A.` – Daniel Roseman Jan 31 '17 at 17:32
  • @DanielRoseman, I want to set the value for the class A and any other classes that may inherit from A, rather than just for class B. I don't think the code you included would do that. – PProteus Jan 31 '17 at 17:37
  • See http://stackoverflow.com/questions/3475488/static-variable-inheritance-in-python – abcabc Jan 31 '17 at 17:48
  • See the post http://stackoverflow.com/questions/3475488/static-variable-inheritance-in-python – abcabc Jan 31 '17 at 17:49
  • 1
    @Faibbus: That's `B`, not `A`. It looks like the questioner wanted `A`. – user2357112 Jan 31 '17 at 17:50
  • @user2357112: `super(A, self).__thisclass__.val_a` would change A's attribute, wouldn't it ? – Faibbus Jan 31 '17 at 18:27
  • 1
    @Faibbus: It would, but `super(A, self).__thisclass__` is a really convoluted way to write `A`, with no benefits. – user2357112 Jan 31 '17 at 18:30
  • I have edited the title and content of the question to make the intent more clear. Thanks for all the advice. – PProteus Feb 01 '17 at 13:00

5 Answers5

2

Can you set static class attributes using super() in python 2.7?

No.

I would expect the following code to work, but it does not

First, __class__ actually gets special-cased by super, to return the super instance's class, rather than performing the MRO search super usually does.

Second, there's no B.__dict__['__class__'] entry for super to bypass, so even if __class__ wasn't special-cased, it'd still be B instead of A.

Third, if you want to refer specifically to class A, rather than whatever happens to come next in self's MRO, you should just refer to A directly.


Long story short: use A, not super.

user2357112
  • 260,549
  • 28
  • 431
  • 505
  • 1
    Thanks for your reply. The suggestion by Faibbus of using `__thisclass__` with `super` does seem to work, actually. – PProteus Jan 31 '17 at 17:43
  • 1
    @PProteus: That's `B`. If you wanted to assign to `B.val_a`, you should assign to `B.val_a` directly, rather than taking a convoluted detour through `super`. It sounds like you wanted to assign to `A.val_a`, though. – user2357112 Jan 31 '17 at 17:48
  • You're right, thanks—I didn't catch that. I think I will just use A.val_a. – PProteus Jan 31 '17 at 17:54
  • The value in setting `A`'s static attributes through `b` is that then I have a consistent interface; I can just use `b.whatever` for everything. – PProteus Jan 31 '17 at 19:38
1

Reference A.val_a directly. val_a in this case is a "static" variable, not an "attribute" of an object instance.

The super call tries to get the object of self instance, and thus access to attribute super(B, self).val_a will fail.

class A(object):
    val_a = 1

class B(A):
    val_b = 2

    def set_a(self, val):
        A.val_a = val 

b = B()
b.set_a(3)

print b.val_a
greedy52
  • 1,345
  • 9
  • 8
  • 1
    One of the main reasons for using `super()` is to avoid hardcoding [base] class name(s) into the code of subclasses. – martineau Jan 31 '17 at 18:32
1

You can't do it with super(), but you can search through base classes of the subclass and do it that way.

Here's what I mean:

class Other(object):
    val_a = 42

class A(Other):
    val_x = 1

    def __str__(self):
        return '{}(val_a={})'.format(self.__class__.__name__, self.val_a)

class B(A):
    val_b = 2

    def set_a(self, val):
        for cls in self.__class__.__mro__[1:]:
            try:
                object.__getattribute__(cls, 'val_a')
            except AttributeError:
                continue
            print('Updating class: {}'.format(cls.__name__))
            cls.val_a = val
            break
        else:
            raise RuntimeError("Can't find a base class with attribute 'val_a'")

b = B()
print(b)  # -> B(val_a=1)                     # B(val_a=42)
print('Other.val_a: {}'.format(Other.val_a))  # Other.val_a: 42
print('A.val_a: {}'.format(A.val_a))          # A.val_a: 42
print('B.val_a: {}'.format(B.val_a))          # B.val_a: 42
print('')
b.set_a(3)                                    # Updating class: Other
print('')
print(b)                                      # B(val_a=3)
print('Other.val_a: {}'.format(Other.val_a))  # Other.val_a: 3
print('A.val_a: {}'.format(A.val_a))          # A.val_a: 3
print('B.val_a: {}'.format(B.val_a))          # B.val_a: 3
martineau
  • 119,623
  • 25
  • 170
  • 301
  • Thanks, that's a pretty neat solution. Perhaps more complicated than I need. – PProteus Jan 31 '17 at 17:57
  • If you know there's only a single base class, you could hardcode things and just unconditionally use `self.__class__.__mro__[1].val_a = val`. – martineau Jan 31 '17 at 18:00
  • 1
    `hasattr(cls, 'val_a')` doesn't care about whether the attribute is actually defined in class `cls`; it'll pick up inherited attributes. The bug isn't visible here only because `val_a` is defined in the next class in the MRO. – user2357112 Jan 31 '17 at 18:00
  • @user2357112: Good catch...think I've fixed that in revised answer. – martineau Jan 31 '17 at 18:27
  • It kind of works better now, but not for good reasons, and probably not for the reasons you're thinking. `cls.__getattribute__` is the `__getattribute__` method used for *instances* of `cls`, not for `cls` itself. It just happens that this is usually `object.__getattribute__`, which doesn't know how to search `cls`'s MRO. If another `__getattribute__` method gets involved, things break. – user2357112 Jan 31 '17 at 18:38
  • The usual way to check if a class attribute is defined in a particular class is to look for it in the class's `__dict__` (or `vars`, if you prefer the function). – user2357112 Jan 31 '17 at 18:39
  • OK, so in your opinion do you think it would be acceptable to explicitly use `object.__getattribute__(cls, 'val_a')` instead (rather than checking for a `__dict__`, and if there is one, also checking its contents)? – martineau Jan 31 '17 at 18:48
  • Probably fine. It's not how I'd write it, and it could get a little weird with attribute names that coincide with metaclass attributes, but for normal attributes, it should work. – user2357112 Jan 31 '17 at 18:58
  • Thanks again. Feel free to [edit] and improved my answer or add your own. – martineau Jan 31 '17 at 19:01
0

According to your code you are trying to make the change for the instance. Then, the function set_a needs to be as below.

class B(A):
    val_b = 2

    def set_a(self, val):
        self.val_a = val

But if you need the change to be applicable for the class level, you can use a class method and you can't use super()

class B(A):
    val_b = 2

    @classmethod
    def set_a_class(cls, val):
        cls.val_a = val

B.set_a_class(5)
print B.val_a

b = B()
print b.val_a

will return

5

5

Community
  • 1
  • 1
saloua
  • 2,433
  • 4
  • 27
  • 37
  • No, sorry. That would only set the value of val_a for the particular instance. I want to set it for the class, A. – PProteus Jan 31 '17 at 17:35
  • But according to you code the function is not a class method then it is only applicable for the particular instance. If you want to set the variable for the class `set_a` needs to be a class method. – saloua Jan 31 '17 at 17:41
  • 1
    It is an answer to your question saying `Can you set static class attributes using super()` otherwise can you please edit your question to make it more clear if you want the method to set the variable for the class. – saloua Jan 31 '17 at 17:50
  • Thanks for the suggestion. I have edited the title and question to make the intent more clear. – PProteus Feb 01 '17 at 12:58
0

See Static variable inheritance in Python

class A(object):
    val_a = 1


class B(A):
    val_b = 2
    def set_a(self, val):
        type(self).val_a = val


b1 = B()
b1.set_a(3)
b2 = B()
b2.set_a(4)
print type(b1).val_a
print type(b2).val_a
print A.val_a
print B.val_a
Community
  • 1
  • 1
abcabc
  • 61
  • 3
  • Thanks for the answer. Unfortunately, just as with Faibbus' suggestion, this only changes the attribute for class B, not A. type(self) is not different from using self.__class__ as far as I can tell. – PProteus Jan 31 '17 at 17:56
  • 1. type(self) is not different from using self.__class__ but looks better. – abcabc Jan 31 '17 at 18:16
  • 2.. It depends upon the application. Assume that you do not want to mess with class A namespace and furthermore set_a method is defined in class A. In that case you may use this solution. – abcabc Jan 31 '17 at 18:29