Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python - what's the difference between @property and setting in __init__()?

Couldn't quite get a straight answer from other threads on this one:

In Python, what's the main difference between using

class Foo(object):
  def __init__(self, x):
    self.x = x

and

class Foo(object):
  def __init__(self, x):
    self._x = x

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

By the looks of it using @property in this way makes x read-only.. but maybe someone has a better answer? Thanks /Fred

like image 755
Fred Avatar asked Apr 06 '17 21:04

Fred


2 Answers

For your example, yes this allows you to have read-only attributes. Further, properties can allow you to seem to have more attributes than you actually do. Consider a circle with .radius and .area. The area can be calculated based on the radius, rather than having to store both

import math

class Circle(object):
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):
        return math.pi * (self.radius ** 2)
like image 151
Ryan Haining Avatar answered Sep 23 '22 12:09

Ryan Haining


The property decorator implementation uses the descriptor protocol, which is how we do data encapsulation in Python OOP. A descriptor is:

In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol. Those methods are __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.

Typically, in other OOP languages you use getters and setters. You'll frequently see people coming from, say, Java writing a Python class like this:

class A(object):
    def __init__(self, x, y):
        self._x = x
        self._y = y
    def getX(self):
        return self._x
    def getY(self):
        return self._y
    def setX(self, x):
        self._x = x
    def setY(self, y):
        self._y = y
    def some_method_that_uses_attributes(self):
        return self.getX() + self.getY()

This is very much not how you would do things in Python. The point of getters and setters is to provide data encapsulation. We encapsulate access to a data attribute by wrapping that in a getter and setter. Then, if we ever want to add something, say, make sure x is never set to a value below 10 (as a contrived example), we simply change the way setX is implemented, and we don't have to modify the rest of our code. In Python, however, we would write the above class as follows:

class A(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def some_method_that_uses_attributes(self):
        return self.x + self.y

A person coming from Java might recoil in horror: "You aren't properly encapsulating your class! This will become a maintenance nightmare!"

Nope, because we have descriptors/properties:

class A(object):
    def __init__(self, x, y):
        self._x = x
        self.y = y
    @property
    def x(self):
        return self._x
    @x.setter
    def x(self, val):
        if val > 10:
            self._x = val
        else:
            raise ValueError("x must be greater than 10")
    def some_method_that_uses_attributes(self):
        return self.x + self.y

And now, we don't have to refractor every method that uses self.x, like some_method_that_uses_attributes. And more importantly, clients of our class don't have a broken interface when we make this change! This is nice because it lets us avoid writing a bunch of boilerplate code, and implementing descriptors is relatively straightfoward in the cases where we do need it. Also, this makes our code nice and pretty, without self.get_this() and self.set_that(3) all over the code, and the much more readable self.this and self.that = 3

like image 45
juanpa.arrivillaga Avatar answered Sep 21 '22 12:09

juanpa.arrivillaga