掌握面向对象编程本质,彻底掌握OOP

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了掌握面向对象编程本质,彻底掌握OOP相关的知识,希望对你有一定的参考价值。

面向对象基本概念

面向对象是一种编程范式。范式是指一组方法论。编程范式是一组如何组织代码的方法论。编程范式指的是软件工程中的一种方法学。

一些主流的编程范式:

  1. OOP - 面向对象编程
    世界观:一切皆对象。
  2. FP - 函数式编程
    世界观:一切皆函数。一般指无副作用的函数。
  3. PP - 过程化编程
  4. IP - 指令式编程
  5. LP - 逻辑化编程
  6. AOP - 面向方面编程 装饰器

设计方法:

  1. 自顶向下
  2. 自底向上

面向对象更进一步的抽象了世界。OOP的世界观:

  1. 世界是由对象组成的
  2. 对象具有运动规律和内部状态
  3. 对象之间可以相互作用

就是一个模板或蓝图,用来生成对象的。我们可以把类看做是一套模具,而模具加工出来的产品就是对象。当我们从一套模具中塑造出一个产品的时候,我们就可以说创建了一个实例。

面向对象的特性:

  1. 唯一性:对象都是唯一的,不存在两个相同的对象,除非他们是同一个对象。
  2. 分类性:对象是可分类的,世界是由不同的类型组成的。

面向对象的三大特征:

  1. 封装
  2. 继承
  3. 多态

面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,而实例则是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据有可能不同。

在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

面向对象的本质:对行为和数据的封装;有时候数据就是数据;而有的时候行为就是行为。我们先使用Python标准库中的namedtuple来实现一个入门的类吧,目的是为了组织数据。命名元组的优势:组织的更好且字段有名称。

  from collections import namedtuple

  Door = namedtuple(‘Door‘, [‘number‘, ‘status‘])

  # 实例化
  door = Door(10010, ‘closed‘)
  print(door.status)
  print(door.number)

: closed
: 10010

以面向对象的方式实现Door,

  class Door:
      def __init__(self, number, status):
          # . 用于访问对象的属性与方法
          self.number = number
          self.status = status

  door = Door(10010, ‘closed‘)  # 调用初始化方法(其他语言中的构造方法)
  print(door.number)  # 获取属性,输出:10010
  print(door.status)  # 获取属性,输出closed

类就是数据与逻辑(或动作)的集合。上述的Door类中只有数据没有逻辑,那么我们在该类中加入开门与关门的动作,用来操纵类中的数据。上述的例子改写如下:

class Door:
    def __init__(self, number, status):
        self.number = number
        self.status = status

    def open_door(self):
        self.status = ‘opened‘

    def close_door(self):
        self.status = ‘closed‘

door = Door(10010, ‘opened‘)

print("door‘s number is: {}".format(door.number))
print("door‘s status is: {}".format(door.status))

print("现在关门做点坏事")
door.close_door()
print("door‘s status is: {}".format(door.status))

print("坏事做完,开启门窗透透气吧")
door.open_door()
print("door‘s status is: {}".format(door.status))

执行上述Python代码:

$ python3 door.py
door‘s number is: 10010
door‘s status is: opened
现在关门做点坏事
door‘s status is: closed
坏事做完,开启门窗透透气吧
door‘s status is: opened

上述代码中,我们通过open_door()close_door()函数来操作了Door类的status数据。

C++中的面向对象

如果大家写过C++Java代码,可以很轻松地用C++Java进行实现。我们看看C++是如何实现上述代码的(只是作为了解,不想了解可以跳过):
```c++
// filename: door.cpp
#include <iostream>

using namespace std;

class Door
{
public:
int number;
string status;

Door(int number, string status)
{
    this->number = number;
    this->status = status;
}

void open_door(void);
void close_door(void);

};

void Door::open_door(void)
{
this->status = "opened";
}

void Door::close_door(void)
{
this->status = "closed";
}

int main(int argc, char *argv[])
{
Door door(10010, "opened");

cout << "door‘s number is: " << door.number << endl;
cout << "door‘s status is: " << door.status << endl;

cout << "现在关闭门窗做点坏事" << endl;
door.close_door();
cout << "door‘s status is: " << door.status << endl;

cout << "坏事做完,开启门窗透透气吧" << endl;
door.open_door();
cout << "door‘s status is: " << door.status << endl;

return 0;

}


编译并运行上述`C++`代码,结果如下:
```bash
$ g++ door.cpp -o door
$ ./door
door‘s number is: 10010
door‘s status is: opened
现在关闭门窗做点坏事
door‘s status is: closed
坏事做完,开启门窗透透气吧
door‘s status is: opened

Java中的面向对象

