【问题标题】:Split class property in two properties将类属性拆分为两个属性
【发布时间】:2015-04-29 18:51:00
【问题描述】:

我在一个类属性中求解一个由两个方程组成的系统,它返回解——两个变量的值。我希望这两个值都是类的属性——如何在不解决系统两次的情况下实现这一点?这是一个例子

#!/usr/bin/python3

class Test(object):
    pass

    def ab(self):
        print("Calc!")
        a = 1
        b = 2
        return [a,b]

    @property
    def a(self):
        return self.ab()[0]


    @property
    def b(self):
        return self.ab()[1]

test = Test()

print(test.a)
print(test.b)

它输出:

Calc!
1
Calc!
2

所以它实际上两次“解决”了方程组(ab 属性)。如果它已经解决了一次,那么输出将是:

Calc!
1
2

我如何做到这一点?

编辑

系统示例:

#!/usr/bin/python3

import scipy
from scipy.optimize import fsolve

class Test(object):
    def __init__(self, c, d):
        self.c = c
        self.d = d

    def ab(self):
        print("Calc!")
        result = fsolve(lambda x: [
                    x[0] + 2*x[1] + self.c
                , 3*x[0] -   x[1] + self.d
            ], [1,1])
        return result

    @property
    def a(self):
        return self.ab()[0]


    @property
    def b(self):
        return self.ab()[1]

test = Test(-5,2)

print(test.a)
print(test.b)

给予:

Calc!
0.142857142857
Calc!
2.42857142857

我希望它只解决一个系统一次:

Calc!
0.142857142857
2.42857142857

编辑 2

真实代码:

#!/usr/bin/env python3

import argparse, os, sys

# ==============
## parsing args:

parser = argparse.ArgumentParser()

argsLip = parser.add_argument_group('Properties of lipid:')
argsLip.add_argument('-A', '--area',
    help = "incompressible area, Ų (default to %(default)s)", 
    dest = 'A_n',
    action = 'store',
    type = float,
    default = 20.0,
    )
argsLip.add_argument('-L',
    help = "basic length in Å (default to %(default)s)", 
    dest = 'L',
    action = 'store',
    type = float,
    default = 15.0,
    )
argsLip.add_argument('-K', '--K-coef',
    help = "bending rigidity in kTL (default to %(default)s)", 
    dest = 'K_f_coef',
    action = 'store',
    type = float,
    default = 0.33,
    )

argsMem = parser.add_argument_group('Properties of membrane:')
argsMem.add_argument('-g', '--gamma',
    help = "surface tension, erg/cm² (default to %(default)s)", 
    dest = 'γ',
    action = 'store',
    type = float,
    default = 30.0,
    )

argsEnv = parser.add_argument_group('Properties of environment:')
argsEnv.add_argument('-T', '--temperature',
    help = "temperature, K (default to %(default)s)", 
    dest = 'T',
    action = 'store',
    type = float,
    default = 323.0,
    )

argsCalc = parser.add_argument_group('Calc options:')
argsCalc.add_argument('-a', '--a-trial',
    help = "trial value of a to be used in nsolve (default to %(default)s)", 
    dest = 'a_trial',
    action = 'store',
    type = float,
    default = 2.0,
    )

args = parser.parse_args()


# =========
## imports:

# symbolic:
import sympy
from sympy import symbols, solve, nsolve, pprint, diff, S, Function
from sympy import sqrt as Sqrt
sympy.init_printing(pretty_print=True, use_unicode=True, wrap_line=False, no_global=True)

# numeric:
import scipy
from scipy import sqrt
from scipy.optimize import fsolve

# constants:
from scipy import pi as π

from scipy.constants import k as k_SI
k = k_SI * 10**7 # J/K → erg/K



# =========
## program:

