深入类和多态
一,鸭子类型和多态
-
鸭子类型
当你看到一只鸟走起来想鸭子,游泳起来像鸭子,叫起来像鸭子,那么这只鸟就叫做鸭子类型
- 我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。
- 我们只关心一个类被不同的魔法函数赋予了不同的特性,我们看他是什么类型,是看他实现了哪些魔法函数,具有哪些特性
- Java 里面如果要实现一个特点的类型,必须继承一个类,而 python 只需要写几个魔法函数,赋予它那种数据类型的特性就行, 这就是鸭子类型
- 又比如 list.extend()方法中,我们并不关心它的参数是不是list,只要它是可迭代的,所以它的参数可以是list/tuple/dict/字符串/生成器等.
例子:比方说我创建了一个类叫狗类,但是我在这个狗类里面定义了一个魔法方法,使他具有鸟的飞行能力,那么我们就称这种为鸭子类型
-
多态
从语言层面上讲就已经是多态的了,背后已经帮我们实现了
class Cat(object): def say(self): print('i am a cat') class Dog(object): def say(self): print('i am a dog') class Duck(object): def say(self): print('i am a Duck')
- 三个对象都能够实现 say 方法,并且能够打印不同的内容,他们定义的方法是一样的,但功能不一样,多态
二,抽象基类(abc模块)
- 在一个基础的类中设定一些特点的方法,所有继承这个抽象基类的子类都必须实现这些方法
- 抽象基类不能被实例化
-
我们在某些情况下希望判定某个对象的类型
from collections.abc import Sized print(isinstance(com, Sized)) # 我们想判断我们实现的类是不是一个Sized的子类,那么我们必须Sized这个抽象基类的接口
-
我们需要某个子类必须实现某些方法,比方说设计一个web框架,必须设计一个抽象基类,每个继承的类都必须实现特点的几个接口,以便于用户使用的方便。
比如数据的存入,要选择不同的数据库,我们不知道用户要用哪种,为了避免日后少改动代码和出错,不许定义一个抽象基类
-
在调用的时候出错,不正式
class CacheBase(): def get(self, key): raise NotImplementedError def set(self, key, value): raise NotImplementedError c = CacheBase() c.set('aa', 'bb') ... Traceback (most recent call last): File "D:/Python/Python笔记/python高级编程和异步IO并发编程/Code/chapter04/abc_text.py", line 50, in <module> c.set('aa', 'bb') File "D:/Python/Python笔记/python高级编程和异步IO并发编程/Code/chapter04/abc_text.py", line 47, in set raise NotImplementedError NotImplementedError # 我们发现上面只有在调用的时候才会出错,在用户创建的时候不会抛出异常
-
在定义类的时候,就会抛出异常,强制对象一定要实现方法
import abc class CacheBase(metaclass=abc.ABCMeta): @abc.abstractmethod def get(self, key): pass @abc.abstractmethod def set(self, key, value): pass c = CacheBase() ... Traceback (most recent call last): File "D:/Python/Python笔记/python高级编程和异步IO并发编程/Code/chapter04/abc_text.py", line 56, in <module> c = CacheBase() TypeError: Can't instantiate abstract class CacheBase with abstract methods get, set
- 实际在开发过程中,一般使用鸭子类型,如果一定要使用,建议用多继承的方法去实现
- 抽象基类设计过度不容易理解他,所以一般是用多继承的方式来
三,isinstance和type的区别
判断一个对象的类型,用 isinstance
而不是 type
class A:
pass
class B(A):
pass
b = B()
print('instance 判断b是否属于A类 =>', isinstance(b, A))
print('instance 判断b是否属于B类 =>', isinstance(b, A))
print('type 判断a是否属于A类 =>', type(b) is A)
print('type 判断a是否属于B类 =>', type(b) is B)
使用type is
判断类型会有偏差,因为 is
是判断两个对象的内存地址,不能顾及到继承,所以不推荐
内置的 isinstance
内部会有优化机制,会从对象本身,到继承一层一层忘上找,判断
四,类变量和对象变量
class Dog:
tall = 20
def __init__(x, y):
self.x = x
self.y = y
dog = Dog(10, 20)
dog.tall # 类变量
dog.x # 实例属性
dog.y # 实例属性,也叫对象属性
五,类和实例的查找顺序
深度算法和广度算法,C3算法
六,类方法,静态方法,实例方法
class Date(): ①
def __init__(self, year, moth, day):
self.year = year
self.moth = moth
self.day = day
def tomorrow(self): ②
self.day += 1
def __str__(self): ③
return '{}/{}/{}'.format(self.year, self.moth, self.day)
if __name__ == '__main__':
date = Date(2019, 9, 11)
print(date)
① 我们实现了一个 Date 类,并且这个类实现了三个方法
② 我们自己定义的方法,每个初始化的实例都有这个方法,我们称他为实例方法
③ 打印实例的时候回调用这个方法
如果我们传入的值是一个字符串,在输出之前要对他进行字符创的处理,我们可以使用静态方法
-
不适用静态方法
class Date(): def __init__(self, year, moth, day): self.year = year self.moth = moth self.day = day def tomorrow(self): self.day += 1 def __str__(self): return '{}/{}/{}'.format(self.year, self.moth, self.day) if __name__ == '__main__': date_str = '2019-12-1' year, moth, day = tuple(date_str.split('-')) date = Date(year, moth, day) print(date)
我们发现,这种方法的使用比较烦,用起来不顺手
-
使用静态方法
staticmethod
staticmethod 的用法:把 实例方法 转换成一个函数,就是真正的函数,如果要使用,必须进入类内部
class Date(): def __init__(self, year, moth, day): self.year = year self.moth = moth self.day = day def tomorrow(self): self.day += 1 @staticmethod ① def parse_from_string(string): year, moth, day = tuple(string.split('-')) return Date(year, moth, day) @staticmethod ② def string_is_date(string): year, moth, day = tuple(string.split('-')) if len(year) == 4 and len(moth) == 2 and day == 2: return True else: return False def __str__(self): return '{}/{}/{}'.format(self.year, self.moth, self.day) if __name__ == '__main__': date_str = '2019-12-1' date = Date.parse_from_string(date_str) print(date)
① staticmethod 的用法不在这里,这里只是演示他能实现这个功能,如果涉及和类实例相关,用classmethod
② 这里介绍了staticmethod 方法的主要用法
classmethod 的用法:把第一个参数变成类对象本身,方便我们在实例之前,对数据进行处理,返回实例
class Date(): def __init__(self, year, moth, day): self.year = year self.moth = moth self.day = day def tomorrow(self): self.day += 1 @classmethod def parse_from_string(cls, string): year, moth, day = tuple(string.split('-')) return cls(year, moth, day) def __str__(self): return '{}/{}/{}'.format(self.year, self.moth, self.day) if __name__ == '__main__': date_str = '2019-12-1' date = Date.parse_from_string(date_str) print(date)
六,数据封装和私有属性
class Date():
def __init__(self, year, moth, day):
self.year = year
self.moth = moth
self.day = day
def tomorrow(self):
self.day += 1
@classmethod
def parse_from_string(cls, string):
year, moth, day = tuple(string.split('-'))
return cls(year, moth, day)
def __str__(self):
return '{}/{}/{}'.format(self.year, self.moth, self.day)
class User:
def __init__(self, birthday):
self.__birthday = birthday
def get_age(self):
# 返回年龄
return 2019 - self.__birthday.year
if __name__ == '__main__':
user = User(Date(1999, 12, 1))
print(user.get_age())
七,python对象的自省机制
我们可以使用
__dict__
来查看 python 类的自省机制,储存在字典里,是效率很高的数据结构
class Person:
pass
class Student:
def __init__(self, name):
self.name = name
if __name__ == '__main__':
xyb = Student('xyb')
print(xyb.__dict__)
print(Student.__dict__)
...
{'name': 'xyb'}
{'__module__': '__main__', '__init__': <function Student.__init__ at 0x000002053AC248C8>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
如果要查找一个对象实现了所有方法,使用
dir()
函数
class Person:
pass
class Student:
def __init__(self, name):
self.name = name
if __name__ == '__main__':
xyb = Student('xyb')
print(dir(xyb))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
在 流畅的python 里面具体介绍了 python 的自省机制,但是如果有几十万上百万的数据要储存,要用元组,字典虽然效率极高,但是占用内存大
八,super的调用顺序
super 调用顺序为
__mro__
打印的结果