day16-物件導向高階和應用

死不悔改奇男子發表於2024-04-21

1. 繼承【補充】

對於Python物件導向中的繼承,我們已學過:

  • 繼承存在意義:將公共的方法提取到父類中,有利於增加程式碼重用性。

  • 繼承的編寫方式:

    # 繼承
    class Base(object):
        pass
    
    class Foo(Base):
        pass
    
    # 多繼承
    class Base(object):
        pass
    
    class Bar(object):
        pass
    
    class Foo(Base,Bar):
        pass
    
  • 呼叫類中的成員時,遵循:

    • 優先在自己所在類中找,沒有的話則去父類中找。
    • 如果類存在多繼承(多個父類),則先找左邊再找右邊。

上述的知識點掌握之後,其實就可以解決繼承相關的大部分問題。

但如果遇到一些特殊情況(不常見),你就可能不知道怎麼搞了,例如:

image

image

image

1.1 mro和c3演算法

如果類中存在繼承關係,在可以透過mro()獲取當前類的繼承關係(找成員的順序)。

示例1:
image

mro(A) = [A] + [B,C]
mro(A) = [A,B,C]
mro(A) = [A] + merge( mro(B), mro(C), [B,C] )
mro(A) = [A] + merge( [object], [object], [] )
mro(A) = [A] + [B,C,object]
mro(A) = [A,B,C,object]
mro(A) = [A] + merge( mro(B), mro(C), [B,C] )
mro(A) = [A] + merge( [], [C], [,C] )
mro(A) = [A] + [B,C]
class C(object):
    pass

class B(object):
    pass

class A(B, C):
    pass

print( A.mro() )   # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]
print( A.__mro__ ) # (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>)

示例2:
image

mro(A) = [A] + merge( mro(B), mro(C), [B,C] )
mro(A) = [A] + merge( [], [D], [] )
mro(A) = [A] + [B,C,D]
mro(A) = [A,B,C,D]
class D(object):
    pass


class C(D):
    pass


class B(object):
    pass


class A(B, C):
    pass


print( A.mro() ) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>]

示例3:
image

mro(A) = [A] + merge( mro(B),mro(C),[B,C])
mro(A) = [A] + merge( [B,D], [C], [B,C])
mro(A) = [A] + merge( [], [C], [C])
mro(A) = [A,B,D,C]
class D(object):
    pass


class C(object):
    pass


class B(D):
    pass


class A(B, C):
    pass


print(A.mro()) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class 'object'>]

示例4:
image

mro(A) = [A] + merge( mro(B), mro(C), [B,C])

mro(A) = [A] + merge( [B,D], [C,D], [B,C])

mro(A) = [A] + [B,C,D] 
mro(A) = [A,B,C,D] 
class D(object):
    pass


class C(D):
    pass


class B(D):
    pass


class A(B, C):
    pass


print(A.mro()) # [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>]

示例5:

image

簡寫為:A -> B -> D -> G -> H -> K -> C -> E -> F -> M -> N -> P -> object
mro(A) = [A] + merge( mro(B),          mro(C),      mro(P),      [B,C,P])
                  []   [N]     [P]          [P]

mro(A) = [A,B,D,G,H,K,C,E,F,M,N,P]

-----------------------------------------------------
mro(B) = [B] + merge( mro(D), mro(E), [D,E])

mro(D) = [D] + merge(mro(G),mro(H), [G,H])

mro(G) = [G]

mro(H) = [H,K]

mro(B) = [B] + merge( [], [E,M], [E])
mro(B) = [B,D,G,H,K,E,M]


-----------------------------------------------------
mro(C) = [C] + merge(mro(E),mro(F),[E,F])

mro(E) = [E,M]

mro(F) = [F,M,N]

mro(C) = [C] + merge([M],[M,N] ,[])
mro(C) = [C,E,F,M,N]
class M:
    pass


class N:
    pass


class E(M):
    pass


class G:
    pass


class K:
    pass


class H(K):
    pass


class D(G, H):
    pass


class F(M, N):
    pass


class P:
    pass


class C(E, F):
    pass


class B(D, E):
    pass


class A(B, C, P):
    pass


