掌握面向?qū)ο缶幊瘫举|(zhì),徹底掌握OOP

面向?qū)ο蠡靖拍?/h3>

面向?qū)ο笫且环N編程范式。范式是指一組方法論。編程范式是一組如何組織代碼的方法論。編程范式指的是軟件工程中的一種方法學(xué)。

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價值的長期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊、雅安服務(wù)器托管、營銷軟件、網(wǎng)站建設(shè)、咸豐網(wǎng)站維護(hù)、網(wǎng)站推廣。

一些主流的編程范式:

  1. OOP - 面向?qū)ο缶幊?
    世界觀:一切皆對象。
  2. FP - 函數(shù)式編程
    世界觀:一切皆函數(shù)。一般指無副作用的函數(shù)。
  3. PP - 過程化編程
  4. IP - 指令式編程
  5. LP - 邏輯化編程
  6. AOP - 面向方面編程 裝飾器

設(shè)計方法:

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

面向?qū)ο蟾M(jìn)一步的抽象了世界。OOP的世界觀:

  1. 世界是由對象組成的
  2. 對象具有運(yùn)動規(guī)律和內(nèi)部狀態(tài)
  3. 對象之間可以相互作用

就是一個模板或藍(lán)圖,用來生成對象的。我們可以把類看做是一套模具,而模具加工出來的產(chǎn)品就是對象。當(dāng)我們從一套模具中塑造出一個產(chǎn)品的時候,我們就可以說創(chuàng)建了一個實(shí)例。

面向?qū)ο蟮奶匦裕?/p>

  1. 唯一性:對象都是唯一的,不存在兩個相同的對象,除非他們是同一個對象。
  2. 分類性:對象是可分類的,世界是由不同的類型組成的。

面向?qū)ο蟮娜筇卣鳎?/p>

  1. 封裝
  2. 繼承
  3. 多態(tài)

面向?qū)ο笞钪匾母拍罹褪穷悾?code>Class)和實(shí)例(Instance),必須牢記類是抽象的模板,而實(shí)例則是根據(jù)類創(chuàng)建出來的一個個具體的“對象”,每個對象都擁有相同的方法,但各自的數(shù)據(jù)有可能不同。

在Class內(nèi)部,可以有屬性和方法,而外部代碼可以通過直接調(diào)用實(shí)例變量的方法來操作數(shù)據(jù),這樣,就隱藏了內(nèi)部的復(fù)雜邏輯。

面向?qū)ο蟮谋举|(zhì):對行為和數(shù)據(jù)的封裝;有時候數(shù)據(jù)就是數(shù)據(jù);而有的時候行為就是行為。我們先使用Python標(biāo)準(zhǔn)庫中的namedtuple來實(shí)現(xiàn)一個入門的類吧,目的是為了組織數(shù)據(jù)。命名元組的優(yōu)勢:組織的更好且字段有名稱。

  from collections import namedtuple

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

  # 實(shí)例化
  door = Door(10010, 'closed')
  print(door.status)
  print(door.number)

: closed
: 10010

以面向?qū)ο蟮姆绞綄?shí)現(xiàn)Door,

  class Door:
      def __init__(self, number, status):
          # . 用于訪問對象的屬性與方法
          self.number = number
          self.status = status

  door = Door(10010, 'closed')  # 調(diào)用初始化方法(其他語言中的構(gòu)造方法)
  print(door.number)  # 獲取屬性,輸出:10010
  print(door.status)  # 獲取屬性,輸出closed

類就是數(shù)據(jù)與邏輯(或動作)的集合。上述的Door類中只有數(shù)據(jù)沒有邏輯,那么我們在該類中加入開門與關(guān)門的動作,用來操縱類中的數(shù)據(jù)。上述的例子改寫如下:

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("現(xiàn)在關(guān)門做點(diǎn)壞事")
door.close_door()
print("door's status is: {}".format(door.status))

print("壞事做完,開啟門窗透透氣吧")
door.open_door()
print("door's status is: {}".format(door.status))

執(zhí)行上述Python代碼:

$ python3 door.py
door's number is: 10010
door's status is: opened
現(xiàn)在關(guān)門做點(diǎn)壞事
door's status is: closed
壞事做完,開啟門窗透透氣吧
door's status is: opened

