面向对象 - 多态与多态性,面向对象进阶:内置方法,反射

多态与多态性

  • 多态:同一种事物有多种形态

动物有多种形态:如狗、猫、猪

class Animal:    # 同一类事物:动物
  def talk(self):
        pass
    
class Dog(Animal):  # 动物的形态之一:狗
    def talk(self):       
        print("汪汪汪")

class Cat(Animal):  # 动物的形态之二:猫
    def talk(self):
        print('喵喵!')

class Pig(Animal):  # 动物的形态之一:猪
    def talk(self):
        print("哼哼哼!")

# 实例化得到三个对象
obj1 = Dog()
obj2 = Cat()
obj3 = Pig()

# 父类的功能是用来统一子类的,定标准的,只要看父类功能就知道子类也有

多态性指的是可以在不用考虑对象具体类型的情况下而直接使用对象,这就需要在设计时,把对象的使用方法统一成一种:例如obj1、obj2、obj3都是动物,但凡是动物肯定有talk方法,于是我们可以不用考虑它们三者的具体是什么类型的动物,而直接使用

obj1.talk()
# 汪汪汪

obj2.talk()
# 喵喵!

obj3.talk()
# 哼哼哼!

在多态性背景下用的继承,父类的功能其实不是真的拿来给子类用的,而是用来给子类定标准的,用来统一子类的,只需要把父类的功能规定好了,子类就肯定有,这样的好处是只要看父类就知道子类有什么功能

更进一步,我们可以定义一个统一的接口来使用

def talk(animal):    # 定义一个函数talk 传进来参数就叫动物
    animal.talk()    # 只要是动物就肯定有talk方法

# 三个对象统一用一个函数talk去调,用起来统一了
talk(obj1)
talk(obj2)
talk(obj3)

综上我们得知,多态性的本质在于不同的类中定义有相同的方法名,这样我们就可以不考虑类而统一用一种方式去使用对象,可以通过在父类引入抽象类的概念来硬性限制子类必须有某些方法名

import abc
# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod   # 该装饰器限制子类必须定义有一个名为talk的方法
    def talk(self): # 抽象方法中无需实现具体的功能
        pass

class Dog(Animal):  # 但凡继承Animal的子类都必须遵循Animal规定的标准
    def talk(self):
        print("汪汪汪")

class Cat(Animal):
    def talk(self):
        print('喵喵!')

class Pig(Animal): 
    def tell(self):  # 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化
        print("哼哼哼!")

obj1 = Dog()
obj2 = Cat()
obj3 = Pig()

obj1.talk()
obj2.talk()
obj3.talk()

但其实我们完全可以不依赖于继承,只需要制造出外观和行为相同对象,同样可以实现不考虑对象类型而使用对象,这正是Python崇尚的“鸭子类型”(duck typing):“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”。比起继承的方式,鸭子类型在某种程度上实现了程序的松耦合度,如下

# 三者看起来都像,三者都有talk功能,然而它们并没有直接的关系,且互相独立

class Dog:
    def talk(self):  
        print("汪汪汪")

class Cat:
    def talk(self):
        print('喵喵!')

class Pig:
    def talk(self):
        print("哼哼哼!")

面向对象进阶

内置方法

  • 什么是内置方法?

    • 定义在类内部,__开头并且__结尾的属性会在满足某种条件下自动触发

  • 为何要用内置方法?

    • 为了定制化我们的类or对象

    isinstance(obj,cls)和issubclass(sub,super)

isinstance(obj,cls)检查是否obj是否是类 cls 的对象

class Foo(object):
    pass

obj = Foo()

print(isinstance(obj,Foo))  # 判断obj是不是Foo的一个实例或者说对象
# True
print(isinstance([1,2,3],list)) # 判断[1,2,3]是不是list的实例
# True

issubclass(sub, super)检查sub类是否是 super 类的派生类

class Foo(object):
    pass

class Bar(Foo):
    pass

print(issubclass(Foo,object)) # 判断Foo是不是object的子类
print(issubclass(Bar, Foo))  # 判断Bar是不是Foo的子类

__str__改变对象的字符串显示