我们知道,Java是源自于C++的。那么我们看看如何用Java代码该怎么写呢(只是作为了解,不想了解可以跳过)?

// filename: Door.java
class DoorConstructor {
    int number;
    String status;

    DoorConstructor(int number, String status) {
        this.number = number;
        this.status = status;
    }

    public void close_door() {
        this.status = "closed";
    }

    public void open_door() {
        this.status = "opened";
    }
}

public class Door {
    public static void main(String args[]) {
        DoorConstructor door = new DoorConstructor(10010, "opened");
        System.out.println("door‘s number is: " + door.number);
        System.out.println("door‘s status is: " + door.status);

        System.out.println("现在关门做点坏事");
        door.close_door();
        System.out.println("door‘s status is: " + door.status);

        System.out.println("坏事做完,开启门窗透透气吧");
        door.open_door();
        System.out.println("door‘s status is: " + door.status);
    }
}

编译并运行:

$ javac Door.java
$ java Door
door‘s number is: 10010
door‘s status is: opened
现在关门做点坏事
door‘s status is: closed
坏事做完,开启门窗透透气吧
door‘s status is: opened

Golang中的面向对象

我们看看Go语言是如何使用面向对象的。先看代码(只是作为了解,不想了解可以跳过):

// filename: door.go
package main

import "fmt"

type Door struct {
    number int
    status string
}

func (d *Door) close_door() {
    d.status = "closed"
}

func (d *Door) open_door() {
    d.status = "opened"
}

func main() {
    door := Door{10010, "opened"}

    fmt.Println("door‘s number is:", door.number)
    fmt.Println("door‘s status is:", door.status)

    fmt.Println("现在关门做点坏事")
    door.close_door()
    fmt.Println("door‘s status is:", door.status)

    fmt.Println("坏事做完,开启门窗透透气吧")
    door.open_door()
    fmt.Println("door‘s status is:", door.status)
}

编译并运行:

$ go build door.go
$ ./door
door‘s number is: 10010
door‘s status is: opened
现在关门做点坏事
door‘s status is: closed
坏事做完,开启门窗透透气吧
door‘s status is: opened

在上述的Go代码中,我们可以看到,数据与方法是分开的。不过Go的方法可以定义在具体的接收者(数据)上面。不像前面的其他编程语言,它们的数据与方法都在一个类中。

javascript中的面向对象

接下来,我们看看JavaScript的面向对象编程是怎样的写法。我们使用ES6的规范来完成上面这个例子的演示:

// filename: door.js
class Door {

 constructor(number, status) {
  this.number = number
  this.status = status
 }

 open_door() {
  this.status = "opened"
 }

 close_door() {
  this.status = "closed"
 }
}

let door = new Door(10010, "opened")

console.log("door‘s number is: ", door.number)
console.log("door‘s status is: ", door.status)

console.log("现在关门做点坏事")
door.close_door()
console.log("door‘s status is: ", door.status)

console.log("坏事做完,开启门窗透透气吧")
door.open_door()
console.log("door‘s status is: ", door.status)

在命令行使用node执行该脚本:

$ node door.js 
door‘s number is: 10010
door‘s status is: opened
现在关门做点坏事
door‘s status is: closed
坏事做完,开启门窗透透气吧
door‘s status is: opened

Lua中的面向对象

我们再看看Lua的面向对象是如何实现的。直接上代码了:

-- filename: door.lua
Door = {}

function Door:new (number, status)
   local door = {}
   door.number = number
   door.status = status
   self.__index = self
   return setmetatable(door, self)
end

function Door:open_door ()
   self.status = "opened"
end

function Door:close_door ()
   self.status = "closed"
end

door = Door:new(10010, "opened")
print("door‘s number is: " .. door.number)
print("door‘s status is: " .. door.status)

print("现在关门做点坏事")
door.close_door(door)
print("door‘s status is: " .. door.status)

print("坏事做完,开启门窗透透气吧")
door:open_door()
print("door‘s status is: " .. door.status)

运行结果为:

$ lua door.lua 
door‘s number is: 10010
door‘s status is: opened
现在关门做点坏事
door‘s status is: closed
坏事做完,开启门窗透透气吧
door‘s status is: opened

上面我们通过六种支持面向对象的编程语言(当然还有很多编程语),简单地演示了这些语言的基本套路。这里所举的例子都是入门级的,限于小白的水平也做不到深入。举这些例子的目的是想告诉大家:面向对象编程只是一种思想,掌握了编程思想,那么使用什么样的语言来完成你的当前的任务就看这门语言提供了哪些特性、自己对这门语言的理解及熟练程度。

实例化的过程

接下来会通过一些具体的实例说明实例化的过程。

