Sympy的一個Bug,牽涉到Python多重繼承問題

Lin_Danny發表於2018-08-17

Sympy(gometry子包)中發現的一個bug

  • 問題來源:獲取點到線段的距離。
  • 點到線段的距離與點到直線的距離不同,點到線段的最短距離並不一定是垂直距離。但是當使用sympy.geometry.line中的Segmnet類建立例項化的線段,並使用Segment類中的 distance() 方法獲取 某個點到這條線段的距離時,發現無論點在何處,計算的始終是點到直線的距離(即垂直距離)。
p1_sym = sympy.geometry.point.Point(1, 0)
p2_sym = sympy.geometry.point.Point(0, 1)
p3_sym = sympy.geometry.point.Point(10, 0)
s1_sym = sympy.geometry.line.Segment(p1_sym, p2_sym)
print(s1_sym.distance(p3_sym))

結果輸出為 9*sqrt(2)/2,這顯然是不對的。
  • Debug後發現,呼叫distance() 方法,始終會跳至 Line 類中的distance() 方法,而不是 Segment 類中的 distance() 方法。究其原因,是因為Segment3D這個類繼承了Segment, LinearEntity2D兩個類,而 LinearEntity2D,有繼承了Line類,最後就使用了Line 類中的distance() 方法。
sympySegment2D的繼承
class Segment2D(LinearEntity2D, Segment):

將繼承先後順序修改,結果正確。繼承順序改為:

class Segment2D(Segment, LinearEntity2D):

看到這裡,bug的原因找到了,是Python的多重繼承問題引起的!!!

Python多重繼承

新式類和經典類

  • Python3 中全部預設是新式類,新式類和經典類的多重繼承關係不同。在菱形繼承(或稱鑽石繼承)中,經典類多繼承屬性搜尋順序: 先深入繼承樹左側,若沒有搜尋到,開始找右側;新式類多繼承屬性搜尋順序: 先水平搜尋,然後再向上移動。
  • 更多經典類和新式類的區別,請自行檢索,但個人認為沒必要糾結於二者的區別,新式類是未來,掌握新式類就完全OK。

繼承關係

這裡多重繼承牽涉到的都是新式類。

1. 簡單多重繼承

class Line():
    def distance(self):
        print('value of A')

class LinearEntity(Line):
    pass

class Segment():
    def distance(self):
        print('value of C')

class Segment2D(LinearEntity, Segment):
    pass

test = Segment2D()
test.distance()
列印結果 : value of A

更改Segment2D類對父類的繼承順序:

class Line():
    def distance(self):
        print('value of A')

class LinearEntity(Line):
    pass

class Segment():
    def distance(self):
        print('value of C')

class Segment2D(Segment, LinearEntity):
    pass

test = Segment2D()
test.distance()
列印結果 : value of c

結論:簡單多重繼承,先一直深入搜尋左邊,沒有找到相關屬性/方法,再搜尋右邊

2. 菱形繼承

當一個子類繼承2個父類,而2個父類又都繼承一個基類,構成了一個菱形.
這裡寫圖片描述

為驗證菱形繼承的繼承關係,修改上面的程式碼,如下:

class Line():
    def distance(self):
        print('value of A')

class LinearEntity(Line):
    pass

class Segment(Line):
    def distance(self):
        print('value of C')

class Segment2D(LinearEntity, Segment):
    pass

test = Segment2D()
test.distance()
列印結果 : value of c

更改Segment2D類對父類的繼承順序:

class Line():
    def distance(self):
        print('value of A')

class LinearEntity(Line):
    pass

class Segment():
    def distance(self):
        print('value of C')

class Segment2D(Segment, LinearEntity):
    pass

test = Segment2D()
test.distance()
列印結果 : value of c

可以看到,不管繼承先後,菱形繼承關係中,都是先左右搜尋,再上下搜尋,即一層一層搜尋。

文末語:文章大部分還是在自己的部落格網站上釋出,更多內容可移步我的個人網站 《浮生,奈若何》,www.linzhongya.top

相關文章