print(A.mro()) # 簡寫為:A -> B -> D -> G -> H -> K -> C -> E -> F -> M -> N -> P -> object

特別補充:一句話搞定繼承關係

不知道你是否發現,如果用正經的C3演算法規則去分析一個類繼承關係有點繁瑣,尤其是遇到一個複雜的類也要分析很久。

所以,我自己根據經驗總結了一句話贈送給大家:從左到右,深度優先,大小鑽石,留住頂端,基於這句話可以更快的找到繼承關係。
image

簡寫為:A -> B -> D -> G -> H -> K -> C -> E -> F -> M -> N -> P -> object

1.2 py2和py3區別(瞭解)

概述:

  • 在python2.2之前,只支援經典類【從左到右,深度優先,大小鑽石,不留頂端】

  • 後來,Python想讓類預設繼承object(其他語言的物件導向基本上也都是預設都繼承object),此時發現原來的經典類不能直接整合整合這個功能,有Bug。

  • 所以,Python決定不再原來的經典類上進行修改了,而是再建立一個新式類來支援這個功能。【從左到右,深度優先,大小鑽石,留住頂端。】

    • 經典類,不繼承object型別

      class Foo:
          pass
      
    • 新式類,直接或間接繼承object

      class Base(object):
          pass
      
      class Foo(Base):
          pass
      
  • 這樣,python2.2之後 中就出現了經典類和新式類共存。(正式支援是2.3)

  • 最終,python3中丟棄經典類,只保留新式類。

詳細文件:
	https://www.python.org/dev/peps/pep-0253/#mro-method-resolution-order-the-lookup-rule
	https://www.python.org/download/releases/2.3/mro/

In classic Python, the rule is given by the following recursive function, also known as the left-to-right depth-first rule.

def classic_lookup(cls, name):
    if cls.__dict__.has_key(name):
        return cls.__dict__[name]
    for base in cls.__bases__:
        try:
            return classic_lookup(base, name)
        except AttributeError:
            pass
    raise AttributeError, name
    
The problem with this becomes apparent when we consider a "diamond diagram":

      class A:
        ^ ^  def save(self): ...
       /   \
      /     \
     /       \
    /         \
class B     class C:
    ^         ^  def save(self): ...
     \       /
      \     /
       \   /
        \ /
      class D
      

Arrows point from a subtype to its base type(s). This particular diagram means B and C derive from A, and D derives from B and C (and hence also, indirectly, from A).

Assume that C overrides the method save(), which is defined in the base A. (C.save() probably calls A.save() and then saves some of its own state.) B and D don't override save(). When we invoke save() on a D instance, which method is called? According to the classic lookup rule, A.save() is called, ignoring C.save()!

