python高級之面向對象高級


python高級之面向對象高級

本節內容

  1. 成員修飾符
  2. 特殊成員
  3. 類與對象
  4. 異常處理
  5. 反射/自省
  6. 單例模式

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四個內置函數實現的,其實這四個內置函數不只可以用在類和對象中,也可以用在模塊等其他地方,只是在類和對象中用的很多,所以單獨提出來進行解釋。

  1. hasattr(key)返回的是一個bool值,判斷某個成員或者屬性在不在類或者對象中
  2. getattr(key,default=xxx)獲取類或者對象的成員或屬性,如果不存在,則會拋出AttributeError異常,如果定義了default那么當沒有屬性的時候會返回默認值。
  3. setattr(key,value)假如有這個屬性,那么更新這個屬性,如果沒有就添加這個屬性並賦值value
  4. 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服務中接收請求也可以使用單例模式來實現,節省資源。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM