Using @property versus getters and setters [duplicate]
Asked Answered
D

13

798

What advantages does the @property notation hold over the classic getter+setter? In which specific cases/situations should a programmer choose to use one over the other?

With properties:

class MyClass(object):
    @property
    def my_attr(self):
        return self._my_attr

    @my_attr.setter
    def my_attr(self, value):
        self._my_attr = value

Without properties:

class MyClass(object):
    def get_my_attr(self):
        return self._my_attr

    def set_my_attr(self, value):
        self._my_attr = value
Doubles answered 7/7, 2011 at 22:42 Comment(0)
B
662

Prefer properties. It's what they're there for.

The reason is that all attributes are public in Python. Starting names with an underscore or two is just a warning that the given attribute is an implementation detail that may not stay the same in future versions of the code. It doesn't prevent you from actually getting or setting that attribute. Therefore, standard attribute access is the normal, Pythonic way of, well, accessing attributes.

The advantage of properties is that they are syntactically identical to attribute access, so you can change from one to another without any changes to client code. You could even have one version of a class that uses properties (say, for code-by-contract or debugging) and one that doesn't for production, without changing the code that uses it. At the same time, you don't have to write getters and setters for everything just in case you might need to better control access later.

Binnings answered 7/7, 2011 at 23:6 Comment(15)
Attribute names with a double underscore are handled specially by Python; it's not just a mere convention. See docs.python.org/py3k/tutorial/classes.html#private-variablesPhosphorylase
They are handled differently, but that doesn't keep you from accessing them. PS: AD 30 C0Binnings
and because "@" characters are ugly in python code, and the dereferencing @decorators gives the same feeling like spaghetti-code.Entourage
I don't agree. How is structured code equal to spaghetti code? Python is a beautiful language. But would be even better with better support for simple things like proper encapsulation and structured classes.Subotica
While I agree in most cases, be careful about hiding slow methods behind a @property decorator. The user of your API expects that property access performs like variable access, and straying too far away from that expectation can make your API unpleasant to use.Threedecker
Since this is the accepted answer, It is worth mentioning how one would leverage Descriptors for @property reuseVentage
So when is it worth using the (single) leading underscore for an attribute?Buckingham
@Buckingham Whenever you'd declare something private in other languages. I'm not really sure why this answer brings up access control in the first paragraph. The second paragraph is important though: a feature allowing you to transparently call a method on getting or setting is really, really nice.Comedy
But ins't @property decorator actually storing the result of computation so that we can access it over and over again without having to recalculate? I thought that was the biggest benefit...Presignify
@PiotrKamoda No, @property calls the getter on each access. You can however use a different decorator to do what you're thinking of.Binnings
-1 for encouraging the use of properties while failing to mention that they are wasteful and needlessly complex in most cases. Direct attribute access is usually better. See the answer from @6502.Northman
The question wasn't about direct attribute access vs. properties. It is obvious that more code takes more time to execute than less code.Binnings
It is obvious that the person asking the question needed guidance, not a pedantic answer that omits directly relevant and important information. I'm not saying your answer is incorrect. I'm saying it fails to address the most important aspects of the question, and furthermore leads readers away from the right approach. Update your answer and I'll happily remove my downvote.Northman
"be careful about hiding slow methods behind a @property decorator." This is true in any OOP language.Ki
@taryinn, True, but it's all the more important if your language makes your getter look like a member/attribute.Lyall
P
176

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.

