【Python3之面向對象進階】


一、isinstance和issubclass

1.isinstance(obj,cls)檢查是否obj是否是類 cls 的對象

class Foo(object):
    pass

obj=Foo()
print(isinstance(obj, Foo))

輸出

True

 

2.issubclass(sub, super)檢查sub類是否是 super 類的派生類

class Foo(object):
    pass

class Bar(Foo):
    pass

print(issubclass(Bar, Foo))

輸出

True

 

二、反射

1.反射定義

反射的概念是由Smith在1982年首次提出的,主要是指程序可以訪問、檢測和修改它本身狀態或行為的一種能力(自省)。

 

2.反射的實現

python面向對象中的反射:通過字符串的形式操作對象相關的屬性。python中的一切事物都是對象,都可以使用反射

四個可以實現自省的函數,下列方法適用於類和對象(一切皆對象,類本身也是一個對象)

  • hasattr(object,name)  

判斷一個對象里面是否有name屬性或者name方法,返回BOOL值,有name特性返回True, 否則返回False。需要注意的是name要用括號括起來。

>>> class test():
...     name="xiaohua"
...     def run(self):
...             return "HelloWord"
...
>>> t=test()
>>> hasattr(t, "name") #判斷對象有name屬性
True
>>> hasattr(t, "run")  #判斷對象有run方法
True
>>>

 

  • getattr(object, name, default=None)

獲取對象object的屬性或者方法,如果存在打印出來,如果不存在,打印出默認值,默認值可選。需要注意的是,如果是返回的對象的方法,返回的是方法的內存地址,如果需要運行這個方法,可以在后面添加一對括號。

>>> class test():
...     name="xiaohua"
...     def run(self):
...             return "HelloWord"
...
>>> t=test()
>>> getattr(t, "name") #獲取name屬性,存在就打印出來。
'xiaohua'
>>> getattr(t, "run")  #獲取run方法,存在就打印出方法的內存地址。
<bound method test.run of <__main__.test instance at 0x0269C878>>
>>> getattr(t, "run")()  #獲取run方法,后面加括號可以將這個方法運行。
'HelloWord'
>>> getattr(t, "age")  #獲取一個不存在的屬性。
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: test instance has no attribute 'age'
>>> getattr(t, "age","18")  #若屬性不存在,返回一個默認值。
'18'
>>>
  • setattr(object, name, values)

給對象的屬性賦值,若屬性不存在,先創建再賦值。

>>> class test():
...     name="xiaohua"
...     def run(self):
...             return "HelloWord"
...
>>> t=test()
>>> hasattr(t, "age")   #判斷屬性是否存在
False
>>> setattr(t, "age", "18")   #為屬相賦值,並沒有返回值
>>> hasattr(t, "age")    #屬性存在了
True
>>>
  • delattr(object, name)

刪除object對象名為name的屬性。

 

綜合例子

class BlackMedium:
    feature='Ugly'
    def __init__(self,name,addr):
        self.name=name
        self.addr=addr

    def sell_house(self):
        print('%s 賣房子' %self.name)
    def rent_house(self):
        print('%s 租房子' %self.name)

b1=BlackMedium('恆大','回龍觀')

#檢測是否含有某屬性
print(hasattr(b1,'name'))
print(hasattr(b1,'sell_house'))

#獲取屬性
n=getattr(b1,'name')
print(n)
func=getattr(b1,'rent_house')
func()

# getattr(b1,'aaaaaaaa') #報錯
print(getattr(b1,'aaaaaaaa','不存在啊'))

#設置屬性
setattr(b1,'sb',True)
setattr(b1,'show_name',lambda self:self.name+'123')
print(b1.__dict__)
print(b1.show_name(b1))

#刪除屬性
delattr(b1,'addr')
delattr(b1,'show_name')
#delattr(b1,'show_name111')#不存在,則報錯

print(b1.__dict__)

輸出

True
True
恆大
恆大 租房子
不存在啊
{'show_name': <function <lambda> at 0x10c9e8f28>, 'sb': True, 'addr': '回龍觀', 'name': '恆大'}
恆大123
{'sb': True, 'name': '恆大'}

 

類也是對象