class MonoFlexible_symbolic(object):
    "This class initiates common symbolic expressions to be used in all MonoFlexible classes."
    def __init__(self):

        a, l = symbols("a l", real=True, positive=True)

        b = Function('b')(a, l)
        ν = Function('ν')(l)

        equation = (
                  3 / (4 * b)
                + 1 / ( 2 * Sqrt(2) * b**(S(3)/4) )
                - ν * ( Sqrt(a) - 1 )**2
            )

        equation_diff_a  = equation.diff(a)
        equation_diff_a2 = equation_diff_a.diff(a)
        equation_diff_l  = equation.diff(l)        .subs(ν.diff(l)  , -3*ν)
        equation_diff_l2 = equation_diff_l.diff(l) .subs(ν.diff(l,2), 12*ν)
        equation_diff_al = equation_diff_a.diff(l) .subs(ν.diff(l)  , -3*ν)

        db_da    = solve( equation_diff_a  , b.diff(a)         )[0]
        d2b_da2  = solve( equation_diff_a2 , b.diff(a,2)       )[0]
        db_dl    = solve( equation_diff_l  , b.diff(l)         )[0]
        d2b_d2l  = solve( equation_diff_l2 , b.diff(l,2)       )[0]
        d2b_dadl = solve( equation_diff_al , b.diff(a).diff(l) )[0]

        # print("db_da =")
        # pprint(
        #     db_da
        # )

        # print("d2b_da2 =")
        # pprint("d2b_da2 =",
        #     d2b_da2
        # )

        # print("db_dl =")
        # pprint(
        #     db_dl
        # )

        # print("d2b_dl2 =")
        # pprint(
        #     d2b_d2l
        # )

        # print("d2b_dadl =")
        # pprint(
        #     cancel(d2b_dadl[0])
        # )

        self.db_da_func = lambda aa, bb, νν: db_da.subs({
                  a: aa
                , b: bb
                , ν: νν
            }).evalf()

        self.d2b_da2_func = lambda aa, bb, νν: d2b_da2.subs({
                  a: aa
                , b: bb
                , ν: νν
            }).evalf()

        self.db_dl_func = lambda aa, bb, νν: db_dl.subs({
                  a: aa
                , b: bb
                , ν: νν
            }).evalf()

        self.d2b_d2l_func = lambda aa, bb, νν: d2b_dl2.subs({
                  a: aa
                , b: bb
                , ν: νν
            }).evalf()

        self.d2b_dadl_func = lambda aa, bb, νν: d2b_dadl.subs({
                  a: aa
                , b: bb
                , ν: νν
            }).evalf()


class MonoFlexible(MonoFlexible_symbolic):
    def __init__(self,
        γ        : "Surface tension of the membrane, erg/cm²",
        T        : "Temperature, K",
        L        : "Length of the hydrocarbon chain, Å",
        A_n      : "Non-compressible area of the lipid, Ų",
        a_trial  : "Initial value for fsolve, default to 2.0" = None,
        K_f_coef : "K_f = k T L * K_f_coef, default to 1/3" = None,
        )       -> "Calculates thermodynamic properties of flexible string":

        super().__init__()

        self.__γ        = γ
        self.__T        = T
        self.__L        = L
        self.__A_n      = A_n
        self.__a_trial  = a_trial
        self.__K_f_coef = K_f_coef

    @property
    def A_n_Å2(self):
        return self.__A_n

    @property
    def A_n(self):
        return self.__A_n * 10**(-16) # Ų → cm²

    @property
    def L_Å(self):
        return self.__L

    @property
    def L(self):
        return self.__L * 10**(-8) # Å → cm

    @property
    def γ(self):
        return self.__γ

    @property
    def T(self):
        return self.__T

    @property
    def a_trial(self):
        "Initial value for numerical equation solving function to find area per lipid."
        a_trial = self.__a_trial or 2.0
        return a_trial

    @property
    def K_f_coef(self):
        K_f_coef = self.__K_f_coef or 1/3
        return K_f_coef

    @property
    def K_f(self):
        "Rigidity of the string."
        return k * self.T * self.L * self.K_f_coef

    @property
    def ν(self):
        return self.K_f * self.A_n / (
            π * k * self.T * self.L**3
            )

    @property
    def ab(self):
        print("Calc!")
        ab = fsolve( lambda x: [
                  3 / ( 4 * x[1] )
                + 1 / ( 2 * sqrt(2) * x[1]**(3/4) )
                - self.ν * (sqrt(x[0]) - 1)**2
                ,
                - k * self.T / self.A_n * self.db_da_func(x[0], x[1], self.ν) * self.ν * (sqrt(x[0]) - 1)**2
                - self.γ
            ]
            , [2., 300.] )
        return ab

    @property
    def a(self):
        return self.ab[0]

    @property
    def b(self):
        return self.ab[1]


# ======
## calc:

def main():

    flexible_kwargs = {
        "γ"        : args.γ,
        "T"        : args.T,
        "L"        : args.L,
        "A_n"      : args.A_n,
        "a_trial"  : args.a_trial,
        "K_f_coef" : args.K_f_coef,
    }

    flexible = MonoFlexible(**flexible_kwargs)

    print( "ν = {ν:.5f}".format(ν=flexible.ν) )
    print( "a = {a:.2f}".format(a=flexible.a) )
    print( "b = {b:.2f}".format(b=flexible.b) )


