Tob*_*ler 12 python arguments optional-parameters
举一个简单的例子,取一个可以返回其属性的class 椭圆,例如面积A,周长C,长轴/短轴a/b,偏心率e等.为了得到它,显然必须提供其两个参数以获得所有其他参数,虽然作为一个特殊情况,只提供一个参数应该假设一个圆圈.三个或更多一致的参数应该产生警告但是有效,否则显然会引发异常.
所以有效Ellipses的一些例子是:
Ellipse(a=5, b=2)
Ellipse(A=3)
Ellipse(a=3, e=.1)
Ellipse(a=3, b=3, A=9*math.pi) # note the consistency
Run Code Online (Sandbox Code Playgroud)
而无效的将是
Ellipse()
Ellipse(a=3, b=3, A=7)
Run Code Online (Sandbox Code Playgroud)
因此构造函数要么包含许多=None参数,
class Ellipse(object):
def __init__(self, a=None, b=None, A=None, C=None, ...):
Run Code Online (Sandbox Code Playgroud)
或者,可能更明智,一个简单的**kwargs,可能添加提供a,b作为位置参数的选项,
class Ellipse(object):
def __init__(self, a=None, b=None, **kwargs):
kwargs.update({key: value
for key, value in (('a', a), ('b', b))
if value is not None})
Run Code Online (Sandbox Code Playgroud)
到现在为止还挺好.但现在实际实现,即确定提供哪些参数,哪些参数不是,并根据它们确定所有其他参数,或在需要时检查一致性.
我的第一种方法是许多人的简单而乏味的组合
if 'a' in kwargs:
a = kwargs['a']
if 'b' in kwargs:
b = kwargs['b']
A = kwargs['A'] = math.pi * a * b
f = kwargs['f'] = math.sqrt(a**2 - b**2)
...
elif 'f' in kwargs:
f = kwargs['f']
b = kwargs['b'] = math.sqrt(a**2 + f**2)
A = kwargs['A'] = math.pi * a * b
...
elif ...
Run Code Online (Sandbox Code Playgroud)
等等*.但是没有更好的方法吗?或者这个类设计完全是bollocks,我应该创建构造函数Ellipse.create_from_a_b(a, b),尽管基本上使"提供三个或更多一致参数"选项不可能?
额外的问题:由于椭圆的周长涉及椭圆积分(或椭圆函数,如果提供圆周并且要获得其他参数),这些不完全是计算上的微不足道的,那么这些计算实际上应该在构造函数中还是放入@property Ellipse.C?
*我想至少有一个可读性改进总是提取a和b计算其余的,但这意味着重新计算已经提供的值,浪费时间和精度......
Łuk*_*ski 15
我的建议主要集中在数据封装和代码可读性上.
a)在不明确的测量上选择对以在内部表示椭圆
class Ellipse(object):
def __init__(a, b):
self.a = a
self.b = b
Run Code Online (Sandbox Code Playgroud)
b)创建属性族以获得关于椭圆的所需度量
class Ellipse(object):
@property
def area(self):
return math.pi * self._x * self._b
Run Code Online (Sandbox Code Playgroud)
c)使用不明确的名称创建工厂类/工厂方法:
class Ellipse(object):
@classmethod
def fromAreaAndCircumference(cls, area, circumference):
# convert area and circumference to common format
return cls(a, b)
Run Code Online (Sandbox Code Playgroud)
样品用法:
ellipse = Ellipse.fromLongAxisAndEccentricity(axis, eccentricity)
assert ellipse.a == axis
assert ellipse.eccentricity == eccentricity
Run Code Online (Sandbox Code Playgroud)
a从其他参数的每个配对计算a都是一样的b从每一个配对a和其他参数a和计算其他参数b下面是一个缩短版只是a,b,e,和f,很容易扩展到其它参数:
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)
Run Code Online (Sandbox Code Playgroud)
它很漂亮Pythonic,虽然__dict__使用可能不是.该__dict__方法是少线,少重复,但你可以通过打破它分成不同的更明确的if self.b is None: self.calculate_b()线条.
我唯一编码e和f,但它的可扩展性.只是模仿e和f代码与任何你想要添加(面积,周长等)的函数,方程a和b.
我没有将你的单参数椭圆的请求包括为圆圈,但这只是在开头检查calculate_a是否只有一个参数,在这种情况下a应该设置为使椭圆成圆形(b如果a是则应该设置)唯一的):
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 = ...
Run Code Online (Sandbox Code Playgroud)
如果只需要这个功能,我的建议是使用Nsh的答案,使用你提到的第二个解决方案.
否则,如果您的项目中出现了这个问题,我想出了一个解决方案:
class YourClass(MutexInit):
"""First of all inherit the MutexInit class by..."""
def __init__(self, **kwargs):
"""...calling its __init__ at the end of your own __init__. Then..."""
super(YourClass, self).__init__(**kwargs)
@sub_init
def _init_foo_bar(self, foo, bar):
"""...just decorate each sub-init method with @sub_init"""
self.baz = foo + bar
@sub_init
def _init_bar_baz(self, bar, baz):
self.foo = bar - baz
Run Code Online (Sandbox Code Playgroud)
这将使您的代码更具可读性,并且您将隐藏此装饰器背后的丑陋细节,这些都是不言自明的.
注意:我们也可以删除@sub_init装饰器,但我认为这是将方法标记为子初始化的唯一合法方式.否则,一个选择是同意在方法名称之前加上一个前缀,比方说_init,但我认为这是一个坏主意.
以下是实施:
import inspect
class MutexInit(object):
def __init__(self, **kwargs):
super(MutexInit, self).__init__()
for arg in kwargs:
setattr(self, arg, kwargs.get(arg))
self._arg_method_dict = {}
for attr_name in dir(self):
attr = getattr(self, attr_name)
if getattr(attr, "_isrequiredargsmethod", False):
self._arg_method_dict[attr.args] = attr
provided_args = tuple(sorted(
[arg for arg in kwargs if kwargs[arg] is not None]))
sub_init = self._arg_method_dict.get(provided_args, None)
if sub_init:
sub_init(**kwargs)
else:
raise AttributeError('Insufficient arguments')
def sub_init(func):
args = sorted(inspect.getargspec(func)[0])
self_arg = 'self'
if self_arg in args:
args.remove(self_arg)
def wrapper(funcself, **kwargs):
if len(kwargs) == len(args):
for arg in args:
if (arg not in kwargs) or (kwargs[arg] is None):
raise AttributeError
else:
raise AttributeError
return func(funcself, **kwargs)
wrapper._isrequiredargsmethod = True
wrapper.args = tuple(args)
return wrapper
Run Code Online (Sandbox Code Playgroud)
这是我的尝试.如果您正在为某些最终用户执行此操作,则可能需要跳过.我所做的可能很适合设置一些快速数学对象库,但只有当用户知道发生了什么时.
想法是所有描述数学对象的变量都遵循相同的模式,a = something*smntng.
因此,在计算变量irl时,在最坏的情况下我会丢失"某事",然后我会去计算那个值,并且在计算那个值时我会丢失任何值,并将其带回来完成计算我正在寻找的原始变量.有一定的递归模式值得注意.
因此,当计算变量时,在每次访问变量时,我必须检查它是否存在,以及它是否不计算它.因为我必须使用每次访问__getattribute__.
我还需要变量之间的函数关系.所以我将固定一个类属性relations,它将用于此目的.它将是变量和适当函数的字典.
但是如果我有所有必要的变量来计算当前的变量,我还必须提前检查.所以我将修改我的表,变量之间的集中数学关系,列出所有依赖项,在我去计算任何东西之前,我将运行列出的依赖项,并在我需要时计算它们.
所以现在它看起来更像是我们将进行半递归的乒乓匹配,其中函数_calc将调用__getattribute__哪些函数_calc再次调用.直到我们用完变量或实际计算出某些东西.
好的:
ifs坏:
__getattribute__并_calc相互呼叫.也没有很好的方法来制定漂亮的错误打印.relations它可能会使代码变得丑陋(也见最后一点).我无法弄清楚如何让它们成为实例方法,而不是类方法或其他更全局的函数,因为我基本上覆盖了.运算符.a需要b其需要e哪个需要a再次进入无限循环).relations在一个dict类型中设置.这意味着每个变量名称只有1个函数依赖项,这在数学术语中不一定正确.value = self.relations[var]["func"]( *[self.__getattribute__(x) for x in requirements["req"]] )这也就是那些要么再次_calc调用__getattribute__,要么_calc变量存在的调用返回值.此外,每个__init__都必须将所有属性设置为None,否则_getattr将调用a.
def cmplx_func_A(e, C):
return 10*C*e
class Elipse():
def __init__(self, a=None, b=None, **kwargs):
self.relations = {
"e": {"req":["a", "b"], "func": lambda a,b: a+b},
"C": {"req":["e", "a"], "func": lambda e,a: e*1/(a*b)},
"A": {"req":["C", "e"], "func": lambda e,C: cmplx_func_A(e, C)},
"a": {"req":["e", "b"], "func": lambda e,b: e/b},
"b": {"req":["e", "a"], "func": lambda e,a: e/a}
}
self.a = a
self.b = b
self.e = None
self.C = None
self.A = None
if kwargs:
for key in kwargs:
setattr(self, key, kwargs[key])
def __getattribute__(self, attr):
val = super(Elipse, self).__getattribute__(attr)
if val: return val
return self._calc(attr)
def _calc(self, var):
requirements = self.relations[var]
value = self.relations[var]["func"](
*[self.__getattribute__(x) for x in requirements["req"]]
)
setattr(self, var, value)
return value
Run Code Online (Sandbox Code Playgroud)
Oputput:
>>> a = Elipse(1,1)
>>> a.A #cal to calculate this will fall through
#and calculate every variable A depends on (C and e)
20
>>> a.C #C is not calculated this time.
1
>>> a = Elipse(1,1, e=3)
>>> a.e #without a __setattribute__ checking the validity, there is no
3 #insurance that this makes sense.
>>> a.A #calculates this and a.C, but doesn't recalc a.e
30
>>> a.e
3
>>> a = Elipse(b=1, e=2) #init can be anything that makes sense
>>> a.a #as it's defined by relations dict.
2.0
>>> a = Elipse(a=2, e=2)
>>> a.b
1.0
Run Code Online (Sandbox Code Playgroud)
这里还有一个问题,与"坏"中的倒数第二点有关.也就是说,我们可以想象我们可以用C和定义一个椭圆A.因为我们可以与他人每个变量在只有1个函数依赖,如果你定义的变量a,并b在e和a|b像我有,你将无法计算它们.始终至少会有一些微型的变量子集需要发送.这可以通过确保尽可能多地定义您的变量来减轻这些变量,尽可能少但是无法避免.
如果你很懒,这是一个很好的方法来短路你需要快速完成的事情,但我不会在某个地方这样做,我希望别人可以使用它!
对于奖励问题,根据请求进行计算可能是明智的(取决于您的用例),但如果之前计算过,请记住计算值。例如
@property
def a(self):
return self._calc_a()
def _calc_a(self):
if self.a is None:
self.a = ...?
return self.a
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1375 次 |
| 最近记录: |