Phosphorylase answered 7/7, 2011 at 23:8 Comment(11)
getters and setters. are more prominent in Java than C++. Probably because C++ has references, and if you really need properties, you can fake them with templates anywayFetching
@6502, when you said “[…] pointless classes everywhere where e.g. a simple tuple would do”: the advantage of a class over a tuple, is that a class instance provides explicit names to access its parts, while a tuple don't. Names are better at readability and avoiding errors, than tuples subscripting, especially when this is to be passed outside of the current module.Asphaltite
@Hibou57: I'm not saying class are useless. But sometimes a tuple is more than enough. The problems is however that who comes from say Java or C++ has no choice but creating classes for everything because other possibilities are just annoying to use in those lanaguages. Another typical symptom of Java/C++ programming using Python is creating abstract classes and complex class hierarchies for no reason where in Python you could just use independent classes thanks to duck typing.Phosphorylase
@Asphaltite for that u can also use namedtuple: doughellmann.com/PyMOTW/collections/namedtuple.htmlKaylor
By the way, C++ does have tuples. They're just a pain to use.Fetching
@JonathonReinhart: it IS in the standard library since 2.6 ... see docs.python.org/2/library/collections.htmlPhosphorylase
It's also possible to use classes with defined __slots__ if you want a class that could easily be a tuple. You can define methods as well, and it's more memory efficient.Unscratched
When "eventually migrating to a property if needed" it's quite likely that you do break code using your classes. Properties often introduce restrictions - any code that didn't expect these restrictions will break as soon as you introduce them.Gabriellagabrielle
I'd say the Pythonic style you're recommending is "code from people used to writing C structs". The only reasons not to add protections to data members is performance. A small bit of notation, just once in the class is no big deal. There is no notational difference for the user. Messing with object state can cause problems that won't be immediately detected, resulting in munged databases, puzzled users and a hefty price to pay for saving a few millisecond in a typical case. Yes, there are exceptions...but those are the cases that should be treated exceptionally. (imho, of course)Nostology
@MikeHousky: python code doesn't need getXXX or setXXX because it has properties. And doesn't need properties unless you want to intercept reads or writes to an attribute. If you need to trace read or write you can do that... but if you do that beforehand using properties without need then you're not doing anyone a favor but just cluttering the code with nonsense complexity. You can add properties later if you find e.g. that having lazy computation of the attribute is a good thing in the specific case. The reason for not adding pointless properties is readability.Phosphorylase
@Phosphorylase My points were that there isn't much clutter, and that you often discover at significant cost when "without need" isn't really true. I'll stick with "safe, until performance requires some danger". To morph one of Guido's quotes: Classes are used more often than they are written.Nostology
C
125

Using properties lets you begin with normal attribute accesses and then back them up with getters and setters afterwards as necessary.

Calcify answered 7/7, 2011 at 22:57 Comment(1)
@GregKrsak It seems strange because it is. The "consenting adults thing" was a python meme from before properties were added. It was the stock response to folks who'd complain about the lack of access modifiers. When properties were added, suddenly encapsulation become desirable. The same thing happened with abstract base classes. "Python was always at war with encapsulation-breaking. Freedom is slavery. Lambdas should only fit on one line."Whiggism
S
75

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:

def set_email(self, value):
    if '@' not in value:
        raise Exception("This doesn't look like an email address.")
    self._email = value

def get_email(self):
    return self._email

email = property(get_email, set_email)

Here's a brief article that introduces the topic of getters and setters in Python.

