python的OOP机制

python的OOP机制

在Python中,实际上一切都是对象,我们使用的内置数据类型,本质上也是类的实例化。例如:

>>> type("123")
<class 'str'>

而类本身也是对象,只不过是元类的对象而已。 例如:

>>> type(int)
<class 'type'>
>>> type(list)
<class 'type'>

从本质上讲,python的OOP机制主要依赖两个基础:1.函数的第一个参数;2.继承属性搜索
OOP不仅是一门技术,更是一种经验。

因为OOP不是在所有场景下都优于POP(Procedure-Oriented Programmin)

python是一门一致性非常好的语言,大多数使用OOP的方式,都可以统一表达为:

object.attribute

这个表达式会在python中启动一次搜索,去搜索对象连接的类树,来寻找attribute首次出现的类。

搜索的顺序大致是从object开始,然后是该对象之上的所有类,自下至上,由左到右。

属性访问就是搜索类树,我们称这种搜索为“继承”。因为树中位置较低的对象继承了树中位置较高的对象所拥有的属性。当从下至上进行搜索时,连接至树中的对象就是树中所有父节点定义的所有属性的并集,直到树的根部。
在这里插入图片描述
图中的五个对象,C1,C2,C3是类的对象,l1和l2是实例对象。在python中类和通过类产生的实例是两种不同的对象类型。
在这里插入图片描述
我们来举一个例:

I2.w

这是一个 object.attribute 表达式,因此它会触发搜索类树。实际搜索顺序如下:

I2, C1, C2, C3

如果找到w,那么就停止搜索;如果搜索结束没找到w,就会引发一个错误。在图中,w属性只在C3中出现了。因此通过搜索将I2.w解析为C3.w,用OOP的术语来讲就是“I2从C3继承了属性w”。
在这里插入图片描述

类和实例

在python中,类和实例是两种不同的对象类型,但在类树中看它们几乎是完全等价的:两者的主要目的都是作为另一种命名空间。类和实例的主要差异在于,类是一种产生实例的工厂。

方法调用

前面我们介绍了python的OOP机制主要依赖两个基础之一:“继承属性搜索”,现在来看另一个基础:“函数的第一个参数”。前文所述的I2.w是一个属性,现在假设w是C3的函数。那么其实际含义应该是“调用C3.w函数来处理I2”,python会自动将I2.w()函数调用映射为C3.w(I2),传入I2作为w函数的第一个参数。

事实上,每当我们以object.attribute来调用附属于类的函数时,总会隐含着这个类的实例。这个实例也是称其为面向对象模型的原因之一。当操作执行的时候,总是有个主体对象,否则这种调用是没有意义的。例如:有一个Employee的类,包含方法giveRaise,除非和加薪的员工结合在一起使用,否则调用giveRaise是没有意义的。

python把隐含的实例传入到方法中的第一个参数,习惯上我们把第一个参数命名为self(这只是个习惯,如果你曾经是C++程序员,那么将类中函数的第一个参数命名为this可能更符合你的习惯)。方法通过这个参数来处理调用的主体。方法能够通过实例(bob.giveRaise())或类(Employee.giveRaise(bob))进行调用。

python在运行bob.giveRaise()时,做了两件事情:

  1. 在bob所在的类树中通过继承搜索giveRaise()方法;
  2. 将bob传入giveRaise()方法的第一个参数self.

使用Employee.giveRaise(bob)来调用,相当于手动完成了上面的两件事情。

编写类树

在编写之前,先介绍一些东西。

  • 每个class语句都会生成一个新的类对象;
  • 每次类调用时,都会生成一个新的实例对象;
  • 实例自动链接到创建它们的类;
  • 类链接到父类的方式是,将父类列在class头部的括号内;括号中从左至右的顺序会决定树中的顺序。

现在,我们来建立最开始的那个类树。让我们暂时先忽略类中的属性,那么代码应该如下所示:

class C2:
    ...


class C3:
    ...


class C1(C2, C3):
    ...


I1 = C1()
I2 = C1()

其中C1继承自C2和C3,有两个父类。这通常被称为“多继承”,也就是父类有多个。如果父类只有一个那就是单继承。

由于继承是按照搜索来进行的,而这个搜索是按照某种特定顺序进行的,因此你要把属性附件到哪一个对象就显得非常重要。例如:C2和C3都有属性z,然后C1.z将会使用C2中的z,而不是C3中的z.

  • 属性通常是在class语句的顶层语句块中通过赋值语句添加到中。例如:

    class C2:
        z = 1
    
  • 属性通常是通过对self参数的赋值来附加给实例的,例如:

    class C1(C2, C3):
        def setname(self, who):
            self.name = who
    

    这个方式实际上是可以将属性附加给类的,但这需要编写额外的代码。例如:

    class C1(C2, C3):
        def setname(self, who):
            self.name = who
    
    
    C1.setname(C1, '赵四')		# 给类附加属性
    
    class C(C1):
        ...
    
    I = C()
    I1 = C1()
    I2 = C1()
    
    print(C.name)
    print(I1.name)
    print(I2.name)
    

    代码执行结果如下所示:

      赵四
      赵四
      赵四
    

附加在类上的属性,该类的子类和实例都会拥有该属性;而附加在实例上属性,只被该实例拥有。

和普通变量一样,类和实例属性不需要事先声明,而是在首次赋值后它的值才会存在。事实上,类树中所有对象都只不过是命名空间对象,我们可以使用恰当的名称来访问或修改其任何属性。例如,编写C1.setname(I1, 'jack')I1.setname('jack')是等效操作。

运算符重载

在python中,直到调用setname之前,C1类都不会把name属性附加到实例上。因此,在调用I1.setname之前,使用I1.name会导致未定义名称的错误。如果需要name这个属性一定存在,通常会在构造时填充好这个属性。

class C2:
    ...


class C3:
    ...


class C1(C2, C3):
    def __init__(self, who):    # 构造函数
        self.name = who


    def setname(self, who):
        self.name = who


I1 = C1('jack')     # 调用__inin__方法
I2 = C1('lucy')     # 调用__inin__方法

print(I1.name)
print(I2.name)

如果有__init__方法(编写这个方法,或者继承这个方法),那么每次从类产生实例时Python都会自动调用__init__方法。新实例会自动传入__init__的第一个参数self,而在类调用括号内的参数会成为__init__的第二及其之后的参数。其效果就是在创建实例的时候,初始化了实例。

由于__init__在实例化的时候自动调用,因此也被称为“构造函数”。构造函数是python中所谓的运算符重载这一大类方法中最常用的方法之一。
运算符重载方法也是可以被继承的,但是它们的名称开头和结束都带有双下划线(__),当能够支持这些操作的实例出现在对应的运算符旁时,python就会自动运行它们。运算符重载方法不是必须的,如果缺省则不支持对应的运算。如果没有__init__方法,类调用将返回一个空实例(实际上就是一个空的命名空间)。

OOP是关于代码重用

类所支持的代码重用方式是python中其它方式难以提供的,事实上,代码重用也是OOP最重要的目的。通过类,我们可以定制现有的代码来实现需求。类其实就是由函数和其它名称所构成的包,很像模块。但是类支持自动属性继承搜索,这样可以实现高层次的定制,而这是模块和函数做不到的。

多态和类

子类可以覆盖父类的函数,从而重新实现子类的行为。而实例对象会根据创建其的类来决定继承搜索从哪个层次开始,从而决定所使用的函数是哪一个。这就是多态的体现。(多态:运算的意义取决于运算的对象)

PS: 文中图片来自于《Python学习手册》的截图

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值