# python code run faster in a function:
if __name__ == "__main__":
    main()

使用默认参数,所以为了测试它——只需运行它。

【问题讨论】:

  • 您的示例与实际使用相去甚远,因此很难确定问题的约束条件。对于给定的实例,是否只有一个解决方案?为什么这些属性而不是普通的属性或方法?你要解决的真正问题是什么?
  • @MikeGraham:但它说明了一切......我解决了两个方程组,只有一个解决方案,我使用 scipy 的 fsolve,它将解决方案作为列表(或数组)返回.但我希望这两种解决方案都是实例的属性,所以我正在寻找一种方法将类方法返回的列表重新绑定为两个属性。
  • @MikeGraham:我添加了一个带有虚拟方程组的示例。

标签: python oop


【解决方案1】:

在我看来,您只是在尝试缓存解决方案。这是一种涉及创建另一个属性的方法:

class Test(object):
    def __init__(self):
        pass

    @property
    def solution(self):
        try:
            return self._solution
        except AttributeError:
            self._solution = self.ab()
            return self._solution

    def ab(self):
        print("Calc!")
        a = 1
        b = 2
        return [a,b]

    @property
    def a(self):
        return self.solution[0]

    @property
    def b(self):
        return self.solution[1]

test = Test()

print(test.a)
print(test.b)

输出:

Calc!
1
2

更新!

在 Python 3.8 中,一个内置的装饰器被添加到名为 functools.cached_property() 的标准库中,这使得以更简单和更简洁的方式实现这个方案(而且它是线程安全的):

import functools

class Test(object):
    def __init__(self):
        pass

    @functools.cached_property
    def ab(self):
        print("Calc!")
        a = 1
        b = 2
        return [a,b]

    @property
    def a(self):
        return self.ab[0]

    @property
    def b(self):
        return self.ab[1]

【讨论】:

  • 在我的生产代码中,如果我使用if self.solution == None: 而不是if not self.solution:,它就可以工作。否则它会显示ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
  • 使用 Python 3.4.3 对我来说效果很好。但是,如果您必须更改它,请将其设为 if self.solution is None。我已经更新了我的答案以做到这一点并且更简洁。
【解决方案2】:

在创建结果时分配属性可能是一种方法

 #!/usr/bin/python3

class Test(object):

    def __init__(self):
        pass

    def ab(self):
        print("Calc!")
        self._a = a = 1
        self._b = b = 2
        return [a,b]

    @property
    def a(self):
        return self._a

    @a.setter
    def a(self, value):
        self._a = value



    @property
    def b(self):
        return self._b

    @b.setter
    def b(self, value):
        self._b = value

test = Test()
results = test.ab()
print(test.a)
print(test.b)

【讨论】:

  • 不适用于我的机器:AttributeError: can't set attribute.
  • 现在是RuntimeError: maximum recursion depth exceeded
  • 我忘记将属性设置为“私有”,但现在可以使用了
  • 是的,现在可以了。但是需要results = test.ab() 行才能使其正常工作。
  • 它可以从 __init__\ 运行,如果需要继承,可以使用 super 函数或覆盖该行为
【解决方案3】:

你当前的类根本没有任何状态,所以它看起来不像是一个好的类设计的例子。它的确切操作完全是硬编码的……很难知道拥有它会带来什么好处。

也许你想要的更像是

class Test(object):
    def __init__(self):
        self.a, self.b = fsolve(lambda x: [
                    x[0] + 2*x[1] - 5
                , 3*x[0] -   x[1] + 2
            ], [1,1])

【讨论】:

  • __init__下计算不好,因为它破坏了继承。我现在不需要它,但我以后可能需要它。实际我在__init__下有参数,系统对这些参数进行求解。
  • @Adobe,这如何适用于__init__ 而不适用于ab?所需的继承看起来如何?真正的类是什么样子的——它是有状态的吗?它是参数化的吗?
  • 我编辑了编辑。请看它是__init__。这样我就可以引入ab方法,如果有方法使用它,一切都会起作用,但如果我把ab放在__init__下面,它就不起作用了。
  • @Adobe,如果您想要更改继承,那么对于此类问题,继承将是一个非常非常不幸的解决方案。您能否发布所有名称和操作都完好无损的实际代码?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-20
  • 2021-09-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多