Submissive answered 7/7, 2011 at 22:52 Comment(7)
Thanks, mac, but that's now the point of the question (sorry if it is unclear). It's not about @property decorator, it's about properties vs. getters/setters in general.Doubles
@BasicWolf - I thought it was implicitly clear I am on the property side of the fence! :) But I add a para to my answer to clarify that.Submissive
HINT: The word "always" is a hint that the author is attempting to convince you with an assertion, not an argument. So is the presence of boldface font. (I mean, if you see CAPS instead, then -- whoa -- it must be right.) Look, the "property" feature happens to be different from Java (Python's de facto nemesis for some reason), and therefore Python's community groupthink declares it to be better. In reality, properties violate the "Explicit is better than implicit" rule, but no one wants to admit it. It made it into the language, so now it is declared "Pythonic" via a tautological argument.Kos
@superbatfish - Python is a strongly idiomatic language: what you are referring to as groupthink is indeed just convention (and therefore the "pythonic way"). Sorry if my answer hurts your feelings, that was not the meaning. Cheer up! :)Submissive
No feelings hurt. :-P I'm just trying to point out that "Pythonic" conventions are inconsistent in this case: "Explicit is better than implicit" is in direct conflict with using a property. (It looks like a simple assignment, but it calls a function.) Therefore, "Pythonic" is essentially a meaningless term, except by the tautological definition: "Pythonic conventions are things that we've defined to be Pythonic."Kos
Now, the idea of having a set of conventions that follow a theme is great. If such a set of conventions did exist, then you could use it as a set of axioms to guide your thinking, not merely a lengthy checklist of tricks to memorize, which is significantly less useful. Axioms could be used for extrapolation, and help you approach problems no one has seen yet. It's a shame that the property feature threatens to render the idea of Pythonic axioms nearly worthless. So all we're left with is a checklist.Kos
I don't agree. I prefer properties in most situations, but when you want to emphasize that setting something has side effects other than modifying the self object, explicit setters may be helpful. For instance, user.email = "..." doesn't look like it can raise an exception because it looks like just setting an attribute, whereas user.set_email("...") makes it clear that there could be side effects like exceptions.Usual
N
70

[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.

class Example(object):
    def __init__(self, x=None, y=None):
        self.x = x
        self.y = y

    def getX(self):
        return self.x or self.defaultX()

    def getY(self):
        return self.y or self.defaultY()

    def setX(self, x):
        self.x = x

    def setY(self, y):
        self.y = y

    def defaultX(self):
        return someDefaultComputationForX()

    def defaultY(self):
        return someDefaultComputationForY()

You may be wondering why I didn't call defaultX and defaultY in the object's __init__ method. The reason is that for our case I want to assume that the someDefaultComputation methods return values that vary over time, say a timestamp, and whenever x (or y) is not set (where, for the purpose of this example, "not set" means "set to None") I want the value of x's (or y's) default computation.

So this is lame for a number of reasons describe above. I'll rewrite it using properties:

class Example(object):
    def __init__(self, x=None, y=None):
        self._x = x
        self._y = y

    @property
    def x(self):
        return self.x or self.defaultX()

    @x.setter
    def x(self, value):
        self._x = value

    @property
    def y(self):
        return self.y or self.defaultY()

    @y.setter
    def y(self, value):
        self._y = value

    # default{XY} as before.

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 and y values in __init__. (Well, of course we could add them using setattr() 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:

e.x[a,b,c] = 10
e.x[d,e,f] = 20

for example. The closest we can get is to override the assignment to imply some special semantics:

e.x = [a,b,c,10]
e.x = [d,e,f,30]

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__ or setattr 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:

def x(self, *args):
    return defaultX()

and be able to then do:

print e.x     -> The default at time T0
e.x = 1
print e.x     -> 1
e.x = None
print e.x     -> The default at time T1

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 the method if that value is not set.

Let's call it an UberProperty.

class UberProperty(object):

    def __init__(self, method):
        self.method = method
        self.value = None
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def clearValue(self):
        self.value = None
        self.isSet = False

I assume method here is a class method, value is the value of the UberProperty, and I have added isSet because None 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).

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

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:

def uberProperty(f):
    return UberProperty(f)

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 the method reference:

class UberProperty(object):

    def __init__(self, method):
        self.method = method

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:

class Example(object):

    @uberProperty
    def x(self):
        return defaultX()

print Example.x     <__main__.UberProperty object at 0x10e1fb8d0>
print Example().x   <__main__.UberProperty object at 0x10e1fb8d0>

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 the x attribute.

class BoundUberProperty(object):
    def __init__(self, obj, uberProperty):
        self.obj = obj
        self.uberProperty = uberProperty
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def getValue(self):
        return self.value if self.isSet else self.uberProperty.method(self.obj)

    def clearValue(self):
        del self.value
        self.isSet = False

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 type UberProperty.

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.

