I have a situation where an object has 15+ parameters where some are mandatory/optional/have defaults/don't have defaults. Let's say the class is Foo
and the params are Bar1
, Bar2
, ..., BarN
.
I could program the class like:
the traditional, tedious way. Easy to define defaults and verbose. However, code will be long and have lots of repetition.
class Foo(object): def __init__(self, bar1, bar2,...barN='somedefault' ): self.bar1 = bar1 self.bar2 = bar2 ... self.barN = barN
abusing(?) the
kwargs
. Does not support defaults.class Foo(object): fields = [ 'bar1', 'bar2', ...n 'barN'] def __init__(self, **kwargs ): for key in kwargs: if key in self.fields: setattr(self, key, kwargs[key]) else: raise Exception('invalid attribute: {0}'.format(key))
For number 2, I could probably change the fields
class attribute into a map { 'attribute_name': 'default_value' }
and then assign the default_value
if that key was not in kwargs
.
The context where I'm working is that I'm creating objects that correspond to servers, firewall rules, storage disks, etc., that have a lot of attributes. In a lot of cases I get the attributes from the API so I can do number 2 (even without the validation). However, sometimes a user will create a firewall rule, attach it to a server and send it to an API (POST with JSON body).
Is there some standard/pythonic way to handle objects with a large amount of attributes? Does the approach in #2 seem good or should I really go with the verbose #1 way?
EDIT: one concrete example is a firewall rule object that has the following parameters:
action
destination_address_end
destination_address_start
destination_port_end
destination_port_start
direction
family
icmp_type
position
protocol
source_address_end
source_address_start
source_port_end
source_port_start
Right now I'm leaning towards something like:
class FirewallRule(object):
attributes = {
'action': 'default_value',
'destination_address_end': 'default_value',
'destination_address_start': 'default_value',
'destination_port_end': 'default_value',
'destination_port_start': 'default_value',
'direction': 'default_value',
'family': 'default_value',
'icmp_type': 'default_value',
'position': 'default_value',
'protocol': 'default_value',
'source_address_end': 'default_value',
'source_address_start': 'default_value',
'source_port_end': 'default_value',
'source_port_start': 'default_value'
}
def __init__(self, **kwargs ):
for key in kwargs:
valur_or_default = kwargs.get(key, attributes[key])
setattr(self, key, valur_or_default)
(In production code I'd obviously add better error handling and validation)
I would combine those different attributes in "attribute sets" where it makes sense to bundle them. Make classes for those sets and hand instances in as arguments. A
None
value could be tested and the default instance for that "attribute set" created and assigned. During that instantiation you can aslo hand in arguments that are defaults for the the given object (server, firewall), but I normally make subclasses to handle that case and have the argument setting explicit and not hidden in another non-related class.If
bar1
,bar2
andbar3
where related you would get.