1. 程式人生 > >【Python】程式設計筆記7

【Python】程式設計筆記7

文章目錄

面向物件高階程式設計

高階特性:多重繼承、定製類、元類等概念。

一、slots

__slots__變數:限制該 class 例項能新增的屬性。

class Student(object):
    __slots__ = ('name', 'age')

s = Student()
s.name = 'Michael'
s.age = 25
s.score = 99
print(s.name)
print(s.age) print(s.score)

==》AttributeError 的錯誤

AttributeError: 'Student' object has no attribute 'score'

注意__slots__ 定義的屬性僅對當前類例項起作用,對繼承的子類是不起作用的。除非在子類中也定義 __slots__,這樣,子類例項允許定義的屬性就是自身的__slots__加上父類的__slots__。

class GraduateStudent(Student):
    pass
g = GraduateStudent()
g.score = 9999
print(g.score) ## 9999

二、@property裝飾器

負責把一個方法變成屬性呼叫的。

class Student(object):
    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

s = Student()
s.score = 60
print(s.score)
s.score = 999
print(s.score)

輸出結果

60
Traceback (most recent call last):
  File "E:/codes/python/basic/6.py", line 169, in <module>
    s.score = 999
  File "E:/codes/python/basic/6.py", line 163, in score
    raise ValueError('score must between 0 ~ 100!')
ValueError: score must between 0 ~ 100!

把一個 getter 方法變成屬性,只需要加上@property 就可以了,此時, @property 本身又建立了另一個裝飾器@score.setter,負責把一個 setter 方法變成屬性賦值。

三、多重繼承——MixIn

MixIn 的目:給一個類增加多個功能。

在設計類的時候,要優先考慮通過多重繼承來組合多個 MixIn 的功能,而不是設計多層次的複雜的繼承關係。

class Animal(object):
    pass
    
class RunnableMixIn(object):
    def run(self):
        print('Running...')
class Mammal(Animal):
    pass 
           
## Dog為多繼承,繼承於 Mammal 和 RunnableMixIn
class Dog(Mammal, RunnableMixIn):
    pass

四、定製類——__xxx__

Python 的 class 中還有許多這樣有特殊用途的函式,可以幫
助我們定製類。

1、__str__()__repr__()

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

print(Student('Michael'))

結果對比

  • 未自定義 __str__() 的結果
    <__main__.Student object at 0x000001994F615FD0>
  • 自定義 __str__() 的結果
    Student object (name: Michael)

在互動的模式下,直接輸出 s,打印出來的 <__main__.Student object at 0x109afb310> 也並不好看。

直接顯示變數呼叫的是 __repr__() 而非 __str__()
==》
區別:__str__()返回使用者看到的字串,而__repr__()返回程式開發者看到的字串,也就是說,__repr__()是為除錯服務的。
==》解決辦法:再定義一個__repr__()

2、__iter__()

__iter__():用於 for…in 迴圈,該函式返回一個迭代物件。

然後,Python 的 for 迴圈就不斷用該迭代物件的 __next__() 方法獲取迴圈的下一個值,直到遇到 StopIteration 錯誤時退出迴圈。

class Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1  # 初始化兩個計數器a,b

    def __iter__(self):
        return self            # 例項本身就是迭代物件,故返回自己

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b   # 計算下一個值
        if self.a > 100000:    # 退出迴圈的條件
            raise StopIteration
        return self.a

for n in Fib():
    print(n)

輸出結果

1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025

3、__getitem__()

可以按照下標取出元素 ==》__getitem__() 方法

class Fib(object):
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a, b = b, a + b
        return a
f = Fib()
print(f[0])
print(f[1])
print(f[100])

輸出結果

1
1
573147844013817084101

==》切片操作
原因:__getitem__() 傳入的引數可能是一個 int,也可能是一個切片物件 slice,所以要做判斷。

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int):    # n 是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice):   # n 是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L

f = Fib()
print(f[0:5])
print(f[:10])

輸出結果

[1, 1, 2, 3, 5]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

分析:沒有對 step 引數、負數等做處理。
==》將物件看出 dict
==》__getitem__() 的引數可能作 key 的 object。其對應的 __setitem__() 方法:將物件看作 list 或 dict 來對集合賦值;__delitem__() 方法:刪除某個元素。

