PascalVKooten
PascalVKooten

Reputation: 21443

Handling arguments in order for preprocessing in Python, when order matters

I would like to make a difference with named variables regarding the order of appearance.

I want to have a pipeline on editing images for which it makes a difference which techniques I apply first. Rather than call the functions separately, I would still like to make use of a wrapper function.

E.g.:

def preprocess(cutoff=None, resize=None, blur=None):
    ....

Now if I call the function like preprocess(resize=(100, 100), cutoff=55), I'd like it to first resize, and then do the cut off, though vice-versa when preprocess(cutoff=55, resize=(100,100)).

What is the best way to accomplish that?

Upvotes: 0

Views: 507

Answers (1)

abarnert
abarnert

Reputation: 365677

You can't use keyword arguments for that. If you accept them via normal parameters like you've tried, the only order is the order of the parameters. If you accept them via **kwargs, you get a dict (which has no order).

If you want to know the order of the arguments, they have to be positional arguments; there's really no way around that.

However, there might be a better design for this. For example, why not just make them separate methods? Then, instead of this:

newfoo = foo.preprocess(resize=(100, 100), cutoff=55)

… you do this:

newfoo = foo.resize(100, 100).cutoff(55)

Or provide simple types that do nothing but wrap up the arguments:

newfoo = foo.preprocess(Resize(100, 100), Cutoff(55))

Then you can have preprocess take its arguments by *args and dispatch based on type(arg).


And if you look at how you'd implement that, it points to another possibility:

class DumbArgWrapper(object):
    def __init__(self, *args):
        self.args = args

class Resize(DumbArgWrapper): pass
class Cutoff(DumbArgWrapper): pass
class Blur(DumbArgWrapper): pass

class ImageProcessor(object):
    def preprocess(self, *args):
        for arg in args:
            if isinstance(arg, Resize):
                do_resize(*arg.args)
            # etc.

If you just move the do_resize code into Resize (in other words, make it a sort of "lazy function" object), it's much cleaner:

class SmartArgWrapper(object):
    def __init__(self, *args):
        self.args = args

class Resize(SmartArgWrapper):
    def __call__(self, target):
        do_resize(target, *self.args)
# etc.

class ImageProcessor(object):
    def preprocess(self, *args):
        for arg in args:
            arg(self)

And having done that, you could just make a simple wrapper that turns any function into a SmartArtWrapper, instead of making a bunch of subclasses.

And then…


The point is, there are plenty of possibilities. To someone who knows what you want and why, there should be one and only one obvious way to do it… but to someone who has no idea what you want and why, that isn't true.

Upvotes: 2

Related Questions