編程思想是什麼,就是用代碼解決現實生活中問題的思路。面試
核心點在過程二字,過程指的是解決問題的步驟,說白了就是先作什麼再幹什麼。這種解決問題的思路就比如是工廠中的流水線。算法
運維同窗工做中接觸到的shell腳本就是典型的按步驟作事。shell
優勢:複雜的問題流程化,進而簡單化。編程
缺點:可擴展性差,好比,一個腳本就是幹一件事情的。運維
核心點是對象二字,對象指的是具備相同屬性和動做的結合體叫對象。ide
面向對象編程就比如在代碼中創造一個世界,創造若干對象,就像現實世界中的萬物同樣,每一個物體均可以有本身的屬性和動做。函數
優勢:可擴展性強ui
缺點:編程的複雜度高於面向過程spa
問:把大象裝進冰箱,總共分幾步?設計
答:三步啊,
這就是典型的面向過程解題的思惟方式。
那面向對象編程是怎麼樣解決這個問題呢?
首先,咱們須要有兩個對象--冰箱和大象。冰箱能夠開門和關門,大象能夠被放進冰箱。
這就是面向對象的思惟方式。
咱們面向的再也不是事物發展的流程,而是操縱某個事物個體。
說了那麼多,那究竟如何在代碼中使用面向對象呢?
首先,咱們來思考這樣一個問題,在現實世界中,咱們如何造一輛汽車?
咱們確定是先由設計師來設計,在圖紙上勾勒出汽車應該有的東西,以及這輛汽車應該有的功能,而後再交給工廠去生產真正的汽車。
在程序中也是同樣,咱們須要先設計一個圖紙,圖紙上描述着物體應該有的特徵和功能,而後依照這個圖紙建立對象。
在Python中,「畫圖紙」的這個過程,咱們經過定義類來實現。
類的語法很簡單,像下面這樣:
class 類名: pass
好比咱們建立一個車類:
class Car: pass
像這樣咱們就建立了一個汽車類,圖紙有了,那怎麼建立一輛汽車對象呢?
根據圖紙建立物體的過程,咱們稱之爲實例化。實例化就是由一個概念到實際物體的過程。
Python中實例化,只須要「類名()」就能夠了。
c1 = Car() # 造一輛車 c2 = Car() # 再造一輛車
車如今有了,這車至少得有一些顏色、排量、品牌等信息吧。不一樣的車,能夠有不一樣的顏色、排量、品牌等信息。
咱們能夠經過下面的代碼來實現:
c1.color = 'red' c1.code = 'SNIS-517' c1.power = '2.0T' c2.color = 'green' c2.code = 'SNIS-594' c2.power = '3.0T'
而後,咱們能夠檢查一下這兩輛車的信息。
print(c1.color) print(c1.code) print(c1.power) print(c2.color) print(c2.code) print(c2.power)
咱們就這樣造了兩輛徹底不一樣的車,可是咱們發現這兩輛車是在造好以後再添加不一樣的信息的。但是這些信息應該是在創造汽車的時候就設計好的,不該該後面再設計的。
好了,咱們如今的需求就是在建立對象的時候就能給對象設置一些初始的屬性信息,在Python中咱們是經過在類中定義__init__()「函數」,來進行初識化操做。這個「函數」被稱爲構造函數(方法)。
class Car: def __init__(self, color, code, power): # self表示當前建立的這個對象,你建立的是誰self就指的誰 self.color = color self.code = code self.power = power c1 = Car('red', 'SNIS-517', '2.0T') c2 = Car('green', 'SNIS-594', '3.0T') print(c1.color) print(c1.code) print(c1.power) print(c2.color) print(c2.code) print(c2.power)
經過執行上面的代碼,咱們發現咱們由一個Car類,創造了兩個不一樣特徵(屬性)的汽車對象。
咱們的這個例子尚未講完,由於汽車不光有特徵,它還應該有動做--車還會跑。並且是全部的汽車都會跑,這怎麼實現呢?
咱們只須要在類中像定義函數同樣定義一個方法便可。類中的方法比普通的函數多一個self參數。其實本質上就是函數,只不過這個函數被綁定給實例對象使用,不能隨意經過名字調用執行它。
class Car: def __init__(self, color, code, power): # self表示當前建立的這個對象,你建立的是誰self就指的誰 self.color = color self.code = code self.power = power def run(self, speed): print('車能夠跑{}邁'.format(speed)) c1 = Car('red', 'SNIS-517', '2.0T') c1.run(70) # 此時,Python會自動把c1這個對象當成run()的第一個參數傳進去
構造函數中的self表示建立的當前對象。
方法中的self表示誰調用的方法,self就是誰。
類是對事物的總結,是一種抽象的概念。類用來描述對象。
對象是類實例化的結果,對象能執行哪些方法,都是由類來定義的。類中定義了什麼,對象就擁有什麼。
再來一個例子,幫助理解面向對象。
from math import pi class Circle: """ 定義了一個圓形類; 提供計算面積(area)和周長(perimeter)的方法 """ def __init__(self, radius): self.radius = radius # 圓的半徑 def area(self): return pi * self.radius * self.radius def perimeter(self): return 2 * pi * self.radius circle = Circle(10) # 實例化一個圓 area1 = circle.area() # 計算圓面積 per1 = circle.perimeter() # 計算圓周長 print(area1, per1) # 打印圓面積和周長
你可能會問:面向對象和麪向過程到底哪一個好?
首先這不能看作是一個誰好誰很差的問題,要具體問題具體分析。沒有絕對的好和很差,這一點很是重要!
咱們藉助幾個例子,再來看一下面向對象和麪向過程的區別:
面向過程版
# 非函數版 print('開冰箱門') print('把大象裝進冰箱') print('關冰箱門') # 函數版 def open_door(): print('開冰箱門') def zhuang(): print('把大象裝進冰箱') def close_door(): print('關冰箱門') open_door() zhuang() close_door()
面向對象版:
class Elephant: def open_door(self): print('開冰箱門') def zhuang(self): print('走進冰箱') def close_door(self): print('把門帶上') dx = Elephant() dx.open_door() dx.zhuang() dx.close_door()
這...面向對象有點麻煩啊...彆着急,繼續往下看:
以一隻豬,名叫佩奇,今年18歲,會使用必殺技「嘟嘟嘴」。他不光能大戰奧特曼,還能大戰蝙蝠俠,蜘蛛俠。
面向過程
# 面向過程 def da_out_man(name, age, jn): print('{}, 今年{}歲,正在使用{}技能對奧特曼瘋狂輸出...'.format(name, age, jn)) def da_bat_man(name, age, jn): print('{}, 今年{}歲,正在使用{}技能對蝙蝠俠瘋狂輸出...'.format(name, age, jn)) def da_spider_man(name, age, jn): print('{}, 今年{}歲,正在使用{}技能對蜘蛛俠瘋狂輸出...'.format(name, age, jn)) da_out_man('小豬佩奇', 18, '嘟嘟嘴') da_bat_man('小豬佩奇', 18, '嘟嘟嘴') da_spider_man('小豬佩奇', 18, '嘟嘟嘴')
面向對象:
class Pig: def __init__(self, name, age, skill): self.name = name self.age = age self.skill = skill def da_out_man(self): print('{}, 今年{}歲,正在使用{}技能對奧特曼瘋狂輸出...'.format(self.name, self.age, self.skill)) def da_bat_man(self): print('{}, 今年{}歲,正在使用{}技能對蝙蝠俠瘋狂輸出...'.format(self.name, self.age, self.skill)) def da_spider_man(self): print('{}, 今年{}歲,正在使用{}技能對蜘蛛俠瘋狂輸出...'.format(self.name, self.age, self.skill)) pq = Pig('小豬佩奇', 18, '嘟嘟嘴') pq.da_out_man() pq.da_bat_man() pq.da_spider_man()
看完這個例子,是否是有點眉目了。在小豬佩奇這個示例中,咱們明顯可以發現使用面向對象的思想來解決問題會更清晰一些。
代碼也更容易編寫一些。
因此,使用哪一種編程思想來解決問題不是絕對的,咱們須要根據實際的需求來抉擇。
面向對象有三大特性:封裝、繼承和多態。
把不少數據封裝到一個對象中,把固定功能的代碼封裝到⼀個代碼塊函、數、對象, 打包成模塊,這都屬於封裝的思想。
具體的狀況具體分析,好比:你寫了⼀個很牛B的函數,那這個也能夠被稱爲封裝。在⾯向對象思想中是把一些看似可有可無的內容組合到一塊兒,統一進行存儲和使用,這就是封裝。
子類能夠自動擁有父類中除了私有屬性外的其餘全部內容。說⽩了, ⼉⼦能夠隨便用爹的東西。可是朋友們, 必定要認清楚⼀個事情。必須先有爹, 後有⼉⼦。 順序不能亂,在Python中實現繼承很是簡單。在聲明類的時候, 在類名後⾯面添加一個小括號,把要繼承的類傳進去就能夠完成繼承關係。
那麼什麼狀況可使用繼承呢?
單純的從代碼層⾯上來看,當兩個類具備相同的功能或者特徵的時候,能夠採用繼承的形式。提取一個父類,這個父類中編寫兩個類中相同的部分。而後兩個類分別去繼承這個類就能夠了。這樣寫的好處是咱們能夠避免寫不少重複的功能和代碼。
若是果語境中出現了了x是一種y。這時,y就是⼀種泛化的概念,x比y更加具體。那這時x就是y的子類。舉個現實中的例子:貓是⼀種動物,貓繼承動物。動物能動,貓也能動。這時貓在建立的時候就有了動物的"動"這個屬性。再好比⽩骨精是⼀個妖怪,妖怪天生就有⼀個比較很差的功能叫"吃人", 白骨精⼀出生就知道如何"吃人"。此時 ⽩骨精繼承妖精。
話很少說,上代碼:
# 妖怪類 class Yao: def eat(self): print("我是妖怪, 我天生就會吃人") # ⽩骨精繼承妖怪 class BaiGuJing(Yao): pass bgj = BaiGuJing() # 我是妖怪, 我天⽣生就會吃⼈人 # 雖然白骨精類中沒有編寫eat,可是他爹有啊,直接拿來⽤ bgj.eat()
繼承是實例對象可以訪問到本身類及父類的屬性和方法。
先來看幾個例子:
class A: def __init__(self, num): self.num = num def func1(self): print(self.num) class B(A): def func1(self): print("B.func1", self.num) obj = B(123) obj.func1()
再來一個:
class A: def __init__(self, num): self.num = num def func1(self): print(self.num) self.func2() def func2(self): print("A.func2") class B(A): def func2(self): print("B.func2") obj = B(123) obj.func1()
總結一下:
示例對象訪問屬性或調用方法時,永遠都是先找本身的,找不到就往父類找。
最後來兩個例子,鞏固一下:
class A: def __init__(self, num): self.num = num def func1(self): print(self.num) self.func2() def func2(self): print("A.func2", self.num) class B(A): def func2(self): print("B.func2", self.num) list1 = [A(1), A(2), B(3)] for i in list1: i.func2()
來一個更繞的:
class A: def __init__(self, num): self.num = num def func1(self): print(self.num) self.func2() def func2(self): print("A.func2", self.num) class B(A): def func2(self): print("B.func2", self.num) list1 = [A(1), A(2), B(3)] for i in list1: i.func1()
在Python中,一個類能夠同時繼承多個父類。說白了, 如今一個⼉子可能會有多個爹了。
既然是能夠有多個爹, 那總得有遠有近。好比. 有一個這樣的牛B的人物, 叫郭醜醜,就有不少個爹。
class QinDie: def eat(self): print("親爹給你好吃的") def play(self): print("親爹會陪你玩") class GanDie: def qian(self): print("乾爹給錢啊") def play(self): print("乾爹會陪你玩") class GuoChouChou(QinDie, GanDie): pass mm = GuoChouChou() mm.eat() mm.qian() mm.play() # 繼承中,實例對象查找方法的順序 # 親爹沒有, 找乾爹 # 親爹有了, 就不找乾爹了
注意:多繼承中的MRO(Method Resolution Order)算法,咱們在後面會具體分析和講解。
同一個對象,多種形態。
這個在Python中實際上是很不容易說明白的,由於咱們一直在用,只是沒有具體的說。好比咱們建立⼀個變量a = 10,咱們知道此時a是整數類型。可是咱們能夠經過程序讓a = "Bob", 這時a又變成了字符串類型。這是咱們都知道的。這個就是多態性。同一個變量a能夠是多種形態。再好比一個打開的文件、一個列表、一個生成器均可以被for循環迭代,它們都是可迭代對象。可能這樣的程序和說法你還get不到具體什麼是多態。
接下來,咱們來看⼀個程序。動物園裏飼養員一天的⼯做,從早上開始餵養猪, 中午喂狗。
來咱們用代碼實現這樣的代碼:
class Animal: def eat(self): print("動物就知道吃") class Pig(Animal): def eat(self): print("豬在吃") class Dog(Animal): def eat(self): print("狗在吃") class Feeder: def feed_animal(self, ani): ani.eat() p = Pig() d = Dog() f = Feeder() f.feed_animal(p) f.feed_animal(d)
當子類和父類都存在相同的eat方法時,子類的eat方法覆蓋了父類的eat方法。在代碼運行的過程當中,子類對象老是會調用本身的eat方法。
此時,雖然都是Animal類的對象,都有eat方法,可是實現的功能卻能夠不一樣。
多態的好處:程序具備超高的可擴展性。
什麼是類的成員呢?很簡單,你在類中定義的內容均可以稱爲類的成員。
到目前爲止,咱們已經學過一些成員了:
class Person: # 方法 def __init__(self, name, age): # 屬性 self.name = name self.age = age # 方法 def say(self): print('我是{self.name},我今年{self.age}歲!'.format(self=self))
在上面的代碼中,__init__和say都是屬於類的成員方法,又稱爲實例方法。
self.name和self.age都是實例對象的屬性,這些稱爲成員變量或實例變量。
也就是說在類中,能夠定義實例變量和實例方法,那還有些什麼呢?
先說什麼是實例變量,說白了,就是每一個實例都應該擁有的變量。好比,人的名字,人的年齡,每一個人的信息都屬於具體的人,這些均可以稱爲實例變量。
class Person: # 方法 def __init__(self, name, age): # 屬性 self.name = name self.age = age # 方法 def say(self): print('我是{self.name},我今年{self.age}歲!'.format(self=self)) p1 = Person('張三', 18) p2 = Person('李四', 20) print(p1.name, p1.age) # 張三 18 print(p2.name, p2.age) # 李四 20 print(id(p1.name), id(p2.name)) # 4567249208 4567250088 print(id(p1.age), id(p2.age)) # 4564114656 4564114720
那什麼是類變量呢?類變量就是這一類事物統一擁有的變量,好比,在座的各位都是中國人,你們都擁有一個同一個祖國--中國。
class Person: # 類變量,全部實例對象都共享這個變量 country = '中國' # 方法 def __init__(self, name, age): # 屬性 self.name = name self.age = age # 方法 def say(self): print('我是{self.name},我今年{self.age}歲!'.format(self=self)) p1 = Person('張三', 18) p2 = Person('李四', 20) print(p1.country, p2.country) # 中國 中國 print(id(p1.country), id(p2.country)) # 4567249032 4567249032
咱們能夠發現,實例對象p1和p2的country屬性都是類中定義的那個,公用的。
咱們嘗試修改一下:
Person.country = 'China' print(p1.country, p2.country) # China China print(id(p1.country), id(p2.country)) # 4551075464 4551075464
從上面的代碼中,咱們能夠看到咱們修改了類變量country,各實例對象中的country屬性也跟着變了。
接下來,咱們再來看一個例子。
class Person: # 類變量,全部實例對象都共享這個變量 country = '中國' # 方法 def __init__(self, name, age): # 屬性 self.name = name self.age = age # 方法 def say(self): print('我是{self.name},我今年{self.age}歲!'.format(self=self)) p3 = Person('王五', 3000) p3.country = '大秦' print(p3.name) # 王五 print(p3.country) # 大秦 p4 = Person('趙六', 30) print(p4.name) # 趙六 print(p4.country) # 中國
爲何王五的country是大秦,而趙六的country是中國呢?
注意,p3.country = '大秦'的時候,並無去改變類中的country,而是給本身添加了一個實例屬性,這個屬性只有在p3這個實例對象中才是存在的,在p4中是不存在的。
類變量,是定義在類中的變量,該類的實例對象均可以訪問到該變量,可是示例對象只能查看不能修改。想要修改類變量,只能經過類名來修改。
示例變量,給實例對象用的。
類變量,實例對象共享的。
案例:經過類變量來記錄建立的實例個數:
class Foo: count = 0 def __init__(self): Foo.count += 1 print(Foo.count) # 0 Foo() Foo() Foo() print(Foo.count) # 3
類中的方法分爲實例方法、靜態方法和類方法。
實例方法是咱們接觸和使用最多的,實例對象能夠直接訪問的方法就叫實例方法(成員方法)。
class Computer: # 實例方法 def play(self): print('個人電腦能夠玩遊戲') c1 = Computer() c1.play() # 實例對象直接調用實例方法
靜態方法不須要傳遞「self」參數,也就是說,當類中的方法不須要傳入實例變量的時候,就可使用靜態方法。
定義靜態方法時須要咱們在該方法上添加@static,示例以下:
class Computer: # 實例方法 def play(self): print('個人電腦能夠玩遊戲') @staticmethod def fire(): # 方法不須要傳入實例對象變量 print('個人電腦很是牛B,能夠煎雞蛋') c1 = Computer() c1.play() # 實例對象直接調用實例方法 c1.fire()
靜態方法和靜態變量同樣,均可以使用類名直接訪問。
Computer.fire()
類方法是由類調用的方法,他的第一個參數必須是類自己。類方法在被調用的時候,不須要調用者傳遞該參數,解釋器會自動的把類當成第一個參數,類方法在定義的時候須要在類方法上添加@classmethod
class Computer: # 實例方法 def play(self): print('個人電腦能夠玩遊戲') @staticmethod def fire(): # 方法不須要傳入實例對象變量 print('個人電腦很是牛B,能夠煎雞蛋') @classmethod def calc(cls, a, b): print(cls) return a + b c1 = Computer() c1.play() # 實例對象直接調用實例方法 c1.fire() ret = Computer.calc(10, 20) # <class '__main__.Computer'> print(ret) # 30
相關面試題
類方法、靜態方法和實例方法有什麼區別?
屬性方法其實就是把類中的方法改形成屬性的一種寫法,該寫法須要在方法上加上@property,例如:
from datetime import datetime from dateutil.relativedelta import relativedelta class Person: def __init__(self, name, birthday): self.name = name self.birthday = birthday @property def age(self): # 人的年齡是一個屬性,可是須要由生日和當前時間計算得出 # 因此咱們把這個方法包裝成屬性 ret = relativedelta(datetime.now(), datetime.strptime(self.birthday, '%Y-%m-%d')) return ret.years p1 = Person('李國慶', '1990-10-01') # 像訪問實例屬性同樣 print(p1.age)
在面向對象中,類中一般會定義屬性和方法,屬性用來描述實例對象的特徵,方法用來描述實例對象的動做。
像年齡這種須要必定動做來得出的屬性,Python中咱們會把這個方法描述成一個屬性。
補充property的進階用法:
class Goods: def __init__(self): # 原價 self.original_price = 100 # 折扣 self.discount = 0.8 @property def price(self): # 實際價格 = 原價 * 折扣 new_price = self.original_price * self.discount return new_price @price.setter def price(self, value): self.original_price = value @price.deleter def price(self): del self.original_price obj = Goods() print(obj.price) # 獲取商品價格 obj.price = 200 # 修改商品原價 print(obj.price) del obj.price # 刪除商品原價 print(obj.price) # AttributeError: 'Goods' object has no attribute 'original_price'
在某些場景下,咱們可能會在類中存儲一些只有咱們本身能訪問的信息。在Python中咱們可使用兩個連續的雙下劃線將屬性或方法變爲私有的。
咱們定義一個Person類,有name屬性和salary屬性,薪資是保密,因此在salary的前面加上兩個連續的下劃線,把它變成私有的。
class Person: def __init__(self, name, salary): self.name = name self.__salary = salary # 薪資是祕密,設置爲私有 p1 = Person('李狗剩', 200) print(p1.name) print(p1.__salary) # AttributeError: 'Person' object has no attribute '__salary'
上面的代碼報錯,由於咱們沒法直接訪問實例對象的私有屬性。
咱們能夠曲線救國,經過另一個方法來獲取這個屬性。
class Person: def __init__(self, name, salary): self.name = name self.__salary = salary # 薪資是祕密,設置爲私有 def dazuiba(self): return self.__salary p1 = Person('李狗剩', 200) print(p1.name) salary = p1.dazuiba() print(salary) # 200
私有屬性不能直接訪問,可是能夠經過其餘的實例方法來訪問私有屬性。這樣作的好處是實現了私有屬性只能查看不能修改。
不只僅實例屬性能夠私有,類屬性也能夠:
class Person: __secret = '人都是自私的' def __init__(self, name, salary): self.name = name self.__salary = salary # 薪資是祕密,設置爲私有 def dazuiba(self): return self.__salary p1 = Person('李狗剩', 200) print(p1.name) salary = p1.dazuiba() print(salary) # 200 print(Person.__secret) # AttributeError: type object 'Person' has no attribute '__secret'
私有方法,顧名思義就是隻能本身調用的方法,別人都不能隨便調用。
class Person: def __init__(self): pass def __play(self): print('嘿嘿嘿') def work(self): print('工做使我快樂!') p1 = Person() p1.work() # 工做使我快樂! p1.__play() # AttributeError: 'Person' object has no attribute '__play'
__play是一個私有方法,只可以在類的內部調用,類的外部沒法調用。
咱們能夠在類的內部調用私有方法:
class Person: def __init__(self): pass def __play(self): print('嘿嘿嘿') def work(self): print('工做使我快樂!') def dazuiba(self): self.__play() p1 = Person() p1.work() p1.dazuiba() # 嘿嘿嘿
在實際的應用場景中,咱們一般會把那些不想暴露給實例對象的方法,定義爲私有方法。
另外還須要注意的一點是,對於私有成員,子類是沒法繼承的。
class A: __secret = '密碼' def __init__(self, salary): self.__salary = salary def __play(self): print('嘿嘿嘿') class B(A): def dazuiba(self): self.__play() b1 = B(200) print(b1.__salary) # 'B' object has no attribute '__salary' b1.__play() # 'B' object has no attribute '__play' b1.dazuiba() # AttributeError: 'B' object has no attribute '_B__play' print(b1.__secret) # AttributeError: 'B' object has no attribute '__secret' print(B.__secret) # AttributeError: type object 'B' has no attribute '__secret'
依賴關係
class Elephant: def __init__(self, name): self.name = name def open(self, ref): print("⼤象要開門了。默唸三聲!開!") # 由外界傳遞進來⼀個冰箱, 讓冰箱開門。這時,⼤象不用揹着冰箱處處跑。 # 類與類之間的關係也就不那麼的緊密了,換句話說只要是有open_door()⽅法的對象均可以接收運⾏ ref.open_door() def close(self, ref): print("⼤象要關門了。默唸三聲!關!") ref.close_door() def enter(self): print("鑽進去") class Refrigerator: def open_door(self): print("冰箱門被打開了") def close_door(self): print("冰箱門被關上了") # 造冰箱 r = Refrigerator() # 造⼤大象 el = Elephant("神奇的大象") el.open(r) # 注意:此時是把一個冰箱做爲參數傳遞進去了。也就是說大象能夠指定任何一個冰箱 el.enter() el.close(r)
關聯關係
class Boy: def __init__(self, name, girl_friend=None): self.name = name self.girl_friend = girl_friend def have_a_dinner(self): if self.girl_friend: print("{}和{}一塊兒去吃晚餐".format(self.name, self.girl_friend.name)) else: print("單身狗。吃什麼飯!") class Girl: def __init__(self, name): self.name = name b = Boy('張大錘') b.have_a_dinner() # 忽然找到女友了 g1 = Girl("如花") b.girl_friend = g1 g2 = Girl("李小花") bb = Boy("張二錘", g2) # 娃娃親,出⽣就有女朋友 bb.have_a_dinner() # 多麼幸福的一家 # 忽然.bb失戀了。娃娃親不跟他好了 bb.girl_friend = None bb.have_a_dinner() # 又單身了