Reputation: 24099
In Python, getters and setters for member data are generally frowned up in lieu of properties. The reasons emphasize cases where we might want to transparently swap out a computation and a value, and cases where members are likely to be read and assigned to in a single express.
What about members that cannot or cannot sensibly be assigned to? Are there any structural reasons to prefer a getter or a property in that case?
The specific case I have in mind is a class with computed members that might be cached.
Upvotes: 0
Views: 67
Reputation: 1423
It depends upon how the trait you have in mind should be perceived to be. Is it an attribute which can be accessed trivially, or should it appear to require "work" to get?
If your trait is a cached value, and the site of access should have no way to influence how that trait is retrieved, then needing to denote the fact that you're using a getter function means you're telling the consumer that there is a process that needs to be performed when there is no need for them to know about it. Why make someone think about the consequences when by design you say they don't need to know?
Suppose however, that your getter may need control at the site of access, like under some conditions, you might want to invalidate the cache. It may not be common in consumer code, but that's for you to decide. In that case, you'd either need a getter with parameters, or a reset
method to go with your cached property.
One last, probably overdone, reason to prefer methods over properties would be the access time for a property descriptor is marginally slower than for a method call, so if the trait is accessed deep within a tight loop, using a property, even a cached one, can have a measurable impact.
EDIT - Clarifying Cost of Descriptors
When retrieving a method or descriptor, you're looking up the name's value. If you're using a function, it's just called after look up. If you're using a descriptor (@property), this is detected, and the descriptor's __get__
method is retrieved and called. This means that the descriptor has an extra look up step. This cost is minimal, and is practically immeasurable for common use, but if its accessed very frequently as in an inner loop, that price can accumulate.
In [1]: class Method(object):
...: def get_my_value(self):
...: return 1
...:
In [2]: class Property(object):
...: @property
...: def my_value(self):
...: return 1
...:
In [3]: M = Method()
In [4]: %timeit M.get_my_value()
The slowest run took 14.74 times longer than the fastest. This could mean that an intermediate result is being cached
10000000 loops, best of 3: 133 ns per loop
In [5]: P = Property()
In [6]: %timeit P.my_value
The slowest run took 8.09 times longer than the fastest. This could mean that an intermediate result is being cached
10000000 loops, best of 3: 181 ns per loop
Upvotes: 1