I have two dataclasses, Route
and Factors
. Route
contains a value and three copies of Factors
.
Route
does not know how many variables Factors
contains. I want to get the name of these variables, and then get the respective value of each one, for each copy of Factors
.
Here is what I currently have:
@dataclass
class Factors:
do: bool # does it do the route
hub: int # how many of the locations are hubs
def __init__(self, do_init):
self.do = do_init
self.hub = 0 # will add later
def __str__(self):
return "%s" % self.do
@dataclass
class Route:
route: tuple
skyteam: Factors
star: Factors
oneworld: Factors
def __init__(self, route):
self.route = route.get('route')
# this could probably be done with one line loop and a variable with names
self.skyteam = Factors(route.get('skyteam'))
self.star = Factors(route.get('star'))
self.oneworld = Factors(route.get('oneworld'))
def __str__(self):
table = [[self.route, "SkyTeam", "StarAlliance", "OneWorld"]] # var name is fine
for var in Factors.__dict__.get('__annotations__').keys(): # for each factor
factor = [var]
factor.append(self.skyteam.__dict__.get(var))
factor.append(self.star.__dict__.get(var))
factor.append(self.oneworld.__dict__.get(var))
table.append(factor)
return tabulate.tabulate(table, tablefmt='plain')
Input is
{'route': ('BOS', 'DXB'), 'skyteam': True, 'star': True, 'oneworld': True}
Current output is
('BOS', 'DXB') SkyTeam StarAlliance OneWorld
do True True True
hub 0 0 0
Maybe I could search Route
for each variable that contains a Factors
datatype and iterate over those?
You may use dataclass.fields
from dataclasses import dataclass, fields
for field in fields(YourDataclass):
print(field.name, getattr(YourDataclass, field.name))
I'd also make use of the __dataclass_fields__
which returns a dict of the variable names and their types. For example something like:
for field in mydataclass.__dataclass_fields__:
value = getattr(mydataclass, field)
print(field, value)
I'd leave the builtin __str__
s alone and just call the function visualize
or something on the Route
class, but that's taste. You also shouldn't overload the __init__
of a dataclass unless you absolutely have to, just splat your input dict into the default constructor.
Final nit, try to use getattr
/setattr
over accessing the __dict__
, dataclasses are popular for using __slots__
to store their attributes, which would break your code in a non-trivial way.
So I'd go with something like this, using the tabulate library to handle the rendering:
from dataclasses import dataclass, fields
import tabulate
@dataclass
class Factor:
do: bool
hub: int = 0 # this is how to add a default value
@dataclass
class Route:
route: tuple
skyteam: Factor
star: Factor
oneworld: Factor
def __post_init__(self):
# turn Factor parameter dicts into Factor objects
for field in fields(self):
if issubclass(field.type, Factor):
setattr(self, field.name, field.type(getattr(self, field.name)))
def visualize(self):
factors = {
field.name: getattr(self, field.name)
for field in fields(self)
if issubclass(field.type, Factor)
}
rows = [[self.route, *factors]] # header
for field in fields(Factor):
rows.append([field.name, *[getattr(f, field.name) for f in factors.values()]])
print(tabulate.tabulate(rows))
Which works out fine for your example:
>>> r = Route(**{'route': ('BOS', 'DXB'), 'skyteam': True, 'star': True, 'oneworld': True})
>>> r.visualize()
-------------- ------- ---- --------
('BOS', 'DXB') skyteam star oneworld
do True True True
hub 0 0 0
-------------- ------- ---- --------
This solution should continue to work both if you add more fields to the Factor class and more factor instances to the Route.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With