class UberObject(object):
    def __init__(self):
        for k in dir(self):
            v = getattr(self, k)
            if isinstance(v, UberProperty):
                v = BoundUberProperty(self, v)
                setattr(self, k, v)

We add this, change our example to inherit from UberObject, and ...

e = Example()
print e.x               -> <__main__.BoundUberProperty object at 0x104604c90>

After modifying x to be:

@uberProperty
def x(self):
    return *datetime.datetime.now()*

We can run a simple test:

print e.x.getValue()
print e.x.getValue()
e.x.setValue(datetime.date(2013, 5, 31))
print e.x.getValue()
e.x.clearValue()
print e.x.getValue()

And we get the output we wanted:

2013-05-31 00:05:13.985813
2013-05-31 00:05:13.986290
2013-05-31
2013-05-31 00:05:13.986310

(Gee, I'm working late.)

Note that I have used getValue, setValue, and clearValue 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.

    • So we either force it be called somewhere or we prevent it from being implemented.
    • We'll see how to do this with a metaclass.
  • We need to make sure we handle the common case where someone 'aliases' a function to something else, such as:

      class Example(object):
          @uberProperty
          def x(self):
              ...
    
          y = x
    
  • We need e.x to return e.x.getValue() by default.

    • What we'll actually see is this is one area where the model fails.
    • It turns out we'll always need to use a function call to get the value.
    • But we can make it look like a regular function call and avoid having to use 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 in e.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:

import datetime

class UberObject(object):
    def uberSetter(self, value):
        print 'setting'

    def uberGetter(self):
        return self

    def __init__(self):
        for k in dir(self):
            v = getattr(self, k)
            if isinstance(v, UberProperty):
                v = BoundUberProperty(self, v)
                setattr(self, k, v)


class UberProperty(object):
    def __init__(self, method):
        self.method = method

class BoundUberProperty(object):
    def __init__(self, obj, uberProperty):
        self.obj = obj
        self.uberProperty = uberProperty
        self.isSet = False

    def setValue(self, value):
        self.value = value
        self.isSet = True

    def getValue(self):
        return self.value if self.isSet else self.uberProperty.method(self.obj)

    def clearValue(self):
        del self.value
        self.isSet = False

    def uberProperty(f):
        return UberProperty(f)

class Example(UberObject):

    @uberProperty
    def x(self):
        return datetime.datetime.now()

[1] I may be behind on whether this is still the case.

Narbonne answered 31/5, 2013 at 4:27 Comment(6)
Yes, this is 'tldr'. Can you please summarize what you're trying to do here?Kling
@Adam return self.x or self.defaultX() this is dangerous code. What happens when self.x == 0?Saccharin
FYI, you can make it so that you can parameterise the getter, kind of. It would involve making the variable a custom class, of which you've overridden the __getitem__ method. It would be weird though, as you'd then have completely non standard python.Thao
@KellyThomas Just trying to keep the example simple. To do it right you'd need to create and delete the x dict entry altogether, because even a None value might have been specifically set. But yes, you're absolutely right this is something you would need to consider in a production use case.Narbonne
Java-like getters allow you do exactly the same computation, don't they?Skatole
This is impressive... if I'm following your line of thought, there is a pattern like it in python, called a descriptor, which was made much easier with the __set_name__ method in 3.6.Ischium
S
27

I think both have their place. One issue with using @property is that it is hard to extend the behaviour of getters or setters in subclasses using standard class mechanisms. The problem is that the actual getter/setter functions are hidden in the property.

You can actually get hold of the functions, e.g. with

class C(object):
    _p = 1
    @property
    def p(self):
        return self._p
    @p.setter
    def p(self, val):
        self._p = val

you can access the getter and setter functions as C.p.fget and C.p.fset, but you can't easily use the normal method inheritance (e.g. super) facilities to extend them. After some digging into the intricacies of super, you can indeed use super in this way:

# Using super():
class D(C):
    # Cannot use super(D,D) here to define the property
    # since D is not yet defined in this scope.
    @property
    def p(self):
        return super(D,D).p.fget(self)

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for D'
        super(D,D).p.fset(self, val)

# Using a direct reference to C
class E(C):
    p = C.p

    @p.setter
    def p(self, val):
        print 'Implement extra functionality here for E'
        C.p.fset(self, val)

Using super() is, however, quite clunky, since the property has to be redefined, and you have to use the slightly counter-intuitive super(cls,cls) mechanism to get an unbound copy of p.

Supplement answered 23/12, 2011 at 12:9 Comment(0)
A
26

Using properties is to me more intuitive and fits better into most code.

Comparing

o.x = 5
ox = o.x

vs.

o.setX(5)
ox = o.getX()

is to me quite obvious which is easier to read. Also properties allows for private variables much easier.

Aquiline answered 7/7, 2011 at 22:53 Comment(0)
D
13

I feel like properties are about letting you get the overhead of writing getters and setters only when you actually need them.

Java Programming culture strongly advise to never give access to properties, and instead, go through getters and setters, and only those which are actually needed. It's a bit verbose to always write these obvious pieces of code, and notice that 70% of the time they are never replaced by some non-trivial logic.

In Python, people actually care for that kind of overhead, so that you can embrace the following practice :

  • Do not use getters and setters at first, when if they not needed
  • Use @property to implement them without changing the syntax of the rest of your code.
Design answered 7/7, 2011 at 23:2 Comment(3)
"and notice that 70% of the time they are never replaced by some non-trivial logic." -- that's a rather specific number, is it coming from someplace, or are you intending it as a handwavy "the vast majority" kinda thing (I'm not being facetious, if there's a study that quantifies that number, I'd be genuinely interested in reading it)Copilot
Oh no sorry. It does sound like I have some study to backup this number, but I only meant it as "most of the time".Design
It's not that people care about the overhead, it's that in Python you can change from direct access to accessor methods without changing the client code, so you have nothing to lose by directly exposing properties at first.Pittance
C
12

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:

class Account(object):
    @property
    def email(self):
        return self._email

    @email.setter
    def email(self, value):
        if '@' not in value:
            raise ValueError('Invalid email address.')
        self._email = value

then the user of the class does not expect that assigning a value to the property could cause an exception:

a = Account()
a.email = 'badaddress'
--> ValueError: Invalid email address.

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:

  • because defining them for all properties in advance is very time consuming,
  • makes the amount of code unnecessarily longer, which makes understanding and maintaining the code more difficult,
  • if you were define them for properties only as needed, the interface of the class would change, hurting all users of the class

Instead of properties and getters/setters I prefer doing the complex logic in well defined places such as in a validation method:

class Account(object):
    ...
    def validate(self):
        if '@' not in self.email:
            raise ValueError('Invalid email address.')

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.

Crowbar answered 3/4, 2013 at 9:28 Comment(1)
@Crowbar I think you misunderstand the concept of properties. Although you can validate the value while setting the property, there is no need to do it. You can have both properties and a validate() method in a class. A property is simply used when you have a complex logic behind a simple obj.x = y assignment, and it is up to what the logic is.Doubles
C
11

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:

  • validate
  • alter data
  • duck type (coerce type to another type)

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.

Chafee answered 1/6, 2015 at 6:54 Comment(0)
S
9

In complex projects I prefer using read-only properties (or getters) with explicit setter function:

class MyClass(object):
...        
@property
def my_attr(self):
    ...

def set_my_attr(self, value):
    ...

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:

my_object.my_atttr = 4.

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, while my_object.my_attr = 4. is actually the cause of slowdown:

def slow_function(my_object):
    my_object.my_attr = 4.
    my_object.do_something()
Saran answered 25/5, 2017 at 1:52 Comment(1)
1) is an important point, and the main reason I mostly avoid using setters, especially in more complex code.Greggrega
C
8

Both @property and traditional getters and setters have their advantages. It depends on your use case.

Advantages of @property

  • You don't have to change the interface while changing the implementation of data access. When your project is small, you probably want to use direct attribute access to access a class member. For example, let's say you have an object foo of type Foo, which has a member num. Then you can simply get this member with num = foo.num. As your project grows, you may feel like there needs to be some checks or debugs on the simple attribute access. Then you can do that with a @property within the class. The data access interface remains the same so that there is no need to modify client code.

    Cited from PEP-8:

    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.

  • Using @property for data access in Python is regarded as Pythonic:

    • It can strengthen your self-identification as a Python (not Java) programmer.

    • It can help your job interview if your interviewer thinks Java-style getters and setters are anti-patterns.

Advantages of traditional getters and setters

  • Traditional getters and setters allow for more complicated data access than simple attribute access. For example, when you are setting a class member, sometimes you need a flag indicating where you would like to force this operation even if something doesn't look perfect. While it is not obvious how to augment a direct member access like foo.num = num, You can easily augment your traditional setter with an additional force parameter:

    def Foo:
        def set_num(self, num, force=False):
            ...
    
  • Traditional getters and setters make it explicit that a class member access is through a method. This means:

    • What you get as the result may not be the same as what is exactly stored within that class.

    • Even if the access looks like a simple attribute access, the performance can vary greatly from that.

    Unless your class users expect a @property hiding behind every attribute access statement, making such things explicit can help minimize your class users surprises.

  • As mentioned by @NeilenMarais and in this post, extending traditional getters and setters in subclasses is easier than extending properties.

  • Traditional getters and setters have been widely used for a long time in different languages. If you have people from different backgrounds in your team, they look more familiar than @property. Also, as your project grows, if you may need to migrate from Python to another language that doesn't have @property, using traditional getters and setters would make the migration smoother.

Caveats

  • Neither @property nor traditional getters and setters makes the class member private, even if you use double underscore before its name:

    class Foo:
        def __init__(self):
            self.__num = 0
    
        @property
        def num(self):
            return self.__num
    
        @num.setter
        def num(self, num):
            self.__num = num
    
        def get_num(self):
            return self.__num
    
        def set_num(self, num):
            self.__num = num
    
    foo = Foo()
    print(foo.num)          # output: 0
    print(foo.get_num())    # output: 0
    print(foo._Foo__num)    # output: 0
    
Computerize answered 6/9, 2018 at 10:14 Comment(0)
P
7

Here is an excerpts from "Effective Python: 90 Specific Ways to Write Better Python" (Amazing book. I highly recommend it).

Things to Remember

✦ Define new class interfaces using simple public attributes and avoid defining setter and getter methods.

✦ Use @property to define special behavior when attributes are accessed on your objects, if necessary.

✦ Follow the rule of least surprise and avoid odd side effects in your @property methods.

✦ Ensure that @property methods are fast; for slow or complex work—especially involving I/O or causing side effects—use normal methods instead.

One advanced but common use of @property is transitioning what was once a simple numerical attribute into an on-the-fly calculation. This is extremely helpful because it lets you migrate all existing usage of a class to have new behaviors without requiring any of the call sites to be rewritten (which is especially important if there’s calling code that you don’t control). @property also provides an important stopgap for improving interfaces over time.

I especially like @property because it lets you make incremental progress toward a better data model over time.
@property is a tool to help you address problems you’ll come across in real-world code. Don’t overuse it. When you find yourself repeatedly extending @property methods, it’s probably time to refactor your class instead of further paving over your code’s poor design.

✦ Use @property to give existing instance attributes new functionality.

✦ Make incremental progress toward better data models by using @property.

✦ Consider refactoring a class and all call sites when you find yourself using @property too heavily.

Promissory answered 26/1, 2020 at 19:42 Comment(1)
Or better yet consider refactoring if you are using a class as a dictionary with dot notation. If your members aren't tightly coupled to your methods why are you using a class in the first place.Ki

© 2022 - 2024 — McMap. All rights reserved.