In [14]: class Heap:
    ...:     def __init__(self):  # 此函数通常叫做构造函数,在Python中更多叫做初始化函数,在对象创建完成后会立刻执行
    ...:         self.data = []

    ...:     def add(self, x):  # 第一个参数是self,其他参数与一般函数定义一样
    ...:         self.data.append(x)

    ...:     def pop(self):
    ...:         if self.data:
    ...:             self.data.pop()
    ...:         else:
    ...:             print(‘heap is empty‘)
    ...:            

In [15]: heap = Heap()  # 实例化Heap类,实例为heap

In [16]: heap.data
Out[16]: []

In [17]: heap.add(3)

In [18]: heap.add(4)

In [19]: heap.add(5)

In [20]: heap.data
Out[20]: [3, 4, 5]

In [21]: heap.pop()

In [22]: heap.pop()

In [23]: heap.data
Out[23]: [3]

In [24]: heap.pop()

In [25]: heap.data
Out[25]: []

In [26]: heap.pop()
heap is empty

上面代码中的self代表heap这个实例。当然,代码中的self并不一定要写为self,还可以是其他Python非关键字。

再来一个例子:

$ cat person.py
class Person:  # 创建一个名为Person的类
    def __init__(self, name, job=None, pay=0):  # 初始化函数接收三个参数,与一般的函数参数具有相同意义
    self.name = name  # 创建对象时填充这些字段
    self.job = job  # self就是将要创建的对象(或实例)
    self.pay = pay

bob = Person(‘Bob Smith‘) # test the class
sue = Person(‘Sue Jones‘, job=‘dev‘, pay=10000) # 自动执行__init__方法
print(bob.name, bob.pay) # 获取对象的属性
print(sue.name, sue.pay) # 不同的对象其自身的数据不一定相同

尽管上面的Person类非常简单,不过它依然演示了一些重要的内容。我们注意到bob的name并不是sue的name,并且sue的pay不是bob的pay。bob和sue它们都是两个独立的信息记录。从技术的角度来看,bob与sue都是namespace objects,就像其他所有的类实例一样,它们创建时都有各自独立的状态信息的拷贝。因为每个类的实例都有自己self属性的集合,可以把类可以理解为一个蓝图、工厂或模具。

一个示例,

class Door:
    def __init__(self, number, status):
        self.number = number
        self.status = status

    def open(self):
        self.status = ‘opened‘

    def close(self):
        self.status = ‘closed‘

door = Door(1, ‘closed‘) # 看起来非常像一个函数调用。事实上,
                         # 确实发生了一些函数调用,它调用了__init__函数,
                         # 第一个参数由解释器自动传入,表示实例本身,
                         # 通常命名为self,也可以为其他非关键字
print(door.__class__)
print(Door.__class__)
print(type.__class__)
# 所有类,都是type或者type的子类的实例

: <class ‘__main__.Door‘>
: <class ‘type‘>
: <class ‘type‘>

__init__函数并不会创建对象,__init__函数初始化对象。对象(或实例)创建过程为:

  1. 首先创建对象
  2. 对象作为self参数传递给__init__函数
  3. 返回self

实例怎么来的?由类的__new__方法实现。如果要改变默认创建默认的创建实例的行为,可以写__new__方法,不过通常是不写的。

class Door:
 #    def __new__(cls): # 创建实例的,可以改变实例创建的行为,这是元编程的体现
 #        pass

    def __init__(self, number, status):
        self.number = number
        self.status = status

    def open(self):
        self.status = ‘opened‘

    def close(self):
        self.status = ‘closed‘

door = Door(1, ‘closed‘) # 看起来非常像一个函数调用。事实上,
                         # 确实发生了一些函数调用,它调用了__init__函数,
                         # 第一个参数由解释器自动传入,表示实例本身,
                         # 通常命名为self
print(door.__class__)
print(Door.__class__)
print(type.__class__)
# 所有类,都是type或者type的子类的实例

: <class ‘__main__.Door‘>
: <class ‘type‘>
: <class ‘type‘>

实例化的时候,传递的参数列表是__init__方法除了第一个参数之外的所有参数,支持函数的所有参数变化。

当没有显式的定义__init__方法的时候,会使用默认的__init__方法,

def __init__(self):
    pass

通过.操作符访问实例的属性或者调用实例的方法。当我们调用实例方法的时候,第一个参数即实例本身,由解释器自动传入。

类的作用域

先给出一些规则:

  • 实例变量的作用域是在实例内部。
  • 所有实例共享类变量。赋值会产生新的变量。
  • 实例可以动态增减属性。
  • 类变量可以通过类直接访问,而且通过类修改变量,会影响所有实例。
  • 方法的作用域是类级别的。

结合一个简单的例子说明,

