天天看點

python 類及繼承1.1 namespace (命名空間)2 類3 繼承4 多重繼承

目錄

1.1 namespace (命名空間)

1.2 屬性 

1.3 命名空間的生存期

 1.4 作用域

1.4.1 全局變量

1.4.2 局部變量

1.5 作用域和命名空間示例

2 類

2.1.1 類定義

2.1.2 類方法(可以先看後面)

2.2 類對象

2.2.1 屬性引用

2.2.2 執行個體化

2.3 執行個體對象

2.3.1 資料屬性

2.3.2 方法

2.4 方法對象

2.4.1  調用

2.5 類變量和執行個體變量

2.5.1 類變量共享(避免使用)

2.5.2 執行個體變量私有(通常用法)

3 繼承

4 多重繼承

4.1 父類調用

4.2 super() 功能

super() 擴充:

函數的裝飾器(由方法到函數)

1.1 namespace (命名空間)

定義:是一個從名字到對象的映射。(一般不用關注)

對象的屬性集合也是一種命名空間的形式。

1.2 屬性 

定義:任何跟在一個點号之後的名稱都稱為 屬性 --- 例如,在表達式 z.real 中,real 是對象 z 的一個屬性。

在表達式 modname.funcname 中,modname 是一個子產品對象而 funcname 是它的一個屬性。在此情況下在 子產品的屬性 和 子產品 中定義的全局名稱之間正好存在一個直覺的映射:它們共享相同的命名空間!

1.3 命名空間的生存期

不同時刻建立的命名空間擁有不同的生存期。

  1. 包含内置名稱的命名空間是在 Python 解釋器啟動時建立的,永遠不會被删除。((内置名稱實際上也存在于一個子產品中;這個子產品稱作  builtins 。))
  2. 子產品的全局命名空間在子產品定義被讀入時建立,也會持續到解釋器退出。(被解釋器的頂層調用執行的語句,從一個腳本檔案讀取或互動式地讀取,被認為是  __main__  子產品調用的一部分,是以它們擁有自己的全局命名空間。)
  3. 一個函數的本地命名空間在這個函數被調用時建立,并在函數傳回或抛出一個不在函數内部處理的錯誤時被删除。

 1.4 作用域

定義:一個 作用域 是一個命名空間可直接通路的 Python 程式的文本區域。

“可直接通路” 意味着對名稱的 非限定引用 會在 命名空間 中查找名稱。

  • 最先搜尋的最内部作用域包含局部名稱(函數内部)
  • 從最近的封閉作用域開始搜尋的任何封閉函數的範圍包含非局部名稱,也包括全局名稱
  • 倒數第二個作用域包含目前子產品的全局名稱
  • 最外面的範圍(最後搜尋)是包含内置名稱的命名空間

1.4.1 全局變量

如果一個名稱被聲明為全局變量,則所有引用和指派将直接指向包含該子產品的全局名稱的中間作用域。

1.4.2 局部變量

要重新綁定在最内層作用域以外找到的變量,可以使用 nonlocal 語句聲明為非本地變量。如果沒有被聲明為非本地變量,這些變量将是隻讀的(嘗試寫入這樣的變量隻會在最内層作用域中建立一個 新的 局部變量,而同名的外部變量保持不變)。( 類似于函數内部的變量)

通常,目前局部作為域将引用目前函數的局部名稱。 在函數以外,局部作用域将引用與全局作用域相一緻的命名空間:子產品的命名空間。 類 定義将在局部命名空間内再放置另一個命名空間。(在一個子產品内定義的函數的全局作用域就是該子產品的命名空間,無論該函數從什麼地方或以什麼别名被調用。)

如果不存在生效的 global 語句 -- 對名稱的指派總是進入最内層作用域。指派不會複制資料 --- 它們隻是将名稱綁定到對象(類似于指針)。

global 語句可被用來表明特定變量生存于全局作用域并且應當在其中被重新綁定;nonlocal 語句表明特定變量生存于外層作用域中并且應當在其中被重新綁定。

1.5 作用域和命名空間示例

def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)
           

