1. 程式人生 > 實用技巧 >Python基礎學習筆記(23)繼承 類部分屬性的補充 方法和函式 利用 pickle 儲存物件

Python基礎學習筆記(23)繼承 類部分屬性的補充 方法和函式 利用 pickle 儲存物件

Python基礎學習(23)繼承 類部分屬性的補充 方法和函式 利用 pickle 儲存物件

一、今日大綱

  • 繼承
  • 類部分屬性的補充
  • 方法和函式
  • 利用 pickle 儲存物件

二、繼承的基本實現

面向物件有三大特性:繼承、封裝、多型;今天我們主要介紹繼承;假如我們要定義一個貓類和一個狗類,他們各自具有下面的繫結方法:

  • 貓:吃、喝、睡、爬樹

  • 狗:吃、喝、睡、看家

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

    def eat(self):
        print(f'{self.name} is eating.')

    def drink(self):
        print(f'{self.name} is drinking.')

    def sleep(self):
        print(f'{self.name} is sleeping.')

    def climb_tree(self):
        print(f'{self.name} is climbing the tree.')


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

    def eat(self):
        print(f'{self.name} is eating.')

    def drink(self):
        print(f'{self.name} is drinking.')

    def sleep(self):
        print(f'{self.name} is sleeping.')

    def guard_house(self):
        print(f'{self.name} is guarding the house.')


xiaobai = Cat('xiaobai')
xiaobai.eat()
xiaobai.drink()
xiaobai.climb_tree()
xiaohei = Dog('xiaohei')
xiaohei.eat()
xiaohei.drink()
xiaohei.guard_house()

根據上面的程式碼我們可以看到,由於貓狗兩個類由於大部分繫結方法比較相似,所以存在大量的重複程式碼,所以我們引入一個概念:繼承;它主要用於解決程式碼的重複問題,下面是繼承的基本實現方式:

class A:
    pass


class B(A):
    pass
    
# B繼承A,A是父類,B是子類
# A是父類 基類 超類
# B是子類 派生類

而根據繼承的思想,我們可以新定義一個父類:Pet,讓CatDog都繼承此類,從而達到解決程式碼重複問題的作用:

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

    def eat(self):
        print(f'{self.name} is eating.')

    def drink(self):
        print(f'{self.name} is drinking.')

    def sleep(self):
        print(f'{self.name} is sleeping.')


class Dog(Pet):
    def guard_house(self):
        print(f'{self.name} is guarding the house.')


class Cat(Pet):
    def climb_tree(self):
        print(f'{self.name} is climbing the tree.')


xiaobai = Cat('xiaobai')
xiaobai.eat()
xiaohei = Dog('xiaohei')
xiaohei.eat()

物件尋找方法的基本邏輯:在定義物件時,先開闢空間,空間裡存放一個類指標指向類;當呼叫某些方法時,物件會首先在自己的名稱空間中尋找,如果沒找到會藉助類指標去類的名稱空間中尋找,如果還未找到,則會藉助類指標去父類中進行尋找。

如果我們這時想給貓和狗類定義一個新的方法:吃貓糧/狗糧

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

    def eat(self):
        print(f'{self.name} is eating.')

    def drink(self):
        print(f'{self.name} is drinking.')

    def sleep(self):
        print(f'{self.name} is sleeping.')
        

class Dog(Pet):
    def guard_house(self):
        print(f'{self.name} is guarding the house.')

    def eat(self):
        print(f'{self.name}吃狗糧。')


class Cat(Pet):
    def climb_tree(self):
        print(f'{self.name} is climbing the tree.')

    def eat(self):
        print(f'{self.name}吃貓糧。')


# 這時在重新呼叫小白和小黑的吃方法
xiaobai = Cat('xiaobai')
xiaobai.eat()  # xiaobai吃貓糧。
xiaohei = Dog('xiaohei')
xiaohei.eat()  # xiaohei吃狗糧。

這時我們發現,程式碼又出現了重複,我們是否可以利用傳參實現把吃貓糧和吃狗糧合併稱為一個方法呢?我們新定義兩個屬性來更直觀地觀察實現方法:智力值、血量;貓吃了貓糧智力值會增加,狗吃了狗糧生命值會增加。

