6.面向对象基础(中)
1、析构方法
析构方法的概述(魔术方法):当一个对象被删除或者被销毁时,Python解释器会默认调用一个方法。
这个方法为__del__()方法,也称为析构方法。
# 析构方法的定义
class Animal(object):
def __init__(self, name): # 构造初始化的方法
self.name = name
print("__init__()方法被调用")
pass
# 析构方法,当对象被销毁时Python解释器会自动调用
def __del__(self):
print("__del__()方法被调用")
print("%s 对象被销毁" % self.name)
dog = Animal("Dog") # 被自动清理
析构方法总结(垃圾回收的机制):
1、当整个程序脚本执行完毕后会自动调用__del__()方法;
2、当对像被手动销毁时也会自动调用 __del__()方法;
3、析构函数一般用于资源回收,利用__del__()方法销毁对象回收内存等资源;
class Animal(object):
def __init__(self, name):
self.name = name
print('这是构造初始化__init__()方法')
pass
def __del__(self):
# 主要的应用就是来操作对象的释放,一旦释放完毕,对象便不能在使用。
print('当在某个作用域下面,没有被使用【引用】的情况下,解析器会自动的调用此函数,来释放内存空间')
print('这是析构__del__()方法')
print('%s 这个对象,被彻底清理了,内存空间也释放了' % self.name)
pass
cat = Animal('小花猫')
del cat # 手动的去清理删除对象 会执行__del__()函数
# print(cat.name) # del后就不能使用cat对象
# input('程序等待中.....') # 阻止程序结束
# print('*'*40)
# dog = Animal('柯基小狗')
# print(dog.name)
2、单继承
在Python中展现面向对象的三大特征:继承、多态、封装。
封装:把内容封装到某个地方,便于后期的在另外一个地方使用(调用)。其实就是使用初始化将内容封装到对象中,然后通过对象直接或self来获取被封装的内容。
多态:定义时的类型和运行时的类型不一样,此时就成为多态。(以封装和继承为前提)不同的子类调用相同的方法,产生不同的结果(多态:顾名思义就是多种状态、形态,就是同一种行为,对于不同的子类【对象】有不同的行为表现)
继承:子类可以继承父类的内容(属性、方法)。
1.对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法,提过效率,精简代码层级,便于扩展。
2.在定义子类时要继承父类,只需要类名后面的小括号()中写上父类的名字,那么父类的属性、方法,会被继承给子类。
class 类名(父类):
子类可以继承父类中公共的属性和方法
pass
# 简单的继承(单继承)
class Animal(object):
def __init__(self, name, color, kind):
self.name = name
self.color = color
self.kind = kind
def eat(self, eat_food):
print("%s是一只%s的%s,正在吃%s。" %
(self.name, self.color, self.kind, eat_food))
def drink(self, drink_food):
print("%s是一只%s的%s,正在喝%s。" %
(self.name, self.color, self.kind, drink_food))
# Cat类继承Animal类,Cat是子类,也称派生类,Animal类是父类,也称为基类
class Cat(Animal): # 继承animal父类,此时dog就是子类
def miao(self): # 不同的地方
print("喵喵喵~~~")
print(self.name)
class Dog(Animal):
def wang(self): # 不同的地方(只关注子类独有的实现)
print("汪汪汪~~~")
cat = Cat("小花", "白色", "小猫")
cat.eat("小鱼干") # 具备了吃的方法(继承了父类的方法)
cat.drink("矿泉水") # 具备了喝的方法(继承了父类的方法)
cat.miao() # Cat类中的方法
print(cat.__dict__)
print(Cat.__mro__)
print("*"*15)
dog = Dog("旺财", "黄色", "小狗")
dog.eat("火腿肠")
dog.drink("鱼汤")
dog.wang()
print(dog.__dict__) # 查看实例对象中的变量
print(Dog.__mro__) # 查看某个类的继承关系
3、多继承
多继承的概念:子类可以继承一个父类(单继承),也可以继承多个父类(多继承)。C类可以继承A、B两个类,可以将A、B中的方法继承给过来,C拥有A、B的属性和方法。
class A(object):
def a(self):
print("A类的方法a输出!")
class B(object):
def b(self):
print("B类的方法b输出!")
# 在小括号里有多个父类名字就是多继承
class C(A, B):
pass # 在C中并没有写方法,只是继承了A、B两个父类。
c = C()
c.a() # C可以调用A父类中的方法a()
c.b() # C可以调用B父类中的方法b()
print(C.__mro__)
class ShenXian(object):
def fly(self):
print("神仙都会飞!")
class Monkey(object):
def chitao(self):
print("猴子喜欢吃桃!")
class SunWuKong(ShenXian, Monkey): # 既是神仙,也是猴子
def thing(self):
print("齐天大圣孙悟空大闹天空")
m = SunWuKong()
m.chitao() # 继承Monkey类的方法
m.fly() # 继承ShenXian类的方法
m.thing() # 调用自己的方法
print(SunWuKong.__mro__)
# 当多个父类中存在相同的方法时的调用,通过 类名.__mro__ 查看继承顺序(魔术方法)
class D(object):
def eat(self): # 父类中存在相同的方法
print('D.eat')
pass
pass
class C(D): # 单继承
def eat(self): # 父类中存在相同的方法(方法覆盖,重写了父类的方法)
print('C.eat')
pass
pass
class B(D): # 单继承
pass
class A(B, C): # 多继承
pass
a = A()
a.eat()
print(A.__mro__) # 可以显示类的依次继承关系(魔术方法)
# 在执行eat的方法时 查找方法的顺序是
# 首先到A里面去查找 如果A中没有 则继续的去B类中去查找 如果B中没有
# 则去C中查找 如果C类中没有 则去D类中去查找,如果还是没有找到 就会报错
# A->B->C->D 是继承的顺序
# 子类的继承顺序不同
class A(object):
def fun(self):
print('A class fun here')
class B(object):
def fun(self):
print('B class fun here')
class C(A, B):
pass
class D(B, A):
pass
c = C() # C->A->B
c.fun()
print(C.__mro__)
# 输出 A class fun here
d = D() # D->B->A
d.fun()
print(D.__mro__)
# 输出 B class fun here
# 重写父类方法
class Base(object):
def test(self):
print('- - - Base test - - -')
class Base1(object):
def test(self):
print('- - - Base1 test - - -')
class A(Base):
def test(self):
print('- - - A test - - -')
class B(Base1):
def test(self): # 与父类的test()同名方法,这就是重写父类方法
print('- - - B test - - -')
class C(A, B):
pass
c = C()
c.test()
print(C.__mro__)
4、继承的传递
类的传递过程中,把父类又称为基类,子类又称为派生类,父类的属性和方法可以一级一级的传递到子类,在设计程序结构的时候一般会不超过三次继承。
案例中的继承关系:Son类继承Father类,Father类继承并没有提供eat()方法,但是Father类继承GrandFather类中有eat()方法。因此,Son类的实例对象lg可以正常调用并执行eat()方法,这就是继承的传递性。
# 间接的级联继承
class GrandFather(object):
def eat(self):
print("爷爷经常吃大豆。")
class Father(GrandFather):
pass
class Son(Father):
pass
lg = Son()
lg.eat()
print(Son.__mro__)
kw = Father()
kw.eat()
print(Father.__mro__)
5、重写父类方法
所谓重写,就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法。
class Father(object):
def chouyan(self):
print("抽芙蓉王!")
def hejiu(self):
print("喝二锅头!")
class Son(Father):
# 与父类chouyan()同名的方法,这就是重写父类方法
def chouyan(self):
print("抽中华!")
# 重写父类方法后,子类调用该方法时就是调用子类中的方法
son = Son()
son.chouyan()
print(Son.__mro__)
class GrandFather(object):
def eat(self):
print("爷爷经常吃大豆。")
class Father(GrandFather):
def eat(self): # 与父类的eat()同名方法,这就是重写父类方法(方法覆盖)
print("爸爸经常吃海鲜。")
class Son(Father):
pass
lg = Son()
lg.eat()
print(Son.__mro__)
重写和调用父类方法:
1.所谓重写,就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法;
2.原因是父类的方法已经不满足子类的需要,那么子类就可以重写父类或者完善父类的方法;
class Dog(object):
def wang(self):
print("汪汪叫~~~")
class KeJi(Dog):
def wang(self): # 重写父类的方法
print("嗷嗷叫~~~")
kj = KeJi()
kj.wang()
print(KeJi.__mro__)
6、调用父类方法
如果在子类中有一个方法需要父类的功能,并且又要添加新的功能,如果直接重写父类方法,那么就要重复写很多代码,此时这就要调用父类方法。
# 1.默认情况下不能调用父类的构造方法,因此会报错
class Dog(object):
def __init__(self, name, color): # 这是父类的构造方法(初始化方法)
self.name = name
self.color = color
def wang(self):
print("汪汪叫~~~")
class KeJi(Dog):# 子类中没有声明__init__()方法
def wang(self): # 重写父类的方法
print("嗷嗷叫~~~")
# 子类中没有__init__(self, name, color),所以默认情况下会调用父类中的__init__(self, name, color)
kj = KeJi() # 报错:__init__() missing 2 required positional arguments
kj.wang()
# 2.在子类中进行了重写,因此不会报错
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
def wang(self):
print("汪汪叫~~~")
class KeJi(Dog):
def __init__(self): # 定义自己的__init__()方法(子类重写父类的初始化方法)
pass
def wang(self): # 重写父类的方法
print("嗷嗷叫~~~")
kj = KeJi()
kj.wang()
# 3.在子类中对__init__()方法进行了重写,因此不会去调用父类里面已经声明的变量
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
def wang(self):
print("汪汪叫~~~")
class KeJi(Dog):
def __init__(self): # 重写父类的初始化方法
pass
def wang(self): # 重写父类的方法
print("嗷嗷叫~~~")
print(self.name) # 使用父类中已经声明的变量(不会去调用),针对这种需求,就要去调用父类的函数
kj = KeJi()
kj.wang()
# 4.在子类中对__init__()方法不重写,可以调用父类里面已经声明的变量
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
def wang(self):
print("汪汪叫~~~")
class KeJi(Dog):
def wang(self): # 重写父类的方法
print("嗷嗷叫~~~")
print(self.name) # 使用父类中已经声明的变量
print(self.color) # 使用父类中已经声明的变量
kj = KeJi("柯基犬", "棕色") # 实例化时需要两个参数
kj.wang()
# 5.调用父类的方法一:父类名.__init__(self, xxx, xxx)
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
def wang(self):
print("汪汪叫~~~")
class KeJi(Dog):
def __init__(self, name, color): # 重写父类的初始化方法
# 需要调用父类的函数
# 调用父类中的构造函数,此时就具备(继承)了name,color两个实例属性
Dog.__init__(self, name, color) # 调用父类的方法,执行完毕就具备name,color属性
pass
def wang(self): # 重写父类的方法
print("嗷嗷叫~~~")
print(self.name)
print(self.color)
kj = KeJi("柯基犬", "棕色") # 需要两个参数
kj.wang()
# 6.调用父类的方法一:父类名.__init__(self, xxx, xxx)扩展:添加新的实例属性
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
def wang(self):
print("汪汪叫~~~")
print(self.name)
print(self.color)
class KeJi(Dog):
def __init__(self, name, color, height, weight): # 重写父类的初始化方法
# 需要调用父类的函数
# 调用父类中的构造函数,此时就具备(继承)了name,color两个实例属性
Dog.__init__(self, name, color)
# 扩展其他的属性
self.height = height
self.weight = weight
pass
def __str__(self):
return '{}的颜色是{}它的身高是{}cm,体重是{}kg'.format(self.name, self.color, self.height, self.weight)
def wang(self): # 重写父类的方法
print("嗷嗷叫~~~")
print(self.name)
print(self.color)
print(self.height)
print(self.weight)
# 实例化子类
kj = KeJi("柯基犬", "棕色", 36, 15) # 需要四个参数
kj.wang()
print(kj)
# 实例化父类
kj1 = Dog("金毛", "黄色") # 需要两个参数
kj1.wang()
# 7.调用父类的方法二:super().__init__(xxx, xxx)
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
def wang(self):
print("汪汪叫~~~")
print(self.name)
print(self.color)
class KeJi(Dog):
def __init__(self, name, color, height, weight): # 重写父类的初始化方法
# 需要调用父类的函数
# 调用父类中的构造函数,此时就具备(继承)了name,color两个实例属性
super().__init__(name, color) # 自动找到父类,然后调用方法
# 扩展其他的属性
self.height = height
self.weight = weight
pass
def __str__(self):
return '{}的颜色是{}它的身高是{}cm,体重是{}kg'.format(self.name, self.color, self.height, self.weight)
def wang(self): # 重写父类的方法
print("嗷嗷叫~~~")
print(self.name)
print(self.color)
print(self.height)
print(self.weight)
kj = KeJi("柯基犬", "棕色", 36, 15) # 需要四个参数
kj.wang()
print(kj)
# 8.调用父类的方法三:super().方法名()
class Dog(object):
def __init__(self, name, color):
self.name = name
self.color = color
def wang(self):
print("汪汪叫~~~")
print(self.name)
print(self.color)
class KeJi(Dog):
def __init__(self, name, color, height, weight): # 重写父类的初始化方法
# 需要调用父类的函数
# 调用父类中的构造函数,此时就具备(继承)了name,color两个实例属性
# 自动找到父类,然后调用方法,如果继承多个父类,会按照顺序诸葛去找然后调用
super(KeJi, self).__init__(name, color)
# 扩展其他的属性
self.height = height
self.weight = weight
pass
def __str__(self):
return '{}的颜色是{}它的身高是{}cm,体重是{}kg'.format(self.name, self.color, self.height, self.weight)
def wang(self): # 重写父类的方法
super().wang() # 调用父类的方法<----------
print("嗷嗷叫~~~")
kj = KeJi("柯基犬", "棕色", 36, 15) # 需要两个参数
kj.wang()
print(kj)
# 9.内部方法的互相调用方法一
class MyClass(object):
def __init__(self):
pass
def func1(self):
print('func1')
self.common_func()
def func2(self):
print("func2")
self.common_func() # 内部方法的互相调用
def common_func(self):
print("内部方法的互相调用")
pass
my = MyClass()
my.func2()
# 9.内部方法的互相调用方法二
class MyClass(object):
def __init__(self):
pass
def func1(self, name):
print("func")
print(name)
self.common_func() # 内部方法的互相调用
def common_func(self):
print("内部方法的互相调用")
pass
class YouClass(MyClass):
def __init__(self):
pass
def fun2(self, name):
print("YouClass-fun2")
self.func1(name) # 内部方法的互相调用
my = MyClass()
my.func1("AC")
you = YouClass()
you.fun2("BD")
# 10.调用父类的方法总结
class Animal(object):
def __init__(self, name):
self.name = name
def eating(self):
print("Eating")
class Cat(Animal):
def __init__(self, name, colors):
# 添加新的属性
self.colors = colors
# 第一种方法
super(Cat, self).__init__(name)
# 第二种方法
# super().__init__(name)
# 第三种方法
# Animal.__init__(self,name)
# 添加新的功能
self.name += " 你好"
def thing(self):
# 调用父类中的方法
super().eating()
print('{}!它颜色是{}'.format(self.name, self.colors)
)
a = Cat('cat', "yellow")
a.thing()
7、多态
Python 天生就是支持多态(多种形态、状态),因为Python是弱类型语言,不需要指定类型。
所谓多态:定义时的类型和运行时的类型不一样,此时就成为多态。(以封装和继承为前提)不同的子类调用相同的方法,产生不同的结果(多态:就是多种状态、形态,就是同一种行为,对于不同的子类【对象】有不同的行为表现)
要想实现多态 必须的有两个前提需要遵守(重要的前提条件):
1、继承:多态必须发生在父类和子类之间;
2、重写: 子类重写父类的方法
多态的作用:增加程序的灵活性、增加程序的拓展性。
鸭子类型(duck typing):
在程序设计中,鸭子类型(英语:duck typing)是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。 “鸭子测试”可以这样表述:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。
class Animal(object): # 定义一个父类(基类)
def talk(self): # 定义一个方法
print("我是一个动物")
# 同一种行为(talk),对于不同的子类(duck、dog、cat、person)有不同的行为表现
class Duck(Animal): # 子类(派生类)
def talk(self): # 定义一个同名方法(满足多态的两个前提条件)
print("我是一只鸭子") # 重写父类的方法(覆盖),有自己独特的特征体现
class Dog(Animal): # 子类(派生类)
def talk(self):
print("我是一只狗")
class Cat(Animal): # 子类(派生类)
def talk(self):
print("我是一只猫")
duck = Duck() # 不同的子类有不同的实现
duck.talk()
dog = Dog()
dog.talk()
cat = Cat()
cat.talk()
# 新增需求(不需要改变原来的代码,只需要在列表中添加新的类)
# 核心思想:对于新增是开放的,对于修改是封闭的
class Bird(Animal):
def talk(self):
print("我是一只鸟")
# 多态的体现:重新定义新的实例化方法
def Func(obj): # 对于完整的多态,一般会定义一个统一的函数去调用
obj.talk()
ListObject = [Duck(), Dog(), Cat(), Bird()]
for item in ListObject: # 循环调用函数
Func(item)
class Person(object):
def talk(self):
print("我是人类")
class Stuednt(Person): # class 子类(派生类)
def talk(self):
Person.talk(self) # 调用父类的方法
super().talk()
super(Stuednt, self).talk()
print("我是三年级的学生") # 重写父类的方法
def Func(obj): # 定义一个统一的函数去调用
obj.talk() # 只要具备这个方法,任何对象都可以进行调用(鸭子类型的体现)
# 原有的实例化调用
# stu = Stuednt()
# stu.talk()
# 多态:完全不同的继承关系(Animal(object)、Person(object))也可以调用相同的方法,一起执行程序
ListObject = [Duck(), Dog(), Cat(), Bird(), Stuednt()]
for item in ListObject: # 循环调用函数
Func(item)
# 鸭子类型的体现
class F1(object):
def show(self):
print("F1_show")
class S1(object):
def show(self):
print("S1_show")
class S2(object):
def show(self):
print("S2_show")
def Fuc(obj):
obj.show()
ListObject = [F1(), S1(), S2()]
for item in ListObject: # 循环调用函数
Fuc(item)
8、类属性和实例属性
类属性的概念:就是类对象所拥有的属性,它被所有类对象的实例对象所共有,类对象和实例对象都可以访问。
实例属性的概念:实例对象所拥有的属性,只能通过实例对象访问。
class Person(object):
country = 'china' # 定义类属性
def __init__(self, name):
self.name = name # 定义实例属性
people = Person('Jarry')
# 通过实例对象去访问属性
print(people.country) # 可以访问类属性
print(people.name) # 实例对象可以访问实例属性
# 通过类对象去访问属性
print(Person.country) # 可以访问类属性:类名.属性的形式进行访问
print(Person.name) # 类对象是不可以访问实例属性
# 不同实例对象指向同一个类属性。实例属性是独立存在的,类属性不同实例对象共享
peop = Person('Tracy')
print(peop.country)
print(peop.name)
如果需要在类的外面修改(创建)类属性,必须通过类对象去引用,然后进行修改。
如果通过实例对象去引用,会产生一个同名的实例属性,这种方式实际修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性。
# 类属性修改与访问(通过实例对象进行修改)
class Person(object):
country = "China" # 类属性
def __init__(self, name):
self.name = name
people = Person("Tom")
people.country = "English" # 通过实例对象修改类属性,会产生一个同名的实例属性
print(Person.country) # 再次访问类属性并没有修改 ,实际上是生成了一个实例属性
print(people.country) # 访问实例属性
# 类属性修改与访问(通过类对象进行修改)
class Person(object):
country = "China" # 类属性
def __init__(self, name):
self.name = name
people = Person("Tom")
Person.country = "English" # 通过类对象修改类属性
print(Person.country) # 再次访问类属性发现已经类属性已经修改
print(people.country) # 访问实例属性
# 实例属性修改与访问(通过实例对象进行修改)
class Person(object):
country = "China" # 类属性
def __init__(self, name):
self.name = name
people = Person("Tom")
print(people.name) # 访问实例属性
people.name = "Tomas" # 修改实例属性
print(people.name)
9、类方法和静态方法
类方法的概念:
1.类对象所拥有的方法,需要用装饰器@classmethod来标识其为类方法。
2.对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数。
3.类方法可以通过类对象,实例对象调用,类方法主要可以对类属性进行访问、修改。
# 类方法可以对类属性进行访问、修改
class Person(object):
country = "China" # 类属性
def __init__(self, name):
self.name = name
@classmethod # 类方法,用装饰器 @classmethod 装饰
def get_country(cls):
return cls.country # 访问类属性
print(Person.get_country()) # 通过类对象获取类属性
# 通过实例对象访问类方法
people = Person('Tom')
people.get_country()
# 在类方法中修改类属性的值
class Person(object):
country = "China" # 类属性
def __init__(self, name):
self.name = name
@classmethod # 类方法,用装饰器 @classmethod 装饰
def get_country(cls):
return cls.country
@classmethod
def change_country(cls, data):
cls.country = data # 在类方法中修改类属性的值
print(Person.get_country()) # 通过类对象获取类属性
Person.change_country("English")
print(Person.country)
静态方法的概念:类对象所拥有的方法,需要用@staticmethod来表示静态方法,静态方法不需要任何参数。
class Person(object):
country = "China" # 类属性
@classmethod # 类方法,用装饰器 @classmethod 装饰
def get_country(cls):
print(cls.country)
@classmethod
def change_country(cls, data):
cls.country = data # 在类方法中修改类属性的值
@staticmethod # 静态方法,用装饰器 @staticmethod 装饰
def get_data(): # 静态方法不用传任何参数
print(Person.country) # 通过类对象引用
Person.get_data() # 通过类对象获取类属性
people = Person() # 一般情况下,没有必要通过实例对象去访问静态方法
people.get_data()
静态方法的作用:
1.由于静态方法主要来存放逻辑性的代码,本身和类以及实例对象没有交互。
2.也就是说,在静态方法中,不会涉及到类中方法和属性的操作。
3.数据资源能够得到有效的充分利用。
# 静态方法案例:返回当前的系统时间
import time # 引入第三方的时间模块
class TimeTest(object):
def __init__(self, hour, mins, second): # 定义实例属性
self.hour = hour
self.mins = mins
self.second = second
@staticmethod
def showTime():
return time.strftime("%D %H:%M:%S", time.localtime())
@staticmethod
def add(x, y):
return x+y
print(TimeTest.showTime()) # 通过类对象进行调用
print(TimeTest.add(33, 55)) # 带有参数的静态方法的调用
t = TimeTest(2, 10, 15) # 传入的参数并没有使用
print(t.add(12, 23))
print(t.showTime()) # 没有必要通过这种方式去访问静态方法
总结
类方法、实例方法、静态方法对比:
1.类方法的第一个参数是类对象cls,通过cls引用的类对象的属性和方法(@classmethod)。
2.实例方法的第一个参数是实例对象self,通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。
3.静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用(@staticmethod)。