I want to use inner classes (mainly dataclass and Enums) to keep things encapsulated. They hold data and defines that are only relevant to the main class, so I'd like to keep them inside it. I get the sense that this is not the most Pythonic way to do things, but I'm not sure how to make it better.
The real problem is that I need some of those inner classes to contain variables that use types of the other inner classes, and Python doesn't seem to allow that.
This is what I would like to do (this is just a pared down example). This keeps everything as part of DataPacket
, so that when you reference the inner classes you use DataPacket
to get to it. ie. DataPacket.DataStatus.GOOD
, etc, and it's clear where that "define" comes from. However the DataStatus
reference in SensorData
is not found unless it is moved out of the DataPacket
class.
from dataclasses import dataclass
from typing import List
from enum import IntEnum
class DataPacket:
class DataStatus(IntEnum):
GOOD = 0
ERROR = 1
UNKNOWN = 255
@dataclass
class SensorData():
sensor_name: int = 0
sensor_type: int = 0
unit_type: int = 0
status: DataStatus = DataStatus.UNKNOWN
value: float = 0
@dataclass
class Sensors():
data: List[SensorData]
count: int = 0
def build_packet(self):
sensors = self.Sensors([])
# Read data from device to fill in sensor values
sensors.count = 1
data = self.SensorData()
data.sensor_name = 1
data.sensor_type = 2
data.unit_type = 3
data.status = self.DataStatus.GOOD
data.value = 100
sensors.data.append(data)
return sensors
packet = DataPacket()
sensors = packet.build_packet()
if sensors.data[0].status == DataPacket.DataStatus.GOOD:
print(sensors.data[0].value)
else:
print("Sensors data error")
This is how to get it to work, but I don't like this structure:
from dataclasses import dataclass
from typing import List
from enum import IntEnum
class DataStatus(IntEnum):
GOOD = 0
ERROR = 1
UNKNOWN = 255
@dataclass
class SensorData():
sensor_name: int = 0
sensor_type: int = 0
unit_type: int = 0
status: DataStatus = DataStatus.UNKNOWN
value: float = 0
@dataclass
class Sensors():
data: List[SensorData]
count: int = 0
class DataPacket:
def build_packet(self):
sensors = Sensors([])
# Read data from device to fill in sensor values
sensors.count = 1
data = SensorData()
data.sensor_name = 1
data.sensor_type = 2
data.unit_type = 3
data.status = DataStatus.GOOD
data.value = 100
sensors.data.append(data)
return sensors
packet = DataPacket()
sensors = packet.build_packet()
if sensors.data[0].status == DataStatus.GOOD:
print(sensors.data[0].value)
else:
print("Sensors data error")
Thanks for your help/suggestions!
The problem you are encountering is because class bodies do not create enclosing scopes. Nesting class definitions isn't a common pattern in Python. You are going to be working against the language to get it to work. Here is a minimal example of your problem:
>>> class Foo:
... class Bar:
... pass
... class Baz:
... bar = Bar()
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in Foo
File "<stdin>", line 5, in Baz
NameError: name 'Bar' is not defined
Here is a way to get around it, refer to Bar
where it is in scope, and assign to the class attribute after the class definition:
>>> class Foo:
... class Bar:
... pass
... class Baz:
... pass
... Baz.bar = Bar()
...
But really, you should just keep the solution you have already, which is perfectly Pythonic. Note, this workaround means you are going to have to abandon the dataclasses.dataclass
code generator, since that requires annotations in the class body. Or, I suppose, you could use string annotations:
>>> import dataclasses
>>> class Foo:
... class Bar:
... pass
... @dataclasses.dataclass
... class Baz:
... bar: "Bar"
...
But if you want a default value, which actually requires the class, it's not going to work.
Your reasoning for nesting the classes is that "They hold data and defines that are only relevant to the main class, so I'd like to keep them inside it." but the main unit of code organization is the module in Python. Everything being in a module here is perfectly acceptable.