1.鸭子类型和多态
多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚"鸭子类型"
所谓多态:定义时的类型和运行时的类型不一样,此时就成为多态。可能大家不太好理解,就是定义的时候我不知道调用谁,只有运行的时候才知道调用谁
我们先看一段代码:
class Cat(object):
#定义cat类型
def info(self):
print("I am Cat")
class Dog(object):
#定义dog类
def info(self):
print("I am Dog")
class Duck(object):
#定义duck类
def info(self):
print("I am Duck")
#将这些类放进一个列表中
animal_list = [Cat,Dog,Duck]
#遍历列表,我们的想法是此时这样调用应该是可以直接调用相应类里的info方法
for animal in animal_list:
animal.info()
执行结果:
Traceback (most recent call last):
File "", line 1, in
File "D:\PyCharm 2019.1\helpers\pydev_pydev_bundle\pydev_umd.py", line 197, in runfile
pydev_imports.execfile(filename, global_vars, local_vars) # execute the script
File "D:\PyCharm 2019.1\helpers\pydev_pydev_imps_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "G:/pythonlianxi/spython/logic/python高级/第一讲/demo1.py", line 23, in
animal.info()
TypeError: info() missing 1 required positional argument: 'self'
以上代码中我们的想法是通过这种方法我们可以直接输出我们想要的结果,但是却忽略了一个很重要的点,就是我们根据平时类的实例化,在上述代码中,我们没有对类进行实例化所以就出来了错误
正确的代码应该是下面这种写法:
class Cat(object):
#定义cat类型
def info(self):
print("I am Cat")
class Dog(object):
#定义dog类
def info(self):
print("I am Dog")
class Duck(object):
#定义duck类
def info(self):
print("I am Duck")
#将这些类放进一个列表中
animal_list = [Cat,Dog,Duck]
#遍历列表,我们的想法是此时这样调用应该是可以直接调用相应类里的info方法
for animal in animal_list:
#将类实例化,通过类名+()的这种形式
animal().info()
执行结果:
I am Cat
I am Dog
I am Duck
此时我们可以知道如果要调用类的方法,先要将类进行实例化
2.抽象基类(abc模块)
- 抽象基类(abstract base class,ABC):抽象基类就是类里定义了纯虚成员函数的类。纯虚函数只提供了接口,并没有具体实现。抽象基类不能被实例化(不能创建对象),通常是作为基类供子类继承,子类中重写虚函数,实现具体的接口。
- 抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。
2.1抽象基类应用场景
- 1.我们去检查某个类是否有某种方法
示例代码:
#检查某个类是否有某种方法
class Demo(object):
def __init__(self,my_list):
self.my_list = my_list
def __len__(self):
return len(self.my_list)
d=Demo(['hello','world'])
from collections.abc import Sized
print(isinstance(d,Sized))
执行结果为:True
Sized是隐藏文件中实现的,其中有实现 len()方法,所以我们在最后判断的时候才会是True
- 2.我们需要强制某个子类必须实现某些方法
- 1此处我们模拟强制实现的场景:
代码1:
#需要某个子类必须实现的方法
#此处为模拟实现
class CacheBase(object):
def get(self,key):
raise ValueError
def set(self,key,value):
raise InterruptedError
class RedisCache(CacheBase):
pass
r = RedisCache()
r.get('zjk')
执行结果:此时程序会直接抛出异常,因为在实例化时没重写父类中的方法
我们重新对程序进行调整,代码如下:
代码2:
class CacheBase(object):
def get(self,key):
raise ValueError
def set(self,key,value):
raise InterruptedError
class RedisCache(CacheBase):
def get(self,key):
pass
r = RedisCache()
r.get('zjk')
此时程序正常运行,不报错,但是由于是模拟的,所以不准确,不建议用这种方法
-
2 正确的写法
代码1没有在实例化对get和set方法重写,如下:
import abc
class CacheBase(metaclass=abc.ABCMeta):
@abc.abstractmethod
def get(self,key):
pass
@abc.abstractmethod
def set(self,key,value):
pass
class RedisCache(CacheBase):
pass
r = RedisCache()
r.get('zjk')
此时执行程序,会报错,说没有相应的get和set方法
所以我们还要在实例中对相应的方法时行 重写
代码如下:
import abc
class CacheBase(metaclass=abc.ABCMeta):
@abc.abstractmethod
def get(self,key):
pass
@abc.abstractmethod
def set(self,key,value):
pass
class RedisCache(CacheBase):
def get(self,key):
pass
def set(self,key,value):
pass
r = RedisCache()
r.get('zjk')
3.isinstance和type的区别
常规的区别
-
- .isinstance函数来判断一个对象是否是一个已知的类型
s='123'
print(isinstance(s,str))
执行结果为:True
- 2.type是直接告诉我们类型
s='123'
print(type(s))
执行结果为:
面向对象中的区别
- type不考虑继承关系
- instance 考虑继承关系
在面向对象编程中,要使用isinstance,避免误判
is 引用是否是同个对象 == 只判断数值是否相等
4.类属性和实例属性
先看下面的代码:
class A:
#类属性
bb = 1
def __init__(self,x,y):
#实例属性
self.x= x
self.y= y
a = A(1,2)
A.bb=11
a.bb=22 #此句的实际作用相当于在实例里增加了一个bb属性,和类的bb属性不是一样的
print(a.x,a.y,a.bb)
print(A.bb)
print(A.x)
分析输出结果我们得出:实例可以向上查找类的属性,但是类不能向下查找属性的属性
5.查找顺序
mro 算法 在Python2.3之后,Python采用了C3算法
6.Python对象的自省机制
自省是通过一定的机制查询到对象的内部结构。
自省就是面向对象的语言所写的程序在运行时,能够知道对象的类型。简单一句就是,运行时能够获知对象的类型。
如:dir 函数 会列出相应的魔法方法和属性 ,type ,isinstance ,hasattri
class Person:
name = 'zjk'
class Student(Person):
def __init__(self, name):
self.name = name
if __name__ == '__main__':
u = Student('zs')
print(u.__dict__)
print(u.name)
u.__dict__['add']= 'HZ'
print(u.__dict__)
print(dir())
7.super函数
为什么要用super函数?
- 因为正常的调用父类中的方法,我们也可以通过 类名+方法名这种硬编码这种方式,一旦父类的名字发生改变,那么我们所有引用到地方全部要进行修改,代码维护性较差。而且通过父类名+方法名这种方法,在调用其方法时,传参的时候需要再传入一个self参数。
- 而通过super().方法名 这种软编码的方法引用父类,如果父类名称发生变化,我们后面的代码不需要进行更新,因为super()会自动解析父类的信息。
- super()在复杂的继承关系中,不是调用父类中方法,而是按照mro算法来进行调用 的
- 想在实例方法中调用父类的方法
class A:
def __init__(self):
print("A")
class B(A):
def __init__(self):
print('B')
super().__init__()
if __name__ == "__main__":
b = B()
- 可以在实例方法中通过调用父类中已有的init初始化方法给新的类进行初始化的快速赋值
class Person(object):
def __init__(self, name, age, heigt):
self.name = name
self.age = age
self.height = heigt
def speak(self):
print('{}说:我{}岁'.format(self.name,self.age))
class Student(Person):
def __init__(self,name,age,height,grade):
super().__init__(name,age,height)
self.grade = grade
def info(self):
print('{}说:我{}岁,我在{}年级'.format(self.name,self.age,self.grade))
if __name__ == '__main__':
u = Student('zs',18,116,5)
u.info()
8.类与对象尝试问题与解决技巧
- 1 如何派生内置不可变类型修改其实例化行为?
场景:想自定义一种新类型的无组,对于传入的可迭代的对象,我们只保留其中int类型且值大于0的元素
IntTuple([2,-2,'zs',['x','y',4]])=>(2,4)
如何继承内置tuple实现inttuple
'''
想自定义一种新类型的无组,对于传入的可迭代的对象,我们只保留其中int类型且值大于0的元素
IntTuple([2,-2,'zs',['x','y',4]])=>(2,4)
如何继承内置tuple实现inttuple
'''
#定义IntTuple类
class IntTuple(tuple):
#定义初始化方法,并传入 可迭代的参数
def __init__(self,iterable):
f = (i for i in iterable if isinstance(i,int) and i>0)
super().__init__(f)
result = IntTuple([2,-2,'zs',['x','y',4]])
print(result)
执行结果如下:
Traceback (most recent call last):
File "", line 1, in
File "D:\PyCharm 2019.1\helpers\pydev_pydev_bundle\pydev_umd.py", line 197, in runfile
pydev_imports.execfile(filename, global_vars, local_vars) # execute the script
File "D:\PyCharm 2019.1\helpers\pydev_pydev_imps_pydev_execfile.py", line 18, in execfile
exec(compile(contents+"\n", file, 'exec'), glob, loc)
File "G:/pythonlianxi/spython/logic/python高级/第一讲/demo6.py", line 15, in
result = IntTuple([2,-2,'zs',['x','y',4]])
File "G:/pythonlianxi/spython/logic/python高级/第一讲/demo6.py", line 12, in init
super().init(f)
TypeError: object.init() takes no parameters
我们可以看到程序报错了
下面我们通过几组代码来分析一下数据类型的一些初始化方法前还有哪些方法可用
第一段代码:
class A:
def __new__(cls, *args, **kwargs):
print("A__new__",cls,args)
return object.__new__(cls) #new方法需要返回
def __init__(self,*args):
print("A__init__",args)
a=A(1,2)
执行结果:
A__new__main.A'> (1, 2)
A__init__ (1, 2)
我们可以看到在init前还有一个new方法
我们将代码再变形一下
class A:
def __new__(cls, *args, **kwargs):
print("A__new__",cls,args)
return object.__new__(cls) #new方法需要返回
def __init__(self,*args):
print("A__init__",args)
#A.__new__(1,2) #直接这样传会报错,因为在上面的代码中我们对类进行实例化,所以上面的代码不会报错,而此片因为没有对类进行实例化,所以 我们要遵守上面代码中一些规则,按规则传入相应的参数,所以要改为下面的写法
A.__new__(A,1,2) #此时代码正常运行
A.__init__(A,1,2)
下面我们来看一下列表先执行的是哪个魔法方法:
代码:
ilist = list('abc')
print(ilist)
l = list.__new__(list,'abc')
print(l)
执行结果:
['a', 'b', 'c']
[]
ilist = list('abc')
print(ilist)
l = list.__new__(list,'abc')
list.__init__(l,'abc')
print(l)
执行结果:
['a', 'b', 'c']
['a', 'b', 'c']
我们通过上面的2个关于list生成的代码,可以看到列表的初始化,不是通过new方法来实现的,而是通过 init来完成
我们再来看一下元组的
ituple =tuple('abc')
print(ituple)
newtuple = tuple.__new__(tuple,'bcd')
print(newtuple)
执行结果:
('a', 'b', 'c')
('b', 'c', 'd')
我们看到可以直接输出,由此可知,元组是通过new来完成,不是通过init来实现的。
所以我们将最开始的代码修改如下:
# #定义IntTuple类
class IntTuple(tuple):
#定义初始化方法,并传入 可迭代的参数
def __new__(cls,iterable):
f = (i for i in iterable if isinstance(i,int) and i>0)
return super().__new__(cls,f)
result = IntTuple([2,-2,'zs',['x','y'],4])
print(result)
此时已经完成了我们想要的功能!!
最后谢谢大家的阅读!!