4、__getattr__()

為了避免由於類的方法或屬性不在造成的 AttributeError 錯誤,使用 __getattr__() ,動態返回一個屬性。

class Student(object):
    def __init__(self):
        self.name = 'Michael'
    def __getattr__(self, attr):
        if attr == 'score':
            return 99
        elif attr == 'age':
        	return lambda:25
        # 若均不匹配,則丟擲 AttributeError 的錯誤
        raise AttributeError('\'Student\'object has no attribute \'%s\'' % attr)

==》當呼叫不存在的屬性時,比如 score, Python 直譯器會試圖呼叫__getattr__(self, ‘score’)來嘗試獲得屬性,從而可以返回score的值99。

常約定 class 只響應特定的幾個屬性,否則丟擲 AttributeError 的錯誤。

5、__call__()

對例項進行直接呼叫。

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self, *args, **kwargs):
        print('My name is %s' % self.name)

s = Student('Michael')
print(s())
# My name is Michael

判斷一個物件是否能被呼叫,若可被呼叫,則該物件是一個 Callable 物件——callable()

>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False

五、列舉類——Enum類

from enum import Enum

# Month型別的列舉類  定義
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
print(Month.Jun)

# 列舉所有成員,value屬性為int常量,預設從1開始計數
for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

精確控制列舉型別 ==》Enum的派生類

from enum import Enum, unique
@unique     # 該裝飾器確保無重複值
class Weekday(Enum):
    Sun = 0    # Sun 的 value 被設定為 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

## 列舉型別的訪問
day1 = Weekday.Mon
print(day1)
print(Weekday.Tue)   # 用成員名稱引用列舉常量
print(Weekday['Tue'])
print(Weekday.Tue.value)
print(day1 == Weekday.Mon)
print(day1 == Weekday.Tue)
print(Weekday(1))    # 根據 value 的值獲得列舉常量
print(day1 == Weekday(1))
# print(Weekday(7))
for name, member in Weekday.__members__.items():
    print(name, '=>', member)

既可以用成員名稱引用列舉常量,又可以直接根據 value 的值獲得列舉常量。

六、使用元類

1、type()

動態語言的函式和類的定義在執行時動態建立的。

type()函式既可以返回一個物件的型別,又可以創建出新的型別。
。。。。。

七、錯誤、除錯和測試

1、錯誤處理

處理機制:try…except…finally…(可以多個except)

如果執行出錯,則後續程式碼不會繼續執行,而是直接跳轉至錯誤處理程式碼,即 except 語句塊,執行完 except 後,如果有 finally 語句塊,則執行 finally 語句塊,至此,執行完畢。

若沒有錯誤,執行完try部分,不執行except部分而執行finally部分。

try:
    print('try...')
    r = 10 / int('a')
    print('result: ', r)
except ValueError as e:
    print('ValueError: ', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError: ', e)
finally:
    print('finally...')
print('END')

輸出結果

try...
ValueError:  invalid literal for int() with base 10: 'a'
finally...
END

Python 所有的錯誤都是從 BaseException 類派生的,常見的錯誤型別和繼承關係看這裡:
https://docs.python.org/3/library/exceptions.html#exception-hierarchy

2、呼叫堆疊

解讀錯誤資訊是定位錯誤的關鍵。我們從上往下可以看到整個錯誤的呼叫函式鏈。

3、記錄錯誤

import logging

def foo(s):
    return 10 / int(s)
def bar(s):
    return foo(s) * 2
def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)

main()
print('END')

4、丟擲錯誤

5、除錯

  • print()可能錯誤的變數值
  • 斷言(assert):assert 表示式, ‘輸出語句’==》表示式為真,繼續執行,否則丟擲 AssertionError錯誤並輸出後面的輸出語句。
  • logging輸出到檔案;
  • 偵錯程式 pdb,可以單步除錯

6、單元測試

用來對一個模組、一個函式或者一個類來進行正確性檢驗的測試工作。
==》確保一個程式模組的行為符合我們設計的測試用例。

測試用例:

  • 輸入正數;
  • 輸入負數;
  • 輸入0;
  • 輸入非數值型別