python學習第19天----內建函式_反射_雙下方法_單例類
1.內建函式補充
1)isinstance(物件,類)
作用:用於判斷物件屬不屬於這個型別,繼承的類也算(即一個子類的物件,通過isinstance判斷它是否屬於父類時,返回的也是True)
#判斷物件所屬型別,包括繼承關係
class A: pass class B(A): pass b = B() print(isinstance(b,B)) print(isinstance(b,A)) 輸出: True TrueView Code
備註:若【type(子類的物件) is 類】方式判斷一個物件是否屬於這個類時,只有當前類才返回True,若是父類則返回False
class A: passView Codeclass B(A): pass b = B() print(isinstance(b,B)) print(isinstance(b,A)) print(type(b) is A) #父類則返回False 輸出: True True False True
總結:type()不包含繼承關係,只管一層;isinstance,包含所有的繼承關係
補充:【==】用來判斷記憶體地址是否相等,【i】s用來判斷記憶體地址是否相同,所以is要求更 苛刻,不僅要求值相等,要要求記憶體地址相等
2)issunclass(A類,B類)
作用:判斷類於類之間的繼承關係;即A類是不是B類的子類
class A(): pass class B(A): pass print(issubclass(A,B)) print(issubclass(B,A)) #B是A的子類,返回True 輸出: False TrueView Code
2.反射
1)什麼是反射:用字串資料型別的變數名來訪問這個變數的值
2)反射的方法:getattr() hasattr() setattr() delattr()
2)反射的使用
①使用類去反射
格式:
類名稱空間.XXX == getattr(名稱空間,'XXX')
#引:不使用反射的情況下,編寫一個學生選課系統
class Student:View Codedef __init__(self) :pass def check_course(self): print("檢視課程") def choose_course(self): print("選擇課程") def choosed_course(self): print("檢視已選課程") s = Student() content = input(">>>") if content == "check_course": s.check_course() elif content == "choose_course": s.choose_course() elif content == "choosed_course": s.choosed_course() 輸入輸出: >>>check_course 檢視課程
問題:對於如上程式,如果學生類中有很多的方法,當根據使用者輸入,判斷哪一個方法執行時,若是有多個方法都需要去一一判斷,程式碼變的非常的冗長;並且如果使用者
#通過反射,拿到類的靜態變數(即靜態欄位,也叫靜態屬性)的值
class Student: ROLE = "STUDENT" #ROLE就是變數 print(Student.ROLE) a = getattr(Student,'ROLE') #'ROLE'是字串型別,即用字串資料型別的變數名訪問到了變數的值 print(a) 輸出: STUDENT STUDENTView Code
注:對於使用者輸入的內容和網路傳輸的內容不能直接用eval()去執行,容易出現安全問題;只有eval()明確的寫在自己的程式碼裡,才能去使用
說明:getattr(Student,'ROLE')方法中,第一個引數的名稱空間中的變數名為第二個引數的變數的值,即從Student這個類的名稱空間中取到ROLE這個變數的值
#通過反射,拿到類的類方法的值
class Student: @classmethod def check_course(cls): print("檢視所有課程") print(getattr(Student,'check_course')) #列印方法的地址值 getattr(Student,'check_course')() #通過反射呼叫類方法 輸出: <bound method Student.check_course of <class '__main__.Student'>> 檢視所有課程View Code
#通過反射,拿到類的靜態方法的值
class Student: @staticmethod def login(): print("使用者登入") print(getattr(Student,'login')) #列印方法的地址值 getattr(Student,'login')() #通過反射呼叫靜態方法 輸出: <function Student.login at 0x000002317FF39048> 使用者登入View Code
#使用反射的方式根據使用者的輸入,呼叫相應的方法(又可能使用者輸入的是一個沒有的東西)
class Student: ROLE = "STUDENT" @classmethod def check_course(cls): print("檢視所有課程") @staticmethod def login(): print("使用者登入") content = input(">>>") getattr(Student,content)() #使用者輸入什麼就執行什麼,如果輸入的內容不存在,程式直接報錯 輸出: >>>check_course 檢視所有課程 >>>aaa AttributeError: type object 'Student' has no attribute 'aaaView Code
#將hasattr()和getattr()一起使用,hasattr()表示輸入的字串在類的名稱空間中是否可以找到,能找到返回True,不能找到返回False
class Student: ROLE = "STUDENT" @classmethod def check_course(cls): print("檢視所有課程") @staticmethod def login(): print("使用者登入") content = input(">>>") print(hasattr(Student,content)) getattr(Student,content)() #使用者輸入什麼就執行什麼,如果輸入的內容不存在,程式直接報錯 輸出: >>>login True 使用者登入View Code
②使用物件去反射
#通過反射,拿到物件的物件屬性和方法
class A: def __init__(self,name): self.name = name def func(self): print("In func...") a = A("阿狸") print(getattr(a,'name')) #通過物件去反射物件屬性 getattr(a,'func')() #通過物件去反射方法 輸出: 阿狸 In func...View Code
③使用模板去反射
#以os模組中的rename方法舉例
import os #os.rename('a1.txt','a2.txt') #將a1.txt改名為a2.txt getattr(os,'rename')('a2.txt','a1.txt') #利用反射,將a2.txt改名為a1.txt print(os.rename) print(getattr(os,'rename')) 輸出: <built-in function rename> <built-in function rename>View Code
總結:os.rename 就相當於getattr(os,'rename')
④反射自己模組中的內容
#正常情況下呼叫本程式中的函式
def lol(): print("英雄聯盟") def dnf(): print("地下城勇士") lol() dnf() 輸出: 英雄聯盟 地下城勇士View Code
#反射自己模組中的函式
#先找到自己當前檔案所在的名稱空間
import sys #匯入sys模組。該模組中的所有東西都是和python直譯器相關的 print(sys.modules) #sys.modules表示所有在當前這個python程式匯入的模組 print(sys.modules['__main__']) #查詢當前所在模組的記憶體地址 輸出: '__main__': <module '__main__' from 'E:/python/project/untitled/練習/基礎程式碼練習.py'>, 表示當前檔案的記憶體地址View Code
#再通過獲取到的當前檔案的記憶體地址,去反射本模組中的函式
def lol(): print("英雄聯盟") def dnf(): print("地下城勇士") import sys file = sys.modules['__main__'] getattr(file,'lol')() getattr(file,'dnf')() 輸出: 英雄聯盟 地下城勇士View Code
3)setattr()和delattr()
#setattr()給修改屬性的值()很少用該方式給一個屬性重新賦值
class A: def __init__(self,name): self.name = name a = A("阿狸") setattr(a,'name',"九尾妖狐") print(a.name) 輸出: 九尾妖狐View Code
#delattr()刪除屬性的值
class A: def __init__(self,name): self.name = name a = A("阿狸") print(a.__dict__) delattr(a,'name') print(a.__dict__) 輸出: {'name': '阿狸'} {}View Code
反射總結:
①方法:hasattr(),getattr(),setattr()
②使用類名的反射:類名.名字 ;getattr(類名,'名字')
③使用物件名的反射:物件名.名字;getattr(物件,'名字')
③模組的反射:模組名.名字;getattr(模組,'名字')
④自己模組的反射:模組名.名字;getattr(sys.modules['__main__'],'名字')
例:學生選課系統
#若不要反射,對於一個學生選課系統,當通過login方法登入時,先判斷身份,並且根據身份例項化物件,再根據每個身份對應的類,讓使用者選擇能夠做的事,這種方式,程式碼會很長
通過反射實現
#先在userinfo檔案中寫入使用者的使用者名稱、密碼、身份如下 -------------------------------------------------------- jianji|123456|Manager ali|qwer|Student guangtou|123com|Teacher ------------------------------------------------------
#程式碼如下
class Manager: OPERATOR_DIC =[ ("建立學生賬號","create_student"), ("建立課程資訊","create_course"), ("檢視學生資訊","check_student") ] def __init__(self,name): self.name = name def create_student(self): print("建立學生賬號") def create_course(self): print("建立課程資訊") def check_student(self): print("檢視學生資訊") class Student: OPERATOR_DIC = [ ("檢視課程", "check_course"), ("選擇課程", "choose_course"), ("檢視已選課程", "choosed_course") ] def __init__(self,name): self.name = name def check_course(self): print("檢視課程") def choose_course(self): print("選擇課程") def choosed_course(self): print("檢視已選課程") def login(): username = input("請輸入使用者名稱:") password = input("請輸入密碼:") with open("userinfo") as f: for line in f: user,passwd,ident = line.strip().split("|") if username == user and password == passwd: print("登入成功") return username,ident import sys def main(): usr,id = login() #login函式的返回值,即jianji Manager file = sys.modules['__main__'] #獲取當前模板,即獲取到當前檔案的名稱空間;相當於cls就是Manager類;如果是學生登入,cls就是Stuent類 cls = getattr(file,id) #等價於getattr(file,'Manager') obj = cls(usr) #例項化一個使用者物件,相當於obj = Student(jianji) # print(usr,id) # print(cls) operator_dic = cls.OPERATOR_DIC while 1 : #通過如下程式碼,可適用類中的所有方法 for num,i in enumerate(operator_dic,1): print(num,i[0]) choice = int(input("請輸入你的選擇>>>")) choice_item = operator_dic[choice-1] #此處獲取到的是一個字串,只能使用getattr()的方式 getattr(obj,choice_item[1])() main() 輸入輸出: 請輸入使用者名稱:jianji 請輸入密碼:123456 登入成功 1 建立學生賬號 2 建立課程資訊 3 檢視學生資訊 請輸入你的選擇>>>1 建立學生賬號View Code
3.雙下方法(內建方法)
補充:
類似【__名字__】的方法叫做類的特殊方法、也叫做雙下方法、也叫內建方法、也叫魔術方法(magic_method)
類中的每一個雙下方法都有它自己的特殊意義
1)【__call__】
#物件()就相當於呼叫__call__方法
class A: def __call__(self, *args, **kwargs): print("執行__call__方法") a = A() a() #執行__call__方法 A()() #和上面結果一樣,相當於呼叫 __call__方法(即先例項化一個物件,再對) 輸出: 執行__call__方法 執行__call__方法View Code
#B類呼叫A類的__call__方法(很多原始碼這麼用)
class A: def __call__(self, *args, **kwargs): print("執行__call__方法") class B: def __init__(self,cls): self.a = cls() #②cls()就相當於A(),即建立了一個A類物件,賦值給本類的屬性a self.a() #即通過A類的物件,呼叫call方法 B(A) #①把B當作引數傳給A 輸出: 執行__call__方法View Code
2)【__len__】
補充:len()方法可計算列表、元組等資料型別的長度
list = [2,3,4,7,6,1,6] tup = (1,2,3,4,5) print(len(list)) print(len(tup)) 輸出: 7 5View Code
#len(obj)就相當於呼叫了這個物件的__len__方法
class list: list = [1,2,3,4,5] def __len__(self): print("執行len方法。。。") return 3 #__len__方法的return值就是len函式的返回值 li = list() print(len(li)) #len(物件)實際就相當於呼叫了obj的__len__方法 輸出: 執行len方法。。。 3View Code
總結:__len__方法的return值就是len函式的返回值;如果一個obj物件沒有__len__方法,那麼len函式就會報錯;即要使用len(obj)方法,必須再類中有__len__方法
#可自己定義物件的長度如何計算
class list: list = [1,2,3,4,5] tup = (1,2,3) def __len__(self): return len(self.tup) li = list() 輸出: 3View Code
總結:內建函式和類的內建方法是有很大關係的
練習:寫一個類,它有一個字串型別的屬性,要求可統計類的obj物件的長度,該物件的長度就是字串的長度
class str_len: def __init__(self): self.s = "我愛北京天安門" def __len__(self): return len(self.s) s = str_len() print(len(s)) 輸出: 7View Code
3)【__new__】
引:__init__是一個初始化方法,它不是構造方法;__new__才是構造方法
補充:例項化一個物件的過程:
①先開闢一個屬於物件的空間
②把物件的空間傳給self,然後執行init方法
③將這個物件的空間返回給呼叫者
1)例項化一個物件時,類本身是不能開闢記憶體空間空間的;開闢記憶體空間是__new__方法;因為所有的類都繼承了object類,而object類中有一個__new__方法
①即先執行本類的__new__方法開闢物件空間,如果本類沒有,只能呼叫object的__new__方法開闢空間
②把物件的空間傳給self,然後執行init方法
③將這個物件的空間返回給呼叫者
class A: def __new__(cls, *args, **kwargs): #此時還沒有物件空間,所以只能傳類空間 obj = object.__new__(cls) print("執行new方法") print(obj) #<__main__.A object at 0x00000224717C5EB8> return obj def __init__(self): print(self) #<__main__.A object at 0x00000224717C5EB8> print("執行init方法") a = A() 輸出: 執行new方法 <__main__.A object at 0x00000224717C5EB8> <__main__.A object at 0x00000224717C5EB8> 執行init方法View Code
總結:__new__方法在例項化物件之後,但是在__init__方法之前執行