類的概念
類(class)抽象的概念,比如說人類、鳥類、水果、是一個總的稱呼,沒有具體到某個物體;
對象(object,指具體實例,instance);
類定義的語法:
class 類名: 執行語句 類變量 類方法
類最重要的兩部分就是類變量和類方法,類成員之間的可以相互調用。
程序可以在類中給新變量賦值就是增加類變量,可以通過del語句刪除已有類的變量。
在__init__構造函數(構造函數后面會說到)里面的是實例變量,程序可以任何位置(類里面或者類外面)增加實例變量,刪除則用del語句。
在實例方法中有一個特別的方法 :__init__ ,這個方法被稱為構造方法 。 構造方法用於構造該類的對象, Python 通過調用構造方法返回該類的對象 。 python提供一個功能就是:若開發者沒有定義構造函數,python會自動提供一個只包含self參數默認的構造方法。
class Bird: '這是學習python的第一個類' eyes = "two" def __init__(self, color,feet): '為python對象增 ' self.color = color self.feet = feet def call(self,cd): print("This bird:",cd)
上面的 Bird類定義了 一個構造方法,該構造方法只是方法名比較特殊:__init__ ,該方法的第一個參數同樣是 self,被綁定到構造方法初始化的對象 。和函數定義文檔類似,類文檔用字符串即可。該文檔同樣被放在類聲明之后、類體之前。
下面是調用類的構造方法創建對象。第二行是調用實例方法。
xique = Bird("green","two") xique.call("gezhi")
#打印實例變量 print(xique.color,xique.feet) #訪問實例變量,對實例變量賦值 xique.color = "brown" #增加實例變量 xique.skin = 'yellow' #打印編輯后的實例變量 print(xique.color,xique.feet,xique.skin) #調用實例方法call(),第一個參數仍是self,self代表實例本身, #是自動綁定的,不需要再次輸入,因此調用實例方法,只需再輸入一個參數即可 xique.call("zhizhizhizhi")
同一個類的多個對象具有相同的特征,類用定義多個對象的相同特征。類不是一個具體的實體,對象才是一個具體的實體
class Person: '這是學習python的第二個類' hair = 'black' def __init__(self,name = "Will",age = 8): '下面對Person對象增加兩個實例變量' self.name = name self.age = age '定義一個say()方法' def say(self,content): print(content)
給對象增加一個實例變量
# 增加一個skills實例變量 p.skills = ['programming','writing'] print(p.skills) #刪除p對象的name實例變量,而不是刪除類中變量,新建一個對象,name實例變量還是構造函數默認的。 del p.name # print(p.name) 會報錯
給對象增加類方法
1 #動態增加方法 2 def info(self): 3 print("-----info函數-----",self) 4 print("動態增加方法") 5 return None 6 p.foo = info # foo實例方法名,info是我們在外面定義的方法,當然二者名字可以相同 7 8 '''方法增加,只用了變量名,后面並沒有加括號方法動態增加,第一個參數並沒有綁定給調 9 用類的對象,所以我們需要手動把第一個參數輸入,第一個參數是實例本身,可用實例名代表''' 10 print(p.foo(p)) 11 12 #如果想自動綁定,調用方法時,不想輸入self參數,可以用type模塊包裝 13 #重新寫一個函數 14 def message(self,comment): 15 print("我的個人信息是 %s " % comment) 16 return None 17 18 p2 = Person() 19 p2.message = message 20 21 from types import MethodType 22 p2.message = MethodType(p2.message,per) 23 24 #綜上,整隊某個對象動態增加的類變量和類方法,只能應用此對象,其他對象需重新增加
實例方法調用另一個實例方法
1 class Dog: 2 skin = "white" 3 def __init__(self,name = 'Aled'): 4 self.name = name 5 def jump(self): 6 print("執行jump方法") 7 def run(self): 8 self.jump() #此處不能寫成jump()必須有self,通過調用實例對象的方法 9 print("調用run方法") 10 11 g = Dog() 12 g.run() #通過run()方法,調用了jump()方法
類中self參數就是實例本身,可以自動綁定。
1 #在構造方法中,self參數(第一個參數)表示該構造函數正在初始化的對象 2 class InConstructor: 3 def __init__(self): 4 #在構造方法中定義一個foo變量(局部變量),臨時的 5 foo = 1 #這是一個局部變量,不是實例變量,外界無法訪問 6 print(type(foo)) 7 print(foo) 8 #把構造方法正在初始化的foo變量編程實例變量,並且重新復制 9 self.foo = 5 10 p = InConstructor() 11 print(p.foo) 12 13 #自動綁定的self參數不依賴具體的調用方式,不管是以方法調用還是函數調用,self參數用一樣可以自動綁定 14 class User: 15 def test(self): 16 print("self參數:",self) 17 u = User() 18 u.test() #self參數: <__main__.User object at 0x013ADCF0> User對象在內存... 19 20 #將User對象的test方法賦值給foo變量 21 foo = u.test #只需將名字賦值,不要加括號 22 #通過foo變量調用test()方法 23 foo() #效果 一樣,因為foo也是指向u.test 24 #self參數可以作為實例方法返回值
self可以作為變量來訪問,或者作為實例方法的返回值
當 self 參數作為對象的默認引用時,程序可以像訪問普通變量一樣來訪 問這個self 參數,甚至可以把 self 參數當成實例方法的返回值 。 看下面程序 。
1 class ReturnSelf: 2 def grow(self): 3 if hasattr(self,'age'): 4 self.age += 1 5 print("有age變量") 6 else: 7 self.age = 22 8 print("無age變量") 9 return self #返回調用該方法的實例對象 10 def isnotexist(self): 11 '實例變量不一定非要在構造方法中定義,也可以在類外,或者類里的實例方法中定義' 12 print(self.age) 13 14 rs = ReturnSelf() 15 print(rs.grow().age) # 22 16 rs.isnotexist() #22 17 rs.grow().grow().isnotexist() #返回值是self實例本身,然后可以多次調用實例方法或變量
類也能調用實例方法
類名.method(參數)
類名.變量名
1 #類調用實例方法 類名.method 2 3 #前面都是通過創建對象,通過對象調用實例方法 4 #類很大程度上類似命名空間和定義變量和定義函數沒有什么不同 5 6 #定義全局空間的foo函數 7 def foo(): 8 print("全局空間的foo方法") 9 #定義全局空間bar變量 10 bar = 20 11 class Bird: 12 #定義Bird空間的foo函數 13 def foo(self): 14 print("Bird空間的foo方法") 15 #定義Bird空間的bar變量 16 bar = 200 17 #調用全局空間的函數和變量 18 foo() 19 print(bar) 20 #調用Bird空間的函數和變量 21 # Bird.foo() 這樣會報錯。缺少self參數 22 print(Bird.bar)
直接類名+方法會報錯,方法里面必須手動添加參數
1 class User: 2 def walk(self): 3 print(self,'正在慢慢走') 4 #通過類調用實例方法 5 # User.walk() 這樣報錯 TypeError
u = User()
User.walk(u) #顯式方法的第一個參數綁定參數值
這樣的調用效果等同於u.walk()
實際上,當通過 User 類調用 walk()實例方法時, Python只要求手動為第一個參數綁定參數值,並不要求必須綁定 User 對象,因此也可使用如下代碼進行調用 。
1 User.walk("任意輸入都有可以,不一定非得是self參數")
如果傳入兩個參數呢
1 class Peaple: 2 def walk(self,name = "Will"): 3 print(name,"坐着",self) 4 #和普通函數調用沒啥區別,就當self是一個變量名,參數隨便輸入 5 Peaple.walk("吃飯") 6 Peaple.walk("吃飯","Alex")
類方法和靜態方法
Python其實可以支持類方法定義,區別前面的實例方法,同時只是靜態方法定義,類方法和靜態方法類似,都可以通過類調用(同時也支持對象調用)區別在於類方法第一個參數為cls,會自動綁定到類,而靜態方法不會自動綁定到類
class Bird: #使用classmethod是類方法 @classmethod def fly(cls): print('類方法fly:',cls) #使用staticmethod修飾的是靜態方法 @staticmethod def info(p): print('靜態方法info:',p) #調用類方法,類會自動綁定到第一個參數cls Bird.fly() #調用靜態方法,不會自動綁定,意思是第一個參數必須手動輸入 Bird.info("真麻煩") #創建Bird對象 b = Bird() #使用對象調用fly類方法,其實還是使用類調用 #因此第一個參數 依然自動綁定到Bird類 b.fly() #使用對象調用info靜態方法,其實還是使用類調用 b.info('fkit')
裝飾器
1 #裝飾器 2 #funA裝飾funB,funB作為參數引入到funA中,同時funA返回值就是修飾后的返回值 3 def funA(funB): 4 print("A") 5 funB() 6 return "最終修飾結果" 7 def funB(): 8 print("B") 9 funB() 10 11 @funA 12 def funB(): 13 print("B") 14 print(type(funB)) #這里是函數名,不是函數調用 15 print(funB) #裝飾函數修飾后,通過被修飾函數名查看返回值
這個函數裝飾器導致被修飾的函數變成了字符串,那么函數裝飾器有什么用?別忘記了,被修飾的函數總是被替換成@符號所引用的函數的返回值,因此被修飾的函數會變成什么,完全由於@符號所引用的函數的返回值決定一一如果@符號所引用的函數的返回值是函數,那么被修飾的函數在替換之后還是函數 。
1 def foo(fn): 2 #定義一個嵌套函數 3 def bar(*args): 4 print("===1===",args) 5 n = args[0] 6 print("===2===",n * (n -1)) 7 #查看傳遞給foo函數的fn函數 8 print(fn.__name__) 9 fn(n * (n -1)) 10 print("*" * 15) 11 return fn(n * (n -1)) 12 return bar 13 14 @foo 15 def my_test(a): 16 print("===my_test函數===",a) 17 print(my_test) #返回值是bar函數<function foo.<locals>.bar at 0x032C3D20> 18 my_test(10) #意思就是my_test函數被bar函數替換,調用my_test函數就是調用bar函數 19 my_test(6,5)
上面裝飾結果相當於foo(my_test),my_test函數會被替換(裝飾)成foo(my_test)的返回值,他的返回值是bar函數,因此funB(my_test函數)就是bar函數
通過修飾函數,也可以在修飾函數中添加權限檢查,邏輯驗證,異常處理
下面將示范通過函數修飾器給函數添加權限檢查的功能:
1 def auth(fn): 2 def auth_fn(*args): 3 print("----模擬執行權限檢查-----") 4 #回調被修飾的目標函數 5 fn(*args) #作為函數參數時前面必須有*,如果在函數里面的參數則為args 6 return auth_fn 7 @auth 8 def bedecorated(a,b): 9 print("執行bedecotated函數,參數a:%s,參數b:%s" % (a, b)) 10 11 #調用bedecorated函數,其實就是調用修飾后返回的auth_fn函數 12 13 bedecorated(4,5)
類命名空間
1 #類命名空間 2 global_fn = lambda p: print("執行lambda表達式,P參數:",p) 3 class Category: 4 cate_fn = lambda p:print("執行lambda表達式,p參數",p) 5 #調用全局的global_fn,為參數p傳入參數值 6 global_fn('fkit') 7 c = Category() 8 c.cate_fn()
綜上,在全局命名空間調用,類命名空間的lambda函數也可以調用,通過類對象調用lambda函數相當於調用類方法,python同樣會自動為該剛方法綁定第一個參數值(self),也就是實例對象本身
成員變量
1 class Address: 2 detail = '廣州' 3 post_code = '2019723' 4 def info(self): 5 #嘗試直接訪問類變量 6 #print(detail) 報錯 7 print(Address.detail) 8 print(Address.post_code) 9 Address.info(32) #通過類調用方法,需要手動輸入第一個參數 10 #通過類來訪問Address類的類變量 11 print(Address.detail) 12 addr = Address() 13 addr.info() 14 #修改Address的類變量 15 Address.detail = "佛山" 16 Address.post_code = '2018723' 17 addr.info()
在類命名空間內定義的變量就屬於類變量 , Python 可以使用類來讀取、修改類變量。 對於類變量而言,它們就是屬於在類命名空間內定義的變量 ,因此程序不能直接訪問這些變量,程序必須使用類名來調用類變量。不管是在全局范圍內還是函數內訪問這些類變量,都必須使用類名進行訪問。
1 #python也可以使用對象訪問類變量,建議使用類訪問類變量 2 class Record: 3 #定義兩個類變量 4 item = '鼠標' 5 date = '2019-07-23' 6 def info(self): 7 print('info方法:',self.item) 8 print('info方法:',self.date) 9 rc = Record() 10 print(rc.item) 11 print(rc.date) 12 rc.info() 13 14 #修改Record類的兩個類變量 15 Record.item = "鍵盤" 16 Record.date = '2020-01-01' 17 rc.info()
Python 允許通過對象訪問類變量 ,但如果程序通過對象嘗試對類變量賦值,此時性質就變了一Python 是動態語言,賦值語句往往意味着定義新變量。因此,如果程序通過對象對類變量賦值,其實不是對“類變量賦值”,而是定義新的實例變量 。例如如下程序 。
1 class Inventory: 2 #定義兩個變量 3 quantity = 2000 4 item = '鼠標' 5 #定義實例方法 6 def change(self,item,quantity): 7 self.item = item 8 self.quantity = quantity 9 #創建Inventory對象 10 iv = Inventory() 11 iv.change('顯示器',500) 12 #訪問iv的item和quantity實例變量 13 print(iv.item) #顯示器 14 print(iv.quantity) #500 15 16 #訪問Inventotry的item和quantity類變量 17 print(Inventory.item) #鼠標 18 print(Inventory.quantity) #2000
通過類修改類變量的值,實例變量不會受到影響
1 Inventory.item = '類變量item' 2 Inventory.quantity = '類變量quantity' 3 #訪問iv對象的實例變量item和quantity 4 print(iv.item) #顯示器 5 print(iv.quantity) #500
同樣修改實例變量的值,這種修改也不影響類變量或者其他對象的實例變量。
1 iv.item = 'iv實例變量item' 2 iv.quantity = 'iv實例變量quantity' 3 print(Inventory.item) 4 print(Inventory.quantity) 5 iv2 = Inventory() 6 print(iv2.item) #類變量item,前面修改了類變量,此處沒有調用change方法創建新的實例變量,所以這樣調用的是類變量 7 #新對象創建實例變量item ,quantity 8 iv2.change('音響',600) 9 print(iv2.item) #音響,這次就不是類變量了,因為創建的實例變量,python內在機制,實例可以調用類變量,其實還是通過類訪問的類變量
使用property函數定義屬性
1 class Retangle: 2 def __init__(self,width,height): 3 self.width = width 4 self.height = height 5 def setsize(self,size_): 6 self.width,self.height = size_ 7 def getsize(self): 8 return self.width,self.height 9 def delsize(self): 10 self.width,self.height = 0,0 11 size = property(getsize,setsize,delsize,'描述事物的屬性') 12 #訪問size的說明文檔 13 print(Retangle.size.__doc__) 14 #通過內置的help函數查看說明文檔 15 help(Retangle.size) 16 rect = Retangle(4,3) 17 print(rect.size) 18 rect.setsize((7,9)) 19 print(rect.size) 20 rect.size = 9,7 21 print(rect.width,rect.height) 22 del rect.size 23 print(rect.width,rect.height) 24 25 class Retangle: 26 def __init__(self,width,height,length): 27 self.width = width 28 self.height = height 29 self.length = length 30 def getsize(self): 31 return self.width,self.height,self.length 32 def setsize(self,size): 33 self.width,self.height,self.length = size 34 def delsize(self): 35 self.width,self.height,self.length = 0,0,0 36 size = property(getsize,setsize,delsize,'描述事物的屬性') 37 rec = Retangle(88,56,72) 38 print(rec.size) 39 rec.size = 85,45,23 40 print(rec.size) 41 print(rec.width,rec.length,rec.height) 42 del rec.size 43 print(rec.size) 44 45 46 class User: 47 def __init__(self,first,last): 48 self.first = first 49 self.last = last 50 def getfullname(self): 51 return self.first + ',' + self.last 52 def setfullname(self,fullname): 53 name = fullname.split(',') 54 self.first = name[0] 55 self.last = name[1] 56 fullname = property(getfullname,setfullname) 57 u = User('孫','悟空') 58 print(u.fullname) 59 print(u.first,u.last) 60 u.fullname = 'smith,jackey' 61 print(u.fullname) 62 print(u.first,u.last)
隱藏和封裝
1 class User: 2 def __hide(self): 3 print("示范隱藏的方法") 4 def getname(self): 5 return self.__name 6 7 def setname(self,name): 8 if len(name) < 3 or len(name) > 8: 9 raise ValueError("輸入長度在3-8之間的") 10 self.__name = name 11 name = property(getname,setname) #參數必須先 12 13 def getage(self): 14 return self.__age #可以和property的屬性區分開來 15 def setage(self,age): 16 if age < 11 or age > 70: 17 raise ValueError("年齡在11到70歲之間") 18 self.__age = age 19 age = property(getage,setage) 20 21 u = User() 22 u.name = "fuck" 23 print(u.name) 24 print(u.getname()) 25 u.age = 56 26 print(u.getage())
set和get函數方法順序無關,但是property函數內參數,必須是先讀取后寫入的順序。
繼承
#繼承 class Fruit: def info(self,weight): #如果后面有參數,函數內就必須初始化,如果沒參數,后面初始化不同寫 self.weight = weight print("this fruit weigh %s" % (self.weight)) class Food: def taste(self): print("不同食物,口味不同") class Apple(Fruit,Food): pass a = Apple() a.info(25) a.taste()
1 #當父類方法名字重合,選擇第一個 2 class Item: 3 def info(self): 4 print("這是一個商品") 5 class Product: 6 def info(self): 7 print('這是一個工藝') 8 9 class Mouse(Product,Item): #父類順序,如果有相同的方法,先調用一個參數 10 pass 11 12 m = Mouse() 13 m.info()
父類方法重寫
1 class Bird: 2 def fly(self): 3 print("我在天空里自由自在地飛翔") 4 class Ostrich(Bird): 5 #重寫Bird類的fly()方法 6 def fly(self): 7 print("我只能在地上奔跑") 8 #創建Ostrich對象 9 os = Ostrich() 10 #執行Ostrich對象的fly(),將輸出“我只能在地上奔跑” 11 os.fly()
子類重寫父類方法,那如何調用父類被重寫的方法。
1 class BaseClass: 2 def foo(self): 3 print("父類中定義的foo方法") 4 class SubClass(BaseClass): 5 def foo(self): 6 print("子類中定義的foo方法") 7 def bar(self): 8 print("執行bar方法") 9 self.foo() 10 #通過類名調用父類被重寫的方法 11 BaseClass.foo(self) 12 sc = SubClass() 13 sc.bar()
使用super函數調用父類構造方法
1 class Employee: 2 def __init__(self,salary): 3 self.salary = salary 4 def work(self): 5 print("普通員工正在寫代碼,工資是:",self.salary) 6 class Customer: 7 def __init__(self,favorite,address): 8 self.favorite = favorite 9 self.address = address 10 def info(self): 11 print("我是一個顧客,我的愛好是:%s,地址是%s" % (self.favorite,self.address)) 12 #manager 繼承了Employee,Customer 13 class Manager(Customer): 14 def __init__(self,favorite,address): 15 print("manager的構造方法") 16 #通過super函數調用父類的構造方法 17 super(Manager,self).__init__(favorite,address) 18 19 20 21 m = Manager("IT","beijing") 22 23 m.info() 24 25 class Fooparent: 26 def __init__(self): 27 self.parents = 'I\'m the parent' # 5 28 print("Parent11") # 1 29 def bar(self,message): 30 print("%s from Parent"% message) # 3 31 class FooChild(Fooparent): 32 def __init__(self): 33 super(FooChild,self).__init__() 34 print("child22") # 2 35 def bar(self,message): 36 super(FooChild,self).bar(message) 37 print("Child bar function") #4 38 print(self.parents) 39 if __name__ == "__main__": 40 foochild = FooChild() #創建一個子類對象 41 foochild.bar("helloworld") 42 43 #從上面可見先執行父類構造函數的打印函數,在執行子類打印函數,然后根據調用執行父類函數
python動態屬性與slots
class Cat: def __init__(self,name): self.name = name def walk_func(self): print("%s慢慢走過每一片草地"% self.name) d1 = Cat('Marry') d2 = Cat('Kitty') Cat.walk = walk_func d1.walk() d2.walk() #__slot__限制動態添加的屬性和方法 class Dog: __slots__ = ('walk','age','name') def __init__(self,name): self.name = name def test(): print("預先定義好的test方法") d = Dog('Snoogy') d.age = 5 print(d.age) # d.weight = 24報錯 Dog.walk = walk_func d.walk() Dog.bar = lambda self:print("abc") d.bar()
type函數定義類
def fn(self): print("fn函數") #使用type函數定義Dog類 Dog = type('Dog',(object,),dict(walk = fn,age = 6)) #創建dog對象 d = Dog() #分別查看d.dog的類型 print(type(d)) print(type(Dog)) print(type(Dog())) print(type(d.walk)) d.walk() print(d.age)