I'm trying to find the python equivalent of c++ const methods. That is,
methods that are forbidden to change any data member of their class.
from typing import Final
age: Final = 2
age += 3 # <----- warn about this -- works!
class Person:
def __init__(self, age: int) -> None:
self.age = age
def happy_birthday(self: Final[Person]) -> None:
self.age += 1 # <----- warn about this -- how?
I've never seen type hints used with self so it looks a bit weird (and doesn't work)
main.py:4: error: Cannot assign to final name "age"
main.py: note: In member "happy_birthday" of class "Person":
main.py:9: error: Final can be only used as an outermost qualifier in a variable annotation [misc]
def happy_birthday(self: Final[Person]) -> None:
^
Is there any other way to achieve this? Note that the attributes should still allow modification in other methods.
There is no such thing.
In C++, const is there for historical and compiler optimization reasons that are not relevant in Python. This notion does not seem to fit well in Python's object-oriented programming model, in which methods and data structures form cohesive units: the object.
If it doesn't have to be a type-hint solution, then you can do what PIG208 suggested in the comments. i.e. you can create a decorator that overwrites the __setattr__ method to throw an exception when modifying any attributes inside the method.
This is also suitable to attach on a per-method basis:
def const_method(func):
def wrapper(instance):
class unsettable_class(instance.__class__):
def __init__(self):
super().__setattr__("__dict__", instance.__dict__)
def __setattr__(self, attr, value):
if hasattr(self, attr):
raise AttributeError(f"Trying to set value: {value} on the read-only attribute: {instance.__class__.__name__}.{attr}")
super().__setattr__(attr, value)
return func(unsettable_class())
return wrapper
You can then use it the same as any other decorator:
class Person:
def __init__(self, age: int) -> None:
self.age = age
@const_method
def happy_birthday(self: "Person") -> None:
self.age += 1 # throws an error since we are using the custom decorator
def rebirth(self: "Person") -> None:
self.age = 0 # will not throw an error since there is no decorator
Unfortunately, the downside is that this method won't give you syntax highlighting in your IDE like type hinting will though.