class People:   # 定义一个People类
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def __str__(self):     # 打印对象自动触发
        return "<%s:%s>" %(self.name,self.age)  # 返回值

obj = People("egon",19)
print(obj)  # print(obj.__str__())
# <egon:19>

__del__是在删除对象时触发

__del__会在对象被删除时自动触发。由于Python自带的垃圾回收机制会自动清理Python程序的资源,所以当一个对象只占用应用程序级资源时,完全没必要为对象定制__del__方法,但在产生一个对象的同时涉及到申请系统资源(比如系统打开的文件)的情况下,关于系统资源的回收,Python的垃圾回收机制便派不上用场了,需要我们为对象定制该方法,用来在对象被删除时自动触发回收系统资源的操作

# 例1:应用程序
class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
      
    def __del__(self):  # del obj在删除对象时会触发这个方法的运行
        print('++++>') 

# del obj   # 即使不运行删除对象代码,程序运行完回收应用程序的资源也会触发__del__运行
print('====end====')  # 当这行代码运行完程序就结束了,结束了就会回收所有的应用程序资
                      # 源并自动清理所有的对象和内存地址
# 打印结果           
# ====end=====
# ++++>



# 例2:操作系统
class People:
    def __init__(self,name,age):
        self.name = name
        self.age = age
        # 文件对象关联的是操作系统的资源一旦打开系统文件,就不会自己关,操作系统资源也不会自动回收
        self.f = open('a.txt',mode='rt',encoding='utf-8')

    def __del__(self):  # 这个时候就要用到__del__来清理操作系统资源了
        self.f.close()

obj = People("egon",19)

# del obj  
print('====end====') # 当这行代码运行完程序就结束了,结束了就会清理对象的内存地址

反射

  • python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

四个内置方法

hasattr 检测是否含有某属性

getattr 获取属性

setattr 设置属性

delattr 删除属性

四个方法适用于类和对象(一切皆对象,类本身也是对象)

class People:
    country = "china"
    def __init__(self,name,age):
        self.name = name
        self.age = age

    def tell_info(self):
        print(self.name,self.age)


obj = People('egon',18)


# hasattr 检测是否含有某属性
# 判断obj这个对象有没有country这个属性
print(hasattr(obj,"country"))  # obj.country 也可以说obj能不能点出.country


# getattr 获取属性
# 通过字符串country获取属性
res = getattr(obj,'country')  # china 拿到属性值
print(res)
# 通过字符串tell_info获取绑定方法
res = getattr(obj,'tell_info')
print(res)
# 调method方法获取到的是obj的信息
method = getattr(obj,'tell_info')
metsetattrhod()


# setattr 设置属性
# 通过字符串xxx赋给新的属性值
setattr(obj,"xxx",1111)  # 等同于obj.xxx = 1111
print(obj.xxx)

# delattr 删除属性
delattr(obj,"name")
print(obj.name)

异常处理

  • 什么是异常

    • 异常是程序错误发生的信号。程序一旦出现错误,就会产生一个异常,如果程序中没有处理该异常,该异常就会抛出来,程序的运行也随即终止。

错误分为两种

  • 1、语法错误

  • 2、逻辑错误

如何处理

  • 语法错误:这种错误在程序运行前就必须改正确

  • 逻辑错误:

    • 针对可以控制的逻辑错误,应该直接在代码层面解决

    • 针对不可以控制的逻辑错误,应该采用try...except...:一种异常产生之后的补救措施

可以控制的逻辑错误:

# 如果错误发生的条件是可预知的,我们需要用if进行处理:在错误发生之前进行预防
age = input(">>>: ").strip()
if age.isdigit():
    age = int(age)

    if age > 19:
        print('too big')
    elif age < 19:
        print('too small')
    else:
        print('you got it')
else:
    print('必须输入数字')

不可以控制的逻辑错误:

# 如果错误发生的条件是不可预知的,则需要用到try...except:在错误发生之后进行处理
# 如何用try...except
"""
print('start...')
try:
    被监测的代码块1
    被监测的代码块2
    被监测的代码块3
    被监测的代码块4
    被监测的代码块5
except 异常的类型1 as e:
    处理异常的代码
except 异常的类型2 as e:
    处理异常的代码
except 异常的类型3 as e:
    处理异常的代码
except (异常的类型4,异常的类型5,异常的类型6) as e:
    处理异常的代码
except Exception:    # 万能异常,当上面所有的异常处理都对不上最终运行此异常处理
    处理异常的代码
else:
    没有发生异常时要执行的代码
finally:
    无论异常与否,都会执行该代码,通常用来进行回收资源的操作
print('end...')

"""


# 案例1
print('start...')

try:
    print(111)
    print(222)
    l=[11,22,33]
    l[100]       # 异常:list index out of range 列表索引超出范围
    print(3333)
except IndexError as e: # 捕捉异常匹配成功并打印e,程序不会崩
    print(e)

print('end...')


# 案例2
print('start...')

try:
    print(111)
    print(222)
    l=[11,22,33]
    l[100]
    print(3333)
except KeyError as e:  # 异常对不上没有匹配成功,程序就崩了
    print(e)

print('end...')


# 案例3
print('start...')

try:
    print(111)
    print(222)
    l=[11,22,33]
    l[0]
    dic = {'k1':111}
    # dic['kkkkk']
    print(3333)
except Exception as e:  # 万能异常,什么异常都能对上
    print(e)
else:  # try内代码块没有异常则执行
    print('else的代码')
finally:      #  无论异常与否,都会执行该模块,通常是进行清理工作
    print('====>finally的代码')

print('end...')


l=[]
l[100]
>>> IndexError: list index out of range # 列表索引超出范围
    
dic={}
dic["k1"]
>>> KeyError: 'k1' 
    
class Foo:
    pass
obj = Foo()
obj.x
>>> AttributeError: 'Foo' object has no attribute 'x'  # Foo对象没有属性x
    
int("asdb")    # 无效的int()的基数为10:'asdb'
>>> ValueError: invalid literal for int() with base 10: 'asdb'
        
1/0  
>>> ZeroDivisionError: division by zero # 零除法错误:被零除法
    
    
def func(x):
    pass

func()        # func()缺少1个必需的位置参数:'x'
>>> TypeError: func() missing 1 required positional argument: 'x'

x
>>> NameError: name 'x' is not defined   # 名字'x'没有定义
    
print('asdfadf'
>>> SyntaxError: unexpected EOF while parsing  # 解析时意外的EOF:语法错误
      
      
      
# 断言:判断条件达到标准,如果不达到标准就抛出异常(测试程序的时候可以用,测试完后应该删掉)
l = [111,222]
if len(l) != 3:    # 判断如果l长度不等于3  方式一
    raise Exception('必须达到三个值')  # Exception: 必须达到三个值 
      
assert len(l)  == 3  # 断定l长度等于3  方式二,跟方式一效果一样
print('后续代码。。。')

# raise  # 主动抛出异常,为别人定规则的时候就可以用到主动抛异常
raise IndexError("索引错误")  # IndexError: 索引错误

# 自定义异常
# 自定义一个权限相关的错误,继承一下内置的异常类型(BaseException:基本的异常)
class Permission(BaseException): 
    def __init__(self,msg): # 自定义的还可以加其他功能
        self.msg = msg

raise Permission("权限错误") # 自定义异常通常配合raise使用,自己抛出自定义的异常,提示效果更强
# __main__.Permission: 权限错误
     

异常的种类

  • 在python中不同的异常可以用不同的类型(python中统一了类与类型,类型即类)去标识,一个异常标识一种错误
# 常见异常
AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x
IOError 输入/输出异常;基本上是无法打开文件
ImportError 无法引入模块或包;基本上是路径问题或名称错误
IndentationError 语法错误(的子类) ;代码没有正确对齐
IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError 试图访问字典里不存在的键
KeyboardInterrupt Ctrl+C被按下
NameError 使用一个还未被赋予对象的变量
SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
TypeError 传入对象类型与要求的不符合
UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它
ValueError 传入一个调用者不期望的值,即使值的类型是正确的

posted @ 2021-01-13 21:53  山风有耳  阅读(124)  评论(0编辑  收藏  举报