上述代碼中,我們通過open_door()close_door()函數(shù)來操作了Door類的status數(shù)據(jù)。

C++中的面向?qū)ο?/h4>

如果大家寫過C++Java代碼,可以很輕松地用C++Java進(jìn)行實(shí)現(xiàn)。我們看看C++是如何實(shí)現(xiàn)上述代碼的(只是作為了解,不想了解可以跳過):

// 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 << "現(xiàn)在關(guān)閉門窗做點(diǎn)壞事" << 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;
}

編譯并運(yùn)行上述C++代碼,結(jié)果如下:

$ g++ door.cpp -o door
$ ./door
door's number is: 10010
door's status is: opened
現(xiàn)在關(guān)閉門窗做點(diǎn)壞事
door's status is: closed
壞事做完,開啟門窗透透氣吧
door's status is: opened

Java中的面向?qū)ο?/h4>

我們知道,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("現(xiàn)在關(guān)門做點(diǎn)壞事");
        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);
    }
}

編譯并運(yùn)行:

$ javac Door.java
$ java Door
door's number is: 10010
door's status is: opened
現(xiàn)在關(guān)門做點(diǎn)壞事
door's status is: closed
壞事做完,開啟門窗透透氣吧
door's status is: opened

Golang中的面向?qū)ο?/h4>

我們看看Go語言是如何使用面向?qū)ο蟮?。先看代碼(只是作為了解,不想了解可以跳過):

// 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("現(xiàn)在關(guān)門做點(diǎn)壞事")
    door.close_door()
    fmt.Println("door's status is:", door.status)

    fmt.Println("壞事做完,開啟門窗透透氣吧")
    door.open_door()
    fmt.Println("door's status is:", door.status)
}

編譯并運(yùn)行:

$ go build door.go
$ ./door
door's number is: 10010
door's status is: opened
現(xiàn)在關(guān)門做點(diǎn)壞事
door's status is: closed
壞事做完,開啟門窗透透氣吧
door's status is: opened

在上述的Go代碼中,我們可以看到,數(shù)據(jù)與方法是分開的。不過Go的方法可以定義在具體的接收者(數(shù)據(jù))上面。不像前面的其他編程語言,它們的數(shù)據(jù)與方法都在一個類中。

JavaScript中的面向?qū)ο?/h4>

接下來,我們看看JavaScript的面向?qū)ο缶幊淌窃鯓拥膶懛āN覀兪褂?code>ES6的規(guī)范來完成上面這個例子的演示:

// 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("現(xiàn)在關(guān)門做點(diǎn)壞事")
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執(zhí)行該腳本:

$ node door.js 
door's number is: 10010
door's status is: opened
現(xiàn)在關(guān)門做點(diǎn)壞事
door's status is: closed
壞事做完,開啟門窗透透氣吧
door's status is: opened

Lua中的面向?qū)ο?/h4>

我們再看看Lua的面向?qū)ο笫侨绾螌?shí)現(xiàn)的。直接上代碼了:

-- 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("現(xiàn)在關(guān)門做點(diǎn)壞事")
door.close_door(door)
print("door's status is: " .. door.status)

print("壞事做完,開啟門窗透透氣吧")
door:open_door()
print("door's status is: " .. door.status)

運(yùn)行結(jié)果為:

$ lua door.lua 
door's number is: 10010
door's status is: opened
現(xiàn)在關(guān)門做點(diǎn)壞事
door's status is: closed
壞事做完,開啟門窗透透氣吧
door's status is: opened

上面我們通過六種支持面向?qū)ο蟮木幊陶Z言(當(dāng)然還有很多編程語),簡單地演示了這些語言的基本套路。這里所舉的例子都是入門級的,限于小白的水平也做不到深入。舉這些例子的目的是想告訴大家:面向?qū)ο缶幊讨皇且环N思想,掌握了編程思想,那么使用什么樣的語言來完成你的當(dāng)前的任務(wù)就看這門語言提供了哪些特性、自己對這門語言的理解及熟練程度。

實(shí)例化的過程

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

