Octaviour
Octaviour

Reputation: 805

pythonic way for function with loads of parameters and return values

I'm trying to run a simulation in Python. For this to work I need to define a number of parameters and derive some others from these. Using these I then call several functions that act on these parameters and return a certain result. Currently my code looks something like this

common(ai, bi, ..., hi):
    # calculations
    return ao, bo, ..., fo
func1(ai, bi, ..., xi):
    ao, bo, ..., fo = common(ai, bi, ..., hi)
    # calculations specific to func1
    return y
func1(ai, bi, ..., xi):
    ao, bo, ..., fo = common(ai, bi, ..., hi)
    # calculations specific to func2
    return z

ai = 0
bi = 1
...
xi = 2

print(func1(ai, bi, ..., xi))
print(func2(ai, bi, ..., xi))

where I abbreviated the parameter list etc with the ... and calculations are performed in the # calculations sections.

I would prefer to call the functions using func1(di=2) and have the default value for all the others. This would however mean using named arguments, in which case I have to specify the default values twice (in func1 and in func2). I do not think **kwargs would work here since that would require passing in the default values explicitly.

I have been toying with the compromise of passing a dictionary to the functions and setting the non-standard argument before passing it on. This makes the function call much harder to understand however, so I felt that there should be an easier way to do this.

Since the functions only consist of relatively long equations, I do not want to add characters to the variable names as would be required when storing them in a dictionary and just calling them from there. This makes reading the equations much more difficult.

My question is whether there is a pythonic way to solve this issue.

Upvotes: 0

Views: 631

Answers (3)

Mykhaylo Kopytonenko
Mykhaylo Kopytonenko

Reputation: 953

The function parameters in programming do not have to be used exactly as in mathematics. You may have an equation:

z = sqrt((x2 - x1)^2 + (y2 - y1)^2),

which is easy to read only if the variables have short names. It is understandable you want to keep that. But programming languages may not work that way, they use their own syntax and tools.

One of the important principles of refactoring in programming is to reduce the number of passed parameters in functions. The easiest way is to encapsulate all passed parameters in one object, and pass only that object between functions:

import math

class Line:
    def __init__(self, x1, x2, y1, y2):
         self.x1 = x1
         self.x2 = x2
         self.y1 = y1
         self.y2 = y2

def getLineLength(line):        
    return math.sqrt((line.x2 - line.x1)**2 + (line.y2 - line.y1)**2)

line = Line(3, 5, -2, 7)
print(getLineLength(line))

Of course, you have to use the object name, and the original equation becomes less readable. And a Python code doesn't have to look exactly as the mathematical formula. But instead, you can now re-use the new class Line in other parts of the code.

If the function is complex enough to see what it does, it can be further refactored:

def getLineLength(line):
    dx = line.x2 - line.x1
    dy = line.y2 - line.y1      
    return math.sqrt(dx**2 + dy**2)

This function may moved inside the Line class:

class Line:
...

def getLength(self):
    dx = self.x2 - self.x1
    dy = self.y2 - self.y1      
    return math.sqrt(dx**2 + dy**2)

line = Line(3, 5, -2, 7)
print(line.getLength())

... and even refactoring further:

import math

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __sub__(self, other):
        return Point(other.x - self.x, other.y - self.y)

class Line:
    def __init__(self, x1, x2, y1, y2):
         self.start = Point(x1, y1)
         self.end = Point(x2, y2)

    def getLength(self):
        d = self.start - self.end
        return math.sqrt(d.x**2 + d.y**2)

line = Line(3, 5, -2, 7)
print(line.getLength())

The new class Point can be re-used in other places.

Thus, it is possible to keep the code clean, simple, and re-usable. The re-usability becomes very important as the script grows on.

Upvotes: 0

jno
jno

Reputation: 1027

You can make a class to hold the vars in its properties:

class SimClass(object):

    def __init__(self, **kw):
        self.ao = kw.get('aa', VA_DEFAULT)
        self.bo = kw.get('bb', VB_DEFAULT)
        # ...
        self.zo = kw.get('zz', VZ_DEFAULT)

    def common(self, ai, bi, ..., hi):
        # calculations set self.ao, self.bo, ..., self.fo
        return

    def func1(self, ai, bi, ..., xi):
        self.common(ai, bi, ..., hi)
        # calculations specific to func1 using self.ao, self.bo, ..., self.fo
        return y

    def func1(self, ai, bi, ..., xi):
        common(ai, bi, ..., hi)
        # calculations specific to func2 using self.ao, self.bo, ..., self.fo
        return z

ai = 0
bi = 1
...
xi = 2

sim = SimClass(bb='BB', cc='CC')

print(sim.func1(ai, bi, ..., xi))
print(sim.func2(ai, bi, ..., xi))

Upvotes: 0

Neo X
Neo X

Reputation: 957

You have two methods with the same type of arguments and return value, the only difference is process inside the functions, namely, they share the same interfaces and functionality.

You want an easy way to define the functions, without having to write default arguments or passing in dictionary many times.

It would be nice to use decorator or class inheritance.

Decorator enables you to define a factory method that returns functions like func1 or func2. The interface is only defined once using default arguments in decorator, returned functions differ in the process methods or core methods.

Class inheritance works similarly, but through method inheritance, it's more flexible and general.

Upvotes: 1

Related Questions