class Foo(object):

    staticField = "old"

    def __init__(self):
        self.name = '123'

    def func(self):
        return 'func'

    @staticmethod
    def bar():
        return 'bar'

print(getattr(Foo, 'staticField'))
print(getattr(Foo, 'func'))
print(getattr(Foo, 'bar'))

輸出

old
<function Foo.func at 0x10f89f488>
<function Foo.bar at 0x10f89f510>

 

模塊的反射

import sys                          
                                    
                                    
def s1():                           
    print('s1')                     
                                    
                                    
def s2():                           
    print('s2')                     
                                    
                                    
this_module = sys.modules[__name__] 
                                    
print(hasattr(this_module, 's1'))   
print(getattr(this_module, 's2'))   

輸出

 

True
<function s2 at 0x108f4d400>

 

3.反射的好處

  • 實現可插拔機制

可以事先定義好接口,接口只有在被完成后才會真正執行,這實現了即插即用,這其實是一種‘后期綁定’,即你可以事先把主要的邏輯寫好(只定義接口),然后后期再去實現接口的功能。

程序員A未實現功能

class FtpClient:
    'ftp客戶端,但是還么有實現具體的功能'
    def __init__(self,addr):
        print('正在連接服務器[%s]' %addr)
        self.addr=addr

但不影響程序員B繼續實現其他邏輯,利用反射事先做判斷

#from module import FtpClient
f1=FtpClient('192.168.1.1')
if hasattr(f1,'get'):        #判斷方法是否實現
    func_get=getattr(f1,'get')
    func_get()
else:
    print('---->不存在此方法')
    print('處理其他的邏輯')

 

  • 動態導入模塊(基於反射當前模塊成員)

 

三、__setattr__,__getattr__,__delattr__

  • __getattr__

攔截點號運算。當對未定義的屬性名稱和實例進行點號運算時,就會用屬性名作為字符串調用這個方法。如果繼承樹可以找到該屬性,則不調用此方法

  • __setattr__

會攔截所有屬性的的賦值語句。如果定義了這個方法,self.arrt = value 就會變成self,__setattr__("attr", value).這個需要注意。當在__setattr__方法內對屬性進行賦值是,不可使用self.attr = value,因為他會再次調用self,__setattr__("attr", value),則會形成無窮遞歸循環,最后導致堆棧溢出異常。應該通過對屬性字典做索引運算來賦值任何實例屬性,也就是使用self.__dict__['name'] = value.

 

class Foo:
    x=1
    def __init__(self,y):
        self.y=y

    def __getattr__(self, item):
        print('----> from getattr:你找的屬性不存在')


    def __setattr__(self, key, value):
        print('----> from setattr')
        # self.key=value #這就無限遞歸了,你好好想想
        # self.__dict__[key]=value #應該使用它

    def __delattr__(self, item):
        print('----> from delattr')
        # del self.item #無限遞歸了
        self.__dict__.pop(item)

#__setattr__添加/修改屬性會觸發它的執行
f1=Foo(10)
print(f1.__dict__) # 因為你重寫了__setattr__,凡是賦值操作都會觸發它的運行,你啥都沒寫,就是根本沒賦值,除非你直接操作屬性字典,否則永遠無法賦值
f1.z=3
print(f1.__dict__)

#__delattr__刪除屬性的時候會觸發
f1.__dict__['a']=3#我們可以直接修改屬性字典,來完成添加/修改屬性的操作
del f1.a
print(f1.__dict__)

#__getattr__只有在使用點調用屬性且屬性不存在的時候才會觸發
f1.xxxxxx

輸出

----> from setattr
{}
----> from setattr
{}
----> from delattr
{}
----> from getattr:你找的屬性不存在

 

四、二次加工

  • 包裝

python為大家提供了標准數據類型,以及豐富的內置方法,其實在很多場景下我們都需要基於標准數據類型來定制我們自己的數據類型,新增/改寫方法,這就用到了我們剛學的繼承/派生知識(其他的標准類型均可以通過下面的方式進行二次加工)

class List(list): #繼承list所有的屬性,也可以派生出自己新的,比如append和mid
    def append(self, p_object):
        ' 派生自己的append:加上類型檢查'
        if not isinstance(p_object,int):
            raise TypeError('must be int')
        super().append(p_object)

    @property
    def mid(self):
        '新增自己的屬性'
        index=len(self)//2
        return self[index]

