python高級之面向對象高級
本節內容
- 成員修飾符
- 特殊成員
- 類與對象
- 異常處理
- 反射/自省
- 單例模式
1.成員修飾符
python的類中只有私有成員和公有成員兩種,不像c++中的類有公有成員(public),私有成員(private)和保護成員(protected).並且python中沒有關鍵字去修飾成員,默認python中所有的成員都是公有成員,但是私有成員是以兩個下划線開頭的名字標示私有成員,私有成員不允許直接訪問,只能通過內部方法去訪問,私有成員也不允許被繼承。
class a: # 說明父類的私有成員無法在子類中繼承
def __init__(self):
self.ge=123
self.__gene=456
class b(a):
def __init__(self,name):
self.name=name
self.__age=18
super(b,self).__init__() # 這一行會報錯
def show(self):
print(self.name)
print(self.__age)
print(self.ge)
print(self.__gene) # 這一行也會報錯
obj=b("xiaoming")
print(obj.name)
print(obj.ge)
# print(obj.__gene) # 這個也會報錯
obj.show()
上面就是類里面的私有成員了。
2.特殊成員
1.__init__
__init__方法可以簡單的理解為類的構造方法(實際並不是構造方法,只是在類生成對象之后就會被執行),之前已經在上一篇博客中說明過了。
2.__del__
__del__方法是類中的析構方法,當對象消亡的時候(被解釋器的垃圾回收的時候會執行這個方法)這個方法默認是不需要寫的,不寫的時候,默認是不做任何操作的。因為你不知道對象是在什么時候被垃圾回收掉,所以,除非你確實要在這里面做某些操作,不然不要自定義這個方法。
3.__call__
__call__方法在類的對象被執行的時候(obj()或者 類()())會執行。
4.__int__
__int__方法,在對象被int()包裹的時候會被執行,例如int(obj)如果obj對象沒有、__int__方法,那么就會報錯。在這個方法中返回的值被傳遞到int類型中進行轉換。
5.__str__
__str__方法和int方法一樣,當對象被str(obj)包裹的時候,如果對象中沒有這個方法將會報錯,如果有這個方法,str()將接收這個方法返回的值在轉換成字符串。
6.__add__
__add__方法在兩個對象相加的時候,調用第一個對象的__add__方法,將第二個對象傳遞進來,至於怎么處理以及返回值,那是程序員自定義的,就如下面的例子:
class abc:
def __init__(self,age):
self.age=age
def __add__(self,obj):
return self.age+obj.age
a1=abc(18)
a2=abc(20)
print(a1+a2)
#執行結果:38
7.__dict__
__dict__方法在類里面有,在對象里面也有,這個方法是以字典的形式列出類或對象中的所有成員。就像下面的例子:
class abc:
def __init__(self,age):
self.age=age
def __add__(self,obj):
return self.age+obj.age
a1=abc(18)
print(abc.__dict__)
print(a1.__dict__)
#執行結果:
{'__add__': <function abc.__add__ at 0x0000020666C9E2F0>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'abc' objects>, '__init__': <function abc.__init__ at 0x0000020666C9E268>, '__doc__': None, '__dict__': <attribute '__dict__' of 'abc' objects>}
{'age': 18}
8.__getitem__ __setitem__ __delitem__
__getitem__方法匹配 對象[索引] 這種方式,__setitem__匹配 對象[索引]=value 這種方式,__delitem__匹配 del 對象[索引] 這種方式,例子如下:
class Foo:
def __init__(self,name,age):
self.name=name
self.age=age
def __getitem__(self, item): # 匹配:對象[item]這種形式
return item+10
def __setitem__(self, key, value): # 匹配:對象[key]=value這種形式
print(key,value)
def __delitem__(self, key): # 匹配:del 對象[key]這種形式
print(key)
li=Foo("alex",18)
print(li[10])
li[10]=100
del li[10]
執行結果:
20
10 100
10
9.__getslice__ __setslice__ __delslice__
這三種方式在python2.7中還存在,用來對對象進行切片的,但是在python3之后,將這些特殊方法給去掉了,統一使用上面的方式對對象進行切片,因此在使用__getitem__ __setitem__ 這兩個方法之前要先判斷傳遞進參數的類型是不是slice對象。例子如下:
class Foo:
def __init__(self,name,age):
self.name=name
self.age=age
self.li=[1,2,3,4,5,6,7]
def __getitem__(self, item): # 匹配:對象[item]這種形式
if isinstance(item,slice): # 如果是slice對象,返回切片后的結果
return self.li[item] # 返回切片結果
elif isinstance(item,int): # 如果是整形,說明是索引
return item+10
def __setitem__(self, key, value): # 匹配:對象[key]=value這種形式
print(key,value)
def __delitem__(self, key): # 匹配:del 對象[key]這種形式
print(key)
def __getslice__(self,index1,index2):
print(index1,index2)
li=Foo("alex",18)
print(li[3:5])
#執行結果:
[4, 5]
10. __iter__
類的對象如果想要變成一個可迭代對象,那么對象中必須要有__iter__方法,並且這個方法返回的是一個迭代器。
for 循環的對象如果是一個可迭代的對象,那么會先執行對象中的__iter__方法,獲取到迭代器,然后再執行迭代器中的__next__方法獲取數據。如果for循環的是一個迭代器,那么直接執行迭代器中的__next__方法。
class Foo:
def __init__(self,name,age):
self.name=name
self.age=age
def __iter__(self):
return iter([1,2,3,4,5]) # 返回的是一個迭代器
li=Foo("alex",18)
# 1.如果類中有__iter__方法,他的對象就是可迭代對象
# 2.對象.__iter()的返回值是一個迭代器
# 3.for循環的如果是迭代器,直接執行.next方法
# 4.for循環的如果是可迭代對象,先執行對象.__iter(),獲取迭代器再執行next
for i in li:
print(i)
#執行結果:
1
2
3
4
5
11.isinstance和issubclass
之前講過isinstance可以判斷一個變量是否是某一種數據類型,其實,isinstance不只可以判斷數據類型,也可以判斷對象是否是這個類的對象或者是這個類的子類的對象,代碼如下:
class Foo:
def __init__(self,name,age):
self.name=name
self.age=age
class Son(Foo):
pass
obj=Son("xiaoming",18)
print(isinstance(obj,Foo))
執行結果:True
issubclass用來判斷一個類是否是某個類的子類,返回的是一個bool類型數據,代碼如下:
class Foo:
def __init__(self,name,age):
self.name=name
self.age=age
class Son(Foo):
pass
obj=Son("xiaoming",18)
print(issubclass(Son,Foo))
執行結果:True
3.類與對象
__new__和__metaclass__
在python中,一切皆對象,我們定義的類其實。。。也是一個對象,那么,類本身是誰的對象呢?在python2.2之前(或者叫經典類中),所有的類,都是class的對象,但是在新式類中,為了將類型(int,str,float等)和類統一,所以,所有的類都是type類型的對象。當然,這個規則可以被修改,在類中有一個屬性 __metaclass__ 可以指定當前類該由哪個類進行實例化。而創建對象過程中,其實構造器不是__init__方法,而是__new__方法,這個方法會返回一個對象,這才是對象的構造器。下面是一個解釋類實例化對象內部實現過程的代碼段:

class Mytype(type):
def __init__(self, what, bases=None, dict=None):
super(Mytype,self).__init__(what, bases, dict)
def __call__(self, *args, **kwargs):
obj=self.__new__(self)
self.__init__(obj, *args, **kwargs)
return obj
class Foo:
__metaclass__=Mytype
def __init__(self,name,age):
self.name=name
self.age=age
def __new__(cls, *args, **kwargs):
return object.__new__(cls)
obj=Foo("xiaoming",18)
print(obj.name,obj.age)
執行結果:xiaoming 18
4.異常處理
python中使用try except finally組合來實現異常撲捉,不像java中是使用try catch finally......其中,except中的Exception是所有異常的父類,下面是一個異常處理的示例:
try:
int("aaa") #可能出現異常的代碼
except IndexError as e: # 捕捉索引異常的子異常,注意,這里的as e在老版本的py中可以寫成,e但是新版本中用as e,",e"未來可能會淘汰
print("IndexError:",e)
except ValueError as e: # 捕捉value錯誤的子異常
print("ValueError:",e)
except Exception as e: # 如果上面兩個異常沒有捕獲到,那么使用Exception捕獲,Exception能夠捕獲所有的異常
print("Exception:",e)
else: # 如果沒有異常發生,執行else中的代碼塊
print("true")
finally: # 不管是否發生異常,在最后都會執行finally中的代碼,假如try里面的代碼正常執行,先執行else中的代碼,再執行finally中的代碼
print("finally")
執行結果:
ValueError: invalid literal for int() with base 10: 'aaa'
finally
那么既然Exception是所有異常的父類,我們可以自已定義Exception的子類,實現自定義異常處理,下面就是實現例子:
class OldBoyError(Exception): # 自定義錯誤類型
def __init__(self,message):
self.message=message
def __str__(self): # 打印異常的時候會調用對象里面的__str__方法返回一個字符串
return self.message
try:
raise OldBoyError("我錯了...") # raise是主動拋出異常,可以調用自定義的異常拋出異常
except OldBoyError as e:
print(e)
執行結果:我錯了...
異常處理里面還有一個斷言,一般用在判斷執行環境上面,只要斷言后面的條件不滿足,那么就拋出異常,並且后面的代碼不執行了。
print(123)
assert 1==2 # 斷言,故意拋出異常,做環境監測用,環境監測不通過,報錯並結束程序
print("456")
執行結果:
assert 1==2 # 斷言,故意拋出異常,做環境監測用,環境監測不通過,報錯並結束程序
123
AssertionError
5.反射/自省
python中的反射/自省的實現,是通過hasattr、getattr、setattr、delattr四個內置函數實現的,其實這四個內置函數不只可以用在類和對象中,也可以用在模塊等其他地方,只是在類和對象中用的很多,所以單獨提出來進行解釋。
- hasattr(key)返回的是一個bool值,判斷某個成員或者屬性在不在類或者對象中
- getattr(key,default=xxx)獲取類或者對象的成員或屬性,如果不存在,則會拋出AttributeError異常,如果定義了default那么當沒有屬性的時候會返回默認值。
- setattr(key,value)假如有這個屬性,那么更新這個屬性,如果沒有就添加這個屬性並賦值value
- delattr(key)刪除某個屬性
注意,上面的key都是字符串,而不是變量,也就是說可以通過字符串處理類中的成員或者對象中的屬性。下面是一個例子代碼:
class Foo:
def __init__(self,name,age):
self.name=name
self.age=age
def show(self):
return self.name,self.age
obj=Foo("xiaoming",18)
print(getattr(obj,"name"))
setattr(obj,"k1","v1")
print(obj.k1)
print(hasattr(obj,"k1"))
delattr(obj,"k1")
show_fun=getattr(obj,"show")
print(show_fun())
執行結果:
xiaoming
v1
True
('xiaoming', 18)
反射/自省能夠直接訪問以及修改運行中的類和對象的成員和屬性,這是一個很強大的功能,並且並不像java中效率很低,所以用的很多。
下面是一個反射/自省用在模塊級別的例子:
import s2
operation=input("請輸入URL:")
if operation in s2.__dict__:
getattr(s2,operation)()
else:
print("404")
#模塊s2中的代碼:
def f1():
print("首頁")
def f2():
print("新聞")
def f3():
print("精選")
執行結果:
請輸入URL:f1
首頁
6.單例模式
這里介紹一個設計模式,設計模式在程序員寫了兩三年代碼的時候,到一定境界了,才會考慮到設計模式對於程序帶來的好處,從而使用各種設計模式,這里只是簡單的介紹一個簡單的設計模式:單例模式。在面向對象中的單例模式就是一個類只有一個對象,所有的操作都通過這個對象來完成,這就是面向對象中的單例模式,下面是實現代碼:
class Foo: # 單例模式
__v=None
@classmethod
def ge_instance(cls):
if cls.__v:
return cls.__v
else:
cls.__v=Foo()
return cls.__v
obj1=Foo.ge_instance()
print(obj1)
obj2=Foo.ge_instance()
print(obj2)
obj3=Foo.ge_instance()
print(obj3)
執行結果:
<__main__.Foo object at 0x000001D2ABA01860>
<__main__.Foo object at 0x000001D2ABA01860>
<__main__.Foo object at 0x000001D2ABA01860>
可以看到,三個對象的內存地址都是一樣的,其實,這三個變量中存儲的都是同一個對象的內存地址,這樣有什么好處呢?能夠節省資源,就比如在數據庫連接池的時候就可以使用單例模式,只創建一個類的對象供其他程序調用,還有在web服務中接收請求也可以使用單例模式來實現,節省資源。
