classmethod 继承_让人眼花缭乱的类继承

Python语言的一个优势是简洁易用。是否简洁易用仅仅是Python语言本身的一个话题,但“好消息”是如果你想创造那种一大堆继承、混乱的内部关系的代码,也是可以的!

今天烦人的代码来自于验证某些math-y数学分析代码。一开始,他们是发现文档和代码对应不上,只得去阅读代码看看代码到底做了什么事情。在一个文件中,找到了一个业务关注的核心类,定义如下所示。

class WeibullFitter(KnownModelParametricUnivariateFitter):
# snip: some math

表面上看,这个数学方法看上去多少是对的,但有个问题是:父类是怎么调用的?没办法,只能往上查看其父类,父类的代码如下:

class KnownModelParametricUnivariateFitter(ParametricUnivariateFitter):

_KNOWN_MODEL = True

这个所谓的基类压根就算不上基类,“鸡肋”还差不多!仅仅是设置了一个属性为True,沿着继承树再往上走,找到基类代码如下:

class ParametricUnivariateFitter(UnivariateFitter):

# ...snip...

呃,虽然这还不是最终的基类,但至少这个类还实现了某些方法。这样的写法肯定让你怀疑代码结构的问题了,目前为止还没找到真正烦人的代码。但目前我们可以讨论一下为什么说继承在某种程度上是有害的。继承会自动产生依赖,这意味着如果要理解子类的行为必须同时了解父类的行为。当然,好的继承的实现会划定这些边界,但显然我们看到的是一个反面例子。

除此之外,由于Python是一个弱类型语言,因此继承的优点之一的多态在Python里都算不上优点了。没错,我们可以通过Python的类型注解去做类型检查,这会让多态派上用场,但这没法判断整个继承树。

撇开这一切不谈,使用继承的主要原因是我们可以将公共的业务逻辑部分抽离开,让子类系统无需处理这些公共的业务逻辑。因此,即便存在多种可能的类型,我们仍然可以调用具体实例的某个方法,并且能够保证如期望那样运行,且可以呈现不同的行为。接着来看ParametricUnivariateFitter这个类,类中定义了如下方法:

def _fit_model(self, Ts, E, entry, weights, show_progress=True):

if utils.CensoringType.is_left_censoring(self): # Oh no.
negative_log_likelihood = self._negative_log_likelihood_left_censoring
elif utils.CensoringType.is_interval_censoring(self): # Oh no no no.
negative_log_likelihood = self._negative_log_likelihood_interval_censoring
elif utils.CensoringType.is_right_censoring(self): # This is exactly what I think it is isn't it.
negative_log_likelihood = self._negative_log_likelihood_right_censoring

# ...snip...

注释是问题发现人提供的。为了满足整个子类树,每个子类都使用了类型检查,因此子类不同的行为是通过类型检查来实现的。这可以说是100%的臭代码!当我们去阅读CensoringType代码的时候,让我们再次确信了这一点。

class CensoringType(Enum): # enum.Enum from the standard library
LEFT = "left"
INTERVAL = "interval"
RIGHT = "right"

@classmethod
def right_censoring(cls, function: Callable) -> Callable:
@wraps(function) # functools.wraps from the standard library def f(model, *args, **kwargs):
cls.set_censoring_type(model, cls.RIGHT)
return function(model, *args, **kwargs)

return f

@classmethod
def left_censoring(cls, function: Callable) -> Callable:
@wraps(function)
def f(model, *args, **kwargs):
cls.set_censoring_type(model, cls.LEFT)
return function(model, *args, **kwargs)

return f

@classmethod
def interval_censoring(cls, function: Callable) -> Callable:
@wraps(function)
def f(model, *args, **kwargs):
cls.set_censoring_type(model, cls.INTERVAL)
return function(model, *args, **kwargs)

return f

@classmethod
def is_right_censoring(cls, model) -> bool:
return cls.get_censoring_type(model) == cls.RIGHT

@classmethod
def is_left_censoring(cls, model) -> bool:
return cls.get_censoring_type(model) == cls.LEFT

@classmethod
def is_interval_censoring(cls, model) -> bool:
return cls.get_censoring_type(model) == cls.INTERVAL

@classmethod
def get_censoring_type(cls, model) -> str:
return model._censoring_type

@classmethod
def str_censoring_type(cls, model) -> str:
return model._censoring_type.value

@classmethod
def set_censoring_type(cls, model, censoring_type) -> None:
model._censoring_type = censoring_type

即便是你不懂Python代码,你也会想到这是一个枚举。@classmethod是Python注解静态方法的修饰符,就像类成员方法中使用self作为第一个参数一样,静态方法使用cls作为方法的第一个参数,以代表类本身。

去看一下right_censoring方法也很重要,因为这些方法看起来是“装饰器”。他们使用了@wraps来修饰定义的局部方法。这个right_censoring方法需要接收一个可调用的函数(也许是一个构造函数),然后将该方法的实现用内部以“f”方法替换。并且在这里面,在调用构造函数之前,修改了构造方法的参数值。

如果你不经常使用Python编程的话,你可能会觉得十分困惑,因此我们来看看这个方法如何使用的:

@CensoringType.right_censoringclass SomeCurveFitterType(SomeHorribleTreeOfBaseClasses):
def __init__(self, model, *args, **kwargs):
# snip
instance = SomeCurveFitterType(model, *args, **kwargs)

在最后一行代码,并没有调用```init``构造函数,而是首先通过内部的f函数,这个函数最重要的一件事就是在调用构造函数前,调用静态方法cls.set_censoring_type(model, cls.RIGHT) 。

如果你对此完全不理解,也不用感到糟糕。装饰器是Python的一种独特的方式去修改类和方法的实现。这个特性允许你在传统的方式中混合使用声明式编程。

最终,为了理解WeibullFilter这个类的行为,你必须阅读半大祖先类代码才能看到BaseFitter类型,然后你必须注意应用了什么装饰器以及装饰器对应的祖先类,只有这样才真正知道这个业务的功能是什么。

如果你是写这些代码的人,也许会觉得这种混入装饰器和继承的写法扩展性很高。可以快速轻松地在框架里插入一个曲线拟合方法。你甚至会以为你的大脑创造了这个星球上最具工程化的框架。而余下的我们,必须像盲人那样使用棍子去探路。
最后我们的问题提交人总结到:

我对此感到很糟糕。这个库看着不错,数学方法本身也不错,并且这个库非常有用,减轻了我很多工作……
这一系列的行为导致了性能问题,我不得不重新做不同的实现。因为这一系列的嵌套调用将简单的处理过程的性能给破坏了——执行时间可能超过10分钟。而新写的代码几乎是瞬间完成,包含注释也就不到20行。

print("正文结束!")

本文翻译自:thedailywtf.com

a1e1cc77698a2b668a9ef55bf5638020.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值