4

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)

elnygren
  • 5,000
  • 4
  • 20
  • 31
  • *"Does not support defaults"* - maybe not directly, but you can always use `kwargs.get(parameter, default)`. The way to deal with this in Python is the same is in other OO languages; define a new class to hold related parameters. – jonrsharpe Jun 24 '15 at 09:30
  • vote `2.` and you can set attributes(`fields`) dynamically – LittleQ Jun 24 '15 at 09:42
  • @jonrsharpe thanks for reminder about `kwargs.get(parameter, default)`. See my edit above. I see your point in doing "subclasses" but I kind think its a bit too much since all of the attributes are directly related to the firewall rule and I'd like to not have too many classes either... – elnygren Jun 24 '15 at 09:48
  • With that example, why not group the `source_` and `destination_` parameters? A single e.g. `Address` class could hold all four parameters and provide defaults for both. *"I'd like to not have too many classes"* - how many is too many? – jonrsharpe Jun 24 '15 at 09:56
  • @jonrsharpe it is simpler for the user to just import FirewallRule and give it a list of parameters `FirewallRule(param1, parame2, param2........)` than to import several classes (`FirewallRule, Address, Source`) and then to instantiate them all in a specific order and pass them as params to each other. Of course, I didn't specify that I'm building and API client that should be simple and quick to use for other people. For internal use I'd probably use your suggestion especially if those `Address` and `Source` could be used elsewhere. : ) – elnygren Jun 24 '15 at 10:23
  • Why do you think that a single, long list of parameters is *easier* for the user? If anything, it's more prone to user error, and harder to keep track of where in the parameter list you are, especially if you're using positional arguments. – jonrsharpe Jun 24 '15 at 10:40
  • @jonrsharpe I guess it boils down to which you prefer: http://pastebin.com/Ute9s99g. However, the first alternative is 1:1 the JSON from our API documentation, so it might be a bit more clearer. The latter would allow reusing things a bit easier if creating complex firewall rule collections. You are right about positional arguments. I'm going to encourage users to use named arguments for clarity, though. – elnygren Jun 24 '15 at 11:09
  • I may be wrong, but I suspect in the loop "for key in kwargs:", it should be "attributes" instead of "kwargs". If the "key" is taken out from the dictionary "kwargs" in the first place, using "get" serves no purpose because the key is by default in the dictionary. – Sean May 24 '20 at 18:10

1 Answers1

0

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:
               ....
Anthon
  • 69,918
  • 32
  • 186
  • 246