Correct/Pythonic way to handle object with large number of attributes given as parameters

2.6k views Asked by At

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:

  1. 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
    
  2. 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)

1

There are 1 answers

2
Anthon On

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 and bar3 where related you would get.

 class Bar123:
      def __init__(self, bar1=None, bar2=None, bar3=None):
           self.bar1 = bar1 
           self.bar2 = bar2
           self.bar3 = bar3

 class ServerBar123:
      def __init__(self, bar1=None, bar2=None, bar3=None):
           self.bar1 = bar1 if bar1 else 25
           self.bar2 = bar2 if bar2 else 75
           self.bar3 = bar3

 class ServerFoo:
      def __init__(self, bar123=None, bar2=None, bar3=None, ....):
           self.bar123 = bar123 if bar123 else ServerBar123()
           if self.bar123.bar1 != 25:
               ....