classmethod 继承_让人眼花缭乱的类继承
Python語言的一個優(yōu)勢是簡潔易用。是否簡潔易用僅僅是Python語言本身的一個話題,但“好消息”是如果你想創(chuàng)造那種一大堆繼承、混亂的內(nèi)部關(guān)系的代碼,也是可以的!
今天煩人的代碼來自于驗證某些math-y數(shù)學(xué)分析代碼。一開始,他們是發(fā)現(xiàn)文檔和代碼對應(yīng)不上,只得去閱讀代碼看看代碼到底做了什么事情。在一個文件中,找到了一個業(yè)務(wù)關(guān)注的核心類,定義如下所示。
class WeibullFitter(KnownModelParametricUnivariateFitter):# snip: some math
表面上看,這個數(shù)學(xué)方法看上去多少是對的,但有個問題是:父類是怎么調(diào)用的?沒辦法,只能往上查看其父類,父類的代碼如下:
class KnownModelParametricUnivariateFitter(ParametricUnivariateFitter):_KNOWN_MODEL = True
這個所謂的基類壓根就算不上基類,“雞肋”還差不多!僅僅是設(shè)置了一個屬性為True,沿著繼承樹再往上走,找到基類代碼如下:
class ParametricUnivariateFitter(UnivariateFitter):# ...snip...
呃,雖然這還不是最終的基類,但至少這個類還實現(xiàn)了某些方法。這樣的寫法肯定讓你懷疑代碼結(jié)構(gòu)的問題了,目前為止還沒找到真正煩人的代碼。但目前我們可以討論一下為什么說繼承在某種程度上是有害的。繼承會自動產(chǎn)生依賴,這意味著如果要理解子類的行為必須同時了解父類的行為。當(dāng)然,好的繼承的實現(xiàn)會劃定這些邊界,但顯然我們看到的是一個反面例子。
除此之外,由于Python是一個弱類型語言,因此繼承的優(yōu)點之一的多態(tài)在Python里都算不上優(yōu)點了。沒錯,我們可以通過Python的類型注解去做類型檢查,這會讓多態(tài)派上用場,但這沒法判斷整個繼承樹。
撇開這一切不談,使用繼承的主要原因是我們可以將公共的業(yè)務(wù)邏輯部分抽離開,讓子類系統(tǒng)無需處理這些公共的業(yè)務(wù)邏輯。因此,即便存在多種可能的類型,我們?nèi)匀豢梢哉{(diào)用具體實例的某個方法,并且能夠保證如期望那樣運行,且可以呈現(xiàn)不同的行為。接著來看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...
注釋是問題發(fā)現(xiàn)人提供的。為了滿足整個子類樹,每個子類都使用了類型檢查,因此子類不同的行為是通過類型檢查來實現(xiàn)的。這可以說是100%的臭代碼!當(dāng)我們?nèi)ラ喿xCensoringType代碼的時候,讓我們再次確信了這一點。
class CensoringType(Enum): # enum.Enum from the standard libraryLEFT = "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注解靜態(tài)方法的修飾符,就像類成員方法中使用self作為第一個參數(shù)一樣,靜態(tài)方法使用cls作為方法的第一個參數(shù),以代表類本身。
去看一下right_censoring方法也很重要,因為這些方法看起來是“裝飾器”。他們使用了@wraps來修飾定義的局部方法。這個right_censoring方法需要接收一個可調(diào)用的函數(shù)(也許是一個構(gòu)造函數(shù)),然后將該方法的實現(xiàn)用內(nèi)部以“f”方法替換。并且在這里面,在調(diào)用構(gòu)造函數(shù)之前,修改了構(gòu)造方法的參數(shù)值。
如果你不經(jīng)常使用Python編程的話,你可能會覺得十分困惑,因此我們來看看這個方法如何使用的:
@CensoringType.right_censoringclass SomeCurveFitterType(SomeHorribleTreeOfBaseClasses):def __init__(self, model, *args, **kwargs):
# snip
instance = SomeCurveFitterType(model, *args, **kwargs)
在最后一行代碼,并沒有調(diào)用```init``構(gòu)造函數(shù),而是首先通過內(nèi)部的f函數(shù),這個函數(shù)最重要的一件事就是在調(diào)用構(gòu)造函數(shù)前,調(diào)用靜態(tài)方法cls.set_censoring_type(model, cls.RIGHT)?。
如果你對此完全不理解,也不用感到糟糕。裝飾器是Python的一種獨特的方式去修改類和方法的實現(xiàn)。這個特性允許你在傳統(tǒng)的方式中混合使用聲明式編程。
最終,為了理解WeibullFilter這個類的行為,你必須閱讀半大祖先類代碼才能看到BaseFitter類型,然后你必須注意應(yīng)用了什么裝飾器以及裝飾器對應(yīng)的祖先類,只有這樣才真正知道這個業(yè)務(wù)的功能是什么。
如果你是寫這些代碼的人,也許會覺得這種混入裝飾器和繼承的寫法擴展性很高。可以快速輕松地在框架里插入一個曲線擬合方法。你甚至?xí)詾槟愕拇竽X創(chuàng)造了這個星球上最具工程化的框架。而余下的我們,必須像盲人那樣使用棍子去探路。
最后我們的問題提交人總結(jié)到:
我對此感到很糟糕。這個庫看著不錯,數(shù)學(xué)方法本身也不錯,并且這個庫非常有用,減輕了我很多工作……
這一系列的行為導(dǎo)致了性能問題,我不得不重新做不同的實現(xiàn)。因為這一系列的嵌套調(diào)用將簡單的處理過程的性能給破壞了——執(zhí)行時間可能超過10分鐘。而新寫的代碼幾乎是瞬間完成,包含注釋也就不到20行。
本文翻譯自:thedailywtf.com
總結(jié)
以上是生活随笔為你收集整理的classmethod 继承_让人眼花缭乱的类继承的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言交通违章编程代码,C语言程序设计之
- 下一篇: 实战 串口通讯