在 python 的方法调用中有选择地设置一些 class 属性的简便方法

convenient way to optionally set some class properties in method call in python

我主要使用 python 作为数值模拟的胶水语言。

通常我会创建一些包装器 class 来初始化一些合理的默认参数,并且与其他地方相比,在更大的脚本中我会调用 run() 方法,该方法可以选择性地覆盖一些参数,然后执行实际的模拟。

它可能看起来像这样:

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) )

但我真的讨厌到处都是样板 if A is not None: self.A = A。通常有几十个参数。

这样会好一点

    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()

但是:

注意:我真的希望 self.A 等继续存储为 class 属性,以便能够在以后的较大的计算中恢复我在哪个计算中使用的参数脚本

您可以使用关键字参数并使用 update() 方法更新对象字典(仅当变量不是 class 的属性时才有效。如果是这种情况,请使用 setattr()方法)。

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])

打印:

45454 [1, 1, 1] (1.0, 2.5, 30.0)

如果您不介意丢失一些检查提供的自动文档,您可以这样做:

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()

如果你想保持 run() 签名完整,这是可能的,但不会那么通用(因为 run() 必须知道它的参数):

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

另请注意,可以通过声明 __call__ 方法使 Python 对象可调用:

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


f = NotAFunc()
f(42)

装饰器也可以做到这一点:

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

注意:以上示例仅适用于(使用和强制执行)关键字参数。要合并位置参数,您可能需要 inspect.signature.

示例:

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})

感谢 提醒您可以通过在装饰器中将 self(实例)传递给 wrapper() 来修改 class 实例。