class Pet:
    def __init__(self, name, food):
        self.name = name
        self.food = food
        self.blood = 100
        self.wise = 100

    def eat(self):
        print(f'{self.name} is eating {self.food}.')

    def drink(self):
        print(f'{self.name} is drinking.')

    def sleep(self):
        print(f'{self.name} is sleeping.')


class Dog(Pet):
    def eat(self):
        self.blood += 100
        Pet.eat(self)  # 手動呼叫父類的方法,由於父類方法沒有例項化,必須吧self傳入

    def guard_house(self):
        print(f'{self.name} is guarding the house.')


class Cat(Pet):
    def eat(self):
        self.wise += 100
        Pet.eat(self)  # # 手動呼叫父類的方法,由於父類方法沒有例項化,必須吧self傳入

    def climb_tree(self):
        print(f'{self.name} is climbing the tree.')


xiaobai = Cat('xiaobai', '貓糧')
xiaobai.eat()  # xiaobai吃貓糧。
xiaohei = Dog('xiaohei', '狗糧')
xiaohei.eat()  # xiaohei吃狗糧。
print(xiaobai.wise, xiaohei.blood)  # 200 200

在子類和父類方法存在重名的時候:子類物件呼叫方法會按照子類 -> 父類的順序檢查,先檢查到的優先呼叫。而如果想同時呼叫子類和父類就要在子類中手動呼叫父類的方法,即新增Classname.func(self)

# 思考題1:
class Father:
    def __init__(self):
        self.func()

    def func(self):
        print('in father')


class Son(Father):
    def func(self):
        print('in son')
        Father.func(self)


Son()


# in son
# in father

# 思考題2:想給貓和狗定製個性屬性
# 貓有eye_color眼睛的顏色
# 狗有size大小
class Pet:
    def __init__(self, name, food):
        self.name = name
        self.food = food
        self.blood = 100
        self.wise = 100

    def eat(self):
        print(f'{self.name} is eating {self.food}.')

    def drink(self):
        print(f'{self.name} is drinking.')

    def sleep(self):
        print(f'{self.name} is sleeping.')


class Dog(Pet):
    def __init__(self, name, food, size):
        Pet.__init__(self, name, food)
        self.size = size

    def eat(self):
        self.blood += 100
        Pet.eat(self)  # 手動呼叫父類的方法,由於父類方法沒有例項化,必須吧self傳入

    def guard_house(self):
        print(f'{self.name} is guarding the house.')


class Cat(Pet):
    def __init__(self, name, food, eye_color):
        Pet.__init__(self, name, food)  # 呼叫了父類的初始化,去完成一些通用屬性的初始化
        self.eye_color = eye_color  # 派生屬性

    def eat(self):
        self.blood += 100
        Pet.eat(self)

    def climb_tree(self):
        print(f'{self.name} is climbing the tree.')


xiaobai = Cat('xiaobai', 'maoliang', 'blue')
xiaohei = Dog('xiaohei', 'gouliang', 'big')
print(xiaobai.__dict__)
print(xiaohei.__dict__)


# {'name': 'xiaobai', 'food': 'maoliang', 'blood': 100, 'wise': 100, 'eye_color': 'blue'}
# {'name': 'xiaohei', 'food': 'gouliang', 'blood': 100, 'wise': 100, 'size': 'big'}

繼承主要分為單繼承和多繼承:單繼承只繼承一個類;而多繼承可以繼承多個類;多繼承的呼叫邏輯如下:

# 單繼承: 只繼承一個類
# 調子類的:子類自己有的時候
# 調父類的:子類自己沒有的時候
# 調子類和父類的:子類父類都有,在子類中呼叫父類的
class A:
    def func(self): print('in A')


class B(A): pass


class C(B): pass


class D(C): pass


d = D()
d.func()  # in A
# 多繼承:繼承多個類
# 按照繼承順序,先繼承的先尋找,找到就停止
# 有一些語言不支援多繼承 java
# python語言的特點:可以在面向物件中支援多繼承

class A:
    def func(self): print('in A.')
class B:
    def func(self): print('in B.')