In [1]: class Door:
   ...:     type = ‘A‘  # 类的直接下级作用域的变量,叫做类变量,所有的实例共享该变量。
   ...:     def __init__(self, number, status):
   ...:         self.number = number  # 关联到实例的变量,叫做实例变量
   ...:         self.status = status
   ...:     def open(self):
   ...:         self.status = ‘opened‘
   ...:     def close(self):
   ...:         self.status = ‘closed‘
   ...:        

In [2]: d1 = Door(10010, ‘closed‘)

In [3]: d2 = Door(10011, ‘opened‘)

In [4]: d1.type
Out[4]: ‘A‘

In [5]: d2.type
Out[5]: ‘A‘

In [6]: d2.open = lambda self: print("haha, it‘s cool!")

In [8]: d2.open
Out[8]: <function __main__.<lambda>(self)>

In [9]: d2.open(d2)
haha, it‘s cool!

In [10]: d1.open()

In [11]: d1.status
Out[11]: ‘opened‘

抛出一个问题:如果执行d1.type = ‘B‘语句后,那么执行d2.type语句会有什么输出呢?

类变量对类和实例都可见。再看一个例子:

In [14]: class HaHa:
    ...:     NAME = ‘HaHa‘
    ...:    
    ...:     def __init__(self, name):
    ...:         self.name = name
    ...:        

In [15]: haha = HaHa(‘haha‘)

In [16]: haha.NAME  # 等价于haha.__class__.NAME
Out[16]: ‘HaHa‘

In [17]: haha.__class__.NAME
Out[17]: ‘HaHa‘

In [19]: haha.NAME = ‘hehe‘  # 等价于haha.__dict__[‘NAME‘] = ‘hehe‘

In [20]: haha.NAME
Out[20]: ‘hehe‘

In [21]: haha.__class__.NAME
Out[21]: ‘HaHa‘

由此可以获得属性的查找顺序:

  1. __dict__
  2. __class__

我们也从中体会到:在Python中,赋值意味着创建。

类方法/静态方法

方法都是类级的。方法的定义都是类级的,但是有的方法使用实例调用,有的方法却是使用类来调用。

In [9]: class Haha:
   ...:     def instance_print(self):
   ...:         print("instance method")
   ...:        
   ...:     @classmethod
   ...:     def class_print(cls):
   ...:         print(id(cls))
   ...:         print("class method")
   ...:        
   ...:     @staticmethod
   ...:     def static_print():
   ...:         print("static method")
   ...:        
   ...:     def xxx_print():
   ...:         print("this is a function")
   ...:        

In [10]: haha = Haha()

In [11]: haha.instance_print()
instance method

In [12]: haha.class_print()
37234952
class method

In [13]: haha.static_print()
static method

In [15]: Haha.xxx_print()
this is a function

In [16]: id(Haha)
Out[16]: 37234952

实例方法与类方法,实例方法和类方法的区别在于传入的第一个参数,实例方法会自动传入当前实例,类方法会自动传入当前类。类方法可以被实例使用,并且被实例使用时,传入的第一个参数还是类。

In [1]: class A:
   ...:     def method_of_instance(self):
   ...:         print(‘method of instance‘)
   ...:        
   ...:     @classmethod
   ...:     def method_of_class(cls):
   ...:         print(‘method of class‘)
   ...:        

In [2]: a = A()

In [3]: a.method_of_instance()
method of instance

In [4]: a.method_of_class()
method of class

In [5]: A.method_of_instance()  # 并不会传入self参数
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-5-ba3f94db75c3> in <module>()
----> 1 A.method_of_instance()

TypeError: method_of_instance() missing 1 required positional argument: ‘self‘

In [6]: A.method_of_class()
method of class

In [7]: A.method_of_instance(a)
method of instance

In [8]: A.method_of_instance(A)
method of instance

再看一个例子,当我们用实例调用方法的时候,总是会传入一个参数,要么是实例本身,要么是它的类。

In [1]: class A:
   ...:     def method_of_instance(self):
   ...:         print(‘method of instance‘)
   ...:        
   ...:     @classmethod
   ...:     def method_of_class(cls):
   ...:         print(‘method of class‘)
   ...:        
   ...:     @staticmethod
   ...:     def static_method():
   ...:         print(‘static method‘)
   ...:        

In [2]: a = A()

In [3]: a.method_of_instance()
method of instance

In [4]: a.method_of_class()
method of class

In [5]: a.static_method()
static method

In [6]: A.method_of_instance()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-ba3f94db75c3> in <module>()
----> 1 A.method_of_instance()

TypeError: method_of_instance() missing 1 required positional argument: ‘self‘

In [7]: A.method_of_class()
method of class

In [8]: A.static_method()
static method