In [14]: class Heap:
    ...:     def __init__(self):  # 此函數(shù)通常叫做構(gòu)造函數(shù),在Python中更多叫做初始化函數(shù),在對象創(chuàng)建完成后會立刻執(zhí)行
    ...:         self.data = []

    ...:     def add(self, x):  # 第一個參數(shù)是self,其他參數(shù)與一般函數(shù)定義一樣
    ...:         self.data.append(x)

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

In [15]: heap = Heap()  # 實(shí)例化Heap類,實(shí)例為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這個實(shí)例。當(dāng)然,代碼中的self并不一定要寫為self,還可以是其他Python非關(guān)鍵字。

再來一個例子:

$ cat person.py
class Person:  # 創(chuàng)建一個名為Person的類
    def __init__(self, name, job=None, pay=0):  # 初始化函數(shù)接收三個參數(shù),與一般的函數(shù)參數(shù)具有相同意義
    self.name = name  # 創(chuàng)建對象時填充這些字段
    self.job = job  # self就是將要創(chuàng)建的對象(或?qū)嵗?    self.pay = pay

bob = Person('Bob Smith') # test the class
sue = Person('Sue Jones', job='dev', pay=10000) # 自動執(zhí)行__init__方法
print(bob.name, bob.pay) # 獲取對象的屬性
print(sue.name, sue.pay) # 不同的對象其自身的數(shù)據(jù)不一定相同

盡管上面的Person類非常簡單,不過它依然演示了一些重要的內(nèi)容。我們注意到bob的name并不是sue的name,并且sue的pay不是bob的pay。bob和sue它們都是兩個獨(dú)立的信息記錄。從技術(shù)的角度來看,bob與sue都是namespace objects,就像其他所有的類實(shí)例一樣,它們創(chuàng)建時都有各自獨(dú)立的狀態(tài)信息的拷貝。因?yàn)槊總€類的實(shí)例都有自己self屬性的集合,可以把類可以理解為一個藍(lán)圖、工廠或模具。

一個示例,

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') # 看起來非常像一個函數(shù)調(diào)用。事實(shí)上,
                         # 確實(shí)發(fā)生了一些函數(shù)調(diào)用,它調(diào)用了__init__函數(shù),
                         # 第一個參數(shù)由解釋器自動傳入,表示實(shí)例本身,
                         # 通常命名為self,也可以為其他非關(guān)鍵字
print(door.__class__)
print(Door.__class__)
print(type.__class__)
# 所有類,都是type或者type的子類的實(shí)例

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

__init__函數(shù)并不會創(chuàng)建對象,__init__函數(shù)初始化對象。對象(或?qū)嵗﹦?chuàng)建過程為:

  1. 首先創(chuàng)建對象
  2. 對象作為self參數(shù)傳遞給__init__函數(shù)
  3. 返回self

實(shí)例怎么來的?由類的__new__方法實(shí)現(xiàn)。如果要改變默認(rèn)創(chuàng)建默認(rèn)的創(chuàng)建實(shí)例的行為,可以寫__new__方法,不過通常是不寫的。

class Door:
 #    def __new__(cls): # 創(chuàng)建實(shí)例的,可以改變實(shí)例創(chuàng)建的行為,這是元編程的體現(xiàn)
 #        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') # 看起來非常像一個函數(shù)調(diào)用。事實(shí)上,
                         # 確實(shí)發(fā)生了一些函數(shù)調(diào)用,它調(diào)用了__init__函數(shù),
                         # 第一個參數(shù)由解釋器自動傳入,表示實(shí)例本身,
                         # 通常命名為self
print(door.__class__)
print(Door.__class__)
print(type.__class__)
# 所有類,都是type或者type的子類的實(shí)例

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

實(shí)例化的時候,傳遞的參數(shù)列表是__init__方法除了第一個參數(shù)之外的所有參數(shù),支持函數(shù)的所有參數(shù)變化。

當(dāng)沒有顯式的定義__init__方法的時候,會使用默認(rèn)的__init__方法,

def __init__(self):
    pass

通過.操作符訪問實(shí)例的屬性或者調(diào)用實(shí)例的方法。當(dāng)我們調(diào)用實(shí)例方法的時候,第一個參數(shù)即實(shí)例本身,由解釋器自動傳入。

類的作用域

