一、函數
當我們發現有一些代碼需要重復使用,你想怎么解決?
函數:是組織好的,可重復使用的,用來實現單一,或相關聯功能的代碼塊。 函數能提高應用的模塊性,和代碼的重復利用率。
如我們前面使用的print() range()函數,但你也可以自己創建函數,這被叫做用戶自定義函數。
1.1 、函數的定義
函數 用關鍵字 def修飾,后接函數標識符名稱和圓括號() 和一個: 冒號
函數內容: 要用一個tap 或四個空格縮進
如:定義一個打印函數
# 函數名 :我命名為了pr,函數內容里面只有一句打印語句
def pr():
print("你好,這是一個函數")
# 函數定義完 不會自己執行,只有在調用的時候才會執行
# 調用函數 如下:直接寫函數名()
pr()
函數名命名規范:
由字母、數字、下畫線 _ 組成,其中數字不能打頭,一般小寫
不能是 Python 關鍵字,但可以包含關鍵字
不能包含空格
1.2 、函數的參數
函數是能夠重復使用 而且不單單針對一個常量,所以我們可能處理很多未知數據,那這些未知的數據 我們又無法確定 那就可以用 參數,
參數一般寫在 函數的括號里面,然后在調用的時候傳進行 最后在函數內容里使用
函數在定義的時候 可以有參 或 無參
有參 可以分為 :必傳 ,可傳
def fun(a,b=0):
pass
我們可以看到上面這個函數 a參數是必傳,也就是在調用fun這個函數的時候 必須傳一個值給a,b是可傳 可不傳 當你不傳的時候b就默認為0
從調用看 參數分為:
位置傳參
def fun01(a,b,c,d=1):
print(a)
print(b)
print(c)
print(d)
fun01(1,2,3)
如上 當我們調用函數時,發現 a=1,b=2,c=3,d=1,我們傳參的時候沒有指定傳給誰 他會按位置順序一 一 匹配
指定傳參
def fun01(a,b,c,d=1):
print(a)
print(b)
print(c)
print(d)
fun01(b=1,c=2,a=3)
如上 當我們調用這個函數時,發現打印出來 3、1、2、1 因為我們在傳參的時候指定參數名等於對應的值
可變參數
*args:接收到的所有按照位置參數方式傳遞進來的參數,是一個元組類型
**kw:接收到的所有按照指定參數方式傳遞進來的參數,是一個字典類型
def fun02(*args):
print(args[1])
fun02(2,3,4)
我們執行上面代碼會打印出來一個3出來 ,相等於 傳了多個參數他會把這多個參數裝進一個元組傳進去 如果你想取任意一個值 只需要取對應索引
def fun03(**kw):
print(kw)
fun03(a=1,b=3)
>>> {'a': 1, 'b': 3}
**kw 我們可以看到 我們必須要傳一個 一對參數進去 鍵值 鍵和值都可以自定義
1.3 、返回值:return
當我們調用一個函數並傳入參數時 ,我們希望得到一個他加工回來的返回值給我們,
這個返回值 可以有 也可以沒有,沒有相當於返回 None,沒有情況比較注重過程
def fun04(a):
print(a)
return 20
fun04(7)
我們看到fun04這個函數 有一個參數a 他的函數體里 只有兩句一個是打印這個參數,一個是返回 20 ,但在控制台 我們只看到 輸出了 7 ,因為你在調用函數的時候 print(a)這個代碼 就是要輸出一個7到控制面板,但是return 20 ,他會返回一個20給調用的結果,因為你沒有輸出到控制台 所以他不會顯示 你可以用 print(fun04(7)) 調用這個函數 他會輸出一個7和20
注:函數體里面 在執行的過程一旦遇到return 就會結束這個函數 ,return 后面在寫任何代碼不會執行
練習:
1.定義一個函數 求兩個數之間的和
2.定義一個函數,求三個數的最大值
3.定義一個函數,輸入任意三個數字是否能構成三角形
二、面向對象
面向對象思想是一種程序設計思想(Object Oriented Programming,簡稱OOP),這里的對象泛指現實中一切事物,每種事物都具備自己的屬性和行為。面向對象思想就是在計算機程序設計過程中,參照現實中事物,將事物的屬性特征、行為特征抽象出來,描述成計算機事件的設計思想。 它區別於面向過程思想,強調的是通過調用對象的行為來實現功能,而不是自己一步一步的去操作實現。
舉例
洗衣服:
面向過程:把衣服脫下來-->找一個盆-->放點洗衣粉-->加點水-->浸泡10分鍾-->揉一揉-->清洗衣服-->擰干-->晾起來
面向對象:把衣服脫下來-->打開全自動洗衣機-->扔衣服-->按鈕-->晾起來
區別:
面向過程:強調步驟。
面向對象:強調對象,這里的對象就是洗衣機。
特點
面向對象思想是一種更符合我們思考習慣的思想,它可以將復雜的事情簡單化,並將我們從執行者變成了指揮者。
面向對象的語言中,包含了三大基本特征,即封裝、繼承和多態。
類和對象
環顧周圍,你會發現很多對象,比如桌子,椅子,同學,老師等。桌椅屬於辦公用品,師生都是人類。那么什么是類呢?什么是對象呢?
什么是類
類:是一組相關屬性和行為的集合。可以看成是一類事物的模板,使用事物的屬性特征和行為特征來描述該類事物。
現實中,描述一類事物:
屬性:就是該事物的狀態信息。
行為:就是該事物能夠做什么。
舉例:小貓。
屬性:名字、體重、年齡、顏色。
行為:走、跑、叫。
什么是對象
對象:是一類事物的具體體現。對象是類的一個實例(對象並不是找個女朋友),必然具備該類事物的屬性和行為。
現實中,一類事物的一個實例:一只小貓。
舉例:一只小貓。
屬性:tom、5kg、2 years、yellow。
行為:溜牆根走、蹦躂的跑、喵喵叫。
類與對象的關系
類是對一類事物的描述,是抽象的。
對象是一類事物的實例,是具體的。
類是對象的模板,對象是類的實體。
類的定義
事物與類的對比
現實世界的一類事物:
屬性:事物的狀態信息。
行為:事物能夠做什么。
class描述事物也是如此:
成員變量:對應事物的屬性,類的全局變量
成員方法:對應事物的行為,類中的方法
一定要記住面向對象最為核心的3大特征:
1.封裝
2.繼承
3.多態
2.1 類的定義格式
定義了一個類
class MyClass:
def __init__(self, name, age):
self.name = name
self.age = age
def run(self):
print("{name} is running".format(name=self.name))
a = MyClass("拜登",70)
a.run()
我們通過分析上面的代碼 來學習 類的定義
class Myclass:
我們分析第一行代碼,我們通過一個關鍵字 calss 來定義一類 ,起名為 MyClass,
類的命名:一般遵循大駝峰式,首字符大寫,后面每一個單詞的首字符大寫
def __init__(self, name, age):
self.name = name
self.age = age
我們看到有一個方法叫 __init__ 這是一個初始化方法,又叫類的 構造方法,當你對類進行實例化的時候,用來初始化實例的屬性,name 和 age都是實例的屬性
什么是實例 就是你創建出的對象, 就是你現在能看到的self 代表類的實例,類方法必須包含此參數,且為第一個參數
def run(self):
print("{name} is running".format(name=self.name))
我們又看到一個方法 run 這個是這個類的一個普通方法,實例化對象后能夠做的行為,並不是所有的類都有的
a = MyClass("拜登",70)
a.run()
類實例化 是什么那 就是 a = MyClass("拜登",70) 這行代碼,我們對類進行了實例化,創建了一個對象a,在實例化的過程中,我們給a對象賦予了兩個屬性就是 name = "拜登"和age=70,然后我們又用這個對象讓他調了run這個方法
self
我們可以看到類里面每一個方法都有一個self參數,它並不是默認參數,這個參數在方法被調用時是不需要傳參的,這與之前所學過函數內容相違背, self就是執行方法的對象
我們用一個id方法查看內存地址 ,來研究一下
class MyClass:
def __init__(self, name, age):
print("這是在__init__方法中",id(self))
self.name = name
self.age = age
def run(self):
print("{name} is running".format(name=self.name))
a = MyClass("拜登",70)
a.run()
print(id(a))
執行后的結果
這是在__init__方法中 2133936776024
拜登 is running
2133936776024
我們可以看到self 的地址和 a的地址一模一樣 ,那我們可以證明 self 就是a, 類對函數被定義后 任何屬於這個類的對象 都是可以使用 這個函數的,所以 誰來調用這個函數,誰就會被綁定self
對象屬性的訪問和修改
只能通過對象來進行
class MyClass:
def __init__(self, name, age):
self.name = name
self.age = age
def run(self):
print("{name} is running".format(name=self.name))
a = MyClass("拜登",70)
a.run()
print(a.age)
a.age = 20
print(a.age)
最后在段代碼 我們 輸出了對象的age屬性,然后又更改為了20 在輸出了
練習面向對象: 請編程 把下面student表數據保存 輸出每一個人的各個科目和分數,並計算每個學生的總的分數。
姓名 | 英語 | 數學 | 語文 |
---|---|---|---|
曹操 | 75 | 61 | 95 |
劉備 | 62 | 43 | 62 |
貂蟬 | 86 | 23 | 62 |
2.2 封裝
我們創建一個類,有些屬性,有些方法,我們不希望被其他人使用,因為那樣很容易就產生錯誤,那么這時,我們就需隱藏實現的細節,只對外公開我們想讓他們使用的屬性和方法,這就叫做封裝。就好比把一些東西用一個盒子封裝起來,只留一個口,內部讓你看不見。
在python里,如果屬性和方法前面是雙下划線,那么這個屬性和方法就變成了私有的屬性
class Dog:
def __init__(self,name,age):
self.__name = name
self.__age = age
def __run(self):
print("run")
a = Dog("哈士奇",15)
a.__run()
print(a.__name,a.__age)
我們看到 屬性 __name , __age 和方法 __run() 以雙下划線開頭,這樣的方法和屬性,類的對象是無法使用的,通過這種方法,就可以將不希望外部訪問的屬性和方法隱藏起來。
屬性隱藏起來,並非不讓外部使用,而是在使用時收到控制,比如年齡,如果年齡屬性定義成age,那么就會出現這樣的情況
a.age = 1000
你見過哪個動物的年齡有10000歲呢,這顯然是有問題的,但age屬性暴露出來了,別人在使用時就可能會犯這樣的錯誤,所以,為了防止這樣的錯誤發生,就可以將age屬性設置成私有的,然后提供一個set_age方法,來設置給age 並且判斷一下
class Dog:
def __init__(self,name,age):
self.__name = name
self.__age = age
def set_age(self, age):
if age > 100 or age < 1:
raise Exception("年齡范圍錯誤")
self.__age = age
def get_age(self):
return self.__age
def __run(self):
print("run")
a = Dog("哈士奇",15)
a.set_age(5)
print(a.get_age())
2.3 繼承
一個類可以繼承一個或者繼承多個父類,新建的類被稱之為派生類或者子類,被繼承的類是父類,可以稱之為基類,超類,繼承是實現代碼重用的重要方式。
單繼承
class Car(object):
def __init__(self, speed, brand):
self.speed = speed
self.brand = brand
def run(self):
print("{brand}在行駛".format(brand=self.brand))
# 燃油車
class Gasolinecar(Car):
def __init__(self, speed, brand, price):
super().__init__(speed, brand)
self.price = price
class Audi(Gasolinecar):
pass
honda = Gasolinecar(130, '本田', 13000)
honda.run()
audi_car = Audi(100, '奧迪', 10000)
audi_car.run()
print(issubclass(Audi, Gasolinecar)) # 判斷Audi是Gasolinecar的子類
print(issubclass(Gasolinecar, Car))
繼承,意味着子類將“擁有”父類的方法和屬性,同時可以新增子類的屬性和方法。在Gasolinecar類里,我沒有寫run方法,但是Gasolinecar的父類定義了run方法,因此,Gasolinecar也有這個方法,因此這個類的對象honda可以使用run方法。
Audi類沒有定義任何方法,但是它繼承了Gasolinecar,因此,Gasolinecar有的屬性和方法,它都擁有,這里就包括了__init__方法。
super()可以用來調用父類的方法,Gasolinecar多傳了一個price屬性,其父類的__init__方法里有兩個參數,因此,可以先調用父類的__init__方法初始化speed, brand,然后在初始化price
多繼承
大部分面向對象的編程語言(除了C++)都只支持單繼承,而不支持多繼承,因為多繼承不僅增加編程復雜度,而且容易導致莫名其妙的錯誤。
Python雖然支持多繼承,但不推薦使用多繼承,推薦使用單繼承,這樣可以保證編程思路更清晰,也可以避免不必要的麻煩。
當以一個子類有多個直接父類時,該子類會繼承得到所有父類的方法,但是如果其中有多個父類包含同名方法會發生什么?此時排在前面的父類中的方法會“遮蔽”后面父類中的方法。
class Person():
def person_walk(self):
print("走路")
class Wolf():
def wolf_run(self):
print("奔跑")
class WolfMan(Person, Wolf):
def __init__(self):
pass
a = WolfMan()
a.person_walk()
a.wolf_run()
2.4 多態
面向對象的多態依賴於繼承, 因為繼承,使得子類擁有了父類的方法, 子類的方法與父類方法重名時是重寫, 同一類事物,有多重形態, 這就是面向對象概念里的多態,多態使得不同的子類對象調用相同的 類方法,產生不同的執行結果,可以增加代碼的外部調用靈活度。
class Base():
def print(self):
print("base")
class A(Base):
def print(self):
print("A")
a = A()
a.print()
父類和子類都有print方法,那么子類A的對象a調用print方法時,調用的是誰的print方法呢?
答案是子類的print方法,如果A類沒有定義print方法,那么a.print()調用的是父類的print方法,但是A類定義了print方法,這種情況稱之為重寫,A類重寫了父類的print方法。
繼承時,子類“擁有”父類的方法和屬性,但這種擁有不是真實意義上的擁有
class Base():
def print(self):
print("base")
class A(Base):
pass
print(id(Base.print))
print(id(A.print))
Base.print 和 A.print 的內存地址是相同的,這說明他們是同一個方法。執行A.print時,python會尋找print方法,它會先從A類的定義中尋找,沒有找到,然后去A的父類里尋找print方法,如果還是找不到,就繼續向上尋找。
這便是子類擁有父類屬性和方法的真相
多態表現:同一類事物,有多重形態
class Animal:
def run(self):
raise NotImplementedError
class People(Animal):
def run(self):
print("人在行走")
class Pig(Animal):
def run(self):
print("豬在跑")
p1 = People()
p1.run()
p2 = Pig()
p2.run()
People和 Pig 都繼承了Animal,都是動物,是同一類事物,他們都有run方法,但是最終的運行結果卻不一樣,這就是多態,同一類事物有多種形態。