class Leaf:
""" This class represents a leaf's tree """
def __init__(self, width: float, length: float, color: str = "green"):
self.width = width
self.length = length
self.color = color
def get_area(self):
return self.width * self.length
l = Leaf(4, 12)
l
<__main__.Leaf at 0x7f7e3a37ae80>
l.width, l.length, l.color
(4, 12, 'green')
l.get_area()
48
By default there is no control on attribute. You can thus change their value.
# not any control
l.width = -5
l.get_area()
-60
All python object are fully dynamic. Attribute are thus created on the fly even on instances.
# really no control
l.weight = 18
vars(l)
{'width': -5, 'length': 12, 'color': 'green', 'weight': 18}
class Leaf:
""" This class represents a leaf's tree """
def __init__(self, width: float, length: float, color: str = "green"):
self.width = width
self.length = length
self.color = color
# for the sake of efficiency it is better to compute it one times in
# the init. If you don't, you will compute it each time you use the
# property
self._area = self.width * self.length
@property
def area(self):
""" Area of the leaf """
return self._area
l = Leaf(4, 12)
area
is now a property and no more a function/method.
l.area
48
You are still able to set the width
or other attributres but not the area
which is a property.
This leads to strange or erroneous behavior.
l.width = -5
l.area
48
try:
l.area = -5
except AttributeError as error:
print("#ERROR#", error)
can't set attribute
We assume the following:
class Leaf:
""" This class represents a leaf's tree """
def __init__(self, width: float, length: float, color: str = "green"):
self._width = width
self._length = length
self.color = color
# for the sake of efficiency it is better to compute it one times in
# the init. If you don't, you will compute it each time you use the
# property
self._area = self.width * self.length
@property
def area(self):
""" Area of the leaf """
return self._area
@property
def width(self):
""" leaf's width """
return self._width
@property
def length(self):
""" leaf's width """
return self._length
l = Leaf(4, 12)
try:
l.width = -5
except AttributeError as error:
print("#ERROR#", error)
can't set attribute
try:
l.length = -5
except AttributeError as error:
print("#ERROR#", error)
can't set attribute
But it is still possible to modify hidden attributes
print(vars(l))
{'_width': 4, '_length': 12, 'color': 'green', '_area': 48}
l._width = - 5
print(vars(l))
print("area =", l.area)
{'_width': -5, '_length': 12, 'color': 'green', '_area': 48} area = 48
We assume the following:
class Leaf:
""" This class represents a leaf's tree """
def __init__(self, width: float, length: float, color: str = "green"):
self._width = width
self._length = length
self.color = color
# for the sake of efficiency it is better to compute it one times in
# the init. If you don't, you will compute it each time you use the
# property
self._area = self.width * self.length
@property
def area(self):
""" Area of the leaf """
return self._area
@property
def width(self):
""" leaf's width """
return self._width
@width.setter
def width(self, val):
""" check width and upadte self """
if val > 0:
self._width = val
self._area = self._width * self._length
else:
raise ValueError(f"Wrong width values: '{val}'")
# other option to define property
def get_length(self):
""" leaf's width """
return self._length
def set_length(self, val):
""" check width and upadte self """
if val > 0:
self._length = val
self._area = self._width * self._length
else:
raise ValueError(f"Wrong width values: '{val}'")
length = property(get_length, set_length)
l = Leaf(4, 12)
print(vars(l), " area =", l.area)
l.length = 24
print(vars(l), " area =", l.area)
l.width = 5
print(vars(l), " area =", l.area)
try:
l.width = -1
except ValueError as error:
print("#ERROR#", error)
{'_width': 5, '_length': 24, 'color': 'green', '_area': 120} area = 120 {'_width': 5, '_length': 24, 'color': 'green', '_area': 120} area = 120 {'_width': 5, '_length': 24, 'color': 'green', '_area': 120} area = 120 #ERROR# Wrong width values: '-1'
from collections import namedtuple
Leaf = namedtuple("Leaf", ["width", "length", "color"], defaults=["green"])
l = Leaf(4, 12)
l
Leaf(width=4, length=12, color='green')
l2 = Leaf(4, 12)
l == l2 # this comparison was not available in previous implementation.
True
l.width, l.length
(4, 12)
for item in l:
print(item)
4 12 green
for i in range(3):
print(l[i])
4 12 green
NamedTuple are immutable object. You cannot set attributes.
try:
l.length = 5
except AttributeError as error:
print(error)
can't set attribute
try:
l.area = l.length * l.width
except AttributeError as error:
print(error)
'Leaf' object has no attribute 'area'
NamedTuple is very fast but very basic. For example, here area
is not available. You can however extend it:
class Leaf(namedtuple(
"Leaf", ["width", "length", "color"], defaults=["green"])):
@property
def area(self):
return self.width * self.length
l = Leaf(4, 12)
l
Leaf(width=4, length=12, color='green')
l.area
48
The aim of dataclass is to be a container. By default, the aim is not to protect data. In the following attributed are thus editable.
from dataclasses import dataclass, field
@dataclass
class Leaf:
""" This class represents a leaf's tree """
width: float
length: float
color: str = "green"
area: float = field(init=False)
def __post_init__(self):
self.area = self.width * self.length
l = Leaf(4, 12)
l
Leaf(width=4, length=12, color='green', area=48)
l.area
48
vars(l)
{'width': 4, 'length': 12, 'color': 'green', 'area': 48}
# you loose the control on attributed relationship
l.length = 2
vars(l)
{'width': 4, 'length': 2, 'color': 'green', 'area': 48}
Fine tuning of container classes can be achieve using pydantic BaseModel
.
from enum import Enum
from pydantic import BaseModel, NonNegativeFloat, ValidationError, validator
class ColorChoice(str, Enum):
green = "green"
very_green = "very_green"
so_green = "so_green"
class Leaf(BaseModel):
width: NonNegativeFloat
length: NonNegativeFloat
color: ColorChoice = ColorChoice.green
@validator("length")
def length_lt_width_validation(cls, v, values):
if v < values["width"]:
raise ValueError(f"Length must be larger than width.")
else:
return v
@property
def area(self):
return self.width * self.length
l = Leaf(width=3, length=8)
l
Leaf(width=3.0, length=8.0, color=<ColorChoice.green: 'green'>)
l.area
24.0
try:
Leaf(width=-2, length=2)
except ValidationError as error:
print(error)
1 validation error for Leaf width ensure this value is greater than or equal to 0 (type=value_error.number.not_ge; limit_value=0)
try:
Leaf(width=12, length=2)
except ValueError as error:
print(error)
1 validation error for Leaf length Length must be larger than width. (type=value_error)
l.dict()
{'width': 3.0, 'length': 8.0, 'color': <ColorChoice.green: 'green'>}
l.json()
'{"width": 3.0, "length": 8.0, "color": "green"}'