先給出一些規(guī)則:

  • 實(shí)例變量的作用域是在實(shí)例內(nèi)部。
  • 所有實(shí)例共享類變量。賦值會產(chǎn)生新的變量。
  • 實(shí)例可以動態(tài)增減屬性。
  • 類變量可以通過類直接訪問,而且通過類修改變量,會影響所有實(shí)例。
  • 方法的作用域是類級別的。

結(jié)合一個簡單的例子說明,

In [1]: class Door:
   ...:     type = 'A'  # 類的直接下級作用域的變量,叫做類變量,所有的實(shí)例共享該變量。
   ...:     def __init__(self, number, status):
   ...:         self.number = number  # 關(guān)聯(lián)到實(shí)例的變量,叫做實(shí)例變量
   ...:         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'

拋出一個問題:如果執(zhí)行d1.type = 'B'語句后,那么執(zhí)行d2.type語句會有什么輸出呢?

類變量對類和實(shí)例都可見。再看一個例子:

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中,賦值意味著創(chuàng)建。

類方法/靜態(tài)方法

方法都是類級的。方法的定義都是類級的,但是有的方法使用實(shí)例調(diào)用,有的方法卻是使用類來調(diào)用。

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

實(shí)例方法與類方法,實(shí)例方法和類方法的區(qū)別在于傳入的第一個參數(shù),實(shí)例方法會自動傳入當(dāng)前實(shí)例,類方法會自動傳入當(dāng)前類。類方法可以被實(shí)例使用,并且被實(shí)例使用時,傳入的第一個參數(shù)還是類。

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參數(shù)
---------------------------------------------------------------------------
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

再看一個例子,當(dāng)我們用實(shí)例調(diào)用方法的時候,總是會傳入一個參數(shù),要么是實(shí)例本身,要么是它的類。

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

# 實(shí)例調(diào)用方法的時候,會傳入實(shí)例本身作為第一個參數(shù);
# 類調(diào)用方法的時候,不會傳遞本身作為第一個參數(shù);
# @classmethod 裝飾器會向方法傳遞一個參數(shù),傳遞的是類本身;

方法的作用域都屬于類級別,具體是實(shí)例方法,還是類方法,或者是靜態(tài)方法,由第一個參數(shù)決定??梢院唵蔚乩斫鉃椋寒?dāng)?shù)谝粋€參數(shù)是實(shí)例的時候,是實(shí)例方法;當(dāng)?shù)谝粋€參數(shù)是類的時候,是類方法,當(dāng)不要求第一個參數(shù)時,是靜態(tài)方法。

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'

所有實(shí)例需要共享一些狀態(tài)、數(shù)據(jù)的時候,就可以使用類變量。當(dāng)在實(shí)例中需要修改類變量的時候,我們就可以把修改的內(nèi)容放到類方法中。

類變量被賦值的話(賦值會產(chǎn)生新的引用),就會變成了實(shí)例變量。

訪問控制

這里主要涉及公有變量、私有變量及公有方法、私有方法。Python中沒有像C++Java中的關(guān)鍵字,諸如:public、privateprotected等關(guān)鍵字。我們看看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'   # 賦值意味著創(chuàng)建

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']

所有雙下劃線,非雙下劃線結(jié)尾的成員,都是私有成員。對于上述的__status私有變量,如何進(jìn)行訪問呢?在Python中,可以通過

_類名+帶雙下劃線的屬性
針對上面的例子就是:_Door__status

Python的私有成員是通過改名實(shí)現(xiàn)的。嚴(yán)格地說,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}

單下劃線開始的變量是一種慣用法,標(biāo)記此成員為私有,但是解釋器不做任何處理。

本來還想介紹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中如何進(jìn)行封裝的?來看一個小例子,

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中,以雙下劃線開始,并且不以雙下劃線結(jié)尾的變量,是私有變量,外界無法直接訪問。通常,我們不定義以雙下線開始,雙下劃線結(jié)尾的變量和方法,因?yàn)檫@在Python中有特殊含義。

接下來看看私有方法,方法也是一樣的規(guī)則,以雙下劃線開頭,非雙下劃線結(jié)尾的方法是私有方法。

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'私有實(shí)例變量'

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

: 私有類變量
: 私有類變量
: 私有實(shí)例變量

