Python學習之==>面向物件程式設計(二)
一、類的特殊成員
我們在 Python學習之==>面向物件程式設計(一)中已經介紹過了構造方法和析構方法,構造方法是在例項化時自動執行的方法,而析構方法是在例項被銷燬的時候被執行,Python類成員中還存在著一些具有特殊意義的方法,下面我們來一一介紹一下:
1、__doc__
表示類的描述資訊
1 class Foo: 2 ''' 3 描述類資訊 4 ''' 5 def func(self): 6 pass 7 8 print(Foo.__doc__) # 描述類資訊View Code
2、__module__ 和 __class__
__module__ 表示當前操作的物件在哪個模組
__class__ 表示當前操作的物件的類是什麼
1 class C: 2 def __init__(self): 3 self.name = 'Jack'/lib/practice.py
1 from lib.practice import C 2 3 obj = C() 4 print(obj.__module__) # lib.practice,即模組 5 print(obj.__class__) # <class 'lib.practice.C'>,即類index.py
3、__init__
構造方法,類再例項化時自動執行的方法
1 class Foo: 2 def __init__(self): 3 print('init') 4 5 obj = Foo() # init,自動執行類中的 __init__ 方法View Code
4、__del__
析構方法,例項被銷燬時自動執行的方法,一般可用於自動關閉檔案、關閉連線、關閉資料庫、刪除測試資料等操作
1 import pymysql 2 class MyDb(object):View Code3 def __init__(self,host,user,db,passwd, 4 port=3306,charset='utf8'): # 建構函式 5 try: 6 self.conn = pymysql.connect( 7 host=host,user=user,passwd=passwd,port=port,db=db,charset=charset, 8 autocommit=True # 自動提交 9 ) 10 except Exception as e: 11 print('資料庫連線失敗!:%s'%e) 12 else: 13 self.cur = self.conn.cursor(cursor=pymysql.cursors.DictCursor) 14 15 def __del__(self): # 解構函式,例項被銷燬的時候執行 16 self.cur.close() 17 self.conn.close() 18 print('資料庫連線關閉') 19 20 def ex_sql(self,sql): 21 try: 22 self.cur.execute(sql) 23 except Exception as e: 24 print('sql語句有問題:%s'%sql) 25 else: 26 self.res = self.cur.fetchall() 27 return self.res 28 29 my = MyDb('118.24.3.40','jxz','jxz','123456') 30 my.ex_sql('select * from stu;') 31 print(my.res) # 可以用例項屬性取值 32 print(my.ex_sql('select * from stu;')) # 也可以用例項方法的返回值 33 print('我是最後一行程式碼') # 執行完最後這行程式碼後再執行解構函式
5、__dict__
將物件中封裝的內容/成員通過字典的形式返回
1 class Foo(): 2 ''' 3 這個類是幹啥的。。 4 ''' 5 def __init__(self, name, age): 6 self.name = name 7 self.age = age 8 9 obj = Foo('alex',99) 10 d = obj.__dict__ # 物件有__dict__方法,顯示物件的所有成員 11 print(d) # {'name': 'alex', 'age': 99} 12 ret = Foo.__dict__ # 類也有__dict__方法,顯示類的所有成員 13 print(ret)# {'__module__': '__main__', '__doc__': '\n 這個類是幹啥的。。\n ', '__init__': <function Foo.__init__ at 0x000001EBA9FAF1E0>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>}View Code
6、__int__
如果一個類中定義了__int__方法,那麼在列印物件時,預設輸出該方法的返回值
1 class Foo: 2 def __init__(self): # 構造方法 3 print('init') 4 5 def __int__(self): 6 return 1 7 8 9 obj = Foo() # init,建立物件時自動執行構造方法 10 r = int(obj) # int後面加上一個物件(obj),它就會自動執行物件(obj)當中的__int__方法,並將返回值賦給int物件(r是int的物件) 11 print(r) # 1 12 print(type(r))# <class 'int'>,r是int類的物件View Code
7、__str__
如果一個類中定義了__str__方法,那麼在列印物件時,預設輸出該方法的返回值
1 class Foo: 2 def __init__(self): # 構造方法 3 print('init') 4 5 def __str__(self): 6 return 'niu' 7 8 obj = Foo() #init,建立物件時自動執行構造方法 9 print(obj,type(obj)) # <__main__.Foo object at 0x00000236A19CD278> <class '__main__.Foo'>,obj的型別是Foo類 10 # niu <class '__main__.Foo'>,類中加上__str__()後的返回 11 # print()函式在執行時會自動呼叫物件中的__str__()方法,所以Foo類中加上__str__()方法之前顯示為記憶體地址,加上__str__()方法之後,顯示為__str__()方法的返回值‘niu’ 12 # print(obj)相當於print(str(obj)) 13 s = str(obj) # str後面加上一個物件(obj),它就會自動執行物件(obj)當中的__str__方法,並將返回值賦給str物件(s是str的物件) 14 print(s)View Code
8、__add__
如果一個類中定義了__add__方法,那麼在兩個物件進行相加時,返回的是該方法的返回值
1 class Foo: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 6 def __add__(self, other): 7 return self.age + other.age 8 9 obj1 = Foo('alex', 19) 10 obj2 = Foo('hiro', 55) 11 r = obj1 + obj2 # 兩個物件相加時,自動執行第一個物件的__add__方法,並且將第二個物件當引數傳入 12 print(r, type(r))# 74 <class 'int'>View Code
9、__call__
物件後面加括號,觸發執行類中的__call__方法
注:構造方法的執行是由建立物件觸發的,即:物件 = 類名() ;而對於 __call__ 方法的執行是由物件後加括號觸發的,即:物件() 或者 類()()
1 class Foo: 2 def __init__(self): # 構造方法 3 print('init') 4 5 def __call__(self, *args, **kwargs): 6 print('call') 7 8 9 obj = Foo() #init,建立物件時自動執行構造方法 10 obj() #call,物件後面加括號直接呼叫類中的__call__()方法View Code
10、__getitem__、__setitem__、__delitem__
用於索引、切片操作,如:列表、字典。以上三個方法分別獲取、設定、刪除資料
1 class Foo(): 2 3 def __init__(self, name, age): 4 self.name = name 5 self.age = age 6 7 def __getitem__(self, item): 8 print(item + 10) 9 10 def __setitem__(self, key, value): 11 print(key, value) 12 13 def __delitem__(self, key): 14 print(key) 15 16 li = Foo('alex',99) 17 # 索引 18 li[8] # 自動執行li物件的類中__getitem__方法,8當作引數傳遞給item 19 li[100] = 123 # 自動執行li物件的類中__setitem__方法,100和123當作引數傳遞給key和value 20 del li[99] # 自動執行li物件的類中__delitem__方法,99當作引數傳遞給keyView Code
1 class Foo(): 2 3 def __init__(self, name, age): 4 self.name = name 5 self.age = age 6 7 def __getitem__(self, item): 8 # 如果item是基本型別,int,str索引獲取 9 # 如果item型別是slice,切片 10 print(type(item)) 11 if type(item) == slice: # 切片是slice型別 12 print('切片處理') 13 print(item.start) 14 print(item.stop) 15 print(item.step) 16 else: 17 print('索引處理') 18 def __setitem__(self, key, value): 19 print(type(key)) 20 if type(key) == slice: 21 print('切片處理') 22 print(key.start) 23 print(key.stop) 24 print(key.step) 25 else: 26 print('索引處理') 27 28 def __delitem__(self, key): 29 print(type(key)) 30 if type(key) == slice: 31 print('切片處理') 32 print(key.start) 33 print(key.stop) 34 print(key.step) 35 else: 36 print('索引處理') 37 38 li = Foo('alex',99) 39 # 切片 40 li[1:3:2] # <class 'slice'>,切片處理,1 3 2 41 li[1:3:2] = [11,22] # <class 'slice'>,切片處理,1 3 2 42 del li[2:4:2] # <class 'slice'>,切片處理,2 4 2View Code
11、__iter__
用於迭代器,之所以列表、字典、元組可以進行for迴圈,是因為型別內部定義了 __iter__方法
1 class Foo(): 2 3 def __init__(self, name, age): 4 self.name = name 5 self.age = age 6 7 def __iter__(self): 8 return 'alex' 9 10 li = Foo('alex',18) 11 # 如果類中有__iter__方法,建立的類就是可迭代物件 12 # 物件.__iter__()方法的返回值是一個迭代器 13 for i in li.__iter__(): 14 # 1、執行li物件的類Foo類中的__iter__方法,並獲取到其返回值alex,是一個可迭代物件 15 # 2、通過li.__iter__()將返回值轉換為迭代器 16 # 3、然後進行迴圈 17 print(i) # a l e x 18 # for迴圈遇到迭代器,直接執行next() 19 # for迴圈遇到可迭代物件,先執行物件.__iter__()方法,再執行next()View Code
12、metaclass和__new__
我們先來看一段程式碼:
1 class Foo: 2 def __init__(self): 3 pass 4 5 obj = Foo() 6 print(type(obj)) #<class '__main__.Foo'>,表示obj物件由Foo類建立 7 print(type(Foo)) #<class 'type'>,表示Foo類物件由type類建立View Code
在Python中有一句話叫做:一切事物皆為物件。通過類例項化的物件是物件,而類本身也是一個物件。
通過上面這段程式碼以及執行結果來看,obj是通過Foo類建立的一個物件,而Foo類同樣也是一個物件,它是通過type類建立的一個類物件。obj物件是通過執行Foo類的構造方法建立,那麼Foo類物件是通過執行type類的構造方法建立。
那麼,類的建立就有以下兩種方式:
(1)普通方式
1 class Foo(): 2 def func(self): 3 print(123) 4 5 obj = Foo() # 例項化 6 obj.func() # 呼叫物件中的方法View Code
(2)特殊方式(type類的構造方法)
1 def function(): 2 print('Hello,world!!') 3 4 Foo = type('Foo',(object,), {'func':function}) 5 # type第一個引數:類名 6 # type第二個引數:當前類的父類,這個類繼承哪個類 7 # type第三個引數:類的成員 8 Foo.func() # 呼叫類物件中的方法View Code
那麼,我們好奇的是既然類物件是由type類例項化產生的,那麼type類內部是如何實現建立類的?類又是如何建立物件的呢?
建立類時,通過指定metaclass=派生類,用來表示該類由誰來例項化建立。所以,我們可以為metaclass設定一個type類的派生類,從而檢視類建立的過程,如下:
- 由MyType類來建立Foo類物件,執行MyType類中的__init__方法
- 執行obj = Foo(),首先執行MyType類中的__call__方法
- MyType類中的__call__方法又會呼叫Foo類中的__new__方法建立物件obj
- 建立物件後再呼叫Foo類中的__init__方法(將obj物件當引數傳入)
1 # type類內部實現建立類的,類建立物件 2 class MyType(type): 3 def __init__(self,*args,**kwargs): 4 print(123) 5 def __call__(self, *args, **kwargs): 6 print(456) 7 obj = self.__new__(self,*args,**kwargs) # 呼叫Foo類中的__new__方法 8 self.__init__(obj) 9 10 class Foo(object,metaclass=MyType): # 執行父類MyType類中的__init__方法 11 def __init__(self): 12 print(111) 13 def __new__(cls, *args, **kwargs): # 建立obj物件 14 print(789) 15 return object.__new__(cls,*args,**kwargs) 16 17 # 第一階段:直譯器從上到下執行程式碼建立Foo類 18 # 第二階段:通過Foo類建立object物件 19 obj = Foo() # 執行MyType類中的__call__方法View Code
二、反射
反射,主要指程式可以訪問、檢測和修改它本身狀態或行為的一種能力。Python中面向物件中的反射則是指:通過字串的形式操作物件中的成員。因Python中一切事物皆為物件,所以都可以使用反射。
1、hasattr:判斷物件中是否有這個成員
1 class Foo: 2 height = 180 3 def __init__(self,name,age): 4 self.name = name 5 self.age = age 6 def show(self): 7 return '%s-%s'%(self.name,self.age) 8 9 obj = Foo('niu',99) 10 print(hasattr(obj,'name')) # True,obj物件中有name這個成員 11 print(hasattr(obj,'age')) # True,obj物件中有age這個成員 12 print(hasattr(obj,'show')) # True,obj物件中有show這個成員 13 print(hasattr(obj,'long')) # False,obj物件中沒有long這個成員hasattr
2、getattr:去物件中獲取某個成員
1 class Foo: 2 height = 180 3 def __init__(self,name,age): 4 self.name = name 5 self.age = age 6 def show(self): 7 return '%s-%s'%(self.name,self.age) 8 9 obj = Foo('niu',99) 10 inp = input('>>>') # 通過輸入成員名來獲取屬性,這樣更靈活 11 r = getattr(obj,inp) # 這裡r可以是name,age,height,show等getattr
直接操作一個類,類也是一個物件:
1 class Foo: 2 height = 180 3 def __init__(self,name,age): 4 self.name = name 5 self.age = age 6 def show(self): 7 return '%s-%s'%(self.name,self.age) 8 9 # 直接操作類,因為類也是一個物件 10 print(getattr(Foo,'height')) # 180getattr
操作檔案,檔案也是一個物件:
1 NAME = 'niu' 2 3 def func(): 4 return 'func' 5 6 class Foo: 7 height = 180 8 9 def __init__(self,name,age): 10 self.name = name 11 self.age = age 12 13 def show(self): 14 return '%s-%s'%(self.name,self.age)practice.py
1 # 操作檔案物件 2 import practice 3 r1 = getattr(practice,'NAME') # practice.py檔案中的NAME常量 4 print(r1) # niu 5 r2 = getattr(practice,'func') # practice.py檔案中的func函式 6 print(r2())# func 7 cls = getattr(practice,'Foo') # practice.py檔案中的Foo類 8 obj = cls('niuren',99) # 例項化 9 print(obj) # <practice.Foo object at 0x0000022C19E9DA58>getattr
3、setattr:給物件中設定一個成員
1 class Foo: 2 height = 180 3 4 def __init__(self,name,age): 5 self.name = name 6 self.age = age 7 8 def show(self): 9 return '%s-%s'%(self.name,self.age) 10 11 obj = Foo('niu',99) 12 setattr(obj,'sex','男') # 給obj物件設定一個sex成員,值為‘男’ 13 print(obj.sex) # 男setattr
4、delattr:刪除物件中的成員
1 class Foo: 2 height = 180 3 4 def __init__(self,name,age): 5 self.name = name 6 self.age = age 7 8 def show(self): 9 return '%s-%s'%(self.name,self.age) 10 11 obj = Foo('niu',99) 12 delattr(obj,'name') # 刪除obj物件中的name成員 13 print(obj.name) # 'Foo' object has no attribute 'name',Foo類中沒有name成員了delattr
5、反射的應用
1 def f1(): 2 return '首頁' 3 4 def f2(): 5 return '新聞' 6 7 def f3(): 8 return '精華'practice.py
1 import practice 2 inp = input('請輸入要檢視的URL:') 3 if hasattr(practice,inp): # 輸入URL來判斷practice檔案是否存在相應的成員 4 func = getattr(practice,inp) # 如果有則獲取這個成員 5 result = func() 6 print(result) 7 else: 8 print('404') # 如果不存在則返回404頁面View Code
三、單例模式
單例模式(Singleton Pattern)是一種常用的軟體設計模式,該模式的主要目的是確保某一個類只有一個例項存在。當你希望在整個系統中,某個類只能出現一個例項時,單例物件就能派上用場。
比如,某個伺服器程式的配置資訊存放在一個檔案中,客戶端通過一個 AppConfig 的類來讀取配置檔案的資訊。如果在程式執行期間,有很多地方都需要使用配置檔案的內容,也就是說,很多地方都需要建立 AppConfig 物件的例項,這就導致系統中存在多個 AppConfig 的例項物件,而這樣會嚴重浪費記憶體資源,尤其是在配置檔案內容很多的情況下。事實上,類似 AppConfig 這樣的類,我們希望在程式執行期間只存在一個例項物件。
1 class Foo: 2 3 __v = None 4 5 @classmethod 6 def get_instance(cls): 7 if cls.__v: 8 return cls.__v 9 else: 10 cls.__v = Foo() 11 return cls.__v 12 13 # 例項化時不需要再使用類名+括號,直接呼叫類方法get_instance 14 obj1 = Foo.get_instance() 15 print(obj1) # <__main__.Foo object at 0x000001B0C8C0D278> 16 obj2 = Foo.get_instance() 17 print(obj2) # <__main__.Foo object at 0x000001B0C8C0D278> 18 # 建立多個物件都是同一個記憶體地址,說明使用的是同一個物件View Code