Python(8)面向对象编程(下)

简介: Python(8)面向对象编程(下)

-多态


  • 看过上面的案例之后,我们又获得了继承的另一个好处,多态
  • 当我们定义一个类时,我们实际上就定义了一种新的数据类型,并且这个数据类型和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会寻找类的属性,所以说,在编写代码时,不要对实例属性和类属性设置相同的名称,因为相同名称的情况下,实例属性会覆盖掉类属性,当删除实例属性后,再次使用相同的名称进行访问,将会重新访问到类属性

目录
相关文章
|
20天前
|
Java C# Python
Python学习七:面向对象编程(中)
这篇文章是关于Python面向对象编程的中级教程,涵盖了析构函数、对象的三大特征(封装、继承、多态)、类属性与实例属性、以及类方法与静态方法的对比。
20 2
|
20天前
|
设计模式 安全 JavaScript
Python学习八:面向对象编程(下):异常、私有等
这篇文章详细介绍了Python面向对象编程中的私有属性、私有方法、异常处理及动态添加属性和方法等关键概念。
17 1
|
6月前
|
Python
Python编程作业五:面向对象编程
Python编程作业五:面向对象编程
67 1
|
2月前
|
存储 Java 程序员
30天拿下Python之面向对象编程
30天拿下Python之面向对象编程
16 3
|
6月前
|
Python
【Python进阶(三)】——面向对象编程
【Python进阶(三)】——面向对象编程
|
2月前
|
Java Python
全网最适合入门的面向对象编程教程:50 Python函数方法与接口-接口和抽象基类
【9月更文挑战第18天】在 Python 中,虽无明确的 `interface` 关键字,但可通过约定实现类似功能。接口主要规定了需实现的方法,不提供具体实现。抽象基类(ABC)则通过 `@abstractmethod` 装饰器定义抽象方法,子类必须实现这些方法。使用抽象基类可使继承结构更清晰、规范,并确保子类遵循指定的方法实现。然而,其使用应根据实际需求决定,避免过度设计导致代码复杂。
|
20天前
|
Java Python
Python学习六:面向对象编程(上)
这篇文章是关于Python面向对象编程的基础知识,包括类和对象的概念、实例方法、属性、self关键字以及魔法方法等。
15 0
|
2月前
|
Python
全网最适合入门的面向对象编程教程:Python函数方法与接口-函数与方法的区别和lamda匿名函数
【9月更文挑战第15天】在 Python 中,函数与方法有所区别:函数是独立的代码块,可通过函数名直接调用,不依赖特定类或对象;方法则是与类或对象关联的函数,通常在类内部定义并通过对象调用。Lambda 函数是一种简洁的匿名函数定义方式,常用于简单的操作或作为其他函数的参数。根据需求,可选择使用函数、方法或 lambda 函数来实现代码逻辑。
|
3月前
|
Python
Python 中的面向对象编程 (OOP)
【8月更文挑战第29天】
30 7
|
3月前
|
机器学习/深度学习 PHP 开发者
探索PHP中的面向对象编程构建你的首个机器学习模型:以Python和scikit-learn为例
【8月更文挑战第30天】在PHP的世界中,面向对象编程(OOP)是一块基石,它让代码更加模块化、易于管理和维护。本文将深入探讨PHP中面向对象的魔法,从类和对象的定义开始,到继承、多态性、封装等核心概念,再到实战中如何应用这些理念来构建更健壮的应用。我们将通过示例代码,一起见证PHP中OOP的魔力,并理解其背后的设计哲学。