3

I'm designing an API for a Python library. The user will create objects with several parameters. In most cases, the user will either leave these at their default values or will set them globally, for all objects. However, it should be possible also to set them individually on a per-object basis.

The most obvious way to do this is to do something like this:

# myModule.py

contrafibularity_threshold = 10.7
pericombobulation_index = 9
compunctuous_mode = False

class Thing:
    def __init__(self):
        self.contrafibularity_threshold = None
        pericombobulation_index = None
        compunctuous_mode = None

    def get_contrafibularity_threshold(self):
        if self.contrafibularity_threshold is not None:
            return self.contrafibularity_threshold
        else:
            return contrafibularity_threshold

    def get_pericombobulation_index(self):
        if self.pericombobulation_index is not None:
            return self.pericombobulation_index
        else:
            return pericombobulation_index

    def get_compunctuous_mode(self):
        if self.compunctuous_mode is not None:
            return self.compunctuous_mode
        else:
            return compunctuous_mode

This works as I would like: it allows the user to do myModule.contrafibularity_threshold = 10.9 to set the global value while also being able to do someThing.contrafibularity_threshold = 11.1 to set it for a particular object. The default may be changed at any time and will affect only those objects to which a specific value has not been assigned.

However, the code above contains a lot of repetition, and seems prone to hard-to-notice bugs if I make a mistake copy-pasting the code. Is there a better (less repetitive, less error-prone, more Pythonic) way to achieve these goals? I don't mind changing the API, as long as the user can change the defaults at both the global and per-object level.

(One could arguably improve the above code by using @property, but that wouldn't resolve the repetitive code issue.)

2 Answers 2

2

In this particular case, I would recommend using class attributes:

class Thing:
   contrafibularity_threshold = 10.7
   pericombobulation_index = 9
   compunctuous_mode = False

It allows the user to do myModule.Thing.contrafibularity_threshold = 10.9 to set the global value while also being able to do someThing.contrafibularity_threshold = 11.1 to set it for a particular object. The default may be changed at any time and will affect only those objects to which a specific value has not been assigned.

Then you just need to use someThing.contrafibularity_threshold instead of someThing.get_contrafibularity_threshold() and you're done.

How this works:

foo.bar first looks up the attribute 'bar' on the foo object itself. If it can't find it there, it looks up 'bar' on type(foo) and if it can't find it there it looks on the superclasses. (It's slightly more complicated than that, there is also the descriptor protocol which is how methods and @propertys are implemented, but we don't need that in this case.)

1
  • Thank you. I was hoping it would be this simple! I didn't know class variables worked this way. (I assumed it would be an error for an instance to have an attribute with the same name as a class attriubte, but this is much more useful!)
    – N. Virgo
    Jan 2, 2019 at 0:05
2

You could use dictionaries:

defaults = {"contrafibularity_threshold": 10.7, "pericocombulation_index": 9, "compunctuous_mode": False}

class Thing:
    def __init__(self):
        self.configurations = {}
    def __getattr__(self, name):
        if name in defaults:
            return self.configurations.get(name, defaults[name])
        raise AttributeError(name)
    def do_stuff(self, etc):
        print(self.contrafibularity_threshold)

The only thing is that setting requires thing.configuration[...], unless you write a __setattr__ method as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.