How to pythonically have partially-mutually exclusive optional arguments?

后端 未结 7 2236
鱼传尺愫
鱼传尺愫 2021-02-13 18:04

As a simple example, take a class Ellipse that can return its properties such as area A, circumference C, major/minor axis a/b

7条回答
  •  盖世英雄少女心
    2021-02-13 18:59

    If the need for such functionality is only for this single class, My advice would be to go with the second solution you have mentioned, using Nsh's answer.

    Otherwise, if this problem arises in number of places in your project, here is a solution I came up with:

    class YourClass(MutexInit):
        """First of all inherit the MutexInit class by..."""
    
        def __init__(self, **kwargs):
            """...calling its __init__ at the end of your own __init__. Then..."""
            super(YourClass, self).__init__(**kwargs)
    
        @sub_init
        def _init_foo_bar(self, foo, bar):
            """...just decorate each sub-init method with @sub_init"""
            self.baz = foo + bar
    
        @sub_init
        def _init_bar_baz(self, bar, baz):
            self.foo = bar - baz
    

    This will make your code more readable, and you will hide the ugly details behind this decorators, which are self-explanatory.

    Note: We could also eliminate the @sub_init decorator, however I think it is the only legal way to mark the method as sub-init. Otherwise, an option would be to agree on putting a prefix before the name of the method, say _init, but I think that's a bad idea.

    Here are the implementations:

    import inspect
    
    
    class MutexInit(object):
        def __init__(self, **kwargs):
            super(MutexInit, self).__init__()
    
            for arg in kwargs:
                setattr(self, arg, kwargs.get(arg))
    
            self._arg_method_dict = {}
            for attr_name in dir(self):
                attr = getattr(self, attr_name)
                if getattr(attr, "_isrequiredargsmethod", False):
                    self._arg_method_dict[attr.args] = attr
    
            provided_args = tuple(sorted(
                [arg for arg in kwargs if kwargs[arg] is not None]))
            sub_init = self._arg_method_dict.get(provided_args, None)
    
            if sub_init:
                sub_init(**kwargs)
            else:
                raise AttributeError('Insufficient arguments')
    
    
    def sub_init(func):
        args = sorted(inspect.getargspec(func)[0])
        self_arg = 'self'
        if self_arg in args:
            args.remove(self_arg)
    
        def wrapper(funcself, **kwargs):
            if len(kwargs) == len(args):
                for arg in args:
                    if (arg not in kwargs) or (kwargs[arg] is None):
                        raise AttributeError
            else:
                raise AttributeError
    
            return func(funcself, **kwargs)
        wrapper._isrequiredargsmethod = True
        wrapper.args = tuple(args)
    
        return wrapper
    

提交回复
热议问题