Python語法基礎-函式和類
[TOC]
1. 函式的定義
python中函式有兩種:
- python自帶的函式
- 使用者定義函式
返回多個值
原來返回值是一個tuple!但是,在語法上,返回一個tuple可以省略括號,而多個變數可以同時接收一個tuple,按位置賦給對應的值,所以,Python的函式返回多值其實就是返回一個tuple,但寫起來更方便
1.1函式的引數
引數 | 含義 | 輸入 |
---|---|---|
位置引數 | def power(x,n) |
實際引數 |
預設引數 | def power(x,n=2) |
實際+預設引數(需要改變時) |
可變引數 | def power(*args) |
傳入任意個引數,內部組裝成tuple |
關鍵字引數 | def person(name, age, **kw) |
傳入帶引數名的引數,組裝成dict |
命名關鍵字引數 | def person(name,age,*, city, job) |
限制關鍵字引數的名字(必須傳入引數名) |
- 順序: 必選引數<---預設引數<---可變引數<---命名關鍵字引數<---關鍵字引數
# 關鍵字引數 def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw) person('hao', 20) # name: Michael age: 30 other: {} person('hao', 20, gener = 'M', job = 'Engineer') # name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'} extra = {'city': 'Beijing', 'job': 'Engineer'} person('Jack', 24, **extra) # 命名關鍵字引數 def person(name, age, *, city='Beijing', job): print(name, age, city, job) person('Jack', 24, job = '123') person('Jack', 24, city = 'Beijing', job = 'Engineer') # Combination # 可變 + 關鍵字引數 def f1(a, b, c=0, *args, **kw): print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw) f1(1, 2, 3, 'a', 'b') # a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99} f1(1, 2, 3, 'a', 'b', x=99) # a = 1 b = 2 c = 0 d = 99 kw = {'ext': None} # 預設引數 + 命名關鍵字引數 + 關鍵字引數 def f2(a, b, c=0, *, d, **kw): print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw) f2(1, 2, d=99, ext=None) # a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
2. 面向物件程式設計
- 面向過程: 根據業務邏輯從上到下寫程式碼
- 面向物件: 對資料與函式繫結到一起,進行封裝,這樣更快速的開發過程,減少程式碼重複使用
資料封裝、繼承和多型是面向物件的三大特點
2.1. 類(抽象概念)和物件(具體概念)
玩具模具(類)-》 火車玩具,飛機玩具..(物件)
類的組成結構
- 類名:狗
- 類的屬性:一組資料(狗的顏色,性別...)
- 類的方法: 執行進行操作的方法行為(行為,會叫,會咬人...)-> 用函式設計
類的組成 | 特性 | 例子 | 例子 |
---|---|---|---|
類名 | 名稱 | 狗 | 人 |
類的屬性 | 一組資料 | 狗的顏色,性別 | 身高,年齡 |
類的方法 | 執行進行操作的方法行為 | 行為,會叫,會咬人 | 跑,打架 |
- 類是建立例項的模板,而例項則是一個一個具體的物件,各個例項擁有的資料都互相獨立,互不影響;
- 方法就是與例項繫結的函式,和普通函式不同,方法可以直接訪問例項的資料;
- 通過在例項上呼叫方法,我們就直接操作了物件內部的資料,但無需知道方法內部的實現細節。
- 繼承可以把父類的所有功能都直接拿過來,這樣就不必重零做起,子類只需要新增自己特有的方法,也可以把父類不適合的方法覆蓋重寫。
(1). 定義類
# 定義類
class Dog(object):
# 定義初始化方法
def __init__(self,weight,color):
"""
self: python會把物件的地址自動傳給self,不要自己傳
weight, color: 接收外部屬性
"""
# 定義屬性
self.weight = weight
self.color = color
# 魔法方法: 當只打印Dog物件的時候,就可以列印這裡的東西
def __str__(self):
msg = "dog weight" + self.weight + "color" + self.color
return "哈哈哈"
def getweight(self):
return self.weight
def getcolor(self):
return self.color
def setweight(self):
self.weight = 100
def setcolor(self):
self.color = "green"
# 定義方法
def bark(self):
"""
self: python直譯器預設把呼叫該class的物件地址給它
"""
print("666")
def run(self):
print("777")
# 建立物件
huskey = Dog(5, 'Black') # 建立一個哈士奇
keji = Dog(10, 'Green')
huskey.bark() # 哈士奇叫
huskey.run() # 哈士奇跑
huskey.weight = 100 # 哈士奇屬性
huskey.color = 'green'
self
表示自己,表示呼叫類的物件本身- python中類似
__***__
的方法,是魔法方法,有特殊用途
(2). 類的資料封裝
面向物件程式設計的一個重要特點就是資料封裝,比如在上面例子中,Dog
類的每個例項都有weight和color,我們可以直接在Dog
類的內部定義訪問資料的函式,這樣,就把“資料”給封裝起來了。這些封裝資料的函式是和Dog
類本身是關聯起來的,我們稱之為類的方法:
class Dog(object):
def __init__(self, weight, color):
self.weight = weight
self.color = color
def print_dog(self):
print('%s: %s' % (self.weight, self.color))
我們從外部看Dog
類,就只需要知道,建立例項需要給出weight
和color
,而如何列印,都是在Dog
類的內部定義的,這些資料和邏輯被“封裝”起來了,呼叫很容易,但卻不用知道內部實現的細節。
(3). 訪問限制
- 從前面
Dog
類的定義來看,外部程式碼還是可以自由地修改一個例項的weight
、color
屬性,我們可以設定內部屬性不被外部訪問:
class Dog(object):
# 加下劃線
def __init__(self, weight, color):
self.__weight = weight
self.__color = color
def print_dog(self):
print('%s: %s' % (self.__weight, __self.color))
huskey = Dog(60, 'green')
print(huskey.__name) # 無法再訪問屬性,會出錯
- 但是如果外部程式碼需要獲取
weight
、color
屬性呢?-> 新增get_weight
、get_color
方法
class Dog(object):
...
def get_weight(self):
return self.__weight
def get_color(self):
return self.__color
- 如果又要允許外部程式碼修改
weight
、color
怎麼辦?->新增set_weight
、set_color
方法
class Dog(object):
...
def set_weight(self,weight):
self.__weight = weight
def set_color(self):
self.__color = color
- 為什麼要費這麼大週摺呢?之前不是可以直接husky.weight=10直接改嗎
因為在方法中,可以對引數做檢查,避免傳入無效的引數:
class Dog(object):
...
def set_weight(self,weight):
if 0<=weight<=100:
self.__weight = weight
else:
raise ValueError('Bad weight')
(4). 獲取物件資訊
# type():判斷物件型別
type([1,2,3])
type('abc') == str
type(huskey)
# isinstance(): 一個物件是否屬於某種型別
isinstance(h, husky)
isinstance(h, cat)
# dir() 獲取一個物件的所有屬性和方法
dir('ABC')
dir('huskey')
2.2. 繼承和多型
繼承就是:父類和子類之間的互動關係
(1)為什麼要用繼承?
最大的好處是子類獲得了父類的全部功能
class Animal(object):
def run(self):
print('Animal is running...')
# Dog繼承父類Animal,並且自動擁有run屬性,可以在Dog物件中直接呼叫
class Dog(Animal):
pass
# 子類可以自己增加一些方法(eat),同時也可以重寫父類的方法(run)---》這就是多型
class Cat(Animal):
def run(self):
print('Dog is running...')
def eat(self):
print('Eating meat...')
(2)什麼是多型?
在繼承關係中,如果一個例項的資料型別是某個子類,那它的資料型別也可以被看做是父類。但是反過來就不行了
C是dog,C也是animal
D是animal,但你不能說D也是Dog
# 首先建立物件並檢視物件型別
a = list() # a是list型別
isinstance(a, list) # true
b = Animal() # b是Animal型別
isinstance(b, Animal) # true
c = Dog() # c是Dog型別
isinstance(c, Dog) # true
# C既是Dog也是Animal
isinstance(c, Animal) # true
# 反過來就錯了
isinstance(b, Dog) # false
(3)那麼為題來了。這樣設定成多型有什麼好處呢?
任何依賴父類作為引數的函式或者方法都可以不加修改地正常執行,原因就在於多型。
當我們需要傳入
Dog
、Cat
、Tortoise
……時,我們只需要接收Animal
型別就可以了,因為Dog、Cat、Tortoise
……都是Animal
型別,然後,按照Animal
型別進行操作即可。由於Animal
型別有run()
方法,因此,傳入的任意型別,只要是Animal
類或者子類,就會自動呼叫實際型別的run()
方法,這就是多型的意思對於一個變數,我們只需要知道它是
Animal
型別,無需確切地知道它的子型別,就可以放心地呼叫run()
方法,而具體呼叫的run()
方法是作用在Animal、Dog、Cat
還是Tortoise
物件上,由執行時該物件的確切型別決定
def run_twice(animal):
animal.run()
run_twice(Animal()) # 呼叫animal的run
run_twice(Dog()) # 呼叫dog的run
run_twice(Cat()) # 呼叫cat的run
3. 面向物件高階程式設計
(1). __slots__
的使用
動態繫結允許我們在程式執行的過程中動態給class加上功能, 但是,如果我們想要限制例項的屬性怎麼辦?比如,只允許對Student
例項新增name
和age
屬性。
在class
中定義一個特殊的__slots__
變數,來限制該class
例項能新增的屬性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定義允許繫結的屬性名稱
s = Student() # 建立新的例項
s.name = 'Michael' # 繫結屬性'name'
s.score = 99 # 繫結屬性'score'-->出錯
__slots__定義的屬性僅對當前類例項起作用,對繼承的子類是不起作用的
(2). @property
的使用
在繫結屬性時,如果我們直接把屬性暴露出去,雖然寫起來很簡單,但是,沒辦法檢查引數,導致可以把成績隨便改,解決方案:
- 通過
set_score
設定成績,在通過get_score
獲取成績,在set_score
中檢查引數(已經講過) - 使用內建的
@property
裝飾器,既可以檢查引數,又可以類似屬性那樣訪問類的變數
@property
廣泛應用在類的定義中,可以讓呼叫者寫出簡短的程式碼,同時保證對引數進行必要的檢查,這樣,程式執行時就減少了出錯的可能性。
class Student(object):
# @property將get_Score方法變成屬性
@property
def score(self):
return self._score
# @score.setter將set_score方法變成屬性賦值
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
s = Student()
s.score = 60 # OK,實際轉化為s.set_score(60)
s.score # OK,實際轉化為s.get_score()
s.score = 9999 # error
(3). 多重繼承MixLn的設計
MixIn的目的就是給一個類增加多個功能,這樣,在設計類的時候,我們優先考慮通過多重繼承來組合多個MixIn的功能,而不是設計多層次的複雜的繼承關係
class Animal(object):
pass
class Bird(Animal):
pass
class Parrot(Bird):
pass
# 此時我們需要加入額外的功能fly
# 先定義好fly的類
class Flyable(object):
def fly(self):
print('Flying...')
# 同時繼承兩個類
class Parrot(Bird, Flyable):
pass
(4). 定製個性化類
1): __str__
和 __repr__
讓列印的object更漂亮:
__str__
: 用print列印__repr__
: 直接輸入物件名
class Student(object):
# 加下劃線
def __init__(self, name):
self.__name = name
def __str__(self):
msg = 'Student name is' + self.__name
return msg
__repr__ = __str__
s = Student()
print(s) # 打印出 Student name is.....
s # 效果跟上面的一樣
2): __iter__
將類定義成類似list或者tuple那種,可以用於for迴圈的作用
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化兩個計數器a,b
def __iter__(self):
return self # 例項本身就是迭代物件,故返回自己
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 計算下一個值
if self.a > 100000: # 退出迴圈的條件
raise StopIteration()
return self.a # 返回下一個值
for i in Fib():
print(n) # 1,1,2,3,5,.......,75025
# 因為__getitem__的作用,可以index某個值
f = Fib()
f[0] # 1
# 也可以切片
print(f[0:5]) # 1,1,2,3,5
3): __call__
直接在例項本身上呼叫的一種構造方法
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
s = Student('Michael')
s() # My name is Michael.
- 例項進行直接呼叫就好比對一個函式進行呼叫一樣,所以你完全可以把物件看成函式,把函式看成物件
4) 列舉類Enum
定義常量
from enum import Enum, unique
# unique保證沒有重複值
@unique
class Weekday(Enum):
Sun = 0 # Sun的value被設定為0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
# 訪問列舉類
print(Weekday.Mon) # Weekday.Mon
print(Weekday.Mon.value) # 1
print(Weekday(1))) # # Weekday.Mon
5) type()
動態建立類Class
type()
函式既可以返回一個物件的型別,又可以創建出新的型別
type()和Class()的功能是一樣的
要建立一個class物件,type()函式依次傳入3個引數:
- class的名稱;
- 繼承的父類集合,注意Python支援多重繼承,如果只有一個父類,別忘了tuple的單元素寫法;
- class的方法名稱與函式繫結,這裡我們把函式fn繫結到方法名hello上。
def fn(self, name='world'): # 先定義函式
print("hello", name)
Hello = type('Hello', (object,), dict(hello=fn)) # 建立Hello class
graph TD; A-->B; A-->C; B-->D; C-->D;