1. 程式人生 > >Python面向對象編程之我見

Python面向對象編程之我見

Python 面向對象

面向對象基本概念

面向對象是一種編程範式。範式是指一組方法論。編程範式是一組如何組織代碼的方法論。編程範式指的是軟件工程中的一種方法學。

一些主流的編程範式:

  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))

執行上述代碼:

$ 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++Java代碼,可以很輕松地用C++Java進行實現。我們看看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;
}

編譯並運行。結果如下:

$ 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是源自於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

我們看看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

上面我們通過四種支持面向對象的編程語言(當然還有很多編程語),簡單地演示了這些語言的基本套路。這裏所舉的例子都是入門級的,限於小白的水平也做不到深入。舉這些例子的目的是想告訴大家:面向對象編程只是一種思想,掌握了編程思想,那麽使用什麽樣的語言來完成你的當前的任務就看這門語言提供了哪些特性、自己對這門語言的理解及熟練程度。

實例化的過程

接下來會通過一些具體的實例說明實例化的過程。

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在眾多編程語言中還算是比較容易入門的,就連我這個機械系的小白也能玩得自嗨,更不用說計算機專業出身的大神了。

使用什麽語言來完成實際的工作都無所謂,關鍵是所使用的語言能提供哪些語言特性,我們要有能力組合這些語言的特性以及標準庫或第三方庫來設計出良好程序。

Python面向對象編程之我見