l=List([1,2,3,4])
print(l)
l.append(5)
print(l)
# l.append('1111111') #報錯,必須為int類型

print(l.mid)

#其余的方法都繼承list的
l.insert(0,-123)
print(l)
l.clear()
print(l)

輸出

[1, 2, 3, 4]
[1, 2, 3, 4, 5]
3
[-123, 1, 2, 3, 4, 5]
[]

 

  • 授權

授權是包裝的一個特性, 包裝一個類型通常是對已存在的類型的一些定制,這種做法可以新建,修改或刪除原有產品的功能。其它的則保持原樣。授權的過程,即是所有更新的功能都是由新類的某部分來處理,但已存在的功能就授權給對象的默認屬性。

實現授權的關鍵點就是覆蓋__getattr__方法

class List:
    def __init__(self,seq,permission=False):
        self.seq=seq
        self.permission=permission
    def clear(self):
        if not self.permission:
            raise PermissionError('not allow the operation')
        self.seq.clear()

    def __getattr__(self, item):
        return getattr(self.seq,item)

    def __str__(self):
        return str(self.seq)

l=List([1,2,3])
# l.clear() #此時沒有權限,拋出異常


l.permission=True
print(l)
l.clear()
print(l)

#基於授權,獲得insert方法
l.insert(0,-123)
print(l)

輸出

[1, 2, 3]
[]
[-123]

 

 五、__str__,__repr__,__format__

改變對象的字符串顯示__str__,__repr__

自定制格式化字符串__format__

 

  • __str__,__repr__

我們先定義一個Student類,打印一個實例:

>>> class Student(object):
...     def __init__(self, name):
...         self.name = name
...
>>> print(Student('Michael'))
<__main__.Student object at 0x109afb190>

打印出一堆<__main__.Student object at 0x109afb190>,不好看。

怎么才能打印得好看呢?只需要定義好__str__()方法,返回一個好看的字符串就可以了:

>>> class Student(object): ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return 'Student object (name: %s)' % self.name ... >>> print(Student('Michael')) Student object (name: Michael)

這樣打印出來的實例,不但好看,而且容易看出實例內部重要的數據。

但是細心的朋友會發現直接敲變量不用print,打印出來的實例還是不好看:

>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>

這是因為直接顯示變量調用的不是__str__(),而是__repr__(),兩者的區別是__str__()返回用戶看到的字符串,而__repr__()返回程序開發者看到的字符串,也就是說,__repr__()是為調試服務的。

解決辦法是再定義一個__repr__()。但是通常__str__()__repr__()代碼都是一樣的,所以,有個偷懶的寫法:

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name=%s)' % self.name
    __repr__ = __str__

 

  • __format__
date_dic={
    'ymd':'{0.year}:{0.month}:{0.day}',
    'dmy':'{0.day}/{0.month}/{0.year}',
    'mdy':'{0.month}-{0.day}-{0.year}',
}
class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day

    def __format__(self, format_spec):
        if not format_spec or format_spec not in date_dic:
            format_spec='ymd'
        fmt=date_dic[format_spec]
        return fmt.format(self)

d1=Date(2016,12,29)
print(format(d1))
print('{:mdy}'.format(d1))

輸出

2016:12:29
12-29-2016

 

六、__del__

析構方法,當對象在內存中被釋放時,自動觸發執行。此方法一般無須定義,因為Python是一門高級語言,程序員在使用時無需關心內存的分配和釋放,因為此工作都是交給Python解釋器來執行,所以,析構函數的調用是由解釋器在進行垃圾回收時自動觸發執行的。

class Foo:

    def __del__(self):
        print('執行刪除')

f1=Foo()
print('**')
del f1
print('------->')

輸出

**
執行刪除
------->

 

七、__setitem__,__getitem__,__delitem__

當實例中有類似字典的操作

class Foo:
    def __init__(self,name):
        self.name=name

    def __getitem__(self, item):
        print(self.__dict__[item])

    def __setitem__(self, key, value):
        self.__dict__[key]=value
    def __delitem__(self, key):
        print('del obj[key]時,我執行')
        self.__dict__.pop(key)
    def __delattr__(self, item):
        print('del obj.key時,我執行')
        self.__dict__.pop(item)