輸出

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
           

将 spam 名稱(标簽)(可寫)挂到 "test spam" 對象(物品)(隻讀)上,通過輸出發現隻改變了最内層作用域的标簽。(意思大概是函數内部發生的事隻會影響函數内部的作用域以及作用域内的對象)

局部 指派(這是預設狀态)不會改變 scope_test 對 spam 的綁定。 nonlocal 指派會改變 scope_test 對 spam 的綁定,而 global 指派會改變子產品層級的綁定。

2 類

2.1.1 類定義

  1. 類定義與函數定義 (def 語句) 一樣必須 被執行 才會起作用。
  2. 在實踐中,類定義内的語句通常都是 函數定義 ,但也允許有其他語句
  3. 類内部的函數定義通常具有一種 特别形式的參數清單,這是方法調用的約定規範所指明的
  4. 進行類定義時,将建立一個新的命名空間,并将其用作局部作用域-- 是以,所有對局部變量的指派都是在這個新命名空間之内。 特别的,函數定義會綁定到這裡的新函數名稱。
  5. 離開類定義時,将建立一個 類對象(也就是類的名稱)。 這基本上是一個包圍在類定義所建立命名空間内容周圍的包裝器;

2.1.2 類方法(可以先看後面)

Python中3種方式定義類方法, 正常方式 (self), @classmethod修飾方式, @staticmethod修飾方式。

  • 普通的類方法,需要通過 self參數 隐式傳遞 目前類的執行個體對象。
  • @classmethod修飾的方法,需要傳遞目前類對象 參數cls(調用時可以不寫)。
  • @staticmethod修飾的方法,定義與普通函數是一樣的,不需要傳執行個體對象和類對象。

@classmethod,@staticmethod修飾方式差別:

  • @staticmethod不需要表示自身對象的self和自身類的cls參數,就跟使用函數一樣。
  • @classmethod也不需要self參數,但第一個參數需要表示自身類的cls參數。
  • 如果在@staticmethod中要調用到這個類的一些屬性方法,隻能直接類名.屬性名或類名.方法名。
  • 而@classmethod因為持有cls參數,可以來調用類的屬性,類的方法,執行個體化對象等,避免寫死。

簡單示例:

class A(object):
    bar = 1
    def foo(self):
        print('foo')

    @staticmethod
    def static_foo():
        print('static_foo')
        print(A.bar)

    @classmethod
    def class_foo(cls):
        print('class_foo')
        print(cls.bar)
        cls().foo()

A.static_foo()
A.class_foo()  

# 輸出結果
static_foo
1
class_foo
1
foo
           

2.2 類對象

類對象支援兩種操作:屬性引用 和 執行個體化 。

2.2.1 屬性引用

屬性引用 使用 Python 中所有屬性引用所使用的标準文法: obj.name。

class MyClass:
    """A simple example class"""
    i = 12345

    def f(self):
        return 'hello world'
           

MyClass.i , MyClass.f , MyClass.__doc__ 都是有效的屬性引用。

MyClass.i 傳回 12345 (一個int對象)

MyClass.f 傳回 <function __main__.MyClass.f(self)>(一個函數對象)

MyClass.__doc__ 傳回 “A simple example class” (一個字元串對象)

2.2.2 執行個體化

類的 執行個體化 使用 函數表示法 。 可以把類對象視為是傳回 該類的一個新執行個體的 不帶參數的函數。

x = MyClass()
           

建立類的新 執行個體 并将此對象配置設定給局部變量 x 。執行個體化操作(“調用”類對象)會建立一個 空對象 。

如果需要建立一個帶有初始狀态的執行個體(對象),需要在類定義時包含一個名為 __init__() 的特殊方法。

def __init__(self):
    self.data = []
           

__init__() 方法

當一個類定義了 __init__() 方法時,類的執行個體化操作會自動為新建立的類執行個體發起調用 __init__()。

class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart

x = Complex(3.0, -4.5)
x.r, x.i
           

x,r 傳回 3.0

x.i 傳回 -4.5

2.3 執行個體對象

執行個體對象了解的唯一操作是 屬性引用 。有兩種有效的屬性名稱,資料屬性 和 方法 。

2.3.1 資料屬性

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(x.counter)
del x.counter
           

print 輸出 16,通過 x.counter 建立 count 資料屬性,并通過 del 删除。

2.3.2 方法

另一類執行個體屬性引用稱為 方法 。 方法是“從屬于”對象的 函數 。函數一般與對象無關,像一些lambda函數、python内置函數等。

作用域:方法是通過執行個體化的對象進行方法的調用,調用後開辟的空間不會釋放,而函數則不同,函數執行完後,為其開辟的記憶體空間立即釋放(存儲到了棧裡)。(參考命名空間和作用域的生存期)

執行個體對象的有效方法名稱依賴于其所屬的類。一個類中所有是函數對象的屬性都是定義了其執行個體的相應方法。

在示例中,x.f 是有效的方法引用,因為 MyClass.f 是一個函數,而 x.i 不是方法,因為 MyClass.i 不是一個函數。 但是 x.f 與 MyClass.f 并不是一回事 --- 它是一個 方法對象,不是函數對象。(即類在執行個體化後,類中的函數對象會變成執行個體的方法對象)

2.4 方法對象

2.4.1  調用

函數是通過“函數名()”的方式調用,方法通過“對象.方法名()”的方式進行調用。

立即調用

x.f()
           

儲存調用

xf = x.f
while True:
    print(xf())
           

上面調用 x.f() 時并沒有帶參數,但是類定義中 f() 的函數定義指定了一個參數。

方法的特殊之處就在于 執行個體對象 會作為函數的第一個參數被傳入(即調用 x.f() 其實就相當于 MyClass.f(x))

總之,調用一個具有 n 個參數的方法就相當于調用 n+1 個參數的對應函數,執行個體對象為第一個參數值。

2.5 類變量和執行個體變量

一般來說,執行個體變量用于每個執行個體的唯一資料,而類變量用于類的所有執行個體共享的屬性和方法:

class Dog:

    kind = 'canine'         # 類變量

    def __init__(self, name):
        self.name = name    # 執行個體變量

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind                  # d,e共享
'canine'
>>> e.kind                  
'canine'
>>> d.name                  # d私有
'Fido'
>>> e.name                  # e私有
'Buddy'
           

2.5.1 類變量共享(避免使用)

class Dog:

    tricks = []             # 類變量 
    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # tricks[]被所有執行個體對象共享改動
['roll over', 'play dead']
           

2.5.2 執行個體變量私有(通常用法)

class Dog:

    def __init__(self, name):
        self.name = name
        self.tricks = []    # 在類執行個體化後建立一個執行個體變量

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks             # d私有一個tricks清單
['roll over']
>>> e.tricks             # e私有一個tricks清單
['play dead']
           

方法可以通過使用 self 參數的方法屬性調用其他方法:(通過self隐形傳遞,參見上2.1.2 類方法)

class Bag:
    def __init__(self):
        self.data = []

    def add(self, x):
        self.data.append(x)

    def addtwice(self, x):
        self.add(x)
        self.add(x)
           

每個值都是一個對象,是以具有 類 (也稱為 類型),并存儲為 object.__class__ 。(類似用type(object))

3 繼承

class DerivedClassName(BaseClassName):
           

或者 該基類來源自其他子產品

class DerivedClassName(modname.BaseClassName):
           

如果請求的屬性在 派生類 中找不到,搜尋将轉往 基類 中進行查找。 如果基類本身也派生自其他某個類,則此規則将被遞歸地應用。

派生類的執行個體化(與類執行個體化無差別): DerivedClassName() 會建立該類的一個新執行個體。

Python有兩個内置函數可被用于繼承機制:

  • 使用 isinstance() 來檢查一個執行個體的類型: isinstance(obj, int) 僅會在 obj.__class__ 為 int 或某個派生自 int 的類時為 True。
  • 使用 issubclass() 來檢查類的繼承關系: issubclass(bool, int) 為 True,因為 bool 是 int 的子類。 但是,issubclass(float, int) 為 False,因為 float 不是 int 的子類。