In [9]: A.method_of_instance(a)
method of instance

# 实例调用方法的时候,会传入实例本身作为第一个参数;
# 类调用方法的时候,不会传递本身作为第一个参数;
# @classmethod 装饰器会向方法传递一个参数,传递的是类本身;

方法的作用域都属于类级别,具体是实例方法,还是类方法,或者是静态方法,由第一个参数决定。可以简单地理解为:当第一个参数是实例的时候,是实例方法;当第一个参数是类的时候,是类方法,当不要求第一个参数时,是静态方法。

In [1]: class A:
   ...:     var = ‘A‘
   ...:    
   ...:     @classmethod
   ...:     def change_var(cls, val):
   ...:         cls.var = val
   ...:        

In [2]: a1 = A()

In [3]: a2 = A()

In [4]: a1.var
Out[4]: ‘A‘

In [5]: a2.var
Out[5]: ‘A‘

In [6]: A.change_var(‘B‘)

In [7]: a1.var
Out[7]: ‘B‘

In [8]: a2.var
Out[8]: ‘B‘

In [9]: a1.change_var(‘C‘)

In [10]: a1.var
Out[10]: ‘C‘

In [11]: a2.var
Out[11]: ‘C‘

再来看一个例子:

In [1]: class Car:
   ...:     country = ‘China‘
   ...:    
   ...:     def __init__(self, length, width, height, owner=None):
   ...:         self.owner = owner
   ...:         self.length = length
   ...:         self.width = width
   ...:         self.height = height
   ...:         self.country = "中国"
   ...:        

In [2]: a1 = Car(1.2, 1.4, 1.5, "James")

In [3]: a2 = Car(2.2, 2.4, 2.5, "Wade")

In [4]: a1.owner, a2.owner
Out[4]: (‘James‘, ‘Wade‘)

In [5]: a1.country, a2.country
Out[5]: (‘中国‘, ‘中国‘)

In [6]: a2.country = "美国"

In [7]: a1.country, a2.country
Out[7]: (‘中国‘, ‘美国‘)

In [8]: Car.country
Out[8]: ‘China‘

In [9]: del a2.country

In [10]: a2.country
Out[10]: ‘China‘

所有实例需要共享一些状态、数据的时候,就可以使用类变量。当在实例中需要修改类变量的时候,我们就可以把修改的内容放到类方法中。

类变量被赋值的话(赋值会产生新的引用),就会变成了实例变量。

访问控制

这里主要涉及公有变量、私有变量及公有方法、私有方法。Python中没有像C++Java中的关键字,诸如:publicprivateprotected等关键字。我们看看Python中是怎么做的。

In [2]: class Door:
   ...:     def __init__(self, number, status):
   ...:         self.number = number
   ...:         self.__status = status
   ...:        
   ...:     def open_door(self):
   ...:         self.__status = ‘opened‘
   ...:        
   ...:     def close_door(self):
   ...:         self.__status = ‘closed‘
   ...:        
   ...:     def door_status(self):
   ...:        return self.__status
   ...:    
   ...:     def __set_number(self, number):
   ...:         self.number = number
   ...:        

In [3]: door = Door(10010, ‘opened‘)

In [4]: door.__status
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-cfaa823e7519> in <module>()
----> 1 door.__status

AttributeError: ‘Door‘ object has no attribute ‘__status‘

In [5]: door.__status = ‘haha‘   # 赋值意味着创建

In [6]: door.__status
Out[6]: ‘haha‘

In [7]: door.__dict__
Out[7]: {‘_Door__status‘: ‘opened‘, ‘__status‘: ‘haha‘, ‘number‘: 10010}

In [8]: door.door_status()
Out[8]: ‘opened‘

In [9]: door.open_door()

In [10]: door.door_status()
Out[10]: ‘opened‘

In [11]: door.close_door()

In [12]: door.door_status()
Out[12]: ‘closed‘

In [13]: door.__set_number(10011)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-13-e7eb0a552659> in <module>()
----> 1 door.__set_number(10011)

AttributeError: ‘Door‘ object has no attribute ‘__set_number‘

In [14]: door.__dict__
Out[14]: {‘_Door__status‘: ‘closed‘, ‘__status‘: ‘haha‘, ‘number‘: 10010}

In [15]: dir(door)
Out[15]:
[‘_Door__set_number‘,   # 变成了这个样子
 ‘_Door__status‘,            # 变成了这个样子
 ‘__class__‘,
 ‘__delattr__‘,
...
 ‘__sizeof__‘,
 ‘__status‘,
 ‘__str__‘,
 ‘__subclasshook__‘,
 ‘__weakref__‘,
 ‘close_door‘,
 ‘door_status‘,
 ‘number‘,
 ‘open_door‘]

