85

It appears to me that except for a little syntactic sugar, property() does nothing good.

Sure, it's nice to be able to write a.b=2 instead of a.setB(2), but hiding the fact that a.b=2 isn't a simple assignment looks like a recipe for trouble, either because some unexpected result can happen, such as a.b=2 actually causes a.b to be 1. Or an exception is raised. Or a performance problem. Or just being confusing.

Can you give me a concrete example for a good usage of it? (using it to patch problematic code doesn't count ;-)

olamundo
  • 23,991
  • 34
  • 108
  • 149
  • 13
    You got it wrong. .setB() is a patch for languages which doesn't have real properties. Python, C# and some others do have properties, and the solution is the same. – liori Oct 12 '09 at 15:23
  • 5
    I have seen java guys a worse thing, which is to write getters and setters for every attribute - just in case, cos it's a pain to refactor everything later. – John La Rooy Oct 12 '09 at 20:36
  • 4
    @gnibbler, in languages that don't support properties (or at least somewhat-equivalent possibilities such as Python's `__getattr__`/`__setattr__`, which vastly predate properties), there's not much else "Java guys" (in any of those languages, including C++!-) can possibly do, to preserve encapsulation. – Alex Martelli Oct 13 '09 at 02:22

8 Answers8

149

In languages that rely on getters and setters, like Java, they're not supposed nor expected to do anything but what they say -- it would be astonishing if x.getB() did anything but return the current value of logical attribute b, or if x.setB(2) did anything but whatever small amount of internal work is needed to make x.getB() return 2.

However, there are no language-imposed guarantees about this expected behavior, i.e., compiler-enforced constraints on the body of methods whose names start with get or set: rather, it's left up to common sense, social convention, "style guides", and testing.

The behavior of x.b accesses, and assignments such as x.b = 2, in languages which do have properties (a set of languages which includes but is not limited to Python) is exactly the same as for getter and setter methods in, e.g., Java: the same expectations, the same lack of language-enforced guarantees.

The first win for properties is syntax and readability. Having to write, e.g.,

x.setB(x.getB() + 1)

instead of the obvious

x.b += 1

cries out for vengeance to the gods. In languages which support properties, there is absolutely no good reason to force users of the class to go through the gyrations of such Byzantine boilerplate, impacting their code's readability with no upside whatsoever.

In Python specifically, there's one more great upside to using properties (or other descriptors) in lieu of getters and setters: if and when you reorganize your class so that the underlying setter and getter are not needed anymore, you can (without breaking the class's published API) simply eliminate those methods and the property that relies on them, making b a normal "stored" attribute of x's class rather than a "logical" one obtained and set computationally.

In Python, doing things directly (when feasible) instead of via methods is an important optimization, and systematically using properties enables you to perform this optimization whenever feasible (always exposing "normal stored attributes" directly, and only ones which do need computation upon access and/or setting via methods and properties).

So, if you use getters and setters instead of properties, beyond impacting the readability of your users' code, you are also gratuitously wasting machine cycles (and the energy that goes to their computer during those cycles;-), again for no good reason whatsoever.

Your only argument against properties is e.g. that "an outside user wouldn't expect any side effects as a result of an assignment, usually"; but you miss the fact that the same user (in a language such as Java where getters and setters are pervasive) wouldn't expect (observable) "side effects" as a result of calling a setter, either (and even less for a getter;-). They're reasonable expectations and it's up to you, as the class author, to try and accommodate them -- whether your setter and getter are used directly or through a property, makes no difference. If you have methods with important observable side effects, do not name them getThis, setThat, and do not use them via properties.