私有屬性在類的內(nèi)部均可訪問,無論是類方法還是實(shí)例方法。接下來再看一個稍微變態(tài)的例子,

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__查看實(shí)例f的屬性
f.__dict__
f._F__private_instance_var

事實(shí)上,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()

一個綜合點(diǎn)的例子,

  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方法進(jìn)行裝飾,可以讓我們訪問status方法像訪問類的屬性一樣。
          """
          return self.__status

  door = Door(1, 'number')
  door.open()
  door.status = 'opened'
  door.get_status()
  door.status # 屬性

還想對status進(jìn)行賦值,但賦值只能是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裝飾的屬性嗎,當(dāng)對此屬性賦值時,會調(diào)用此方法
    def status(self, value):
        if value in ('closed', 'opened'):
            self.__status = value
        else:
            raise ValueError(value)

    @status.deleter # 當(dāng)刪除此屬性時,會調(diào)用此方法
    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中,如果沒有顯式的指定繼承哪個類,默認(rèn)是繼承自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__   # 看一下實(shí)例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  # 賦值意味著創(chuàng)建

In [13]: b.z   # 再次訪問z
Out[13]: 3

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

無論是類變量還是實(shí)例變量都可以繼承;類方法、實(shí)例方法和靜態(tài)方法都可以繼承,但私有的除外。

方法重寫: 子類覆蓋父類的方法。有的子類就是需要有點(diǎn)兒個性,那么可以覆蓋或重寫父類的方法即可。

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對象調(diào)用父類的方法。

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, 返回當(dāng)前類的父類
   ...:        

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(),相當(dāng)于super(D, cls)
   ...:        

In [3]: a = A()

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

當(dāng)父類定義了帶參數(shù)的初始化方法時,子類要顯式的定義初始化方法,并且在初始化方法里初始化父類。

多繼承與MRO(Method Resolution Order)

本節(jié)內(nèi)容小白理解的也不是很深刻,從網(wǎng)上找了很多資料,在這里羅列一下,僅供參考。

MRO:方法查找順序。MRO的兩個原則:

  1. 本地優(yōu)先:自己定義或重寫的方法優(yōu)先;否則按照繼承列表,從左向右查找。
  2. 單調(diào)性:所有子類,也要滿足查找順序。

Python通過C3算法來確定是否滿足MRO的兩個原則。

下面的兩種寫法在Python3中的寫法是等價的,

class A:
    pass

class A(object):
    pass

在Python2.3之前,沒有一個最上層的基類;從2.4版本開始,Python引入了object這個最上層的基類,即所有類都繼承自object,但是為了兼容,必須要顯式指定。在Python2中,如果是第一種寫法,無法使用super方法。

針對Python3,因?yàn)椴挥眉嫒菖f風(fēng)格,所以兩種寫法是等效的,通常使用第一種寫法。

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)

關(guān)于C3算法是如何工作的,這里給出小白學(xué)習(xí)時參考的博文,地址為:https://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path

以上面的類C為例進(jìn)行一個推演,

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

總結(jié)

寫了這么多,是該總結(jié)一下了。本文開始介紹了一些主流的編程范式及面向?qū)ο缶幊痰奶攸c(diǎn)。

Python在眾多編程語言中還算是比較容易入門的,就連我這個機(jī)械系的小白也能玩得自嗨,更不用說計算機(jī)專業(yè)出身的大神了。

使用什么語言來完成實(shí)際的工作都無所謂,關(guān)鍵是所使用的語言能提供哪些語言特性,我們要有能力組合這些語言的特性以及標(biāo)準(zhǔn)庫或第三方庫來設(shè)計出良好程序。

如果我們理解了面向?qū)ο缶幊痰谋举|(zhì),那么我們就可以解決所有面向?qū)ο缶幊痰膯栴},而不是解決一兩門編程語言的問題。這就是所謂一通百通的威力。

當(dāng)前文章:掌握面向?qū)ο缶幊瘫举|(zhì),徹底掌握OOP
網(wǎng)站網(wǎng)址:http://muchs.cn/article40/gjgeho.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動態(tài)網(wǎng)站、網(wǎng)站建設(shè)網(wǎng)站設(shè)計公司、ChatGPT、微信小程序、網(wǎng)站制作

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

綿陽服務(wù)器托管