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:
Here is a way to get around it, refer to
Barwhere it is in scope, and assign to the class attribute after the class definition: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.dataclasscode generator, since that requires annotations in the class body. Or, I suppose, you could use string annotations: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.