所有双下划线,非双下划线结尾的成员,都是私有成员。对于上述的__status私有变量,如何进行访问呢?在Python中,可以通过

_类名+带双下划线的属性
针对上面的例子就是:_Door__status

Python的私有成员是通过改名实现的。严格地说,Python里没有真正的私有成员。除非真的有必要,并且清楚知道会有什么后果,否则不要用这个黑魔法。

接下来再看看以单下划线开始的变量,

In [1]: class A:
   ...:     def __init__(self):
   ...:         self._a = 3
   ...:        

In [2]: a = A()

In [3]: a._a
Out[3]: 3

In [4]: a._a = 4

In [5]: a._a
Out[5]: 4

In [6]: a.__dict__
Out[6]: {‘_a‘: 4}

单下划线开始的变量是一种惯用法,标记此成员为私有,但是解释器不做任何处理。

本来还想介绍property装饰器呢,留给大家自己摸索一下吧。

封装

先看一个例子,

Heap = namedtuple(‘Heap‘, [‘add‘, ‘pop‘])

def heap_factory():
    data = []

    def add(x):
        pass

    def pop():
        pass

    return Heap(add, pop)

heap = heap_factory()
# 对外界来说,data是不可见的,外界无法访问data

在Python中如何进行封装的?来看一个小例子,

class A:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

a = A(1, 2, 3)
print(a.x)
print(a.y)
print(a.z)
a.x = 2
print(a.x)

: 1
: 2
: 3
: 2

下面是封装的例子,

class B:
    def __init__(self, x, y, z):
        self.x = x
        self.__y = y
        self._z = z

b = B(1, 2, 3)
b.x
b.__y
b._z

在Python中,以双下划线开始,并且不以双下划线结尾的变量,是私有变量,外界无法直接访问。通常,我们不定义以双下线开始,双下划线结尾的变量和方法,因为这在Python中有特殊含义。

接下来看看私有方法,方法也是一样的规则,以双下划线开头,非双下划线结尾的方法是私有方法。

class D:
    def __private_method(self):
        print(‘private method‘)

d = D()
d.__private_method()

# 通过dir(d)时,也看不到__private_method()方法。

 Traceback (most recent call last):
  File "<stdin>", line 6, in <module>
AttributeError: ‘D‘ object has no attribute ‘__private_method‘

一个稍微综合的例子,

class F:
    __private_class_var = u‘私有类变量‘

    def __init__(self):
        self.__private_instance_var = u‘私有实例变量‘

    @classmethod
    def public_class_method(cls):
        print(cls.__private_class_var)

    def public_instance_method(self):
        print(self.__private_class_var)
        print(self.__private_instance_var)

f = F()
f.public_class_method()
f.public_instance_method()

: 私有类变量
: 私有类变量
: 私有实例变量

私有属性在类的内部均可访问,无论是类方法还是实例方法。接下来再看一个稍微变态的例子,

class G:
    __private_class_var = ‘private class var‘

    def public_instance_method(self):
        print(G.__private_class_var)

g = G()
g.public_instance_method()
G.__private_class_var

再来一个例子,

class H:
    __private_class_var = ‘private class var‘

    @staticmethod
    def public_static_method():
        print(H.__private_class_var)

h = H()
h.public_static_method()
H.public_static_method()

前面说过,类的私有属性是不能直接被访问的,这是真的吗?接着看F这个例子,

class F:
    __private_class_var = ‘private class var‘

    def __init__(self):
        self.__private_instance_var = ‘private instance var‘

    @classmethod
    def public_class_method(cls):
        print(cls.__private_class_var)

    def public_instance_method(self):
        print(self.__private_class_var)
        print(self.__private_instance_var)

f = F()
f.public_class_method()
f.public_instance_method()

# 使用__dict__查看实例f的属性
f.__dict__
f._F__private_instance_var

事实上,Python的私有属性并不是真正私有,而是一个变量重命名而已。看一个例子说明此问题:

class J:
    def __init__(self):
        self.__a = 1
        self.__b = 2

    def __private_method(self):
        print(‘private method‘)

j = J()
j._J__a
j._J__private_method()

一个综合点的例子,

  class Door:
      def __init__(self, number, status):
          self.number = number
          self.__status = status

      def open(self):
          self.__status = ‘opened‘

      def close(self):
          self.__status = ‘closed‘

      def get_status(self):
          return self.__status

      @property
      def status(self):
          """
使用`property`装饰器描述符对status方法进行装饰,可以让我们访问status方法像访问类的属性一样。
          """
          return self.__status

  door = Door(1, ‘number‘)
  door.open()
  door.status = ‘opened‘
  door.get_status()
  door.status # 属性

