Explaining Properties in OOP Python

The aim of this page📝 is to simplistically illustrate Python’s concept of properties — the way to maintain/modify an object’s attributes, different from the usual getters and setters.

Here, I focus only on getter functions, also known as accessors, which are methods, conventionally named as verbs starting with get. What Python properties do is offer a different use of functionally similar implementation. For the object user/client, there is no difference between a simple instance attribute and a getter (which may compute any additional behavior before returning the value)

  • First, let’s look at the code
class RefrigeratedShippingContainer(ShippingContainer):

MAX_CELSIUS = 4.0

def __init__(self, owner_code, contents, *, celsius):
super().__init__(owner_code, contents)
if celsius > RefrigeratedShippingContainer.MAX_CELSIUS:
#^^^^^^^^^^^^^^^^^^^^^establishing a class_invariant: temperature must be below max
raise ValueError("Temperature too hot!")
#^^^^^^^^^^^^^^^^^^^^^break if arriving celsius argument has a value > 4.0
self.celsius = celsius
  • the initializer is establishing an important class invariant that the temperature should not be below some specified maximum
  • at this moment, the creation of an instance of the container object is the only way of defining the temperature
  • there are 2 major handicaps:
  • => 1. the temperature can be accessed only in its initial form
  • => 2. the temperature cannot be changed later
  • the term invariant is also used in the context of iteration/loops
  • it is a constant condition that must hold for the iteration to proceed or object to be instantiated
  • it is a constant condition for computing to proceed
  • breaking the loop invariant means breaking out of the loop
  • breaking the class invariant means breaking the program and raising an exception
  • as is common, you need to create what is commonly known as backing field/property
  • for more — on the etymology and comparative approach — see Explaining Backing Property in Kotlin and Java < Stack Overflow)
  • in any case, Python uses underscore in this point as an implementation detail
  • in addition, the underscore signifies that it is no longer to be considered to be part of the public interface
  • also known as “discouraging meddling”
  • for the sake of illustration of the value of the accessor, I am returning a dictionary containing both the max and current temperature
  • …in other words, you may return the value in a customized/enriched/transformed format impossible with a simple instance attribute
""" THIS IS CLASSICAL JAVA IMPLEMENTATION """

class RefrigeratedShippingContainer(ShippingContainer):

MAX_CELSIUS = 4.0

def __init__(self, owner_code, contents, *, celsius):
super().__init__(owner_code, contents)
if celsius > RefrigeratedShippingContainer.MAX_CELSIUS:
raise ValueError("Temperature too hot!")
self._celsius = celsius

def get_celsius(self):
return {"max_temperature": RefrigeratedShippingContainer.MAX_CELSIUS,
"current_temperature": self._celsius}

>>> r = oop_11_17_test.RefrigeratedShippingContainer.create_with_items("MRP", ["fish"], celsius=3)
>>> r.get_celsius()
{'max_temperature': 4.0, 'current_temperature': 3}
  • theoretically, you could make a mess by changing the value of an implementation detail
  • setters (mutators) are not discussed in this post
  • properties allow getters and setters to be exposed as seemingly regular attributes
  • ..in the Java world, attributes are called fields and there are no properties
  • ..in C#, there are properties similar to Python, see Properties Vs Fields In C#
  • anyways, properties have elevated capabilities compared to attributes
  • same as with static and class methods — in Python — decorators are the basis of the property system
  • instead of getters and setters, we can do something like this
class RefrigeratedShippingContainer(ShippingContainer):

MAX_CELSIUS = 4.0

def __init__(self, owner_code, contents, *, celsius):
super().__init__(owner_code, contents)
if celsius > RefrigeratedShippingContainer.MAX_CELSIUS:
raise ValueError("Temperature too hot!")
self._celsius = celsius

@property
def celsius(self):
return {"max_temperature": RefrigeratedShippingContainer.MAX_CELSIUS,
"current_temperature": self._celsius}

>>> r = oop_11_17_test.RefrigeratedShippingContainer.create_with_items("MRP", ["fish"], celsius=3)
>>> r.celsius
{'max_temperature': 4.0, 'current_temperature': 3}
  • see, the access of a property is identical to the access to an attribute
  • … → no () is required as opposed to the JAVA implementation of a getter
  • … → importantly/unusually, the name of a function within the implementation is a noun
  • … yet, within the implementation, property acts as a fully-fledged function
  • which may be a useful object feature, in case you are looking for creating a read-only attribute ⟹ define a property for accessing a value, but without a setter
>>> r.celsius = 5Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

can't set attribute

--

--

Infrastructure Support Engineer/Technical Writer (snowplow.io) with a passion for Python/writing documentation. More about me: https://pavol.kutaj.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Pavol Kutaj

Infrastructure Support Engineer/Technical Writer (snowplow.io) with a passion for Python/writing documentation. More about me: https://pavol.kutaj.com