class C(A, B): pass
C().func()  # 繼承第一個傳入的類

三、類部分屬性的補充

  1. object

    所有在 Python3x 中的類,都是繼承 object 類的。一般按照引用的邏輯來講,我們定義一個類中未定義__init__的物件,發現並不會報錯,這就是因為物件的類中其實是繼承了object類的,所以我們以後在定義類的時候最好還是把object寫出來,雖然實際上可以省略,但是這是一個約定的寫法,即:

    class A: pass  # 等價於 class A(object): pass
    
    
    class B: pass
    
    
    class C(A, B): pass
    
    
    print(C.__bases__)  # (<class '__main__.A'>, <class '__main__.B'>)
    print(B.__bases__)  # (<class 'object'>,)
    
  2. 類屬性的補充

    # 類屬性的補充
    # class_name.__name__  # 類的名字
    # class_name.__doc__ # 類的文件字串
    # class_name.__base__  # 類的第一個父類
    # class_name.__bases__  # 類的所有父類構成的元組
    # class_name.__dict__  # 類的字典屬性
    # class_name.__module__  # 類定義所在的模組
    # object_name.__class__  # 例項對應的類
    
    class A: pass
    
    
    class B:
        """
        這個類主要是用來賣萌
        """
        pass
    
    
    class C(A, B): pass
    
    
    print(A.__base__)  # <class 'object'>
    print(B.__base__)  # <class 'object'>
    print(C.__bases__)  # (<class '__main__.A'>, <class '__main__.B'>)
    print(C.__class__)  # <class 'type'>
    print(C.__module__)  # __main__
    print(B.__doc__)  # 這個類主要是用來賣萌
    

四、函式和方法

# 繫結方法和普通的函式
from types import FunctionType, MethodType


# FunctionType: 函式
# MethodType: 方法 是對一個物件進行的操作
class A:
    def func(self):
        print('in func')


print(A.func)  # 函式 <function A.func at 0x0000019D33A1BD90>
a = A()
print(a.func)  # 方法 <bound method A.func of <__main__.A object at 0x0000019D33A1F780>>
print(isinstance(a.func, MethodType))  # True
print(isinstance(a.func, FunctionType))  # False
print(isinstance(A.func, MethodType))  # False
print(isinstance(A.func, FunctionType))  # True

# isinstance 和 type
# isinstance和type大部分時間用法是比較類似的
# 但是isinstance可以鑑別物件的類的父類,而type是不可以的
a = 1
b = 'abc'
print(isinstance(a, int))  # True
print(isinstance(a, float))  # False
print(isinstance(b, str))  # True
print(type(a) is int)  # True
print(type(b) is str)  # True


class Cat:
    pass


xiaobai = Cat()
print(type(xiaobai) is Cat)  # True
print(isinstance(xiaobai, Cat))  # True


class Animal: pass


class Cat(Animal): pass


xiaobai = Cat()
print(type(xiaobai) is Cat)  # True
print(type(xiaobai) is Animal)  # False
print(isinstance(xiaobai, Cat))  # True
print(isinstance(xiaobai, Animal))  # True

五、利用 pickle 儲存物件

class Course:
    def __init__(self, name, period, price):
        self.name = name
        self.period = period
        self.price = price

python = Course('python', '6 month', 21800)
linux = Course('python', '5 month', 19800)
go = Course('python', '4 month', 12800)
import pickle
with open('pickle_file', 'ab') as f:
    pickle.dump(python, f)
    pickle.dump(linux, f)
    pickle.dump(go, f)
with open('pickle_file', 'rb') as f:
    while True:
        try:
            ret = pickle.load(f)
            print(ret.__dict__)
        except EOFError:
            break
# {'name': 'python', 'period': '6 month', 'price': 21800}
# {'name': 'python', 'period': '5 month', 'price': 19800}
# {'name': 'python', 'period': '4 month', 'price': 12800}


# 在遊戲中儲存物件
# json不可以連續load連續dump
# json不可以dump和load物件(模組中沒有類的宣告,讀取過程中會出現報錯)
# AttributeError: Can't get attribute 'Course' on <module '__main__' from 'D:/Python/Python_Project/day24/03 pickle用法.py'>