How to pythonically have partially-mutually exclusive optional arguments?

后端 未结 7 2249
鱼传尺愫
鱼传尺愫 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 19:05

    1. Check that you have enough parameters
    2. Calculate a from every pairing of the other parameters
    3. Confirm every a is the same
    4. Calculate b from every pairing of a and another parameter
    5. Calculate the other parameters from a and b

    Here's a shortened version with just a, b, e, and f that easily extends to other parameters:

    class Ellipse():
        def __init__(self, a=None, b=None, e=None, f=None):
            if [a, b, e, f].count(None) > 2:
                raise Exception('Not enough parameters to make an ellipse')
            self.a, self.b, self.e, self.f = a, b, e, f
            self.calculate_a()
            for parameter in 'b', 'e', 'f':  # Allows any multi-character parameter names
                if self.__dict__[parameter] is None:
                    Ellipse.__dict__['calculate_' + parameter](self)
    
        def calculate_a(self):
            """Calculate and compare a from every pair of other parameters
    
            :raises Exception: if the ellipse parameters are inconsistent
            """
            a_raw = 0 if self.a is None else self.a
            a_be = 0 if not all((self.b, self.e)) else self.b / math.sqrt(1 - self.e**2)
            a_bf = 0 if not all((self.b, self.f)) else math.sqrt(self.b**2 + self.f**2)
            a_ef = 0 if not all((self.e, self.f)) else self.f / self.e
            if len(set((a_raw, a_be, a_bf, a_ef)) - set((0,))) > 1:
                raise Exception('Inconsistent parameters')
            self.a = a_raw + a_be + a_bf + a_ef
    
        def calculate_b(self):
            """Calculate and compare b from every pair of a and another parameter"""
            b_ae = 0 if self.e is None else self.a * math.sqrt(1 - self.e**2)
            b_af = 0 if self.f is None else math.sqrt(self.a**2 - self.f**2)
            self.b = b_ae + b_af
    
        def calculate_e(self):
            """Calculate e from a and b"""
            self.e = math.sqrt(1 - (self.b / self.a)**2)
    
        def calculate_f(self):
            """Calculate f from a and b"""
            self.f = math.sqrt(self.a**2 - self.b**2)
    

    It's pretty Pythonic, though the __dict__ usage might not be. The __dict__ way is fewer lines and less repetitive, but you can make it more explicit by breaking it out into separate if self.b is None: self.calculate_b() lines.

    I only coded e and f, but it's extensible. Just mimic e and f code with the equations for whatever you want to add (area, circumference, etc.) as a function of a and b.

    I didn't include your request for one-parameter Ellipses to become circles, but that's just a check at the beginning of calculate_a for whether there's only one parameter, in which case a should be set to make the ellipse a circle (b should be set if a is the only one):

    def calculate_a(self):
        """..."""
        if [self.a, self.b, self.e, self.f].count(None) == 3:
            if self.a is None:
                # Set self.a to make a circle
            else:
                # Set self.b to make a circle
            return
        a_raw = ...
    

提交回复
热议问题