面向?qū)ο笫且环N編程范式。范式是指一組方法論。編程范式是一組如何組織代碼的方法論。編程范式指的是軟件工程中的一種方法學(xué)。
讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對(duì)這個(gè)行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊(cè)、雅安服務(wù)器托管、營銷軟件、網(wǎng)站建設(shè)、咸豐網(wǎng)站維護(hù)、網(wǎng)站推廣。
一些主流的編程范式:
世界觀:一切皆對(duì)象。
世界觀:一切皆函數(shù)。一般指無副作用的函數(shù)。
設(shè)計(jì)方法:
面向?qū)ο蟾M(jìn)一步的抽象了世界。OOP的世界觀:
類就是一個(gè)模板或藍(lán)圖,用來生成對(duì)象的。我們可以把類看做是一套模具,而模具加工出來的產(chǎn)品就是對(duì)象。當(dāng)我們從一套模具中塑造出一個(gè)產(chǎn)品的時(shí)候,我們就可以說創(chuàng)建了一個(gè)實(shí)例。
面向?qū)ο蟮奶匦裕?/p>
面向?qū)ο蟮娜筇卣鳎?/p>
面向?qū)ο笞钪匾母拍罹褪穷悾?code>Class)和實(shí)例(Instance
),必須牢記類是抽象的模板,而實(shí)例則是根據(jù)類創(chuàng)建出來的一個(gè)個(gè)具體的“對(duì)象”,每個(gè)對(duì)象都擁有相同的方法,但各自的數(shù)據(jù)有可能不同。
在Class內(nèi)部,可以有屬性和方法,而外部代碼可以通過直接調(diào)用實(shí)例變量的方法來操作數(shù)據(jù),這樣,就隱藏了內(nèi)部的復(fù)雜邏輯。
面向?qū)ο蟮谋举|(zhì):對(duì)行為和數(shù)據(jù)的封裝;有時(shí)候數(shù)據(jù)就是數(shù)據(jù);而有的時(shí)候行為就是行為。我們先使用Python標(biāo)準(zhǔn)庫中的namedtuple
來實(shí)現(xiàn)一個(gè)入門的類吧,目的是為了組織數(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):
# . 用于訪問對(duì)象的屬性與方法
self.number = number
self.status = status
door = Door(10010, 'closed') # 調(diào)用初始化方法(其他語言中的構(gòu)造方法)
print(door.number) # 獲取屬性,輸出:10010
print(door.status) # 獲取屬性,輸出closed
類就是數(shù)據(jù)與邏輯(或動(dòng)作)的集合。上述的Door類中只有數(shù)據(jù)沒有邏輯,那么我們?cè)谠擃愔屑尤腴_門與關(guān)門的動(dòng)作,用來操縱類中的數(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++
或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
是源自于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
我們看看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ù)與方法都在一個(gè)類中。
接下來,我們看看JavaScript
的面向?qū)ο缶幊淌窃鯓拥膶懛āN覀兪褂?code>ES6的規(guī)范來完成上面這個(gè)例子的演示:
// 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
我們?cè)倏纯?code>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)然還有很多編程語),簡單地演示了這些語言的基本套路。這里所舉的例子都是入門級(jí)的,限于小白的水平也做不到深入。舉這些例子的目的是想告訴大家:面向?qū)ο缶幊讨皇且环N思想,掌握了編程思想,那么使用什么樣的語言來完成你的當(dāng)前的任務(wù)就看這門語言提供了哪些特性、自己對(duì)這門語言的理解及熟練程度。
接下來會(huì)通過一些具體的實(shí)例說明實(shí)例化的過程。
In [14]: class Heap:
...: def __init__(self): # 此函數(shù)通常叫做構(gòu)造函數(shù),在Python中更多叫做初始化函數(shù),在對(duì)象創(chuàng)建完成后會(huì)立刻執(zhí)行
...: self.data = []
...: def add(self, x): # 第一個(gè)參數(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這個(gè)實(shí)例。當(dāng)然,代碼中的self
并不一定要寫為self
,還可以是其他Python非關(guān)鍵字。
再來一個(gè)例子:
$ cat person.py
class Person: # 創(chuàng)建一個(gè)名為Person的類
def __init__(self, name, job=None, pay=0): # 初始化函數(shù)接收三個(gè)參數(shù),與一般的函數(shù)參數(shù)具有相同意義
self.name = name # 創(chuàng)建對(duì)象時(shí)填充這些字段
self.job = job # self就是將要?jiǎng)?chuàng)建的對(duì)象(或?qū)嵗? self.pay = pay
bob = Person('Bob Smith') # test the class
sue = Person('Sue Jones', job='dev', pay=10000) # 自動(dòng)執(zhí)行__init__方法
print(bob.name, bob.pay) # 獲取對(duì)象的屬性
print(sue.name, sue.pay) # 不同的對(duì)象其自身的數(shù)據(jù)不一定相同
盡管上面的Person類非常簡單,不過它依然演示了一些重要的內(nèi)容。我們注意到bob的name并不是sue的name,并且sue的pay不是bob的pay。bob和sue它們都是兩個(gè)獨(dú)立的信息記錄。從技術(shù)的角度來看,bob與sue都是namespace objects
,就像其他所有的類實(shí)例一樣,它們創(chuàng)建時(shí)都有各自獨(dú)立的狀態(tài)信息的拷貝。因?yàn)槊總€(gè)類的實(shí)例都有自己self
屬性的集合,可以把類可以理解為一個(gè)藍(lán)圖、工廠或模具。
一個(gè)示例,
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') # 看起來非常像一個(gè)函數(shù)調(diào)用。事實(shí)上,
# 確實(shí)發(fā)生了一些函數(shù)調(diào)用,它調(diào)用了__init__函數(shù),
# 第一個(gè)參數(shù)由解釋器自動(dòng)傳入,表示實(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ù)并不會(huì)創(chuàng)建對(duì)象,__init__
函數(shù)初始化對(duì)象。對(duì)象(或?qū)嵗﹦?chuàng)建過程為:
__init__
函數(shù)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') # 看起來非常像一個(gè)函數(shù)調(diào)用。事實(shí)上,
# 確實(shí)發(fā)生了一些函數(shù)調(diào)用,它調(diào)用了__init__函數(shù),
# 第一個(gè)參數(shù)由解釋器自動(dòng)傳入,表示實(shí)例本身,
# 通常命名為self
print(door.__class__)
print(Door.__class__)
print(type.__class__)
# 所有類,都是type或者type的子類的實(shí)例
: <class '__main__.Door'>
: <class 'type'>
: <class 'type'>
實(shí)例化的時(shí)候,傳遞的參數(shù)列表是__init__
方法除了第一個(gè)參數(shù)之外的所有參數(shù),支持函數(shù)的所有參數(shù)變化。
當(dāng)沒有顯式的定義__init__
方法的時(shí)候,會(huì)使用默認(rèn)的__init__
方法,
def __init__(self):
pass
通過.
操作符訪問實(shí)例的屬性或者調(diào)用實(shí)例的方法。當(dāng)我們調(diào)用實(shí)例方法的時(shí)候,第一個(gè)參數(shù)即實(shí)例本身,由解釋器自動(dòng)傳入。
先給出一些規(guī)則:
結(jié)合一個(gè)簡單的例子說明,
In [1]: class Door:
...: type = 'A' # 類的直接下級(jí)作用域的變量,叫做類變量,所有的實(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'
拋出一個(gè)問題:如果執(zhí)行d1.type = 'B'
語句后,那么執(zhí)行d2.type
語句會(huì)有什么輸出呢?
類變量對(duì)類和實(shí)例都可見。再看一個(gè)例子:
In [14]: class HaHa:
...: NAME = 'HaHa'
...:
...: def __init__(self, name):
...: self.name = name
...:
In [15]: haha = HaHa('haha')
In [16]: haha.NAME # 等價(jià)于haha.__class__.NAME
Out[16]: 'HaHa'
In [17]: haha.__class__.NAME
Out[17]: 'HaHa'
In [19]: haha.NAME = 'hehe' # 等價(jià)于haha.__dict__['NAME'] = 'hehe'
In [20]: haha.NAME
Out[20]: 'hehe'
In [21]: haha.__class__.NAME
Out[21]: 'HaHa'
由此可以獲得屬性的查找順序:
__dict__
__class__
我們也從中體會(huì)到:在Python中,賦值意味著創(chuàng)建。
方法都是類級(jí)的。方法的定義都是類級(jí)的,但是有的方法使用實(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ū)別在于傳入的第一個(gè)參數(shù),實(shí)例方法會(huì)自動(dòng)傳入當(dāng)前實(shí)例,類方法會(huì)自動(dòng)傳入當(dāng)前類。類方法可以被實(shí)例使用,并且被實(shí)例使用時(shí),傳入的第一個(gè)參數(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() # 并不會(huì)傳入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
再看一個(gè)例子,當(dāng)我們用實(shí)例調(diào)用方法的時(shí)候,總是會(huì)傳入一個(gè)參數(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í)候,會(huì)傳入實(shí)例本身作為第一個(gè)參數(shù);
# 類調(diào)用方法的時(shí)候,不會(huì)傳遞本身作為第一個(gè)參數(shù);
# @classmethod 裝飾器會(huì)向方法傳遞一個(gè)參數(shù),傳遞的是類本身;
方法的作用域都屬于類級(jí)別,具體是實(shí)例方法,還是類方法,或者是靜態(tài)方法,由第一個(gè)參數(shù)決定??梢院唵蔚乩斫鉃椋寒?dāng)?shù)谝粋€(gè)參數(shù)是實(shí)例的時(shí)候,是實(shí)例方法;當(dāng)?shù)谝粋€(gè)參數(shù)是類的時(shí)候,是類方法,當(dāng)不要求第一個(gè)參數(shù)時(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'
再來看一個(gè)例子:
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ù)的時(shí)候,就可以使用類變量。當(dāng)在實(shí)例中需要修改類變量的時(shí)候,我們就可以把修改的內(nèi)容放到類方法中。
類變量被賦值的話(賦值會(huì)產(chǎn)生新的引用),就會(huì)變成了實(shí)例變量。
這里主要涉及公有變量、私有變量及公有方法、私有方法。Python中沒有像C++
或Java
中的關(guān)鍵字,諸如:public
、private
或protected
等關(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', # 變成了這個(gè)樣子
'_Door__status', # 變成了這個(gè)樣子
'__class__',
'__delattr__',
...
'__sizeof__',
'__status',
'__str__',
'__subclasshook__',
'__weakref__',
'close_door',
'door_status',
'number',
'open_door']
所有雙下劃線,非雙下劃線結(jié)尾的成員,都是私有成員。對(duì)于上述的__status
私有變量,如何進(jìn)行訪問呢?在Python中,可以通過
_類名+帶雙下劃線的屬性
針對(duì)上面的例子就是:_Door__status
Python的私有成員是通過改名實(shí)現(xiàn)的。嚴(yán)格地說,Python里沒有真正的私有成員。除非真的有必要,并且清楚知道會(huì)有什么后果,否則不要用這個(gè)黑魔法。
接下來再看看以單下劃線開始的變量,
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
裝飾器呢,留給大家自己摸索一下吧。
先看一個(gè)例子,
Heap = namedtuple('Heap', ['add', 'pop'])
def heap_factory():
data = []
def add(x):
pass
def pop():
pass
return Heap(add, pop)
heap = heap_factory()
# 對(duì)外界來說,data是不可見的,外界無法訪問data
在Python中如何進(jìn)行封裝的?來看一個(gè)小例子,
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)時(shí),也看不到__private_method()方法。
Traceback (most recent call last):
File "<stdin>", line 6, in <module>
AttributeError: 'D' object has no attribute '__private_method'
一個(gè)稍微綜合的例子,
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í)例方法。接下來再看一個(gè)稍微變態(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
再來一個(gè)例子,
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這個(gè)例子,
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的私有屬性并不是真正私有,而是一個(gè)變量重命名而已。看一個(gè)例子說明此問題:
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()
一個(gè)綜合點(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`裝飾器描述符對(duì)status方法進(jìn)行裝飾,可以讓我們?cè)L問status方法像訪問類的屬性一樣。
"""
return self.__status
door = Door(1, 'number')
door.open()
door.status = 'opened'
door.get_status()
door.status # 屬性
還想對(duì)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裝飾器,可以把方法裝飾成了一個(gè)同名屬性
def status(self):
return self.__status
@status.setter # @xxxx.setter xxxx代表被@property裝飾的屬性嗎,當(dāng)對(duì)此屬性賦值時(shí),會(huì)調(diào)用此方法
def status(self, value):
if value in ('closed', 'opened'):
self.__status = value
else:
raise ValueError(value)
@status.deleter # 當(dāng)刪除此屬性時(shí),會(huì)調(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
啥也不說,先來一個(gè)例子,
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中,如果沒有顯式的指定繼承哪個(gè)類,默認(rèn)是繼承自object類,也就是新式類。
子類獲得父類一些(非全部)方法和屬性??匆粋€(gè)例子,
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)兒個(gè)性,那么可以覆蓋或重寫父類的方法即可。
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對(duì)象,可以使用super對(duì)象調(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()等價(jià)于super(__class__, self) -> Base
...:
In [6]: b = B()
In [7]: b.my_print()
I am b class
I am base class
子類能否繼承祖先類的屬性呢?看一個(gè)例子:
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對(duì)象不但可以使用父類的屬性,還能使用祖先的屬性。super(type, obj)
返回super
對(duì)象,指代type
的父類。
super對(duì)象持有類級(jí)別的成員。舉個(gè)例子看看,
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ù)的初始化方法時(shí),子類要顯式的定義初始化方法,并且在初始化方法里初始化父類。
本節(jié)內(nèi)容小白理解的也不是很深刻,從網(wǎng)上找了很多資料,在這里羅列一下,僅供參考。
MRO:方法查找順序。MRO的兩個(gè)原則:
Python通過C3算法來確定是否滿足MRO的兩個(gè)原則。
下面的兩種寫法在Python3中的寫法是等價(jià)的,
class A:
pass
class A(object):
pass
在Python2.3之前,沒有一個(gè)最上層的基類;從2.4版本開始,Python引入了object這個(gè)最上層的基類,即所有類都繼承自object,但是為了兼容,必須要顯式指定。在Python2中,如果是第一種寫法,無法使用super方法。
針對(duì)Python3,因?yàn)椴挥眉嫒菖f風(fēng)格,所以兩種寫法是等效的,通常使用第一種寫法。
Python支持多繼承,接下來看一個(gè)例子:
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í)時(shí)參考的博文,地址為:https://makina-corpus.com/blog/metier/2014/python-tutorial-understanding-python-mro-class-search-path
以上面的類C
為例進(jìn)行一個(gè)推演,
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)
另外一個(gè)推演,
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é)一下了。本文開始介紹了一些主流的編程范式及面向?qū)ο缶幊痰奶攸c(diǎn)。
Python在眾多編程語言中還算是比較容易入門的,就連我這個(gè)機(jī)械系的小白也能玩得自嗨,更不用說計(jì)算機(jī)專業(yè)出身的大神了。
使用什么語言來完成實(shí)際的工作都無所謂,關(guān)鍵是所使用的語言能提供哪些語言特性,我們要有能力組合這些語言的特性以及標(biāo)準(zhǔn)庫或第三方庫來設(shè)計(jì)出良好程序。
如果我們理解了面向?qū)ο缶幊痰谋举|(zhì),那么我們就可以解決所有面向?qū)ο缶幊痰膯栴},而不是解決一兩門編程語言的問題。這就是所謂一通百通的威力。
當(dāng)前文章:掌握面向?qū)ο缶幊瘫举|(zhì),徹底掌握OOP
網(wǎng)站網(wǎng)址:http://aaarwkj.com/article40/gjgeho.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動(dòng)態(tài)網(wǎng)站、網(wǎng)站建設(shè)、網(wǎng)站設(shè)計(jì)公司、ChatGPT、微信小程序、網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)