Here is a pure Python-specific design question:
class MyClass(object):
...
def get_my_attr(self):
...
def set_my_attr(self, value):
...
and
class MyClass(object):
...
@property
def my_attr(self):
...
@my_attr.setter
def my_attr(self, value):
...
Python lets us to do it either way. If you would design a Python program, which approach would you use and why?
I would prefer to use neither in most cases. The problem with properties is that they make the class less transparent. Especially, this is an issue if you were to raise an exception from a setter. For example, if you have an Account.email property:
then the user of the class does not expect that assigning a value to the property could cause an exception:
As a result, the exception may go unhandled, and either propagate too high in the call chain to be handled properly, or result in a very unhelpful traceback being presented to the program user (which is sadly too common in the world of python and java).
I would also avoid using getters and setters:
Instead of properties and getters/setters I prefer doing the complex logic in well defined places such as in a validation method:
or a similiar Account.save method.
Note that I am not trying to say that there are no cases when properties are useful, only that you may be better off if you can make your classes simple and transparent enough that you don't need them.
The short answer is: properties wins hands down. Always.
There is sometimes a need for getters and setters, but even then, I would "hide" them to the outside world. There are plenty of ways to do this in Python (
getattr
,setattr
,__getattribute__
, etc..., but a very concise and clean one is:Here's a brief article that introduces the topic of getters and setters in Python.
[TL;DR? You can skip to the end for a code example.]
I actually prefer to use a different idiom, which is a little involved for using as a one off, but is nice if you have a more complex use case.
A bit of background first.
Properties are useful in that they allow us to handle both setting and getting values in a programmatic way but still allow attributes to be accessed as attributes. We can turn 'gets' into 'computations' (essentially) and we can turn 'sets' into 'events'. So let's say we have the following class, which I've coded with Java-like getters and setters.
You may be wondering why I didn't call
defaultX
anddefaultY
in the object's__init__
method. The reason is that for our case I want to assume that thesomeDefaultComputation
methods return values that vary over time, say a timestamp, and wheneverx
(ory
) is not set (where, for the purpose of this example, "not set" means "set to None") I want the value ofx
's (ory
's) default computation.So this is lame for a number of reasons describe above. I'll rewrite it using properties:
What have we gained? We've gained the ability to refer to these attributes as attributes even though, behind the scenes, we end up running methods.
Of course the real power of properties is that we generally want these methods to do something in addition to just getting and setting values (otherwise there is no point in using properties). I did this in my getter example. We are basically running a function body to pick up a default whenever the value isn't set. This is a very common pattern.
But what are we losing, and what can't we do?
The main annoyance, in my view, is that if you define a getter (as we do here) you also have to define a setter.[1] That's extra noise that clutters the code.
Another annoyance is that we still have to initialize the
x
andy
values in__init__
. (Well, of course we could add them usingsetattr()
but that is more extra code.)Third, unlike in the Java-like example, getters cannot accept other parameters. Now I can hear you saying already, well, if it's taking parameters it's not a getter! In an official sense, that is true. But in a practical sense there is no reason we shouldn't be able to parameterize an named attribute -- like
x
-- and set its value for some specific parameters.It'd be nice if we could do something like:
for example. The closest we can get is to override the assignment to imply some special semantics:
and of course ensure that our setter knows how to extract the first three values as a key to a dictionary and set its value to a number or something.
But even if we did that we still couldn't support it with properties because there is no way to get the value because we can't pass parameters at all to the getter. So we've had to return everything, introducing an asymmetry.
The Java-style getter/setter does let us handle this, but we're back to needing getter/setters.
In my mind what we really want is something that capture the following requirements:
Users define just one method for a given attribute and can indicate there whether the attribute is read-only or read-write. Properties fail this test if the attribute writable.
There is no need for the user to define an extra variable underlying the function, so we don't need the
__init__
orsetattr
in the code. The variable just exists by the fact we've created this new-style attribute.Any default code for the attribute executes in the method body itself.
We can set the attribute as an attribute and reference it as an attribute.
We can parameterize the attribute.
In terms of code, we want a way to write:
and be able to then do:
and so forth.
We also want a way to do this for the special case of a parameterizable attribute, but still allow the default assign case to work. You'll see how I tackled this below.
Now to the point (yay! the point!). The solution I came up for for this is as follows.
We create a new object to replace the notion of a property. The object is intended to store the value of a variable set to it, but also maintains a handle on code that knows how to calculate a default. Its job is to store the set
value
or to run themethod
if that value is not set.Let's call it an
UberProperty
.I assume
method
here is a class method,value
is the value of theUberProperty
, and I have addedisSet
becauseNone
may be a real value and this allows us a clean way to declare there really is "no value". Another way is a sentinel of some sort.This basically gives us an object that can do what we want, but how do we actually put it on our class? Well, properties use decorators; why can't we? Let's see how it might look (from here on I'm going to stick to using just a single 'attribute',
x
).This doesn't actually work yet, of course. We have to implement
uberProperty
and make sure it handles both gets and sets.Let's start with gets.
My first attempt was to simply create a new UberProperty object and return it:
I quickly discovered, of course, that this doens't work: Python never binds the callable to the object and I need the object in order to call the function. Even creating the decorator in the class doesn't work, as although now we have the class, we still don't have an object to work with.
So we're going to need to be able to do more here. We do know that a method need only be represented the one time, so let's go ahead and keep our decorator, but modify
UberProperty
to only store themethod
reference:It is also not callable, so at the moment nothing is working.
How do we complete the picture? Well, what do we end up with when we create the example class using our new decorator:
in both cases we get back the
UberProperty
which of course is not a callable, so this isn't of much use.What we need is some way to dynamically bind the
UberProperty
instance created by the decorator after the class has been created to an object of the class before that object has been returned to that user for use. Um, yeah, that's an__init__
call, dude.Let's write up what we want our find result to be first. We're binding an
UberProperty
to an instance, so an obvious thing to return would be a BoundUberProperty. This is where we'll actually maintain state for thex
attribute.Now we the representation; how do get these on to an object? There are a few approaches, but the easiest one to explain just uses the
__init__
method to do that mapping. By the time__init__
is called our decorators have run, so just need to look through the object's__dict__
and update any attributes where the value of the attribute is of typeUberProperty
.Now, uber-properties are cool and we'll probably want to use them a lot, so it makes sense to just create a base class that does this for all subclasses. I think you know what the base class is going to be called.
We add this, change our example to inherit from
UberObject
, and ...After modifying
x
to be:We can run a simple test:
And we get the output we wanted:
(Gee, I'm working late.)
Note that I have used
getValue
,setValue
, andclearValue
here. This is because I haven't yet linked in the means to have these automatically returned.But I think this is a good place to stop for now, because I'm getting tired. You can also see that the core functionality we wanted is in place; the rest is window dressing. Important usability window dressing, but that can wait until I have a change to update the post.
I'll finish up the example in the next posting by addressing these things:
We need to make sure UberObject's
__init__
is always called by subclasses.We need to make sure we handle the common case where someone 'aliases' a function to something else, such as:
We need
e.x
to returne.x.getValue()
by default.e.x.getValue()
. (Doing this one is obvious, if you haven't already fixed it out.)We need to support setting
e.x directly
, as ine.x = <newvalue>
. We can do this in the parent class too, but we'll need to update our__init__
code to handle it.Finally, we'll add parameterized attributes. It should be pretty obvious how we'll do this, too.
Here's the code as it exists up to now:
[1] I may be behind on whether this is still the case.
I am surprised that nobody has mentioned that properties are bound methods of a descriptor class, Adam Donohue and NeilenMarais get at exactly this idea in their posts -- that getters and setters are functions and can be used to:
This presents a smart way to hide implementation details and code cruft like regular expression, type casts, try .. except blocks, assertions or computed values.
In general doing CRUD on an object may often be fairly mundane but consider the example of data that will be persisted to a relational database. ORM's can hide implementation details of particular SQL vernaculars in the methods bound to fget, fset, fdel defined in a property class that will manage the awful if .. elif .. else ladders that are so ugly in OO code -- exposing the simple and elegant
self.variable = something
and obviate the details for the developer using the ORM.If one thinks of properties only as some dreary vestige of a Bondage and Discipline language (i.e. Java) they are missing the point of descriptors.
In Python you don't use getters or setters or properties just for the fun of it. You first just use attributes and then later, only if needed, eventually migrate to a property without having to change the code using your classes.
There is indeed a lot of code with extension .py that uses getters and setters and inheritance and pointless classes everywhere where e.g. a simple tuple would do, but it's code from people writing in C++ or Java using Python.
That's not Python code.
In complex projects I prefer using read-only properties (or getters) with explicit setter function:
In long living projects debugging and refactoring takes more time than writing the code itself. There are several downsides for using
@property.setter
that makes debugging even harder:1) python allows creating new attributes for an existing object. This makes a following misprint very hard to track:
If your object is a complicated algorithm then you will spend quite some time trying to find out why it doesn't converge (notice an extra 't' in the line above)
2) setter sometimes might evolve to a complicated and slow method (e.g. hitting a database). It would be quite hard for another developer to figure out why the following function is very slow. He might spend a lot of time on profiling
do_something()
method, whilemy_object.my_attr = 4.
is actually the cause of slowdown: