152

I am interested in how to use @property in Python. I've read the python docs and the example there, in my opinion, is just a toy code:

class C(object):
    def __init__(self):
        self._x = None

    @property
    def x(self):
        """I'm the 'x' property."""
        return self._x

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

    @x.deleter
    def x(self):
        del self._x

I do not know what benefit(s) I can get from wrapping the _x filled with the property decorator. Why not just implement as:

class C(object):
    def __init__(self):
        self.x = None

I think, the property feature might be useful in some situations. But when? Could someone please give me some real-world examples?

codeforester
  • 39,467
  • 16
  • 112
  • 140
xiao 啸
  • 6,350
  • 9
  • 40
  • 51
  • 11
    This is the best and cleanest explanation I've found about property decorator [[click here](http://www.programiz.com/python-programming/property)] – Anubis Apr 11 '16 at 16:31
  • 2
    @Anubis in the last example in the link you provided, setting c = Celsius(-500) didn't throw any ValueError, which I think is not achieving the intended result. – Sajuuk Jun 14 '17 at 03:55
  • Agree with @Anubis. It's implemented correctly here: https://www.python-course.eu/python3_properties.php – anon01 Mar 26 '18 at 22:01

10 Answers10

97

Other examples would be validation/filtering of the set attributes (forcing them to be in bounds or acceptable) and lazy evaluation of complex or rapidly changing terms.

Complex calculation hidden behind an attribute:

class PDB_Calculator(object):
    ...
    @property
    def protein_folding_angle(self):
        # number crunching, remote server calls, etc
        # all results in an angle set in 'some_angle'
        # It could also reference a cache, remote or otherwise,
        # that holds the latest value for this angle
        return some_angle

>>> f = PDB_Calculator()
>>> angle = f.protein_folding_angle
>>> angle
44.33276

Validation:

class Pedometer(object)
    ...
    @property
    def stride_length(self):
        return self._stride_length

    @stride_length.setter
    def stride_length(self, value):
        if value > 10:
            raise ValueError("This pedometer is based on the human stride - a stride length above 10m is not supported")
        else:
            self._stride_length = value
benosteen
  • 1,366
  • 7
  • 5
  • 1
    I like the PDB_Calculator example -- complicated things are abstracted away, the whole thing works and the user can enjoy simplicity! – Adam Kurkiewicz Jun 05 '13 at 20:33
  • 2
    possibly, from pro standpoint, these are very good examples. But, as a noobie, i find this examples quite ineffective. my bad ... :( – kmonsoor Mar 11 '14 at 08:30
85

One simple use case will be to set a read only instance attribute , as you know leading a variable name with one underscore _x in python usually mean it's private (internal use) but sometimes we want to be able to read the instance attribute and not to write it so we can use property for this:

>>> class C(object):

        def __init__(self, x):
            self._x = x

        @property
        def x(self):
            return self._x

>>> c = C(1)
>>> c.x
1
>>> c.x = 2
AttributeError        Traceback (most recent call last)

AttributeError: can't set attribute
mouad
  • 67,571
  • 18
  • 114
  • 106
  • 11
    One can still set `c._x`, if the user wants. Python has actually not real private attributes. –  Oct 13 '16 at 23:03
22

Take a look at this article for a very practical use. In short, it explains how in Python you can usually ditch explicit getter/setter method, since if you come to need them at some stage you can use property for a seamless implementation.

Eli Bendersky
  • 263,248
  • 89
  • 350
  • 412
16

One thing I've used it for is caching slow-to-look-up, but unchanging, values stored in a database. This generalises to any situation where your attributes require computation or some other long operation (eg. database check, network communication) which you only want to do on demand.

class Model(object):

  def get_a(self):
    if not hasattr(self, "_a"):
      self._a = self.db.lookup("a")
    return self._a

  a = property(get_a)

This was in a web app where any given page view might only need one particular attribute of this kind, but the underlying objects themselves might have several such attributes - initialising them all on construction would be wasteful, and properties allow me to be flexible in which attributes are lazy and which aren't.

detly
  • 29,332
  • 18
  • 93
  • 152
  • 1
    Can't you use `@cached_property` for this? – adarsh Nov 11 '14 at 15:27
  • @adarsh - Sounds interesting. Where is that? – detly Nov 11 '14 at 21:41
  • I've been using it but I forgot that it wasn't a built in, but you can use it with this, https://pypi.python.org/pypi/cached-property/0.1.5 – adarsh Nov 11 '14 at 21:54
  • 2
    Interesting. I think it was first published after this answer, but anyone reading this should probably use it instead. – detly Nov 11 '14 at 21:58
  • 1
    `cached_property` is now in the standard library via [functools](https://docs.python.org/3/library/functools.html#functools.cached_property) – tantrix Mar 04 '22 at 02:47
10

Reading through the answers and comments, the main theme seems to be the answers seem to be missing a simple, yet useful example. I have included a very simple one here that demonstrates the simple use of the @property decorator. It's a class that allows a user to specify and get distance measurement using a variety of different units, i.e. in_feet or in_metres.

class Distance(object):
    def __init__(self):
        # This private attribute will store the distance in metres
        # All units provided using setters will be converted before
        # being stored
        self._distance = 0.0

    @property
    def in_metres(self):
        return self._distance

    @in_metres.setter
    def in_metres(self, val):
        try:
            self._distance = float(val)
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_feet(self):
        return self._distance * 3.2808399

    @in_feet.setter
    def in_feet(self, val):
        try:
            self._distance = float(val) / 3.2808399
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

    @property
    def in_parsecs(self):
        return self._distance * 3.24078e-17

    @in_parsecs.setter
    def in_parsecs(self, val):
        try:
            self._distance = float(val) / 3.24078e-17
        except:
            raise ValueError("The input you have provided is not recognised "
                             "as a valid number")

Usage:

>>> distance = Distance()
>>> distance.in_metres = 1000.0
>>> distance.in_metres
1000.0
>>> distance.in_feet
3280.8399
>>> distance.in_parsecs
3.24078e-14
Mike
  • 439
  • 1
  • 6
  • 13
  • for me personally, the best examples of getters/setters are of showing people the kind of changes you need to make later on but obviously, that takes a little more time. – dtc Sep 01 '17 at 00:16
  • Yes, this is a perfect example where an attribute is a derivative of some other attribute. You want this attribute to be automatically updated when the other updates. – Chen Lizi Apr 10 '21 at 04:36
8

Property is just an abstraction around a field which give you more control on ways that a specific field can be manipulated and to do middleware computations. Few of the usages that come to mind is validation and prior initialization and access restriction

@property
def x(self):
    """I'm the 'x' property."""
    if self._x is None:
        self._x = Foo()

    return self._x
Stanislav Ageev
  • 778
  • 4
  • 10
6

Yes, for the original example posted, the property will work exactly the same as simply having an instance variable 'x'.

This is the best thing about python properties. From the outside, they work exactly like instance variables! Which allows you to use instance variables from outside the class.

This means your first example could actually use an instance variable. If things changed, and then you decide to change your implementation and a property is useful, the interface to the property would still be the same from code outside the class. A change from instance variable to property has no impact on code outside the class.

Many other languages and programming courses will instruct that a programmer should never expose instance variables, and instead use 'getters' and 'setters' for any value to be accessed from outside the class, even the simple case as quoted in the question.

Code outside the class with many languages (e.g. Java) use

object.get_i()
    #and
object.set_i(value)

#in place of (with python)
object.i
    #and 
object.i = value

And when implementing the class there are many 'getters' and 'setters' that do exactly as your first example: replicate a simply instance variable. These getters and setters are required because if the class implementation changes, all the code outside the class will need to change. But python properties allow code outside the class to be the same as with instance variables. So code outside the class does not need to be changed if you add a property, or have a simple instance variable. So unlike most Object Oriented languages, for your simple example you can use the instance variable instead of 'getters' and 'setters' that are really not needed, secure in the knowledge that if you change to a property in the future, the code using your class need not change.

This means you only need create properties if there is complex behaviour, and for the very common simple case where, as described in the question, a simple instance variable is all that is needed, you can just use the instance variable.

innov8
  • 2,093
  • 2
  • 24
  • 31
6

another nice feature of properties over using setters and getters it that they allow you to continue to use OP= operators (eg +=, -=, *= etc) on your attributes while still retaining any validation, access control, caching, etc that the setters and getters would supply.

for example if you wrote the class Person with a setter setage(newage), and a getter getage(), then to increment the age you would have to write:

bob = Person('Robert', 25)
bob.setage(bob.getage() + 1)

but if you made age a property you could write the much cleaner:

bob.age += 1
quizdog
  • 473
  • 3
  • 8
5

The short answer to your question, is that in your example, there is no benefit. You should probably use the form that doesn't involve properties.

The reason properties exists, is that if your code changes in the future, and you suddenly need to do more with your data: cache values, protect access, query some external resource... whatever, you can easily modify your class to add getters and setters for the data without changing the interface, so you don't have to find everywhere in your code where that data is accessed and change that too.

SpoonMeiser
  • 19,918
  • 8
  • 50
  • 68
4

Something that many do not notice at first is that you can make your own subclasses of property. This I have found very useful for exposing read only object attributes or attribute you can read and write but not remove. It is also an excellent way to wrap functionality like tracking modifications to object fields.

class reader(property):
    def __init__(self, varname):
        _reader = lambda obj: getattr(obj, varname)
        super(reader, self).__init__(_reader)

class accessor(property):
    def __init__(self, varname, set_validation=None):
        _reader = lambda obj: getattr(obj, varname)
        def _writer(obj, value):
            if set_validation is not None:
               if set_validation(value):
                  setattr(obj, varname, value)
        super(accessor, self).__init__(_reader, _writer)

#example
class MyClass(object):
   def __init__(self):
     self._attr = None

   attr = reader('_attr')
  • I like this. Am I reading this correctly in that reader is read only while accessor is read/write without deletion capability? How would you add data validation though? I'm fairly new to Python but I'm thinking there is probably a way to add a callback to the `attr = reader('_attr')` line or some form of prechecking like `attr = if self.__isValid(value): reader('_attr')`. Suggestions? – Gabe Spradlin Sep 23 '14 at 15:57
  • Sorry just realized I was asking about data validation for a read only variable. But obviously this would only apply to the setter portion of the accessor class. So change `attr = reader('_attr')` to `attr = accessor('_attr')`. Thanks – Gabe Spradlin Sep 23 '14 at 16:08
  • You are right that if you wanted validation then you would add a function to validate and raise Exception if invalid (or whatever behavior you liked including doing nothing) to the __init__. I modified the above with one possible pattern. The validator should return True|False to guide whether the set happens or not. –  Oct 15 '14 at 23:33