0

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

0

1 Answer 1

1

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:

  • start with an _, are regular fields
  • start with __ (and end with at most one _), are just name mangled, but they still can be accessed (even modified/deleted) from outside the class

So, 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_casting 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:

  • I removed __ 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)
  • For Py27 compatibility, the classes inherit object. If the compatibility isn't mandatory, the inheritance relation can be deleted (in Py3x it is by default)
  • I added the try/except blocks to illustrate the behavior, in production they should be deleted
  • As I stated above, if anyone wants to alter this behavior, can
Sign up to request clarification or add additional context in comments.

4 Comments

This way the whole object portfolio cannot be changed, but I can still change attributes of the object portfolio. I edited the question to make it clearer. Descriptors seem like a great thing, I need to learn how they work.
Hmm, I was afraid that this would be the next question. The general answer is that you can't make them truly bullet proof. A consumer of your class/instance will be able to play with them. Regarding the question itself, I don't have an answer. An alternative would be to make the portfolio type objects use LockedAttribute as well, and make them read-only (,or initialize them in GenericStrategy.__init__ - which would break any aggregation), but I doubt that this would fit your needs.
I felt like it was impossible... Thanks for the answer anyway, the locked attribute class is great
Thank you. You can take a look at [Python]: abc module, or [Python]: __setattr__ method, maybe you could something from there to get you closer to your goal.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.