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
,让Cat
和Dog
都继承此类,从而达到解决代码重复问题的作用:
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() # 继承第一个传入的类
三、类部分属性的补充
-
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'>,)
-
类属性的补充
# 类属性的补充 # 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'>