Is it pythonic to use properties to limit the mutability of class attributes (variables and methods)?
For attributes/properties/variables at either the class or instance level: yes, absolutely! The decorator @property is built in specifically to give control over read, write, and delete. Common use cases include:
- enforcing read-only or write-only
- ensuring valid values
- ensuring consistency with other state
- presenting limited internal state in multiple ways
- triggering other system logic
But really the most authoritative I can get is to quote PEP-8, the Python style guide.
With this in mind, here are the Pythonic guidelines:
- For simple public data attributes, it is best to expose just the attribute name, without complicated accessor/mutator methods. Keep in mind that Python provides an easy path to future enhancement, should you find that a simple data attribute needs to grow functional behavior. In that case, use properties to hide functional implementation behind simple data attribute access syntax.
For methods: no, not really. Most of the above usages don't typically apply to methods or functions. It's certainly possible to overwrite these dynamically at either the class or the object level, but it's not commonly needed. Doing so can certainly be ok, but it's unusual enough that it always bears closer scrutiny.
- Is there any reason why I shouldn't be doing this?
First is readability. There's no reason to make one obvious line of code into six boilerplate lines just so you can have pass-through getters and setters. Python (thankfully!) was designed so that calling code has no idea whether an attribute is a raw attribute or a property. That means you can change it whenever is convenient for you, instead of being locked into the decision.
Second is readability. For the method hiding property, there isn't much value added, and that style isn't idiomatic. It takes much more effort to figure out what's going on in the first place. It's also awkward later when the method you think you're calling is called something else.
Third is convention. Looking specifically at your note about public/private, it seems you want a way to mark things public or private. But in your code sample, you're already correctly using a single leading underscore to indicate private content. As a reminder, it's possible to get at _private names simply by referring to them. It's just considered to be use at your own risk.
Fourth is a placeholder to show just how unimportant five is.
Fifth is performance. This is almost certainly not the source of any performance issue, but there's technically a very slight performance penalty for using a property. It's minuscule, and you shouldn't care about this unless your profiler has measured it and is absolutely certain this is the problem. It's not.
- If not, is it pythonic and why/why not?
To sum up, I'd definitely stick with the Zen of Python's "Simple is better than complex."