-多态
- 看过上面的案例之后,我们又获得了继承的另一个好处,多态
- 当我们定义一个类时,我们实际上就
定义了一种新的数据类型
,并且这个数据类型和Python的基础数据类型,列表、字符串、字典等没有什么太大的区别,例如:
- 先定义三个变量为不同的实例 >>> a = list() >>> b = Animal() >>> c = Dog() - 使用'isinstance()'进行判断 >>> isinstance(a,list) True >>> isinstance(b,Animal) True >>> isinstance(c,Dog) True >>> isinstance(c,Animal) True >>> isinstance(b,Dog) False 可以看到'a'、'b'、'c'分别对应着'list'、'Animal'、'Dog'三种类型,并且'c'不仅对应着'Dog',而且还对应的'Animal',这是因为'Dog'是从'Animal'继承下来的,即'Dog'本身就是'Animal'的一种,所以'c'才可以对应两种类型 #注意: 在继承关系中,如果一个实例的数据类型是某个子类,那么这个实例的数据类型也可以被看作是父类,但是,父类不能看作是子类,可以看到最后一个'isinstance(b,Dog)'的输出是'False'
要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:
>>> def run_twice(animal): ... animal.run() ... animal.run() ... >>> run_twice(Animal()) Animal is running!!! Animal is running!!! >>> run_twice(Dog()) Dog is running!!! Dog is running!!! - 只看上面的感觉没有什么,但是现在来创建一个新的'Animal'的子类,去调用'run_twice'函数 >>> class Tortoise(Animal): ... def run(self): ... return print('Tortoise is running slowly!!!') ... >>> run_twice(Tortoise()) Tortoise is running slowly!!! Tortoise is running slowly!!! 可以发现新创建的子类'Tortoise'可以在调用'run_twice'函数时作为参数直接传入,并且可以正常运行,输出的数据都不一样,这就是因为'多态'的特性
- 看过上面的案例后,其实可以看出多态的作用就是:让具有不同功能的函数可以使用相同的函数名称,从而可以使用一个函数名调用不同功能的函数
- 多态的特点:
只需要关心对象的方法名是否相同,不需要关系对象所属的类型
对象所属的类之间,继承关系可有可无,因为就算是子类,但是只要有相应的方法,最终还是会按照子类的方法执行,父类的方法不用考虑
多态还可以增加代码的外部调用灵活度,让代码更加通用,兼容性更强
多态是调用方法的技巧,不会影响类的内部设计
对于多态的个人理解:
其实就是在把类作为参数传入函数时,无需考虑是什么类(不管是子类还是父类),只要说传入类的方法和函数内调用的方法名称是相同的就行,例如:
这里定义一个run_test函数 >>> def run_test(aaaa): ... aaaa.run() ... 根据上面的函数内容,我们在传入类时,其实只要保证类中有'run'的方法就行,就跟上面的案例一样,在传入'Dog'、'Animal'后,输出的内容都是他们自己'run'方法的操作步骤 >>> class Animal(object): ... def run(self): ... return print('Animal is running!!') ... >>> class Dog(Animal): ... def run(self): ... return print('Dog is running !!!') ... >>> run_test(Dog()) Dog is running !!! >>> run_test(Animal()) Animal is running!! 但是比如传入的'Dog'类中并没有新定义'run'方法,那么他会从'Animal'类中继承'run'方法,那么输出的就是'Animal'的'run'方法了, >>> class Dog(Animal): ... pass ... >>> run_test(Dog()) Animal is running!! 如果父类'Animal'也没有'run'方法的话,就会报错 >>> class Animal(object): ... pass ... >>> run_test(Dog()) #在重新定义Animal后,先传入Dog发现输出还是原来的,需要重新定义Dog Animal is running!! >>> class Dog(Animal): ... pass ... >>> run_test(Dog()) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in run_test AttributeError: 'Dog' object has no attribute 'run'
扩展:
根据上面调用函数传入类来说,静态语言和动态语言是不一样的
对于静态语音例如JAVA来说,如果需要传入的类是Animal,那么传入的参数必须是Animal类或者它的子类,否则就算传入的类有run方法,也是无法调用的
但是对于动态语言来说,只要保证传入的类中有run方法即可,这就是动态语言的鸭子类型
六、获取对象信息
- 当我们拿到一个对象的引用时,怎么能够知道这个对象的相关信息,如是什么类型、有什么方法呢,下面来看几个函数,这些函数可以帮助我们获取对象信息
-Type()
- 使用
type()
函数可以判断对象类型,下面直接来看案例:
>>> type(123) <class 'int'> >>> type('123') <class 'str'> >>> type(None) <class 'NoneType'> - 当一个变量指向函数或者类,也可以使用type判断 >>> class Animal(object): ... pass ... >>> type(abs) #判断abs函数 <class 'builtin_function_or_method'> >>> a = Animal() >>> type(a) #判断a实例 <class '__main__.Animal'> - 可以看到type返回的信息中,都是以'class'开头的,其实可以看出返回的都是'对象'对应的'类',可以使用'if'语句进行判断 >>> type(123) == type(345) True >>> type(123) == int True >>> type('123') == str True >>> type('123') == type(123) False - 从上面可以看出,判断基本的数据类型可以使用'int','str'等,但是如果想要判断一个对象是否是函数呢?可以使用'Types'模块中定义的常量进行判断 >>> import types >>> def fn(): #定义一个函数 ... pass ... >>> type(fn) == types.FunctionType True >>> type(abs) == types.BuiltinFunctionType True >>> type(lambda x :x ) == types.LambdaType True >>> type((x for x in range(10))) == types.GeneratorType True
-isinstance()
- 对于类的继承关系来说,使用
Type()
就很不方便,我们想要判断类的类型,可以使用isinstance()
函数
- 现在来创建几个类 >>> class Animal(object): ... def run(self): ... return print('Animal is running!!!') ... >>> class Dog(Animal): ... def run(self): ... return print('Dog is running!!!') ... 现在的继承关系为:object——>Animal——>Dog -现在使用'isinstance()'去判断 >>> a = Animal() >>> b = Dog() >>> isinstance(a,Animal) True >>> isinstance(b,Dog) True >>> isinstance(b,Animal) True >>> isinstance(b,object) True >>> isinstance(a,Dog) False 可以看到Dog因为是子类,所以既是'Animal'、'Dog'也是'object',和上面的一样,子类可以当作父类,而父类不能当作子类
一般来说可以使用type()
判断的类型,也可以使用isinstance()
进行判断
>>> isinstance('a',str) True >>> isinstance(123,int) True >>> isinstance(b'a',bytes) True
还可以判断一个变量是否是某种类型的一种,其实就是在后面多加几中数据类型,判断是否在多种数据类型之中,例如:
>>> isinstance([1,2,3],(list,tuple)) True >>> isinstance((1,2,3),(list,tuple)) True >>> isinstance(1,(str,int)) True >>> isinstance('1',(str,int)) True
推荐直接使用isinstance()
进行判断,可以将指定类型以及子类一次性进行判断
-dir()
- 如果想要
获取指定对象的所有属性和方法
,可以使用dir()
函数,dir()
函数会返回一个包含字符串list,例如:
>>> dir("abc") ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] >>> dir(123) ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
上面说过了,类似于__xx__这种的属性和方法在Python中都是有特殊用途的,例如:
- 在Python调用'len()'函数,实际上,在'len()'函数的内部,会自动去调用'__len__()'方法,所以下面的作用是相同的 >>> len("aaa") 3 >>> "aaa".__len__() 3
在创建类时,也可以加__len__()
方法:
>>> class Test(object): ... def __len__(self): ... return 100 ... >>> aaa = Test() >>> len(aaa) 100 >>> class Test(object): ... def __init__(self,name): ... self.name = name ... def __len__(self): ... return len(self.name) ... >>> aaa = Test('aaaaaa') >>> aaa.__len__() 6
剩下的都是普通属性和方法,例如:
- lower()返回小写的字符串 >>> 'AAAAA'.lower() 'aaaaa'
配合getattr()、setattr()、hasattr()可以直接操作一个对象的状态:
#注释: getattr(实例名称,"属性"):获取指定'实例'的'属性' setattr(实例名称,"属性","设置属性的值"):设置指定'实例'的'属性' hasattr(实例名称,"属性"):判断'实例'是否有指定的'属性' >>> class Test(object): ... def __init__(self,name): ... self.name = name ... def cj(self): ... return self.name * self.name ... >>> aaa = Test(9) >>> hasattr(aaa,'name') True >>> aaa.name 9 >>> hasattr(aaa,'sorce') False >>> setattr(aaa,'sorce',20) >>> hasattr(aaa,'sorce') True >>> aaa.sorce 20 >>> getattr(aaa,'sorce') 20 >>> getattr(aaa,'acx') #没有则会报错 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Test' object has no attribute 'acx' >>> getattr(aaa,'acx',123) #如果acx属性不存在,则返回默认值123 123
除了判断实例的属性,还可以判断实例的方法
,这个方法是对象里的方法
>>> hasattr(aaa,'cj') True >>> getattr(aaa,'cj') #可以看到判断是一个函数 <function Test.cj at 0x000001B6A277ECB0> >>> bbb = getattr(aaa,'cj') >>> bbb <bound method Test.cj of <__main__.Test object at 0x000001B6A2756D40>> >>> bbb() 81
小结:
- 通过上面所说的一系列内置函数,我们可以对任意一个Python对象进行解析,需要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息,例如:
可以写: sum = obj.x + obj.y 就不要这样写: sum = getattr(obj, 'x') + getattr(obj, 'y')
- 一般来说可以利用这些内置函数做if判断,判断一个对象丽是否存在指定的方法
def readImage(fp): if hasattr(fp, 'read'): return readData(fp) return None
七、实例属性和类属性
- 由于Python是动态语言,所以根据
类创建的实例可以任意绑定属性
,而给实例绑定属性的方法就是通过创建实例时设置的变量,即实例变量(实例属性)
,或者通过self变量
:
>>> class Student(object): ... def __init__(self,name): ... self.name = name ... >>> test = Student('zhangsan') #这种在创建实例时设置或者使用test.score创建的属性都叫实例属性,和类属性不互通 >>> test.score = 98 >>> test.score 98 >>> test.name 'zhangsan'
如果Student
类本身就需要绑定一个属性的话,可以直接在class中定义属性,而这种属性叫做类属性
,归Student
类所有:
>>> class Student(object): ... name = 'zhangsan' ... >>> test = Student() >>> test.name #可以看到,test实例是没有设置name属性的,但是输出的数据是类中的name属性的值,这是因为当实例没有该属性时,会继续查找类中的相应属性的值 'zhangsan' >>> Student.name 'zhangsan' >>> test.name = 'lisi' #给test实例的name属性赋值 >>> test.name 'lisi' >>> Student.name 'zhangsan' >>> del test.name #删除test实例的name属性的值 >>> test.name #发现又变成了类的name属性 'zhangsan'
从上面可以看出,当实例没有指定的属性时,python会寻找类的属性,所以说,在编写代码时,不要对实例属性和类属性设置相同的名称,因为相同名称的情况下,实例属性会覆盖掉类属性,当删除实例属性后,再次使用相同的名称进行访问,将会重新访问到类属性