1. 程式人生 > >Python學習之==>面向物件程式設計(二)

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):
3 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('我是最後一行程式碼') # 執行完最後這行程式碼後再執行解構函式
View Code

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當作引數傳遞給key
View 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 2
View 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類的派生類,從而檢視類建立的過程,如下:

  1. 由MyType類來建立Foo類物件,執行MyType類中的__init__方法
  2. 執行obj = Foo(),首先執行MyType類中的__call__方法
  3. MyType類中的__call__方法又會呼叫Foo類中的__new__方法建立物件obj
  4. 建立物件後再呼叫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'))  # 180
getattr

操作檔案,檔案也是一個物件:

 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