0

I have a superclass where one property is an enumeration.

There are some specific subclasses that inherit from this class where only one case of the enumeration makes sense, so ideally I would like to prevent it being changed in these subclasses.

Here is a minimal example using medical professionals:

from enum import Enum, unique, auto

# My enum for medical specialities
@unique
class Speciality (Enum):
    Anaesthetics = auto()
    Dermatology = auto()
    General_Practice = auto()
    Surgery = auto()

# My superclass
class Doctor:
    speciality = Specialty # Could be any case

# An example subclass
class GP (Doctor):
    speciality = Specialty.GeneralPractice # Must be General Practice

Is there a way to make the speciality property immutable in the GP subclass? If not, is there a way to implement similar functionality?

Chris
  • 4,009
  • 3
  • 21
  • 52
  • 1
    Have you looked at [@property and @setter](https://stackoverflow.com/questions/6618002/using-property-versus-getters-and-setters)? If you want a setter to fail (i.e. a read-only property), raise an exception? – Jens Nov 26 '19 at 22:13
  • @Jens Thanks for your response. No I’m not familiar with those. Could that also prevent mutation at any point after initialisation? – Chris Nov 26 '19 at 22:14
  • Yes, the point of these is to have more controlled access to object properties. – Jens Nov 26 '19 at 22:18
  • 1
    @Jens eh, no, there are several built-in immutable types. `str`, `tuple`, `bytes`, `frozenset` ... but you are correct, user-defined types are pretty much always mutable, but you can add some things to prevent accidental mutation – juanpa.arrivillaga Nov 26 '19 at 22:20
  • Looking at your example though, it looks to me like the `specialty` is redundant: it is encoded in the class, no? I mean, the class `GP` has the `GeneralPracticioner` specialty. If that’s what you’re after, consider removing the `specialty` property and instead use [`type()`](https://docs.python.org/3/library/functions.html#type) or [`isinstance()`](https://docs.python.org/3/library/functions.html#isinstance). – Jens Nov 26 '19 at 22:21
  • @Jens That’s what I thought about mutability in Python. Yes in this case the class name implies the speciality, so `type()` would be a possibility, but I have other classes (e.g. Surgeon) and so it would be convenient to have an instance of specialty for each. They will have a speciality property anyway due to inheritance, so I am also trying to guard against it changing. I will consider your `type()` idea though - might just need a small refactor of the class structures. – Chris Nov 26 '19 at 22:33
  • @Jens How might I use `@property` and `@setter`? – Chris Nov 26 '19 at 22:33
  • 1
    Also, it might be interesting to poke around this thread: https://stackoverflow.com/questions/14594120/python-read-only-property – Jens Nov 27 '19 at 23:05

1 Answers1

1

Based on the initial conversation and using property decorators, this should work:

>>> from abc import ABC
>>> class Doctor(ABC):
...     """Abstract Base Class for doctors."""
...     pass
... 
>>> class GP(Doctor):
...     @property
...     def specialty(self):
...         return Specialty.GeneralPractice
...     @specialty.setter
...     def specialty(self, value):
...         raise ValueError("Unable to modify a Doctor's specialty")

which then allows you to read, but not write the specialty property:

>>> gp = GP()
>>> gp.specialty
<Speciality.General_Practice: 3>
>>> gp.specialty = Speciality.Dermatology
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in specialty
ValueError: Unable to modify a Doctor's specialty

Further reading: Property and Abstract Base Classes.

Jens
  • 8,423
  • 9
  • 58
  • 78
  • Thanks for this and the links - I’ll do some reading and experimentation for a bit. – Chris Nov 26 '19 at 23:16
  • @Chris the above is not an ideal solution because you’d replicate the setter & property use across classes. I would probably aim at using ABC and make more use of the dynamic [duck typing](https://en.wikipedia.org/wiki/Duck_typing). – Jens Nov 26 '19 at 23:49
  • I’m still getting my head around abstract base classes, but the decorator solution works great as well. Thanks! – Chris Nov 27 '19 at 22:29
  • @Chris great! On second thought, I would be tempted to raise [`AttributeError`](https://docs.python.org/3.8/library/exceptions.html#AttributeError) instead of [`ValueError`](https://docs.python.org/3.8/library/exceptions.html#ValueError), just to be more precise with Python’s error semantics. – Jens Nov 27 '19 at 23:08
  • 1
    Thanks. I was just reading on the link you shared that not having a corresponding setter will raise an Attribute Error anyway if you try to set the variable. I guess it makes more sense to use the same in a custom implementation. – Chris Nov 27 '19 at 23:19