前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python的多继承和和Scala的trait

Python的多继承和和Scala的trait

作者头像
哒呵呵
发布2018-08-06 14:16:04
6490
发布2018-08-06 14:16:04
举报
文章被收录于专栏:鸿的学习笔记鸿的学习笔记

在业务设计过程中,除了继承这种增量进化,有些时候我们只需要给类添加功能而不是想变成某种类型,那么我们可以选择组合。在这篇文章会先介绍Python的多继承和Scala的trait对组合的实现,最后再来讨论两者的优劣和如何更好的使用它们。 python 那么从一段Python代码开始,看看Python如何处理组合的问题,以及我们要如何避免多继承的问题。

代码语言:javascript
复制
class A:
    def a(self):
        return 'a'

class B:
    def b(self):
        return 'b'

class C(A,B):
    pass

这里的C同时继承了A,B的方法a,b(为了页面展示省去了内置方法),bases方法给出了C继承的父类A和B。

代码语言:javascript
复制
dir(C)
Out[11]: 
[...,'a','b']

C.__bases__
Out[12]: (__main__.A, __main__.B)

对于多继承,还需要解决子类继承的多个父类的实现了同一个方法的问题,这个问题就是“菱形问题”,对上面的代码再做一定的修改:

代码语言:javascript
复制
class A:
    def a(self):
        return 'a'

class B(A): 
    def b(self):
        return 'b'

class NewB(A):
    def b(self):
        return 'NewB'

class C(NewB,B):
    pass

NewB和B都继承了A,那么继承了B和NewB的C调用的是哪个方法?Python对于多继承同名方法的调用是基于C3算法实现的,这篇文章不对C3算法做深入的了解,简单来说就是: 深度优先,从左到右,移除继承列表中重复类型,保留最后一个。 在实际过程中,可以使用__mro__方法查看这个类的方法解析顺序。下面的代码也证明了C中的b方法来源于NewB这个类。

代码语言:javascript
复制
C.__mro__
Out[14]: (__main__.C, __main__.NewB, __main__.B, __main__.A, object)

C().b()
Out[15]: 'NewB'

当然我们也可以跳过方法继承顺序直接使用我们想要的类,唯一要做的就是显性的传入self这个参数。例如:

代码语言:javascript
复制
class C(NewB,B):

    def b(self):
        return B.b(self)


C().b()
Out[17]: 'b'

不过最好的办法是使用super(),遵循方法继承顺序,不容易引起混乱。

代码语言:javascript
复制
class C(NewB,B):
    def b(self):
        return super().b()


C().b()
Out[21]: 'NewB'

注意的是,使用Python的多继承的过程中,极力避免子类继承多个不同类型的类,如果某个类是一个混入类,在其后面加上Mixin。

Scala 了解完Python的多继承,再来讨论Scala的trait的使用。

代码语言:javascript
复制
scala> trait A {
     |     def a:String = "A"
     |   }
defined trait A

一个简单的特质A实现了,我们可以使用extends或者是with加入到类B当中:

代码语言:javascript
复制
scala> class B extends A {
     |     def b: String = "b"
     |   }
defined class B

用extends关键字实际上是表示B隐性的继承了特质A

代码语言:javascript
复制
scala> val b = new B
b: B = B@169bb4dd

scala> b.a
res0: String = A

特质也可以作为一个类型来使用,例如:

代码语言:javascript
复制
scala> val a:A = b
a: A = B@169bb4dd

scala> a.a
res1: String = A

在某个类已经继承了某个父类的时候,就需要使用with关键字来混入trait了。(可以连续使用with来混入多个特质)

代码语言:javascript
复制
scala> class NewA
defined class NewA

scala>   class C extends NewA with A {
     |     def b: String = "b"
     |   }
defined class C

Scala的特质可以像类一样的定义,但是不能传入任何构造参数,例如:

代码语言:javascript
复制
scala> trait D(x:Int)
<console>:1: error: traits or objects may not have parameters
       trait D(x:Int)

同样的trait也面临着继承顺序的问题,于Python不同的是,Scala的继承顺序关键在于super方法的调用,而不是同名方法的调用顺序的确定,因为添加进子类的两个trait的同名方法在编译期便会报错。

代码语言:javascript
复制
scala> trait A {
     |     def name:String = "A"
     |   }
defined trait A

scala> trait B{
     |     def name:String = "B"
     |   }
defined trait B

scala> class C extends NewA with A with B{
     |     def b: String = "b"
     |   }
<console>:10: error: class C inherits conflicting members:
  method name in trait A of type => String  and
  method name in trait B of type => String
(Note: this can be resolved by declaring an override in class C.)

我们设计这么一个类来展示Scala如何确定super的调用方法:

代码语言:javascript
复制
scala> abstract class Basic{
     |     def get():Unit
     |     def put(x:String)
     |   }
defined class Basic

scala>   class A extends Basic{
     |     private val buf = new ArrayBuffer[String]
     |     def get() = println(buf.result())
     |     def put(x:String) = {buf += x}
     |   }
defined class A

scala>   trait B extends Basic{
     |     abstract override def put(x:String) = {super.put("B"+x)}
     |   }
defined trait B

scala>   trait C extends Basic{
     |     abstract override def put(x:String) = {super.put("C"+x)}
     |   }
defined trait C

scala> val test = new A with B with C
test: A with B with C = $anon$1@497ed877

scala> test.put("A")

scala> test.get()
ArrayBuffer(BCA)

从代码中可以看出来首先起作用的是特质C,其次是B,最后A,再将BCA放入buf列表当中,Scala将这种调用顺序称为线性化,它将所有类和它继承的类以及特质按照一定顺序排列起来,从右至左开始执行。

如何评价: 继承可以让新手顺利的使用专家设计出来的框架,但是其本身基于依赖的实现方式会导致耦合的问题。所以很重要的一点就是,在需求的实现过程中,应该区分我们要做的是功能扩展还是使用某些功能,如果仅仅只是使用某些功能(组合),所以Scala和Python给出了两种不同的实现方式,Scala选择了trait(特质),Python因为历史原因,保持了多继承的方式。它们都提供了一种混入(mix-in)的机制,让某一种类型的扩展出一种其它类型的功能。多继承的问题在于,它会导致写出来的类产生混乱,无法判断你继承的类到底属于哪一种类型。Python的多继承在一定程度上并没有Scala的灵活,它的多继承在处理同名方法时采用的是覆盖的方式,而组合的核心在于“能做什么”,而不是“是什么”,功能的混入不应该像类的继承,而是相对独立,正因为如此,trait逐渐成为了现在语言的发展趋势。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 鸿的学习笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档