The complaint that properties "hide the implementation" is wholly unjustified: most all of OOP is about implementing information hiding -- making a class responsible for presenting a logical interface to the outside world and implementing it internally as best it can. Getters and setters, exactly like properties, are tools towards this goal. Properties just do a better job at it (in languages that support them;-).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 4
    Also, take a look at C++'s operator overloading. the same "an outside user wouldn't expect..." argument applies. There are some cases where you just don't want the user to know. In those cases it is up to you to make sure you don't surprise the user. – Oren S Oct 12 '09 at 16:39
  • 5
    @Oren, good point, and Python's operator overloading's much like C++'s (assignment can't be overloaded as it's not an operator, &c, but the general concept is similar). It's up to the coder's courtesy, discipling and common sense to avoid e.g. `__add__` changing self and/or doing something that has nothing remotely to do with addition -- that doesn't mean operator overloading's a bad thing when used tastefully and sensibly (though Java's designers disagree as they deliberately left it out of _their_ language!-). – Alex Martelli Oct 13 '09 at 02:12
  • "... you can (without breaking the class's published API) simply eliminate those methods and the property that relies on them, making b a normal "stored" attribute of x's class rather than a "logical" one obtained and set computationally." - ??? I don't understand this, I get that if you change the underlying internal attributes that comprise the property the public "interface" provided by the property doesn't have to change, but this is also true of getters/setters. Both are just an abstraction layer above a value which should be internal to the object. What are you trying to say here? – Adam Parkin Mar 07 '12 at 22:25
  • 8
    @Adam Parkin If a client program calls 'GetTaxRate()' you no longer have the ability to remove the method 'GetTaxRate' without breaking backwards comparability. You can however remove a '@property' without breaking backwards compatibility because direct access and '@property' use the same syntax. – AlexLordThorsen Jul 04 '13 at 06:19
35

The idea is to allow you to avoid having to write getters and setters until you actually need them.

So, to start off you write:

class MyClass(object):
    def __init__(self):
        self.myval = 4

Obviously you can now write myobj.myval = 5.

But later on, you decide that you do need a setter, as you want to do something clever at the same time. But you don't want to have to change all the code that uses your class - so you wrap the setter in the @property decorator, and it all just works.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • 3
    Your example is exactly what I meant by "using it to patch problematic code"... well maybe "problematic" isn't the right word to use here, but it's a patch IMO. – olamundo Oct 12 '09 at 14:13
  • I back @olamundo thought: too often I've seen variables becoming `@property` to add some logic in the setter/getter and not breaking compatibility. This intentionally gives the `@property` side effects, and is confusing for future programmers on the same code. – Lorenzo Belli Jun 19 '17 at 10:23
15

but hiding the fact that a.b=2 isn't a simple assignment looks like a recipe for trouble

You're not hiding that fact though; that fact was never there to begin with. This is python -- a high-level language; not assembly. Few of the "simple" statements in it boil down to single CPU instructions. To read simplicity into an assignment is to read things that aren't there.

When you say x.b = c, probably all you should think is that "whatever just happened, x.b should now be c".

Lee B
  • 2,137
  • 12
  • 16
  • I agree with what you said, but I still consider it as "hiding" the inner workings in the sense that an outside user wouldnt expect any side affects as a result of an assignment, usually. – olamundo Oct 12 '09 at 14:21
  • 5
    That's true noam. On the other hand though, OOP is all about having objects that are black boxes, except for the interfaces they present, so one should probably assume that strange, magical things are going on beneath the surface ;) – Lee B Oct 12 '09 at 14:45
  • 2
    a.b = 2 is hardly less of an assignment than x = 2. If they are implemented differently, so be it, the whole point of OOP is to hide implementation details. If a.b = 2 sets something to 490, then it's a nasty side effect that wouldn't be solved by a.setB(2) anyways. If you implicitly think that a.b = 2 should be a very fast operation when you see it, then you should realize you are in Python and are already at least an order of magnitude slower than C. – Clueless Oct 12 '09 at 16:06
5

A basic reason is really simply that it looks better. It is more pythonic. Especially for libraries. something.getValue() looks less nice than something.value

In plone (a pretty big CMS), you used to have document.setTitle() which does a lot of things like storing the value, indexing it again and so. Just doing document.title = 'something' is nicer. You know that a lot is happening behind the scenes anyway.

Reinout van Rees
  • 13,486
  • 2
  • 36
  • 68
