Why can't access the property setter using the super?

Ian Kelly ian.g.kelly at gmail.com
Tue Mar 19 12:02:52 EDT 2019


Here's the thing: the result of calling super() is not an instance of the
base class. It's just a proxy object with a __getattribute__ implementation
that looks up class attributes in the base class, and knows how to pass
self to methods to simulate calling base class methods on an instance. This
works fine for looking up methods and property getters. It doesn't work so
well for simulating other behaviors, like setters, or comparisons, or
iteration, etc.

A property is one example of what in Python is known as a descriptor, which
is described here:
https://docs.python.org/3/reference/datamodel.html#implementing-descriptors

A settable property has a __set__ method per the descriptor protocol. When
you try to set an attribute with the name of the property, Python looks up
the name in the class dict, finds the property object, and then calls its
__set__ method. The class of the super proxy object, however, does not
contain this property, and because you're not doing an attribute lookup,
the super proxy's __getattribute__ does not get called. So all Python sees
is that you tried to set an attribute that doesn't exist on the proxy
object.

The way I would suggest to get around this would be change your super()
call so that it looks up the property from the base class instead of trying
to set an attribute directly. For example, this should work:

    super(HeatedRefrigeratedShippingContainer,
self.__class__).celsius.fset(self, value)

On Tue, Mar 19, 2019 at 9:12 AM Arup Rakshit <ar at zeit.io> wrote:

> I have 3 classes which are connected to them through inheritance as you
> see in the below code:
>
> import iso6346
>
> class ShippingContainer:
>     """docstring for ShippingContainer"""
>
>     HEIGHT_FT = 8.5
>     WIDTH_FT = 8.0
>     next_serial = 1337
>
>     @classmethod
>     def _get_next_serial(cls):
>         result = cls.next_serial
>         cls.next_serial += 1
>         return result
>
>     @staticmethod
>     def _make_bic_code(owner_code, serial):
>         return iso6346.create(owner_code=owner_code,
>                               serial=str(serial).zfill(6))
>
>     @classmethod
>     def create_empty(cls, owner_code, length_ft, *args, **keyword_args):
>         return cls(owner_code, length_ft, contents=None, *args,
> **keyword_args)
>
>     # ... skipped
>
>     def __init__(self, owner_code, length_ft, contents):
>         self.contents  = contents
>         self.length_ft = length_ft
>         self.bic = self._make_bic_code(owner_code=owner_code,
>
> serial=ShippingContainer._get_next_serial())
>         # ... skipped
>
>
> class RefrigeratedShippingContainer(ShippingContainer):
>     MAX_CELSIUS = 4.0
>     FRIDGE_VOLUME_FT3 = 100
>
>     def __init__(self, owner_code, length_ft, contents, celsius):
>         super().__init__(owner_code, length_ft, contents)
>         self.celsius = celsius
>
>     # ... skipped
>
>     @staticmethod
>     def _make_bic_code(owner_code, serial):
>         return iso6346.create(owner_code=owner_code,
>                               serial=str(serial).zfill(6),
>                               category='R')
>     @property
>     def celsius(self):
>         return self._celsius
>
>     @celsius.setter
>     def celsius(self, value):
>         if value > RefrigeratedShippingContainer.MAX_CELSIUS:
>             raise ValueError("Temperature too hot!")
>         self._celsius = value
>
>     # ... skipped
>
>
> class HeatedRefrigeratedShippingContainer(RefrigeratedShippingContainer):
>     MIN_CELSIUS = -20.0
>
>     @RefrigeratedShippingContainer.celsius.setter
>     def celsius(self, value):
>         if value < HeatedRefrigeratedShippingContainer.MIN_CELSIUS:
>             raise ValueError("Temperature too cold!")
>         super().celsius = value
>
>
>
>
> Now when I run the code :
>
> Python 3.7.2 (v3.7.2:9a3ffc0492, Dec 24 2018, 02:44:43)
> [Clang 6.0 (clang-600.0.57)] on darwin
> Type "help", "copyright", "credits" or "license" for more information.
> >>> from shipping import *                                        >>> h1 =
> HeatedRefrigeratedShippingContainer.create_empty('YML', length_ft=40,
> celsius=-18)
> Traceback (most recent call last):
>   File "<stdin>", line 1, in <module>
>   File "/Users/aruprakshit/python_playground/shipping.py", line 23, in
> create_empty
>     return cls(owner_code, length_ft, contents=None, *args, **keyword_args)
>   File "/Users/aruprakshit/python_playground/shipping.py", line 47, in
> __init__
>     self.celsius = celsius
>   File "/Users/aruprakshit/python_playground/shipping.py", line 92, in
> celsius
>     super().celsius = value
> AttributeError: 'super' object has no attribute 'celsius'
> >>>
>
>
> Why here super is denying the fact that it has no celsius setter, although
> it has. While this code doesn’t work how can I solve this. The thing I am
> trying here not to duplicate the validation of temperature which is already
> in the  setter property of the RefrigeratedShippingContainer class.
>
>
>
>
> Thanks,
>
> Arup Rakshit
> ar at zeit.io
>
>
>
> --
> https://mail.python.org/mailman/listinfo/python-list
>



More information about the Python-list mailing list