Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pythonically have partially-mutually exclusive optional arguments?

As a simple example, take a class Ellipse that can return its properties such as area A, circumference C, major/minor axis a/b, eccentricity eetc. In order to get that, one obviously has to provide precisely two of its parameters to obtain all the other ones, though as a special case providing only one parameter should assume a circle. Three or more parameters that are consistent should yield a warning but work, otherwise obviously raise an exception.

So some examples of valid Ellipses are:

Ellipse(a=5, b=2)
Ellipse(A=3)
Ellipse(a=3, e=.1)
Ellipse(a=3, b=3, A=9*math.pi)  # note the consistency

while invalid ones would be

Ellipse()
Ellipse(a=3, b=3, A=7)

The constructor would therefore either contain many =None arguments,

class Ellipse(object):
    def __init__(self, a=None, b=None, A=None, C=None, ...):

or, probably more sensible, a simple **kwargs, maybe adding the option to provide a,b as positional arguments,

class Ellipse(object):
    def __init__(self, a=None, b=None, **kwargs):
        kwargs.update({key: value
                       for key, value in (('a', a), ('b', b))
                       if value is not None})

So far, so good. But now comes the actual implementation, i.e. figuring out which parameters were provided and which were not and determine all the others depending on them, or check for consistency if required.

My first approach would be a simple yet tedious combination of many

if 'a' in kwargs:
    a = kwargs['a']
    if 'b' in kwargs:
        b = kwargs['b']
        A = kwargs['A'] = math.pi * a * b
        f = kwargs['f'] = math.sqrt(a**2 - b**2)
        ...
    elif 'f' in kwargs:
        f = kwargs['f']
        b = kwargs['b'] = math.sqrt(a**2 + f**2)
        A = kwargs['A'] = math.pi * a * b
        ...
    elif ...

and so on*. But is there no better way? Or is this class design totally bollocks and I should create constructors such as Ellipse.create_from_a_b(a, b), despite that basically making the "provide three or more consistent parameters" option impossible?

Bonus question: Since the ellipse's circumference involves elliptic integrals (or elliptic functions if the circumference is provided and the other parameters are to be obtained) which are not exactly computationally trivial, should those calculations actually be in the constructor or rather be put into the @property Ellipse.C?


* I guess at least one readability improvement would be always extracting a and b and calculating the rest from them but that means recalculating the values already provided, wasting both time and precision...

like image 447
Tobias Kienzler Avatar asked Feb 19 '15 11:02

Tobias Kienzler


People also ask

What is an optional argument in Python?

This is known as an optional argument or optional parameter in Python. But if you have passed some value into the optional argument while calling the function, then the function will take that new value instead of the default value. DelftStack articles are written by software geeks like you.

What is a default argument in Python?

In python, there is something called a default argument. It is also known as optional argument or optional parameter in Python. An argument or a parameter both mean the same thing. You can use these words interchangeably. An argument or a parameter is an input that a function takes.

Why can’t I have more arguments than a function accepts?

This is unless we use optional arguments. In which case, we can specify fewer arguments than a function accepts because some arguments are optional. A Python optional argument is an argument with a default value. You can specify a default value for an argument using the assignment operator.

Can two events be mutually exclusive?

If two events are mutually exclusive, they are not independent. Also, independent events cannot be mutually exclusive. In probability theory, two events are mutually exclusive or disjoint if they do not occur at the same time. A clear case is the set of results of a single coin toss, which can end in either heads or tails, but not for both.


1 Answers

My proposal is focused on data encapsulation and code readability.

a) Pick pair on unambigous measurements to represent ellipse internally

class Ellipse(object):
    def __init__(a, b):
        self.a = a
        self.b = b

b) Create family of properties to get desired metrics about ellipse

class Ellipse(object):
    @property
    def area(self):
        return math.pi * self._x * self._b

c) Create factory class / factory methods with unambigous names:

class Ellipse(object):
    @classmethod
    def fromAreaAndCircumference(cls, area, circumference):
        # convert area and circumference to common format
        return cls(a, b)

Sample usage:

ellipse = Ellipse.fromLongAxisAndEccentricity(axis, eccentricity)
assert ellipse.a == axis
assert ellipse.eccentricity == eccentricity
like image 137
Łukasz Rogalski Avatar answered Oct 02 '22 15:10

Łukasz Rogalski