1. 程式人生 > 實用技巧 >python學習第19天----內建函式_反射_雙下方法_單例類

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
True
View Code

備註:若【type(子類的物件) is 類】方式判斷一個物件是否屬於這個類時,只有當前類才返回True,若是父類則返回False

class A: pass
class B(A): pass b = B() print(isinstance(b,B)) print(isinstance(b,A)) print(type(b) is A) #父類則返回False 輸出: True True False True
View Code

總結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
True
View Code

2.反射

1)什麼是反射:用字串資料型別的變數名來訪問這個變數的值

2)反射的方法:getattr() hasattr() setattr() delattr()

2)反射的使用

①使用類去反射

格式:

類名稱空間.XXX == getattr(名稱空間,'XXX')

#引:不使用反射的情況下,編寫一個學生選課系統

class Student:
    
def __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 檢視課程
View Code

問題:對於如上程式,如果學生類中有很多的方法,當根據使用者輸入,判斷哪一個方法執行時,若是有多個方法都需要去一一判斷,程式碼變的非常的冗長;並且如果使用者

#通過反射,拿到類的靜態變數(即靜態欄位,也叫靜態屬性)的值

class Student:
    ROLE = "STUDENT"         #ROLE就是變數
print(Student.ROLE)
a = getattr(Student,'ROLE')       #'ROLE'是字串型別,即用字串資料型別的變數名訪問到了變數的值
print(a)
輸出:
STUDENT
STUDENT
View 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 'aaa
View 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
5
View 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方法。。。
3
View 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()
輸出:
3
View Code

總結:內建函式和類的內建方法是有很大關係的

練習:寫一個類,它有一個字串型別的屬性,要求可統計類的obj物件的長度,該物件的長度就是字串的長度

class str_len:
    def __init__(self):
        self.s = "我愛北京天安門"
    def __len__(self):
        return len(self.s)
s = str_len()
print(len(s))
輸出:
7
View 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__方法之前執行