9.2. Accessor Property
Disable attribute modification
Logging value access
Check boundary
Raise exceptions such as
ValueError
orTypeError
Check argument type
In Python you can also define a setter method for a property using the
@propertyname.setter
decorator. The setter method should have the same
name as the property, followed by .setter
, and it should take a single
parameter that represents the new value of the property.
Here's an example of using the @propertyname.setter
decorator to define a
read-write property of a class:
>>> class MyClass:
... x = property()
...
... @x.getter
... def x(self):
... return self._x
...
... @x.setter
... def x(self, value):
... self._x = value
>>>
>>> # Create an instance of MyClass
>>> obj = MyClass()
>>>
>>> # Change the value of the property
>>> obj.x = 1
>>>
>>> # Access the property like an attribute
>>> print(obj.x)
1
In this example, the MyClass
class defines a x = property()
,
private attribute _x
.
The @x.getter
decorator defines a getter method for the x
property.
The x()
method returns the value of the _x
attribute.
The @x.setter
decorator defines a setter method for the x
property.
The x()
method takes a single parameter value
that represents the new
value of the _x
attribute.
The obj.x = 1
expression calls the x()
setter method to set the value
of the _x
attribute to 1. The obj.x
expression calls the x()
getter method to retrieve the new value of the _x
attribute.
9.2.1. SetUp
>>> from dataclasses import dataclass
>>> from datetime import date
9.2.2. Problem
>>> class Point:
... x: int
... y: int
... z: int
End-user's code:
>>> pt = Point()
>>> pt.x = 1
>>> pt.y = 2
>>> pt.z = 3
>>> print(pt.x, pt.y, pt.z)
1 2 3
Let's introduce the same feature, that z-axis cannot be negative. And still we cannot change previously defined API (methods).
Woops, we don't have placeholders to inject such validation. We previously considered this as an overhead and we removed it. We are not future proof.
9.2.3. Solution
However in Python, you have properties, which is exactly for that reason.
>>> class Point:
... x: int
... y: int
... z = property()
...
... @z.getter
... def z(self):
... return self._z
...
... @z.setter
... def z(self, value):
... if value < 0:
... raise ValueError('Value cannot be negative')
... self._z = value
End-users code is left unchanged:
>>> pt = Point()
>>> pt.x = 1
>>> pt.y = 2
>>> pt.z = 3
>>> print(pt.x, pt.y, pt.z)
1 2 3
And new feature is working:
>>> pt.z = -1
Traceback (most recent call last):
ValueError: Value cannot be negative
9.2.4. Protocol
myattribute = property()
- creates property@myattribute.getter
- getter for attribute@myattribute.setter
- setter for attribute@myattribute.deleter
- deleter for attributeMethod name must be the same as attribute name
myattribute
has to beproperty
@property
- creates property and a getter
>>> class MyClass:
... myattribute = property()
...
... @myattribute.getter
... def myattribute(self):
... return ...
...
... @myattribute.setter
... def myattribute(self):
... ...
...
... @myattribute.deleter
... def myattribute(self):
... ...
9.2.5. Property Descriptor
name = property()
- creates propertyPreferred way
>>> class User:
... name = property()
...
... def __init__(self, name):
... self._name = name
...
... @name.getter
... def name(self):
... return self._name
...
... @name.setter
... def name(self, value):
... self._name = value
...
... @name.deleter
... def name(self):
... del self._name
9.2.6. Property Decorator
@property
- creates property and a getterTypically used when, there is only getter and no setter and deleter methods
>>> class User:
... def __init__(self, name):
... self._name = name
...
... @property
... def name(self):
... return self._name
...
... @name.setter
... def name(self, value):
... self._name = value
...
... @name.deleter
... def name(self):
... del self._name
9.2.7. Property callable
name = property(fget=get_name, fset=set_name, fdel=del_name, doc='docstring for "name" property')
Property's arguments are method references
get_name
,set_name
,del_name
and a docstringNot recommended
>>> class User:
... def __init__(self, name):
... self._name = name
...
... def get_name(self):
... return self._name
...
... def set_name(self, value):
... self._name = value
...
... def del_name(self):
... del self._name
...
... name = property(fget=get_name, fset=set_name, fdel=del_name, doc='docstring for "name" property')
9.2.8. Use Case - 1
>>> class User:
... def __init__(self):
... self._name = None
...
... def set_name(self, name):
... self._name = name.title()
...
... def get_name(self):
... if self._name:
... firstname, lastname = self._name.split()
... return f'{firstname} {lastname[0]}.'
...
... def del_name(self):
... self._name = None
>>>
>>>
>>> mark = User()
>>>
>>> mark.set_name('MARK WaTNeY')
>>> print(mark.get_name())
Mark W.
>>>
>>> mark.del_name()
>>> print(mark.get_name())
None
>>> class User:
... name = property()
...
... def __init__(self):
... self._name = None
...
... @name.getter
... def name(self):
... if self._name:
... firstname, lastname = self._name.split()
... return f'{firstname} {lastname[0]}.'
...
... @name.setter
... def name(self, name):
... self._name = name.title()
...
... @name.deleter
... def name(self):
... self._name = None
>>>
>>>
>>> mark = User()
>>>
>>> mark.name = 'MARK WaTNeY'
>>> print(mark.name)
Mark W.
>>>
>>> del mark.name
>>> print(mark.name)
None
9.2.9. Use Case - 2
>>> class User:
... name = property()
... _name: str
...
... def __init__(self, name):
... self.name = name
...
... @name.getter
... def name(self):
... return self._name
...
... @name.setter
... def name(self, new_name):
... if any(letter in '0123456789' for letter in new_name):
... raise ValueError('Name cannot have digits')
... self._name = new_name
...
... @name.deleter
... def name(self):
... self._name = None
>>> mark = User('Mark Watney')
>>> mark.name = 'Melissa Lewis'
>>> mark.name = 'Rick Martinez 1'
Traceback (most recent call last):
ValueError: Name cannot have digits
>>> mark = User('Mark Watney')
>>> mark = User('Rick Martinez 1')
Traceback (most recent call last):
ValueError: Name cannot have digits
>>> mark = User('Mark Watney')
>>> print(f'Name is: {mark.name}')
Name is: Mark Watney
>>>
>>> del mark.name
>>> print(f'Name is: {mark.name}')
Name is: None
9.2.10. Use Case - 6
Kelvin is an absolute scale (no values below zero)
>>> class KelvinTemperature:
... value: float
>>>
>>> t = KelvinTemperature()
>>> t.value = -1 # Should raise ValueError('Kelvin cannot be negative')
>>> class KelvinTemperature:
... value = property()
...
... @value.setter
... def value(self, newvalue):
... if newvalue < 0:
... raise ValueError('Negative Kelvin Temperature')
... self._value = newvalue
>>>
>>>
>>> t = KelvinTemperature()
This will pass:
>>> t.value = 1
This will raise an exception:
>>> t.value = -1
Traceback (most recent call last):
ValueError: Negative Kelvin Temperature
9.2.11. Assignments
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: Accessor Property Deleter
# - Difficulty: easy
# - Lines: 6
# - Minutes: 3
# %% English
# 1. Define class `Point` with `x`, `y`, `z` attributes
# 2. Define property `position` in class `Point`
# 3. Deleting `position` sets all attributes to 0 (`x=0`, `y=0`, `z=0`)
# 4. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `Point` z atrybutami `x`, `y`, `z`
# 2. Zdefiniuj property `position` w klasie `Point`
# 3. Usunięcie `position` ustawia wszystkie atrybuty na 0 (`x=0`, `y=0`, `z=0`)
# 4. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> pt = Point()
>>> pt.x = 1
>>> pt.y = 2
>>> pt.z = 3
>>> del pt.position
>>> assert pt.x == 0
>>> assert pt.y == 0
>>> assert pt.z == 0
"""
class Point:
x: int
y: int
z: int
# Define property `position` in class `Point`
# Deleting `position` sets all attributes to 0 (`x=0`, `y=0`, `z=0`)
# type: Callable[[Self], None]
def position():
...
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: Accessor Property Setter
# - Difficulty: easy
# - Lines: 9
# - Minutes: 5
# %% English
# 1. Define class `Point` with:
# - Attribute `x: int`
# - Attribute `y: int`
# - Attribute `z: int`
# - Property `position`
# 2. Setting `position`:
# - If argument is not list, tuple, set raise Type Error
# - If argument has length other than 3, raise Value
# - Else sets `x`, `y`, `z` attributes from sequence
# 3. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `Point` z:
# - Atrybut `x: int`
# - Atrybut `y: int`
# - Atrybut `z: int`
# - Property `position`
# 2. Ustawianie `position`:
# - Jeżeli argument nie jest list, tuple, set podnieś TypeError
# - Jeżeli argument nie ma długości 3, podnieś ValueError
# - W przeciwnym wypadku ustaw kolejne atrybuty `x`, `y`, `z` z sekwencji
# 3. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> pt = Point()
>>> pt.position = 1, 2, 3
>>> assert pt.x == 1
>>> assert pt.y == 2
>>> assert pt.z == 3
>>> pt.position = (4, 5, 6)
>>> assert pt.x == 4
>>> assert pt.y == 5
>>> assert pt.z == 6
>>> pt.position = [7, 8, 9]
>>> assert pt.x == 7
>>> assert pt.y == 8
>>> assert pt.z == 9
>>> pt.position = {'a':1, 'b':2, 'c':3}
Traceback (most recent call last):
TypeError
>>> pt.position = 1, 2
Traceback (most recent call last):
ValueError
"""
# Define class `Point` with `x`, `y`, `z` attributes
# Define property `position` in class `Point`
# Setting `position`:
# - If argument is not list, tuple, set raise TypeError
# - If argument has length other than 3, raise ValueError
# - Else sets `x`, `y`, `z` attributes from sequence
# type: type[Point]
class Point:
x: int
y: int
z: int
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: Accessor Property NonNegative
# - Difficulty: easy
# - Lines: 6
# - Minutes: 5
# %% English
# 1. Define class `Point` with:
# - Attribute `x: int`
# - Attribute `y: int`
# - Attribute `z: int`
# - Property `position`
# 3. Setting `position` raises ValueError if any value is less than 0
# 4. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj klasę `Point` z:
# - Atrybut `x: int`
# - Atrybut `y: int`
# - Atrybut `z: int`
# - Property `position`
# 3. Ustawianie `position` podnosi wyjątek, jeżeli którakolwiek wartość jest mniejsza od 0
# 4. Uruchom doctesty - wszystkie muszą się powieść
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> pt = Point()
>>> pt.position = 1, 2, 3
>>> assert pt.x == 1
>>> assert pt.y == 2
>>> assert pt.z == 3
>>> pt.position = -1, 2, 3
Traceback (most recent call last):
ValueError
>>> pt.position = 1, -2, 3
Traceback (most recent call last):
ValueError
>>> pt.position = 1, 2, -3
Traceback (most recent call last):
ValueError
"""
# Define class `Point` with:
# - Attribute `x: int`
# - Attribute `y: int`
# - Attribute `z: int`
# - Property `position`
# Setting `position` raises ValueError
# if any value is less than 0
# type: type[Point]
class Point:
x: int
y: int
z: int