What's the correct way to implement static immutable properties in Python?
Minimal example:
A program module 'Family' has a class Parent
, defined below:
class Parent():
def __init__(self, type):
self.type = type
The parent can be of either of the two string types: 'mother'
or 'father'
. An external user of this class should be able to set the type when constructing, and later query parent.type
to get either of these two values. Furthermore, other parts of the Family module utilise the Parent
class and rely on either of these two values being returned. Therefore, the requirements of the type are as follows:
A naive approach could encourage setting type by passing a string:
parent = Parent('mother')
But this allows for accidental misspellings (eg. Parent('omther')
). To prevent this, I utilise the following approach:
class Parent():
TYPE_MOTHER = 'mother'
TYPE_FATHER = 'father'
TYPES = [TYPE_MOTHER, TYPE_FATHER]
def __init__(self, type):
assert type in self.TYPES, ('Invalid type "%s"' % type)
self.type = type
parent = Parent(Parent.TYPE_MOTHER)
However, nothing would stop the user from changing these static variables as they like, eg:
parent = Parent('mother')
Parent.TYPE_MOTHER = 'burrito'
print(parent.type == Parent.TYPE_MOTHER)
#> False
To solve this, I considered using @staticmethod
and @property
annotations:
class Parent():
@property
@staticmethod
def TYPE_MOTHER():
return 'mother'
@property
@staticmethod
def TYPE_FATHER():
return 'father'
This wouldn't prevent the user from passing a string to constructor (eg. Parent('mother')
) but at least it would prevent them from screwing up the Family module by changing what the parent types can be.
Problems I have with this method:
Questions for you:
I've found one tidy answer to my question - Enum
class (doc).
from enum import Enum
class Parent():
class TYPES(Enum):
MOTHER = 'mother'
FATHER = 'father'
def __init__(self, type:TYPES):
assert type in self.TYPES, ('Invalid type "%s"' % type)
self.type = type
parent = Parent(Parent.TYPES.MOTHER)
In which case user could still overwrite Parent.TYPES
. I can't think of another way of preventing it than using __setattr__
and catching the malicious write. If you can think of anything here share your thoughts.
Such pattern provides following benefits:
__setattr__
assert type in self.TYPES
or assert isinstance(type, self.TYPES)
So far, I can see two differences in usage:
.value
property of the Enum. So instead of parent.type
(which would return "TYPES.MOTHER"
) I need to useparent.type.value
which correctly returns 'mother'
.list(self.TYPES)
I think with Enum I could also drop the ALL_CAPS standard, given that I don't need to signify that types
is static.