继承的简介、super()
- 继承是面向对象三大特性之一
- 通过继承我们可以使一个类获取到其他类的属性和方法
- 在定义类时,可以在类名后面的括号中指定当前类的父类(超类、基类、super)
子类(衍生类)可以直接继承父类中所有属性和方法(包括特殊方法__init__等)
例:
class Animal():
def __init__(self, name):
self._name = name
def run(self):
print('动物会跑~~~')
def bark(self):
print('动物会叫~~~')
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
class Dog(Animal): ---> Animal为父类
# 继承了父类Animal的所有方法和属性,也可以添加新的方法和属性
# 当方法和属性的名称与父类的一致时,会覆盖掉从父类中继承过来的同名方法和属性
# 这叫<方法的重写>,也叫覆盖,override
# 特殊方法也可重写
def __init__(self, name, age): ---> 在子类Dog中新添加了属性age
# 可以直接调用父类的__init__来初始化父类中定义的属性,但是此方法死板(父类名称改变时就出错了)
# Animal.__init__(self, name) ---> 此语句替代了原来的 self._name = name
# super()可以用来获取当前类的父类,并且通过super()返回的对象调用父类时,不需要传递self
super().__init__(name) ---> 此方法比上面的方法灵活,可以动态获取父类,而不管父类有没有改变
self._age = age ---> 剩余的没有原本没有在父类中的属性要单独初始化
def run(self):
print('狗会跑~~~')
def sleep(self):
print('狗会睡觉~~~')
@property ---> 由于在父类中已有属性name的装饰器,因此不需要添加了
def age(self): ---> 但是age还没有装饰器,因此Dog中添加
return self._age
@age.setter
def age(self, age):
self._age = age
- 继承的好处:
通过继承可以让子类获取到父类的方法和属性,避免编写重复性代码
并且符合OCP原则, 所以经常通过继承来对一个类进行扩展
- 在创建类时,如果省略了父类,则默认父类为object
object是所有类的父类,所有类都继承自object,如
class Person(object):
pass
- 继承是可以多代继承的,比如当类FemaleDog是从类Dog继承的,那么FemaleDog也是Animal的子类
- issubclass(a, b)
检查类a是否是类b的子类,是则返回True,否则返回False
如上例中:
issubclass(FemaleDog, Animal) ---> True
issubclass(Dog, Animal) ---> True
issubclass(Dog, object) ---> True
issubclass(Animal, object) ---> True
issubclass(Animal, Dog) ---> False
- isinstance(a, b)
检查对象a是否是类b的实例,是则返回True,否则返回False
如果该类b是对象a的父类,也会返回True
由于object是所有类的父类,因此所有的对象都是object的实例
isinstance(print, object) ---> True
方法的重写(覆盖) —> 包括特殊方法
- 上文提到了,如果在子类中存在有跟父类同名的方法,则通过子类去调用该方法时
会调用子类的方法而不是父类的方法,这个特点成为方法的重写(覆盖,override)
- 当我们去调用一个对象的方法时,
会优先去当前对象中寻找是否具有该方法,如果有则直接调用
如果没有,则去当前对象的父类中寻找,若父类中有则直接调用
如果没有,则去父类的父类中寻找,以此类推,知道找到object(所以类的父类)
如果依然没有找到,则报错
多重继承(无特殊情况,不建议使用)
- 在Python中是支持多重继承的,也就是可以为一个类同时指定多个父类
可以在类名的()后边添加多个类,来实现多重继承
多重继承,会使子类同时拥有多个父类,并且会获取到所有父类中的方法
- 在开发中若无特殊情况,应该尽量避免使用多重继承,因为多重继承会让代码过于复杂
- 如果多个父类中有同名的方法,(如果子类也跟他们同名,则会在子类里直接调用)则会在
第一个父类中寻找,然后找第二个。。。以此类推
前边父类的方法会覆盖后边父类的方法
例子:
class A(object):
pass
class B(object):
pass
class AB(B, A): ---> 子类AB同时继承了父类B和A,顺序是B,然后才是A
pass
- 类名.__bases__ 这个属性可以用来获取当前类的所有父类,例:
AB.__bases__ ---> (<class '__main__.B'>, <class '__main__.A'>)
A.__bases__ ---> (<class 'object'>,)
多态
- 多态是面向对象的三大特征之一
- 字面上理解是 多种形态
如:狗(狼狗、哈士奇、藏獒...)
- 一个对象可以以不同形态去呈现
定义两个类:
class A():
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
class B():
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
a = A('孙悟空')
b = B('猪八戒')
# 定义一个函数
def say_hello(obj):
print('你好 %s' %obj.name)
# 对于say_hello()函数来说,只要对象中含有name属性,那么这个对象就可以作为参数传递
# 这个函数并不会考虑对象的类型,只要该对象有name属性即可
say_hello(a) ---> 你好 孙悟空
say_hello(b) ---> 你好 猪八戒
# 下面这个例子就违反了多态:只适用于一种类型的对象,无法处理其他对象(导致函数适应性差)
def say_hello_2(obj):
# 做类型检查
if isinstance(obj, A):
print('你好 %s' %obj.name)
say_hello_2(b) --->
---> 在say_hello_2()中我们做了一个类型检查,只有obj是A类型的对象时,才可以正常使用
其他类型的对象都无法使用该函数,这个函数就违反了多态
面向对象的特征总结
面向对象的三大特征:
- 封装
确保对象中的数据安全
- 继承
保证了对象的可扩展性
- 多态
保证了程序的灵活性
属性和方法(总结)
# 涉及到的内容:
类属性
实例属性
类方法
实例方法
静态方法
# 定义一个类
class A(object):
# 类属性,直接在类中定义的属性是类属性
类属性可以通过类或类的实例访问到
但是类属性只能通过类对象来修改,无法通过实例对象来修改
count = 0
def __init__(self, name):
# 实例属性,通过实例对象添加的属性属于实例属性
实例属性只能通过实例对象来访问和修改,类对象无法访问修改
self._name = name
# 实例方法
在类定义中,以self作为第一个参数的方法都是实例方法
实例方法在调用时,python会将调用对象作为self传入(不用我们传入)
实例方法可以通过实例和类去调用(两者等价)
# 假设a = A('孙悟空'),a是类A的一个对象
当通过实例调用时,会自动将当前调用对象作为self传入,如a.test()
当通过类调用时,不会自动传递self,此时必须手动传递self,如A.test(a)
def test(self):
print('这是test方法~~~', self) ---> 这是test方法~~~ <__main__.A object at 4664654998>
# 类方法
# 在类内部使用 @classmethod 来修饰的方法属于类方法
# 类方法的第一个参数时cls,也会被自动传递,cls就是当前的类对象
类方法和实例方法的区别:
实例方法第一个参数是self,类方法的第一个参数是cls
类方法可以通过类或者实例去调用,没有区别(唯一区别是第一个参数不一样)
@classmethod
def test_2(cls):
print('这是test_2方法,他是一个类方法~', cls)
---> 这是test_2方法,他是一个类方法~ <class '__main__.A'>
---> 类方法要用类去调用: A.test_2()
# 调用
a.test_2() # 通过实例调用
A.test_2() # 通过类调用
# 静态方法
# 在类中使用 @staticmethod 来修饰的方法属于静态方法
# 静态方法不需要指定任何默认参数,可以通过类和实例去调用
# 静态方法,基本上是一个和当前类无关的方法,而只是一个保存到当前类中的函数
# 静态方法一般是一些工具方法,和当前类无关
@staticmethod
def test_3():
print('test_3执行了~')
# 调用
a.test_3()
Dog.test_3() ---> 如果test_3有参数,就要传参数
a = A()
垃圾回收(了解)
- 程序在运行过程中会产生垃圾,会影响到程序的运行性能,必须被及时清理
- 什么是垃圾:没用的东西就是垃圾
- 在程序中,没有被引用的<对象>就是垃圾
- 要进行垃圾回收: 将垃圾对象从内存中删除
- 在Python中有自动的垃圾回收机制,可自动删除没有被引用的对象删除(所以不用手动删除)
class A():
def __init__(self):
self.name = 'A类'
# a = A()
a = None # 将a设置为了None,此时没有任何变量对A()对象进行引用,就变成了垃圾
print(a.name)
input('请按回车键退出!')
特殊方法
也称魔术方法,使用__来开头,__结尾,如__del__
# 定义一个Perdon类
class Person(object):
'''人类'''
def __init__(self, name, age):
self.name = name
self.age = age
# __str__()这个特殊方法会在尝试将对象转换为字符串的同时调用
# 他的作用可以用来指定对象转换为字符串的结果
# 使用print函数时用到
def __str__(self):
return 'Person [name=%s, age=%d]' % (self.name, self.age)
---> 打印对象p1: print(p1) ---> Person [name=孙悟空, age=18]
# __repr__()这个特殊方法会在对当前对象使用repr()函数是调用
# 作用是指定对象在<交互模式>中直接输出的效果
# 在交互界面时起作用
def __repr__(self):
return 'Hello'
... 还有很多特殊方法,去python帮助手册查询
# 创建两个Person类的实例
p1 = Person('孙悟空', 18)
p2 = Person('猪八戒', 40)
# 单打印一个对象时,实际上打印的是对象中的特殊方法 __str__()的返回值
print(p1) ---> <__main__.Person object at 4638749>
# 所以当我们在类Person中添加一段代码:
def __str__(self):
return 'hello'
# 那么打印对象print(p1),会返回 hello