3

You are correct, it is just syntactic sugar. It may be that there are no good uses of it depending on your definition of problematic code.

Consider that you have a class Foo that is widely used in your application. Now this application has got quite large and further lets say it's a webapp that has become very popular.

You identify that Foo is causing a bottleneck. Perhaps it is possible to add some caching to Foo to speed it up. Using properties will let you do that without changing any code or tests outside of Foo.

Yes of course this is problematic code, but you just saved a lot of $$ fixing it quickly.

What if Foo is in a library that you have hundreds or thousands of users for? Well you saved yourself having to tell them to do an expensive refactor when they upgrade to the newest version of Foo.

The release notes have a lineitem about Foo instead of a paragraph porting guide.

Experienced Python programmers don't expect much from a.b=2 other than a.b==2, but they know even that may not be true. What happens inside the class is it's own business.

John La Rooy
  • 295,403
  • 53
  • 369
  • 502
2

Here's an old example of mine. I wrapped a C library which had functions like "void dt_setcharge(int atom_handle, int new_charge)" and "int dt_getcharge(int atom_handle)". I wanted at the Python level to do "atom.charge = atom.charge + 1".

The "property" decorator makes that easy. Something like:

class Atom(object):
    def __init__(self, handle):
        self.handle = handle
    def _get_charge(self):
        return dt_getcharge(self.handle)
    def _set_charge(self, charge):
        dt_setcharge(self.handle, charge)
    charge = property(_get_charge, _set_charge)

10 years ago, when I wrote this package, I had to use __getattr__ and __setattr__ which made it possible, but the implementation was a lot more error prone.

class Atom:
    def __init__(self, handle):
        self.handle = handle
    def __getattr__(self, name):
        if name == "charge":
            return dt_getcharge(self.handle)
        raise AttributeError(name)
    def __setattr__(self, name, value):
        if name == "charge":
            dt_setcharge(self.handle, value)
        else:
            self.__dict__[name] = value
Andrew Dalke
  • 14,889
  • 4
  • 39
  • 54
1

getters and setters are needed for many purposes, and are very useful because they are transparent to the code. Having object Something the property height, you assign a value as Something.height = 10, but if height has a getter and setter then at the time you do assign that value you can do many things in the procedures, like validating a min or max value, like triggering an event because the height changed, automatically setting other values in function of the new height value, all that may occur at the moment Something.height value was assigned. Remember, you don't need to call them in your code, they are auto executed at the moment you read or write the property value. In some way they are like event procedures, when the property X changes value and when the property X value is read.

Avenida Gez
  • 409
  • 5
  • 5
0

It is useful when you try to replace inheritance with delegation in refactoring. The following is a toy example. Stack was a subclass in Vector.

class Vector:
    def __init__(self, data):
        self.data = data

    @staticmethod
    def get_model_with_dict():
        return Vector([0, 1])


class Stack:
    def __init__(self):
        self.model = Vector.get_model_with_dict()
        self.data = self.model.data


class NewStack:
    def __init__(self):
        self.model = Vector.get_model_with_dict()

    @property
    def data(self):
        return self.model.data

    @data.setter
    def data(self, value):
        self.model.data = value


if __name__ == '__main__':
    c = Stack()
    print(f'init: {c.data}') #init: [0, 1]

    c.data = [0, 1, 2, 3]
    print(f'data in model: {c.model.data} vs data in controller: {c.data}') 
    #data in model: [0, 1] vs data in controller: [0, 1, 2, 3]

    c_n = NewStack()
    c_n.data = [0, 1, 2, 3]
    print(f'data in model: {c_n.model.data} vs data in controller: {c_n.data}') 
    #data in model: [0, 1, 2, 3] vs data in controller: [0, 1, 2, 3]

Note if you do use directly access instead of property, the self.model.data does not equal self.data, which is out of our expectation.

You can take codes before __name__=='__main__' as a library.

Tengerye
  • 1,796
  • 1
  • 23
  • 46