还想对status进行赋值,但赋值只能是opened或closed,该怎么破?

class Door:
    def __init__(self, number, status):
        self.number = number
        self.__status = status

    def open(self):
        self.__status = ‘opened‘

    def close(self):
        self.__status = ‘closed‘

    @property # @proverty装饰器,可以把方法装饰成了一个同名属性
    def status(self):
        return self.__status

    @status.setter # @xxxx.setter xxxx代表被@property装饰的属性吗,当对此属性赋值时,会调用此方法
    def status(self, value):
        if value in (‘closed‘, ‘opened‘):
            self.__status = value
        else:
            raise ValueError(value)

    @status.deleter # 当删除此属性时,会调用此方法
    def status(self):
        raise NotImplementedError(‘You can not delete status of door‘)

door = Door(1, ‘number‘)
door.open()
door.status # 属性
door.status = ‘xxxx‘
door.get_status()
door.status
door.status = ‘closed‘

del door.status

继承

啥也不说,先来一个例子,

In [1]: class Base:
   ...:     def __init__(self):
   ...:         self.x = 0
   ...:        

In [2]: class A(Base):
   ...:     pass
   ...:

In [3]: a = A()

In [4]: a.x  # 访问父类中的x
Out[4]: 0

在Python3中,如果没有显式的指定继承哪个类,默认是继承自object类,也就是新式类。

子类获得父类一些(非全部)方法和属性。看一个例子,

In [1]: class Base:
   ...:     def __init__(self):
   ...:         self.x = 1
   ...:         self._y = 2
   ...:         self.__z = 3
   ...:        

In [2]: class A(Base):
   ...:     def get_x(self):
   ...:         print(self.x)
   ...:        
   ...:     def get_y(self):
   ...:         print(self._y)
   ...:        
   ...:     def get_z(self):
   ...:         print(self.__z)
   ...:        

In [3]: a = A()

In [4]: a.get_x()
1

In [5]: a.get_y()
2

In [6]: z.get_z()  # 私有属性,无法继承
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-6-b29b1f799fa1> in <module>()
----> 1 z.get_z()

NameError: name ‘z‘ is not defined

In [7]: a.__dict__   # 看一下实例a的属性信息
Out[7]: {‘_Base__z‘: 3, ‘_y‘: 2, ‘x‘: 1}

In [9]: b = B()

In [10]: b.get_z()
3

In [11]: b.__dict__
Out[11]: {‘_Base__z‘: 3, ‘_y‘: 2, ‘x‘: 1}

In [12]: b.z = 3  # 赋值意味着创建

In [13]: b.z   # 再次访问z
Out[13]: 3

In [14]: b.__dict__  # 再次查看__dict__
Out[14]: {‘_Base__z‘: 3, ‘_y‘: 2, ‘x‘: 1, ‘z‘: 3}

无论是类变量还是实例变量都可以继承;类方法、实例方法和静态方法都可以继承,但私有的除外。

方法重写: 子类覆盖父类的方法。有的子类就是需要有点儿个性,那么可以覆盖或重写父类的方法即可。

In [1]: class Base:
   ...:     def my_print(self):
   ...:         print(‘I am base class‘)
   ...:        

In [2]: class A(Base):
   ...:     def my_print(self):
   ...:         print(‘I am a class‘)
   ...:        

In [3]: a = A()

In [4]: a.my_print()
I am a class

如果还要父类的方法呢?可以使用super()方法。super()方法返回super对象,可以使用super对象调用父类的方法。

In [1]: class Base:
   ...:     def my_print(self):
   ...:         print(‘I am base class‘)
   ...:        

In [2]: class A(Base):
   ...:     def my_print(self):
   ...:         print(‘I am a class‘)
   ...:        

In [3]: a = A()

In [4]: a.my_print()
I am a class

In [5]: class B(Base):
   ...:     def my_print(self):
   ...:         print(‘I am b class‘)
   ...:         super().my_print()  # super()等价于super(__class__, self) -> Base
   ...:        

In [6]: b = B()

In [7]: b.my_print()
I am b class
I am base class

子类能否继承祖先类的属性呢?看一个例子:

In [5]: class TopBase:
   ...:     def my_print(self):
   ...:         print(‘Top class‘)
   ...:        

In [6]: class Base(TopBase):
   ...:     def my_print(self):
   ...:         print(‘Base class‘)
   ...:        

In [7]: class A(Base):
   ...:     def my_print(self):
   ...:         super(Base, self).my_print()  # super(Base, self) -> TopBase, 返回当前类的父类
   ...:        

In [8]: a = A()

In [9]: a.my_print()
Top class

通过上面的例子的演示,super对象不但可以使用父类的属性,还能使用祖先的属性。super(type, obj)返回super对象,指代type的父类。