4 多重繼承

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>
           

按照深度優先,從左至右的 靜态 假設下:如果某一屬性在 DerivedClassName 中未找到,則會到 Base1 中搜尋它,然後(遞歸地)到 Base1 的基類中搜尋,如果在那裡未找到,再到 Base2 中搜尋,依此類推。

4.1 父類調用

super() 函數是用于調用父類(超類)的一個方法。super 是用來解決多重繼承問題的,直接用類名調用父類方法在使用單繼承的時候沒問題,但是如果使用多繼承,會涉及到查找順序(MRO)、重複調用(鑽石繼承)等種種問題。(MRO 就是類的方法解析順序表, 其實也就是繼承父類方法時的順序表。)

4.2 super() 功能

  • 方法擴充
  • 隔離更改 

内置類的 方法擴充 示例: 

class LoggingDict(dict):
    def __setitem__(self, key, value):
        logging.info('Settingto %r' % (key, value))
        super().__setitem__(key, value)
           

此類具有與其父項(dic)相同的功能,但它擴充了 [setitem] 方法,以在更新密鑰時建立日志條目。建立日志條目後,該方法使用 super() 來委派使用鍵/值對實際更新字典的工作。

在 super() 之前,我們将用dict._setitem_(self, key, value)硬連接配接。但是,super() 更好,因為它是間接引用。間接的一個好處是 隔離更改 ,我們不必按名稱指定 父類 。如果需要變動基類,則 super() 引用将自動跟随不用改動。

class LoggingDict(SomeOtherMapping):            # 新的基類
    def __setitem__(self, key, value):
        logging.info('Settingto %r' % (key, value))
        super().__setitem__(key, value)         # 不用改動
           

執行個體研究:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class FooParent(object):
    def __init__(self):
        self.parent = 'I\'m the parent.'
        print ('Parent')
    
    def bar(self,message):
        print ("%s from Parent" % message)
 
class FooChild(FooParent):
    def __init__(self):
        super(FooChild,self).__init__() 
       #等同super().__init__()    
        print ('Child')
        
    def bar(self,message):
        super(FooChild, self).bar(message)
       #等同super().bar(message)
        print ('Child bar fuction')
        print (self.parent)
 
if __name__ == '__main__':
    fooparent = FooParent()
    fooChild = FooChild()
    fooChild.bar('HelloWorld')
           

fooparent = FooParent()執行個體化

Parent
           

一個__init__函數初始化後的執行個體對象,沒有__init__函數則為空的執行個體對象

fooChild = FooChild() 執行個體化

Parent 
Child 
           

類似上面dict.__setitem__方法的擴充,該super(FooChild,self).__init__() 是對FooParent.__init__()的擴充,是以輸出兩個值。

fooChild.bar('HelloWorld') 屬性引用

HelloWorld from Parent
Child bar fuction
I’m the parent.
           

同樣的使用super().bar(message) 對FooParent.bar()方法的擴充。

super() 擴充:

函數的裝飾器(由方法到函數)

裝飾器仍然是一個 Python 函數,實作由 閉包 (不用改動)支撐,裝飾器的傳回值也是一個 函數對象。讓函數在無需修改任何代碼的前提下給其增加功能(類似super())。

def debug(func):
#定義外層 debug(func),return wrapper,然後定義内層wrapper(),return func

    def wrapper(): 
        #增加一些額外的功能
        #……
        print("[DEBUG]: enter {}()".format(func.__name__))
        #……
        return func() 
#執行額外的功能,再執行func----相當于被裝飾func多了這些額外的功能
    return wrapper


@debug

def say_hello():

    print("hello!")
           

常見功能

  • 計算函數的運作時間
  • 計算函數的運作次數
  • 給函數插入運作日志
  • 讓函數實作事務一緻性:讓函數要麼一起運作成功,要麼一起運作失敗
  • 實作緩存處理
  • 權限校驗:在函數外層套上權限校驗的代碼,實作權限校驗

計算函數運作時間示例:

def timer(func):
    def wrapper(*args, **kwargs):  #因為被裝飾sum()有多個參數
        start_time = time.time()
        res = func(*args, **kwargs)
        print("[Time out]: %.4f s" % (time.time() - start_time))
        return res
    return wrapper

#timer裝飾器,在運作sum()時,同時計算其運作時間,與sum()函數無關。
@timer     
def sum(a, b):
    time.sleep(2)
    return a + b

print("計算結果:", sum(1, 2))

# 運作結果:
[Time out]: 2.0019 s
計算結果: 3
           

計算函數運作次數示例:

import logging
counts= 0

def count(f):
    def wrapper(*args,**kwargs):
        global counts
        counts += 1
        result = f(*args,**kwargs)
        logging.warning("%s processed %s times!"%(f.__name__,counts))
        return result
    return wrapper 

@count
def hello(s):
    print(s)

hello("well")
hello("Hello!")
hello("Word!")

#輸出
WARNING:root:hello processed 1 times!
WARNING:root:hello processed 2 times!
WARNING:root:hello processed 3 times!
well
Hello!
Word!
           

運作日志裝飾器:

def trace_func(func):  
    def tmp(*args, **kargs): 
        #*args 可以傳入元組和清單,**kargs可以傳入字典,兩者都是可選參數
        print('Start %s(%s, %s)...' % (func.__name__, args, kargs) )
        return func(*args, **kargs)  
    return tmp  
@trace_func  
def log_test_with_empty_parameter():  
    pass  
@trace_func  
def log_test_with_many_parameter(a_int, b_string, c_list, d_dict):  
    pass  
@trace_func  
def log_test_with_key_parameter(a = 'www', b = 1, c = [1,2]):  
    pass  
if __name__ == '__main__':  
    log_test_with_empty_parameter()     
    log_test_with_many_parameter(1, 'wwww', [1,2,'c'], {1: 'a', 2 : 'ww'})  
    log_test_with_key_parameter(1, 'wwww', c = [3, 4]) 

#運作結果
Start log_test_with_empty_parameter((), {})...  
Start log_test_with_many_parameter((1, 'wwww', [1, 2, 'c'], {1: 'a', 2: 'ww'}), {})...  
Start log_test_with_key_parameter((1, 'wwww'), {'c': [3, 4]})...  
           

權限校驗裝飾器:

user_list = [
    {'name':'user1','passwd':'123'},
    {'name':'user2','passwd':'123'},
    {'name':'user3','passwd':'123'},
] 
#初始狀态,用來儲存登陸的使用者,
client_dic = {'username':None,'login':False}
#添加新功能
def auth_func(func):
    def wrapper(*args,**kwargs):
        #參數檢查,判斷是否有使用者登入,如果有不用驗證,直接執行函數的功能
        if client_dic['username'] and client_dic['login']:
            res = func(*args,**kwargs)
            return res 
        username = input('使用者名:').strip()
        passwd = input('passwd:').strip() 
        #對比清單,檢查使用者名和密碼是否正确
        for user_dic in user_list:
            if username == user_dic['name'] and passwd == user_dic['passwd']:
                client_dic['username'] = user_dic['name']
                client_dic['login'] = True
                res = func(*args,**kwargs)
                return res
        else:
            print('使用者名或者密碼錯誤!')
    return wrapper

@auth_func
def index():
    print("歡迎來到首頁") 

@auth_func
def home(name):
    print("歡迎回家:%s"%name) 

@auth_func
def shoppping_car():
    print('購物車裡有[%s,%s,%s]'%('面包','可樂','薯片'))

index()
home('root')
shoppping_car()
           

參考資料:https://rhettinger.wordpress.com/

https://docs.python.org/zh-cn/3/tutorial/classes.html

https://www.runoob.com/python/python-func-super.html

http://www.python88.com/topic/148/

https://www.cnblogs.com/junneyang/p/5332307.html

https://blog.csdn.net/qq_33531400/article/details/79324551

python 類及繼承1.1 namespace (命名空間)2 類3 繼承4 多重繼承

下次再見