2

I use python mostly as glue-language for numerical simulations.

Typically I create some wrapper class which initialize some reasonable default parameters, and than elsewhere in larger script I call run() method which optionally overrides some parameters and than executes the actual simulation.

It could look something like this:

class MyCalculationClass():

    def __init__():
        # set some defaults
        self.A = 45454
        self.B = np.zeros(16,11)
        self.C = (1.0,2.5,30.0)

    def runRaw():
        return whatever(self.A,self.B,self.C)

    def run( A=None, B=None, C=None ):
        # optionally override some defaults
        if A is not None:
            self.A = A
        if B is not None:
            self.B = B
        if C is not None:
            self.C = C
        # run the actual calculation
        return self.runRaw()

mycalc1 = MyClaculationClass()
# .... Over the Hills and Far Away
mycalc1.run(B=np.zeros(11,12) )

but I really hate the boilerplate if A is not None: self.A = A everywhere. Typically there are tens of parameters.

This would be slightly nicer

    def run( A=self.A, B=self.B, C=self.C ):
        # optionally override some defaults
        self.A = A
        self.B = B
        self.C = C
        # run the actual calculation
        self.runRaw()

but:

  • it does not work
  • still it is too much boilerplate

NOTE: I really want self.A etc. to keep being stored as class property in order to be able to recover which parameters I used in which calculation later in the larger script

Cœur
  • 37,241
  • 25
  • 195
  • 267
Prokop Hapala
  • 2,424
  • 2
  • 30
  • 59

3 Answers3

3

if you don't mind loosing a bit of the automagic documentation provided by inspection, you could do something like this:

class Foo(object):
    def __init__(self):
        self._set(A=42, B=[], C="something")

    def _set(self, **kw):
        for name, val in kw.items():
            if val is not None:
                setattr(self, name, val)


   def run(self, **kw):
       self._set(**kw)
       self.runRaw()

If you want to keep the run() signature intact, it's possible but won't be as generic (since run() has to know it's params) :

   def run(self, A=None, B=None, C=None):
       self._set(A=A, B=B, C=C)
       self.runRaw()

Also note that Python objects can be made callable by declaring a __call__ method:

class NotAFunc(object):
    def __call__(self, arg):
        print("{} called with {}".format(self, arg))


f = NotAFunc()
f(42)
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
1

You can use keyword arguments and update object dictionary with update() method (works only if the variables aren't properties of the class. If that's the case, use setattr() method).

class MyCalculationClass():

    def __init__(self):
        # set some defaults
        self.A = 45454
        self.B = [0, 0, 0, 0, 0]
        self.C = (1.0,2.5,30.0)

    def runRaw(self):
        print(self.A, self.B, self.C)

    def run(self, **kwargs):
        self.__dict__.update(kwargs)
        return self.runRaw()

c = MyCalculationClass()
c.run(B=[1, 1, 1])

Prints:

45454 [1, 1, 1] (1.0, 2.5, 30.0)
Andrej Kesely
  • 168,389
  • 15
  • 48
  • 91
0

This may also be possible with a decorator:

import functools

def set_if_none(method):
    @functools.wraps(method)
    def wrapper(self, **kwargs):  # no *args without inspect.signature
        for k, v in kwargs.items():
            if v is not None and hasattr(self, k):
                setattr(self, k, v)
        result = method(self, **kwargs)  # no *args
        return result
    return wrapper

Note: the above example only works for (uses and enforces) keyword arguments. To incorporate positional arguments, you'd probably need inspect.signature.

Example:

class MyClass(object):

    def __init__(self, a=1, b=2):
        self.a = a
        self.b = b

    def __repr__(self):
        return 'MyClass(%s)' % self.__dict__

    @set_if_none
    def meth(self, a=None, b=None):
        pass

>>> mc = MyClass()

>>> mc
MyClass({'a': 1, 'b': 2})

>>> mc.meth(a=10)

>>> mc
MyClass({'a': 10, 'b': 2})

Credit to this answer for the reminder that you can modify the class instance by passing self (the instance) to wrapper(), in the decorator.

Brad Solomon
  • 38,521
  • 31
  • 149
  • 235