super对象持有类级别的成员。举个例子看看,

In [1]: class Base:
   ...:     @classmethod
   ...:     def my_print(cls):
   ...:         print(‘Base class‘)
   ...:        

In [2]: class A(Base):
   ...:     @classmethod
   ...:     def my_print(cls):
   ...:         print(‘A class‘)
   ...:         super().my_print()  # 这里的super(),相当于super(D, cls)
   ...:        

In [3]: a = A()

In [4]: a.my_print()
A class
Base class

当父类定义了带参数的初始化方法时,子类要显式的定义初始化方法,并且在初始化方法里初始化父类。

多继承与MRO(Method Resolution Order)

本节内容小白理解的也不是很深刻,从网上找了很多资料,在这里罗列一下,仅供参考。

MRO:方法查找顺序。MRO的两个原则:

  1. 本地优先:自己定义或重写的方法优先;否则按照继承列表,从左向右查找。
  2. 单调性:所有子类,也要满足查找顺序。

Python通过C3算法来确定是否满足MRO的两个原则。

下面的两种写法在Python3中的写法是等价的,

class A:
    pass

class A(object):
    pass

在Python2.3之前,没有一个最上层的基类;从2.4版本开始,Python引入了object这个最上层的基类,即所有类都继承自object,但是为了兼容,必须要显式指定。在Python2中,如果是第一种写法,无法使用super方法。

针对Python3,因为不用兼容旧风格,所以两种写法是等效的,通常使用第一种写法。

Python支持多继承,接下来看一个例子:

In [1]: class A:
   ...:     def my_print(self):
   ...:         print(‘A‘)
   ...:        

In [2]: class B:
   ...:     def my_print(self):
   ...:         print(‘B‘)
   ...:        

In [3]: class C(A, B):
   ...:     pass
   ...:

In [4]: c = C()

In [5]: c.my_print()
A

In [6]: class D(B, A):
   ...:     pass
   ...:

In [7]: d = D()

In [8]: d.my_print()
B

In [9]: class E(A):
   ...:     def my_print(self):
   ...:         print(‘E‘)
   ...:        

In [10]: class F(E, B):
    ...:     pass
    ...:

In [11]: f = F()

In [12]: f.my_print()
E

In [13]: class G(E, A):
    ...:     pass
    ...:

In [14]: g = G()

In [15]: g.my_print()
E

In [16]: class H(A, E):
    ...:     pass
    ...:
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-16-7127b631affd> in <module>()
----> 1 class H(A, E):
      2     pass

TypeError: Cannot create a consistent method resolution
order (MRO) for bases E, A

In [17]: A.__mro__
Out[17]: (__main__.A, object)

In [18]: E.__mro__
Out[18]: (__main__.E, __main__.A, object)

In [19]: G.__mro__
Out[19]: (__main__.G, __main__.E, __main__.A, object)

关于C3算法是如何工作的,这里给出小白学习时参考的博文,地址为:https://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path

以上面的类C为例进行一个推演,

class C(A, B) ==>
mro(C) => [C] + merge(mro(A), mro(B), [A, B])
       => [C] + merge([A, O], [B, O], [A, B])
       => [C, A] + merge([O], [B, O], [B])
       => [C, A, B] + merge([O], [O])
       => [C, A, B, O]
C.__mro__
(__main__.C, __main__.A, __main__.B, object)

另外一个推演,

class E(A):
class H(A, E):
mro(H) => [H] + merge(mro(A), mro(E), [A, E])
       => [H] + merge([A, O], [E, A, O], [A, E])
       => [H] + # A在列表中,但[E, A, O]中的A不是首元素,因此抛出异常
       raise TypeError

总结

写了这么多,是该总结一下了。本文开始介绍了一些主流的编程范式及面向对象编程的特点。

Python在众多编程语言中还算是比较容易入门的,就连我这个机械系的小白也能玩得自嗨,更不用说计算机专业出身的大神了。

使用什么语言来完成实际的工作都无所谓,关键是所使用的语言能提供哪些语言特性,我们要有能力组合这些语言的特性以及标准库或第三方库来设计出良好程序。

如果我们理解了面向对象编程的本质,那么我们就可以解决所有面向对象编程的问题,而不是解决一两门编程语言的问题。这就是所谓一通百通的威力。

以上是关于掌握面向对象编程本质,彻底掌握OOP的主要内容,如果未能解决你的问题,请参考以下文章

使用 Dojo 掌握面向对象开发

JavaSE之类与对象

VSCode自定义代码片段——JS中的面向对象编程

VSCode自定义代码片段9——JS中的面向对象编程

图说OOP[基础]

后端技术面试38讲,带你掌握软件编程本质原理