f1=Foo('sb')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='alex'
print(f1.__dict__)

輸出

del obj.key時,我執行
del obj[key]時,我執行
{'name': 'alex'}

 

八、__call__

對象后面加括號,觸發執行。

注:構造方法的執行是由創建對象觸發的,即:對象 = 類名() ;而對於 __call__ 方法的執行是由對象后加括號觸發的,即:對象() 或者 類()()

class Foo:

    def __init__(self):
        pass
    
    def __call__(self, *args, **kwargs):

        print('__call__')


obj = Foo() # 執行 __init__
obj()       # 執行 __call__

 

九、eval(),exec()

1、eval()

  • 函數的作用

計算指定表達式的值。也就是說它要執行的Python代碼只能是單個運算表達式(注意eval不支持任意形式的賦值操作),而不能是復雜的代碼邏輯,這一點和lambda表達式比較相似。

  • 函數定義
eval(expression, globals=None, locals=None)
  • 參數說明:
  1. expression:必選參數,可以是字符串,也可以是一個任意的code對象實例(可以通過compile函數創建)。如果它是一個字符串,它會被當作一個(使用globals和locals參數作為全局和本地命名空間的)Python表達式進行分析和解釋。
  2. globals:可選參數,表示全局命名空間(存放全局變量),如果被提供,則必須是一個字典對象。
  3. locals:可選參數,表示當前局部命名空間(存放局部變量),如果被提供,可以是任何映射對象。如果該參數被忽略,那么它將會取與globals相同的值。
  4. 如果globals與locals都被忽略,那么它們將取eval()函數被調用環境下的全局命名空間和局部命名空間。
  • 返回值:
  1. 如果expression是一個code對象,且創建該code對象時,compile函數的mode參數是'exec',那么eval()函數的返回值是None;
  2. 否則,如果expression是一個輸出語句,如print(),則eval()返回結果為None;
  3. 否則,expression表達式的結果就是eval()函數的返回值;
x = 10

def func():
    y = 20
    a = eval('x + y')
    print('a: ', a)
    b = eval('x + y', {'x': 1, 'y': 2})
    print('b: ', b)
    c = eval('x + y', {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
    print('c: ', c)
    d = eval('print(x, y)')
    print('d: ', d)

func()

輸出

a:  30
b:  3
c:  4
10 20
d:  None
  • 對輸出結果的解釋:
  1. 對於變量a,eval函數的globals和locals參數都被忽略了,因此變量x和變量y都取得的是eval函數被調用環境下的作用域中的變量值,即:x = 10, y = 20,a = x + y = 30
  2. 對於變量b,eval函數只提供了globals參數而忽略了locals參數,因此locals會取globals參數的值,即:x = 1, y = 2,b = x + y = 3
  3. 對於變量c,eval函數的globals參數和locals都被提供了,那么eval函數會先從全部作用域globals中找到變量x, 從局部作用域locals中找到變量y,即:x = 1, y = 3, c = x + y = 4
  4. 對於變量d,因為print()函數不是一個計算表達式,沒有計算結果,因此返回值為None

 

2.exec()

  • 函數的作用:

動態執行Python代碼。也就是說exec可以執行復雜的Python代碼,而不像eval函數那么樣只能計算一個表達式的值。

  • 函數定義:
exec(object[, globals[, locals]])
  • 參數說明:
  1. object:必選參數,表示需要被指定的Python代碼。它必須是字符串或code對象。如果object是一個字符串,該字符串會先被解析為一組Python語句,然后在執行(除非發生語法錯誤)。如果object是一個code對象,那么它只是被簡單的執行。
  2. globals:可選參數,同eval函數
  3. locals:可選參數,同eval函數
  • 返回值:

exec函數的返回值永遠為None.

需要說明的是在Python 2中exec不是函數,而是一個內置語句(statement),但是Python 2中有一個execfile()函數。可以理解為Python 3把exec這個statement和execfile()函數的功能夠整合到一個新的exec()函數中去了:

  • eval()函數與exec()函數的區別:
  1. eval()函數只能計算單個表達式的值,而exec()函數可以動態運行代碼段。
  2. eval()函數可以有返回值,而exec()函數返回值永遠為None。
x = 10

def func():
    y = 20
    a = exec('x + y')
    print('a: ', a)
    b = exec('x + y', {'x': 1, 'y': 2})
    print('b: ', b)
    c = exec('x + y', {'x': 1, 'y': 2}, {'y': 3, 'z': 4})
    print('c: ', c)
    d = exec('print(x, y)')
    print('d: ', d)

func()

輸出

a:  None
b:  None
c:  None
10 20
d:  None

 

x = 10
expr = """
z = 30
sum = x + y + z
print(sum)
"""
def func():
    y = 20
    exec(expr)
    exec(expr, {'x': 1, 'y': 2})
    exec(expr, {'x': 1, 'y': 2}, {'y': 3, 'z': 4})

func()

輸出

60
33
34
  • 對輸出結果的解釋:

前兩個輸出跟上面解釋的eval函數執行過程一樣,不做過多解釋。關於最后一個數字34,我們可以看出是:x = 1, y = 3是沒有疑問的。關於z為什么還是30而不是4,這其實也很簡單,我們只需要在理一下代碼執行過程就可以了,其執行過程相當於:

x = 1
y = 2

def func():
    y = 3
    z = 4
    
    z = 30
    sum = x + y + z
    print(sum)

func()

 

十、元類

1.元類的定義

元類是用來控制如何創建類的,正如類是創建對象的模板一樣,而元類的主要目的是為了控制類的創建行為

元類的實例化的結果為我們用class定義的類,正如類的實例為對象(f1對象是Foo類的一個實例Foo類是 type 類的一個實例)

type是python的一個內建元類,用來直接控制生成類,python中任何class定義的類其實都是type類實例化的對象

 

2.創建類的方式

  • 使用class關鍵字
class Chinese(object):
    country='China'
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def talk(self):
        print('%s is talking' %self.name)
  • 手動模擬class創建類的過程:將創建類的步驟拆分開,手動去創建

准備工作:

創建類主要分為三部分

  1 類名

  2 類的父類

  3 類體

 

#類名
class_name='Chinese'
#類的父類
class_bases=(object,)
#類體
class_body="""
country='China'
def __init__(self,name,age):
    self.name=name
    self.age=age
def talk(self):
    print('%s is talking' %self.name)
"""

 

步驟一(先處理類體->名稱空間):類體定義的名字都會存放於類的名稱空間中(一個局部的名稱空間),我們可以事先定義一個空字典,然后用exec去執行類體的代碼(exec產生名稱空間的過程與真正的class過程類似,只是后者會將__開頭的屬性變形),生成類的局部名稱空間,即填充字典

class_dic={}
exec(class_body,globals(),class_dic)


print(class_dic)
#{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}

步驟二:調用元類type(也可以自定義)來產生類Chinense

Foo=type(class_name,class_bases,class_dic) #實例化type得到對象Foo,即我們用class定義的類Foo


print(Foo)
print(type(Foo))
print(isinstance(Foo,type))
'''
<class '__main__.Chinese'>
<class 'type'>
True
'''

我們看到,type 接收三個參數:

  1. 第 1 個參數是字符串 ‘Foo’,表示類名

  2. 第 2 個參數是元組 (object, ),表示所有的父類

  3. 第 3 個參數是字典,這里是一個空字典,表示沒有定義屬性和方法

補充:若Foo類有繼承,即class Foo(Bar):.... 則等同於type('Foo',(Bar,),{})

一個類沒有聲明自己的元類,默認他的元類就是type,除了使用元類type,用戶也可以通過繼承type來自定義元類 

 

自定制元類精簡版
class Mytype(type):
    def __init__(self,what,bases=None,dict=None):
        print(what,bases,dict)

    def __call__(self, *args, **kwargs):
        print('--->')
        obj=object.__new__(self)
        self.__init__(obj,*args,**kwargs)
        return obj
class Room(metaclass=Mytype):
    def __init__(self,name):
        self.name=name

r1=Room('alex')
print(r1.__dict__)


 


免責聲明!

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



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