This is not good. It probably breaks C (its state doesn't get saved), defeating the whole purpose of inheriting from C in the first place.

Why was this not a problem in classic Python? Diamond diagrams are rarely found in classic Python class hierarchies. Most class hierarchies use single inheritance, and multiple inheritance is usually confined to mix-in classes. In fact, the problem shown here is probably the reason why multiple inheritance is unpopular in classic Python.

Why will this be a problem in the new system? The 'object' type at the top of the type hierarchy defines a number of methods that can usefully be extended by subtypes, for example __getattr__().

(Aside: in classic Python, the __getattr__() method is not really the implementation for the get-attribute operation; it is a hook that only gets invoked when an attribute cannot be found by normal means. This has often been cited as a shortcoming -- some class designs have a legitimate need for a __getattr__() method that gets called for all attribute references. But then of course this method has to be able to invoke the default implementation directly. The most natural way is to make the default implementation available as object.__getattr__(self, name).)

Thus, a classic class hierarchy like this:

class B     class C:
    ^         ^  def __getattr__(self, name): ...
     \       /
      \     /
       \   /
        \ /
      class D
      

will change into a diamond diagram under the new system:

      object:
        ^ ^  __getattr__()
       /   \
      /     \
     /       \
    /         \
class B     class C:
    ^         ^  def __getattr__(self, name): ...
     \       /
      \     /
       \   /
        \ /
      class D


and while in the original diagram C.__getattr__() is invoked, under the new system with the classic lookup rule, object.__getattr__() would be invoked!

Fortunately, there's a lookup rule that's better. It's a bit difficult to explain, but it does the right thing in the diamond diagram, and it is the same as the classic lookup rule when there are no diamonds in the inheritance graph (when it is a tree).

總結:Python2和Python3在關於物件導向的區別。

  • Py2:

    • 經典類,未繼承object型別。【從左到右,深度優先,大小鑽石,不留頂端】

      class Foo:
          pass
      
    • 新式類,直接獲取間接繼承object型別。【從左到右,深度優先,大小鑽石,留住頂端 -- C3演算法】

      class Foo(object):
          pass
      

      class Base(object):
        pass
      
      class Foo(Base):
        pass
      
  • Py3

    • 新式類,丟棄了經典類只保留了新式類。【從左到右,深度優先,大小鑽石,留住頂端 -- C3演算法】

      class Foo:
          pass
      
      class Bar(object):
          pass
      

2. 內建函式補充

本次要給講解的內建函式共8個,他們都跟物件導向的知識相關。

  • classmethod、staticmethod、property 。

  • callable,是否可在後面加括號執行。

    • 函式

      def func():
          pass
      
      print( callable(func) ) # True
      
    • class Foo(object):
          pass
      
      print( callable(Foo) ) # True
      
    • 類中具有__call__方法的物件

      class Foo(object):
      	pass
      
      obj = Foo()
      print( callable(obj) ) # False
      
      class Foo(object):
      
          def __call__(self, *args, **kwargs):
              pass
          
      obj = Foo()
      print( callable(obj) ) # True
      

    所以當你以後在見到下面的情況時,首先就要想到handler可以是:函式、類、具有call方法的物件 這三種,到底具體是什麼,需要根據程式碼的呼叫關係才能分析出來。

    def do_something(handler):
        handler()
    
  • super,按照mro繼承關係向上找成員。

    class Top(object):
        def message(self, num):
            print("Top.message", num)
            
    class Base(Top):
        pass
    
    class Foo(Base):
    
        def message(self, num):
            print("Foo.message", num)
            super().message(num + 100)
    
    
    obj = Foo()
    obj.message(1)
    
    >>> Foo.message 1
    >>> Top.message 101
    
    class Base(object):
    
        def message(self, num):
            print("Base.message", num)
            super().message(1000)
    
    
    class Bar(object):
    
        def message(self, num):
            print("Bar.message", num)
    
    
    class Foo(Base, Bar):
        pass
    
    
    obj = Foo()
    obj.message(1)
    
    >>> Base.message 1
    >>> Bar.message 1000
    

    應用場景

    假設有一個類,他原來已實現了某些功能,但我們想在他的基礎上再擴充套件點功能,重新寫一遍?比較麻煩,此時可以用super。

    info = dict() # {}
    info['name'] = "武沛齊"
    info["age"] = 18
    
    value = info.get("age")
    
    print(value)
    
    class MyDict(dict):
    
        def get(self, k):
            print("自定義功能")
            return super().get(k)
    
    
    info = MyDict()
    info['name'] = "武沛齊" # __setitem__
    info["age"] = 18       # __setitem__
    print(info)
    
    value = info.get("age")
    
    print(value)
    

image

  • type,獲取一個物件的型別。

    v1 = "武沛齊"
    result = type(v1)
    print(result) # <class 'str'>
    
    v2 = "武沛齊"
    print( type(v2) == str )  # True
    
    v3 = [11, 22, 33] # list(...)
    print( type(v3) == list )  # True
    
    class Foo(object):
        pass
    
    v4 = Foo()
    
    print( type(v4) == Foo )  # True
    
  • isinstance,判斷物件是否是某個類或其子類的例項。

    class Top(object):
        pass
    
    
    class Base(Top):
        pass
    
    
    class Foo(Base):
        pass
    
    
    v1 = Foo()
    
    print( isinstance(v1, Foo) )   # True,物件v1是Foo類的例項
    print( isinstance(v1, Base) )  # True,物件v1的Base子類的例項。
    print( isinstance(v1, Top) )   # True,物件v1的Top子類的例項。
    
    class Animal(object):
        def run(self):
            pass
    
    class Dog(Animal):
        pass
    
    class Cat(Animal):
        pass
    
    data_list = [
        "alex",
        Dog(),
        Cat(),
        "root"
    ]
    
    for item in data_list:
        if type(item) == Cat:
            item.run()
        elif type(item) == Dog:
            item.run()
        else:
            pass
        
    for item in data_list:
        if isinstance(item, Animal):
            item.run()
        else:
            pass
    
  • issubclass,判斷類是否是某個類的子孫類。

    class Top(object):
        pass
    
    
    class Base(Top):
        pass
    
    
    class Foo(Base):
        pass
    
    
    print(issubclass(Foo, Base))  # True
    print(issubclass(Foo, Top))   # True
    

3.異常處理

在程式開發中如果遇到一些 不可預知的錯誤 或 你懶得做一些判斷 時,可以選擇用異常處理來做。

import requests

while True:
    url = input("請輸入要下載網頁地址:")
    res = requests.get(url=url)
    with open('content.txt', mode='wb') as f:
        f.write(res.content)

上述下載影片的程式碼在正常情況下可以執行,但如果遇到網路出問題,那麼此時程式就會報錯無法正常執行。

try:
    res = requests.get(url=url)
except Exception as e:
    程式碼塊,上述程式碼出異常待執行。
print("結束")
import requests

while True:
    url = input("請輸入要下載網頁地址:")
    
    try:
        res = requests.get(url=url)
    except Exception as e:
        print("請求失敗,原因:{}".format(str(e)))
        continue
        
    with open('content.txt', mode='wb') as f:
        f.write(res.content)
num1 = input("請輸入數字:")
num2 = input("請輸入數字:")
try:
    num1 = int(num1)
    num2 = int(num2)
    result = num1 + num2
    print(result)
except Exception as e:
    print("輸入錯誤")

以後常見的應用場景:

  • 呼叫微信的API實現微信訊息的推送、微信支付等

  • 支付寶支付、影片播放等

  • 資料庫 或 redis連線和操作

  • 呼叫第三方的影片播放發的功能,由第三方的程式出問題導致的錯誤。

異常處理的基本格式:

try:
    # 邏輯程式碼
except Exception as e:
    # try中的程式碼如果有異常,則此程式碼塊中的程式碼會執行。
try:
    # 邏輯程式碼
except Exception as e:
    # try中的程式碼如果有異常,則此程式碼塊中的程式碼會執行。
finally:
    # try中的程式碼無論是否報錯,finally中的程式碼都會執行,一般用於釋放資源。

print("end")

"""
try:
    file_object = open("xxx.log")
    # ....
except Exception as e:
    # 異常處理
finally:
    file_object.close()  # try中沒異常,最後執行finally關閉檔案;try有異常,執行except中的邏輯,最後再執行finally關閉檔案。
"""

3.1 異常細分

import requests

while True:
    url = input("請輸入要下載網頁地址:")
    
    try:
        res = requests.get(url=url)
    except Exception as e:
        print("請求失敗,原因:{}".format(str(e)))
        continue
        
    with open('content.txt', mode='wb') as f:
        f.write(res.content)

之前只是簡單的捕獲了異常,出現異常則統一提示資訊即可。如果想要對異常進行更加細緻的異常處理,則可以這樣來做:

import requests
from requests import exceptions

while True:
    url = input("請輸入要下載網頁地址:")
    try:
        res = requests.get(url=url)
        print(res)    
    except exceptions.MissingSchema as e:
        print("URL架構不存在")
    except exceptions.InvalidSchema as e:
        print("URL架構錯誤")
    except exceptions.InvalidURL as e:
        print("URL地址格式錯誤")
    except exceptions.ConnectionError as e:
        print("網路連線錯誤")
    except Exception as e:
        print("程式碼出現錯誤", e)

# 提示:如果想要寫的簡單一點,其實只寫一個Exception捕獲錯誤就可以了。

如果想要對錯誤進行細分的處理,例如:發生Key錯誤和發生Value錯誤分開處理。

基本格式:

try:
    # 邏輯程式碼
    pass

except KeyError as e:
    # 小兵,只捕獲try程式碼中發現了鍵不存在的異常,例如:去字典 info_dict["n1"] 中獲取資料時,鍵不存在。
    print("KeyError")

except ValueError as e:
    # 小兵,只捕獲try程式碼中發現了值相關錯誤,例如:把字串轉整型 int("無誒器")
    print("ValueError")

except Exception as e:
    # 王者,處理上面except捕獲不了的錯誤(可以捕獲所有的錯誤)。
    print("Exception")

Python中內建了很多細分的錯誤,供你選擇。

常見異常:
"""
AttributeError 試圖訪問一個物件沒有的屬性,比如foo.x,但是foo沒有屬性x
IOError 輸入/輸出異常;基本上是無法開啟檔案
ImportError 無法引入模組或包;基本上是路徑問題或名稱錯誤
IndentationError 語法錯誤(的子類) ;程式碼沒有正確對齊
IndexError 下標索引超出序列邊界,比如當x只有三個元素,卻試圖訪問n x[5]
KeyError 試圖訪問字典裡不存在的鍵 inf['xx']
KeyboardInterrupt Ctrl+C被按下
NameError 使用一個還未被賦予物件的變數
SyntaxError Python程式碼非法,程式碼不能編譯(個人認為這是語法錯誤,寫錯了)
TypeError 傳入物件型別與要求的不符合
UnboundLocalError 試圖訪問一個還未被設定的區域性變數,基本上是由於另有一個同名的全域性變數,導致你以為正在訪問它
ValueError 傳入一個呼叫者不期望的值,即使值的型別是正確的
"""
更多異常:
"""
ArithmeticError
AssertionError
AttributeError
BaseException
BufferError
BytesWarning
DeprecationWarning
EnvironmentError
EOFError
Exception
FloatingPointError
FutureWarning
GeneratorExit
ImportError
ImportWarning
IndentationError
IndexError
IOError
KeyboardInterrupt
KeyError
LookupError
MemoryError
NameError
NotImplementedError
OSError
OverflowError
PendingDeprecationWarning
ReferenceError
RuntimeError
RuntimeWarning
StandardError
StopIteration
SyntaxError
SyntaxWarning
SystemError
SystemExit
TabError
TypeError
UnboundLocalError
UnicodeDecodeError
UnicodeEncodeError
UnicodeError
UnicodeTranslateError
UnicodeWarning
UserWarning
ValueError
Warning
ZeroDivisionError
"""

3.2 自定義異常&丟擲異常

上面都是Python內建的異常,只有遇到特定的錯誤之後才會丟擲相應的異常。

其實,在開發中也可以自定義異常。

class MyException(Exception):
    pass
try:
    pass
except MyException as e:
    print("MyException異常被觸發了", e)
except Exception as e:
    print("Exception", e)

上述程式碼在except中定義了捕獲MyException異常,但他永遠不會被觸發。因為預設的那些異常都有特定的觸發條件,例如:索引不存在、鍵不存在會觸發IndexError和KeyError異常。

對於我們自定義的異常,如果想要觸發,則需要使用:raise MyException()類實現。

class MyException(Exception):
    pass


try:
    # 。。。
    raise MyException()
    # 。。。
except MyException as e:
    print("MyException異常被觸發了", e)
except Exception as e:
    print("Exception", e)
class MyException(Exception):
    def __init__(self, msg, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.msg = msg


try:
    raise MyException("xxx失敗了")
except MyException as e:
    print("MyException異常被觸發了", e.msg)
except Exception as e:
    print("Exception", e)
class MyException(Exception):
    title = "請求錯誤"


try:
    raise MyException()
except MyException as e:
    print("MyException異常被觸發了", e.title)
except Exception as e:
    print("Exception", e)

案例一:你我合作協同開發,你呼叫我寫的方法。

  • 我定義了一個函式

    class EmailValidError(Exception):
        title = "郵箱格式錯誤"
    
    class ContentRequiredError(Exception):
        title = "文字不能為空錯誤"
        
    def send_email(email,content):
        if not re.match("\w+@live.com",email):
            raise EmailValidError()
        if len(content) == 0 :
            raise ContentRequiredError()
        # 傳送郵件程式碼...
        # ...
    
  • 你呼叫我寫的函式

    def execute():
        # 其他程式碼
        # ...
        
        try:
            send_email(...)
        except EmailValidError as e:
            pass
        except ContentRequiredError as e:
            pass
        except Exception as e:
            print("傳送失敗")
    
    execute()
    
    # 提示:如果想要寫的簡單一點,其實只寫一個Exception捕獲錯誤就可以了。
    

案例二:在框架內部已經定義好,遇到什麼樣的錯誤都會觸發不同的異常。

import requests
from requests import exceptions

while True:
    url = input("請輸入要下載網頁地址:")
    try:
        res = requests.get(url=url)
        print(res)    
    except exceptions.MissingSchema as e:
        print("URL架構不存在")
    except exceptions.InvalidSchema as e:
        print("URL架構錯誤")
    except exceptions.InvalidURL as e:
        print("URL地址格式錯誤")
    except exceptions.ConnectionError as e:
        print("網路連線錯誤")
    except Exception as e:
        print("程式碼出現錯誤", e)
        
# 提示:如果想要寫的簡單一點,其實只寫一個Exception捕獲錯誤就可以了。

案例三:按照規定去觸發指定的異常,每種異常都具備被特殊的含義。

image

3.4 特殊的finally

try:
    # 邏輯程式碼
except Exception as e:
    # try中的程式碼如果有異常,則此程式碼塊中的程式碼會執行。
finally:
    # try中的程式碼無論是否報錯,finally中的程式碼都會執行,一般用於釋放資源。

print("end")

當在函式或方法中定義異常處理的程式碼時,要特別注意finally和return。

def func():
    try:
        return 123
    except Exception as e:
        pass
    finally:
        print(666)

func()

在try或except中即使定義了return,也會執行最後的finally塊中的程式碼。

練習題

  1. 補充程式碼實現捕獲程式中的錯誤。

    # 迭代器
    class IterRange(object):
        def __init__(self, num):
            self.num = num
            self.counter = -1
    
        def __iter__(self):
            return self
    
        def __next__(self):
            self.counter += 1
            if self.counter == self.num:
                raise StopIteration()
            return self.counter
    
        
    obj = IterRange(20)
    
    while True:
        try:
             ele = next(obj)
        except StopIteration as e:
             print("資料獲取完畢")
             break
        print(ele)
        
    
  2. 補充程式碼實現捕獲程式中的錯誤。

    class IterRange(object):
        def __init__(self, num):
            self.num = num
            self.counter = -1
    
        def __iter__(self):
            return self
    
        def __next__(self):
            self.counter += 1
            if self.counter == self.num:
                raise StopIteration()
            return self.counter
    
    
    class Xrange(object):
        def __init__(self, max_num):
            self.max_num = max_num
    
        def __iter__(self):
            return IterRange(self.max_num)
    
    
    data_object = Xrange(100)
    obj_iter = data_object.__iter__()
    
    while True:
        try:
            ele = next(obj_iter)
        except StopIteration as e:
            print("資料獲取完畢")
            break
        print(ele)
    
  3. 補充程式碼實現捕獲程式中的錯誤。

    def func():
        yield 1
        yield 2
        yield 3
        
    gen = func()
    while True:
        try:
            ele = next(gen)
        except StopIteration as e:
            print("資料獲取完畢")
            break
        print(ele)
    
  4. 補充程式碼實現捕獲程式中的錯誤。(注意:本案例用於練習,在真實開發中對於這種情況建議還是自己做判斷處理,不要用異常)

    num = int("武沛齊")
    
    try:
        num = int("武沛齊")
    except ValueError as e:
        print("轉換失敗")
    
  5. 補充程式碼實現捕獲程式中的錯誤。(注意:本案例用於練習,在真實開發中對於這種情況建議還是自己做判斷處理,不要用異常)

    data = [11,22,33,44,55]
    data[1000]
    
    try:
        data = [11,22,33,44,55]
        data[1000]
    except IndexError as e:
        print("轉換失敗")
    
  6. 補充程式碼實現捕獲程式中的錯誤。(注意:本案例用於練習,在真實開發中對於這種情況建議還是自己做判斷處理,不要用異常)

    data = {"k1":123,"k2":456}
    data["xxxx"]
    
    try:
        data = {"k1":123,"k2":456}
        data["xxxx"]
    except KeyError as e:
        print("轉換失敗")
    
  7. 分析程式碼,寫結果

    class MyDict(dict):
    
        def __getitem__(self, item):
            try:
                return super().__getitem__(item) # KeyError
            except KeyError as e:
                return None
    
    
    info = MyDict()
    info['name'] = "武沛齊"
    info['wx'] = "wupeiq666"
    
    print(info['wx'])     # wupeiq666     info['wx']  -> __getitem__
    print(info['email'])  # None      info['email']  -> __getitem__
    
  8. 看程式碼寫結果

    def run(handler):
        try:
            num = handler()
            print(num)
            return "成功"
        except Exception as e:
            return "錯誤"
        finally:
            print("END")
    
        print("結束")
    
    
    res = run(lambda: 123)
    print(res)
    
    >>> 123
    >>> END
    >>> 成功
    
    def func():
        print(666)
        return "成功"
    
    def run(handler):
        try:
            num = handler()
            print(num)
            return func()
        except Exception as e:
            return "錯誤"
        finally:
            print("END")
    
        print("結束")
    
    
    res = run(lambda: 123)
    print(res)
    
    >>> 123
    >>> 666
    >>> END
    >>> 成功
    

4.反射

反射,提供了一種更加靈活的方式讓你可以實現去 物件 中操作成員(以字串的形式去 物件 中進行成員的操作)。

class Person(object):
    
    def __init__(self,name,wx):
        self.name = name
        self.wx = wx
	
    def show(self):
        message = "姓名{},微信:{}".format(self.name,self.wx)
        
        
user_object = Person("武沛齊","wupeiqi666")


# 物件.成員 的格式去獲取資料
user_object.name
user_object.wx
user_object.show()

# 物件.成員 的格式去設定資料
user_object.name = "吳培期"
user = Person("武沛齊","wupeiqi666")

# getattr 獲取成員
getattr(user,"name") # user.name
getattr(user,"wx")   # user.wx


method = getattr(user,"show") # user.show
method()
# 或
getattr(user,"show")()

# setattr 設定成員
setattr(user, "name", "吳培期") # user.name = "吳培期"

Python中提供了4個內建函式來支援反射:

  • getattr,去物件中獲取成員

    v1 = getattr(物件,"成員名稱")
    v2 = getattr(物件,"成員名稱", 不存在時的預設值)
    
  • setattr,去物件中設定成員

    setattr(物件,"成員名稱",值)
    
  • hasattr,物件中是否包含成員

    v1 = hasattr(物件,"成員名稱") # True/False
    
  • delattr,刪除物件中的成員

    delattr(物件,"成員名稱")
    

以後如果再遇到 物件.成員 這種編寫方式時,均可以基於反射來實現。

案例:

class Account(object):

    def login(self):
        pass

    def register(self):
        pass

    def index(self):
        pass

    
def run(self):
    name = input("請輸入要執行的方法名稱:") # index register login xx run ..
    
    account_object = Account()
    method = getattr(account_object, name,None) # index = getattr(account_object,"index")
    
    if not method:
        print("輸入錯誤")
        return
    method()

4.1 一切皆物件

在Python中有這麼句話:一切皆物件。 每個物件的內部都有自己維護的成員。

  • 物件是物件

    class Person(object):
        
        def __init__(self,name,wx):
            self.name = name
            self.wx = wx
    	
        def show(self):
            message = "姓名{},微信:{}".format(self.name,self.wx)
            
            
    user_object = Person("武沛齊","wupeiqi666")
    user_object.name
    
  • 類是物件

    class Person(object):
        title = "武沛齊"
    
    Person.title
    # Person類也是一個物件(平時不這麼稱呼)
    
  • 模組是物件

    import re
    
    re.match
    # re模組也是一個物件(平時不這麼稱呼)。
    

由於反射支援以字串的形式去物件中操作成員【等價於 物件.成員 】,所以,基於反射也可以對類、模組中的成員進行操作。

簡單粗暴:只要看到 xx.oo 都可以用反射實現。

class Person(object):
    title = "武沛齊"

v1 = Person.title
print(v1)
v2 = getattr(Person,"title")
print(v2)
import re

v1 = re.match("\w+","dfjksdufjksd")
print(v1)

func = getattr(re,"match")
v2 = func("\w+","dfjksdufjksd")
print(v2)

4.2 import_module + 反射

在Python中如果想要匯入一個模組,可以透過import語法匯入;其實也可以透過字串的形式匯入。

示例一:

# 匯入模組
import random

v1 = random.randint(1,100)
# 匯入模組
from importlib import import_module

m = import_module("random")

v1 = m.randint(1,100)

示例二:

# 匯入模組exceptions
from requests import exceptions as m
# 匯入模組exceptions
from importlib import import_module
m = import_module("requests.exceptions")

示例三:

# 匯入模組exceptions,獲取exceptions中的InvalidURL類。
from requests.exceptions import InvalidURL
# 錯誤方式
from importlib import import_module
m = import_module("requests.exceptions.InvalidURL") # 報錯,import_module只能匯入到模組級別。
# 匯入模組
from importlib import import_module
m = import_module("requests.exceptions")
# 去模組中獲取類
cls = m.InvalidURL

在很多專案的原始碼中都會有 import_modulegetattr 配合實現根據字串的形式匯入模組並獲取成員,例如:

from importlib import import_module

path = "openpyxl.utils.exceptions.InvalidFileException"

module_path,class_name = path.rsplit(".",maxsplit=1) # "openpyxl.utils.exceptions"   "InvalidFileException"

module_object = import_module(module_path)

cls = getattr(module_object,class_name)

print(cls)

總結

  1. 瞭解 mro和c3演算法

  2. python2和python3在物件導向中的區別。

  3. 內建函式

    staticmethod,classmethod,property,callable,type,isinstance,issubclass,super
    getattr,setattr,hasattr,delattr
    
  4. 異常處理

  5. 根據字串的形式匯入模組 import_module

  6. 根據字串的形式操作成員 反射-getattr,setattr,hasattr,delattr

作業

  1. super的作用?
根據類繼承關係向上尋找成員
  1. 看圖分析類A的繼承關係
    image
繼承關係:A->B->C->M->D->F->J->G->H
  1. 看程式碼寫結果

    class Foo(object):
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        @property
        def message(self):
            return "{}-{}".format(self.name, self.age)
    
    
    class Bar(Foo):
        def __init__(self, email, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.email = email
    
        def total(self):
            data = "{}-{}-{}".format(self.name, self.age, self.email)
            return data
    
    
    obj1 = Foo("武沛齊", 20)
    print(obj1.message)	# 武沛齊-20
    
    obj2 = Bar("xx@live.com", "root", 100)
    print(obj2.message)	# root-100
    print(obj2.total())	# root-100-xx@live.com
    
  2. 看程式碼寫結果

    class Foo(object):
        def __call__(self, *args, **kwargs):
            return 666
        
    data_list = [
        "武沛齊",
        dict,
    	lambda :123,
        True,
        Foo,
        Foo()
    ]
    
    for item in data_list:
        if callable(item):
            val = item()
            print(val)
        else:
            print(item)
     
     >>> 武沛齊
     >>> {}
     >>> 123
     >>> True
     >>> <__main__.Foo object at 0x0000022DC5259550>
     >>> 666
    
  3. 如何主動觸發一個異常?

raise 異常類(...)
  1. 反射的作用?
以字串的形式去物件中操作成員
  1. 看程式碼寫結果

    class Person(object):
        title = "北京"
    
        def __init__(self, name, wx):
            self.name = name
            self.wx = wx
    
        def show(self):
            message = "姓名{},微信:{}".format(self.name, self.wx)
            return message
    
        @property
        def message(self):
            return 666
    
        @staticmethod
        def something():
            return 999
    
    
    obj = Person("武沛齊", "wupeiqi666")
    
    print(getattr(obj, 'wx'))   # wupeiqi666
    print(getattr(obj, 'message'))  # 666
    print(getattr(obj, 'show')())   # 姓名武沛齊,微信:wupeiqi666
    print(getattr(obj, 'something')())  # 999
    

相關文章