Reputation: 1303
I have a class:
class Portfolio:
def __init__(self, value):
self.value = value
class GenericStrategy:
def __init__(self, portfolio: Portfolio):
self.portfolio = portfolio
def modify_ptf_value(new_value):
self.portfolio.value = new_value
# This should return an error
I'will write some strategies which will inherit from GenericStrategy
. I'd like their methods to be able to read the attribute portfolio but not to modify it, nor its attributes.
I read something about the @properties
decorator, but it only works if I don't want the attribute (and its attributes) to be accessible from the outside, i can still modify the attribute (and its attributes) from methods 'inside' the object.
Is there a way to make the attribute (and its attributes) 'read-only' except for the __init__
method? Is my design wrong and should start over? I know it is up to the user not to modify "protected" attributes, but I would like to make it bullet proof. Any idea is well accepted, even if it requires a substantial change in the class design.
Thanks
Upvotes: 0
Views: 23489
Reputation: 41112
As opposed to other (commonly used) programming languages Python comes with a new approach regarding accessing class/instance members. For example, nothing is really private, the fields/methods that:
_
, are regular fields__
(and end with at most one _
), are just name mangled, but they still can be accessed (even modified/deleted) from outside the classSo, at the end it's a matter of convention, and it relies that it will be followed by those who write code. Bottom line is there's nothing that would prevent an user gaining access to a class/instance's internals.
Note: In other language it's possible too to access private members: there are methods officially supported (like Reflection ([Oracle]: Trail: The Reflection API) for Java), or not so officially supported (which require some "tricks" - e.g.: reinterpret_cast
ing a class
to a struct
with the same structure for C++). Nowadays, more and more languages tend to offer a way to alter an instance structure.
Anyway, there is the so called Descriptor Protocol ([Python]: Descriptor HowTo Guide) which is one of the Python's most powerful (and also most misunderstood) features.
Using descriptors (as a side comment, properties rely on them), I wrote a piece of code that achieves (on some degree) what you're asking for:
class LockedAttribute(object):
def __init__(self, name):
self._name = name
self._set_count = 0
self._set_treshold = 1
def __get__(self, instance, cls):
return instance.__dict__[self._name]
def __set__(self, instance, value):
if self._set_count >= self._set_treshold:
raise AttributeError("Can't set attribute '{}'".format(self._name))
else:
instance.__dict__[self._name] = value
self._set_count += 1
def __delete__(self, instance):
raise AttributeError("Can't delete attribute '{}'".format(self._name))
class GenericStrategy(object):
portfolio = LockedAttribute("portfolio")
def __init__(self, portfolio):
self.portfolio = portfolio
try:
self.portfolio = portfolio
except AttributeError as e:
print(" ERROR: {}".format(e))
def set_portfolio(self, new_value):
self.portfolio = new_value
if __name__ == "__main__":
strategy = GenericStrategy("some portfolio name")
print("Portfolio: {}".format(strategy.portfolio))
try:
del strategy.portfolio
except AttributeError as e:
print(" ERROR: {}".format(e))
try:
strategy.set_portfolio("some 2nd portfolio name")
except AttributeError as e:
print(" ERROR: {}".format(e))
try:
strategy.portfolio = "some 3rd portfolio name"
except AttributeError as e:
print(" ERROR: {}".format(e))
print("Portfolio: {}".format(strategy.portfolio))
Notes:
__
from the private attribute's name (portfolio
) to avoid using the mangling that I was talking about, in my code (would make it more difficult to read)object
. If the compatibility isn't mandatory, the inheritance relation can be deleted (in Py3x it is by default)try/except
blocks to illustrate the behavior, in production they should be deletedUpvotes: 1