day29 元类及异常处理

  • Post author:
  • Post category:其他



元类及异常处理


元类


什么是元类




python


中,一切皆对象,对象是由类产生的,那么类是不是对象呢?


举例:

class A:
    pass
print(type(A))
# <class 'type'>


从上例中可以看出,在

python


中,类也是对象,是由某个兑现实例化来的,而且这个类的名字叫做

type


,那么这个类又是什么呢?这个类就是元类



元类就是产生类的的类


实例化类对象


普通的类是由元类实例化产生的,那么我们如何按照普通类的方式去生成类对象呢?


由于定义类时会使用

class


关键字,实际上

class


关键字就帮我们做了实例化的过程


p = type()  # 通过元类实例化一个普通的类对象
# TypeError: type() takes 1 or 3 arguments


经过上方的代码测试,发现会报错,错误信息为缺少一个或者三个参数,我们来看一下源代码

class type():
    def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
        """
            type(object_or_name, bases, dict)
            type(object) -> the object's type
            type(name, bases, dict) -> a new type
            # (copied from class doc)
            """
        pass


在python中的源代码信息如上,通过上述信息可以发现,在我们使用type元类想要创建一个类对象时需要传入一个或者三个参数:


传入一个参数时:返回实例化这个对象的类


传入三个参数时分别是:


  • name


    :创建的类对象的名称,

    字符串类型


  • bases


    :创建的这个类对象的父类们,

    tuple类型


  • dict


    :这个类对象的名称空间,

    dict类型


那么我们传入这三个参数进行试验:

p = type("Aclass",(object,),{"a":10})
print(type(p)) # <class 'type'>


通过上方的测试代码我们创建了一个类对象,并且将这个对象的地址赋值给了

p


总结




  • python


    中,一切皆对象,类也是对象,普通类是由元类实例化产生的类对象


  • 元类是用来产生类的类,英文为

    metaclass


  • 我们可以通过

    type(class_name,bases,dict)


    自己实例化元类的类对象


元类可以做什么?


元类的主要作用


  • 自定义类对象时使用


  • 限制类对象的实例化过程时使用


如何使用元类?


1、定义类对象的使用过程

__init__


方法(重点)


# 需求:在定义类时类名必须大写,类中的属性及方法必须小写
class MyMetaClass(type):
    def __init__(self,class_name,bases,dict):
        if not class_name.istitle():
            raise Exception("类名必须为大驼峰命名法")
        for k in self.__dict__:
            if not k.islower():
                raise Exception("属性及方法名必须小写")
        super().__init__(class_name,bases,dict)
​
​
class person(metaclass=MyMetaClass): # 将person的元类指定为 MyMetaClass
     pass#     raise Exception("类名必须为大驼峰命名法")
# Exception: 类名必须为大驼峰命名法
​
​
class Person(metaclass=MyMetaClass):
    def My(self):
        pass
    
#     raise Exception("属性及方法名必须小写")
# Exception: 属性及方法名必须小写


通过上述的方法,我们可以高度定义类对象的实例化,从而限制类的定义


与普通的实例化相同,在进行对象的实例化时会调用

__init__


方法,并执行,此时只要在实例化过程中进行限制就可以定制化类对象的实例化


2、类中的

__new__


方法(用的比较少)



在以前说过,类的实例化会进行两步:


  • 产生一个空的对象


  • 对产生的空对象进行初始化


元类的实例化产生类对象与普通的类进行实例化相似,也会经过这两个步骤


使用

__init__


方法可以进行初始化过程,那么如何产生空的对象呢?



使用

__new__


方法可以创建空的对象


# __new__的源代码解释
@staticmethod # known case of __new__
    def __new__(*args, **kwargs): # real signature unknown
        """ Create and return a new object.  See help(type) for accurate signature. """
        pass


上述的源代码显示


  • __new__


    方法为静态方法(非绑定方法)


  • __new__


    方法是用来创建一个空对象并将其返回


那么其中的

*args




**kwargs


是什么呢?


class MyMetaClass(type):
    def __new__(cls, *args, **kwargs):
        print(cls)  # <class '__main__.MyMetaClass'>
        print(args)  # ('A', (), {'__module__': '__main__', '__qualname__': 'A',"a":10})
        print(kwargs)  # {}
class A(metaclass=MyMetaClass):
    a = 10


通过上述方法可以看见:


  • 其中

    cls




    MyMetaClass






  • args


    中的值为使用元类进行创建对象时传入的三个参数


  • kwargs


    为创建对象时传入的关键字参数


通过类的实例化过程我们可以知道,会首先执行

__new__


方法,然后再执行

__init__


方法





__new__


中也会将对象的名称空间进行传入,那么是否可以在

__new__


方法中进行定制化呢?


class MyMetaClass(type):
    def __new__(cls, *args, **kwargs):
        if not args[0].istitle():
            raise Exception("类名必须为大驼峰命名法")
        for k in args[2].values():
            if not k.islower():
                raise Exception("属性及方法名必须小写")
        return type.__new__(cls,*args,**kwargs)
​
class A(metaclass=MyMetaClass):
    B = 10# Traceback (most recent call last):
#   File "H:/py8-study/practice/day29/元类的引出.py", line 69, in <module>
#     class A(metaclass=MyMetaClass):
#   File "H:/py8-study/practice/day29/元类的引出.py", line 66, in __new__
#     raise Exception("属性及方法名必须小写")
# Exception: 属性及方法名必须小写


通过上述

__new__




__init__


的学习,产生了以下几个疑问:



1、

__init__




__new__


有什么区别呢?



  • __init__


    方法是在创建了类对象后初识化时执行的


  • __new__


    方法是在创建对象时执行


2、在

__new__


中也可以进行类的定制化,那么在实现定制化时应该使用

__init__


方法还是

__new__


方法呢?




  • 在进行定制化时两者都可以使用,按照合理的思维可以是对类对象的名字进行限制或定制化时,应该使用

    __new__


    方法,在不符合条件时,就不让类进行实例化,


  • 如果对类对象的属性及方法进行限制或定制化时使用

    __init__


    方法,只有符合条件时才能进行初始化


  • 但是推荐的是使用

    __init__


    方法,因为

    __init__


    方法的使用较为简单



  • 在使用

    __new__


    创建对象时必须讲对象进行返回,而创建对象的方法在

    type


    类中,所以每次必须调用

    type.__new__(cls,*args,**kwargs)


    ,并将其返回




3、有了

__new__


为什么还要使用

__init__


方法呢?



  • 虽然在元类创建类对象时看着两者的方法作用没什么区别,但是在创建普通的对象时区别很大,

    __new__


    就是用来产生空对象的,

    __init__


    就是用来产生初始化对象的,两者的功能交叉很小,因为在实例化普通对象时没有名称空间的字典。



  • 在元类实例化类对象时虽然看着两者功能很相似,但是不同的方法有不同的职责,在进行创建对象时就使用

    __new__


    方法,进行对象初始化时就使用

    __init__


    方法



3、

__call__


方法



我们在进行程序编写时经常会出现 以下错误:

class A:
    b = 10
    def func(self):
        pass

a = A()
a.func()
a.b()

# Traceback (most recent call last):
#   File "H:/py8-study/practice/day29/元类的引出.py", line 82, in <module>
#     a.b()
# TypeError: 'int' object is not callable


我们执行上述程序时,调用

func


函数时就不会报错,但是调用

b()


时就会报错,这是为什么的?



这时因为在对象类时有一个

__call__


方法进行控制,在进行调用时会执行类的

__call__


方法



在上述例子中,由于

func




function


类实例化的对象,而

b




int


类实例化的对象,而在

func


中会将函数的执行结果进行返回,而在

int


中会抛出异常







那么

__call__


在什么时候会被调用呢?


在用类进行实例化对象时会被调用


如何使用

__call__


进行控制实例化对象的过程

class MyMeta(type):
    def __call__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)


class A(metaclass=MyMeta):
    def __init__(self,name,age):
        self.age = age
        self.name = name

# 在此处执行时,不添加下方代码不会打印
                
a = A("lee",18)
# <class '__main__.A'>
# ('lee', 18)
# {}


从上述代码中可以看出,只有在实例化过程中才会调用

__call__


方法


并且调用者时

A


,其参数就是实例化时输入的参数


因为在

A


类对象中没有

__call__


方法,所以继续寻找他的类

MyMeta


,并使用其中的

__call__


方法





那么我们就可以使用这个特性将元类中的

__call__


方法进行继承并自定义,增加我们的限制等


下方的单例模式就是这个的应用方式之一


单例设计模式


当我们设计的类只希望产生一个对象时就要用到单例设计模式

class SingleMetaClass(type):
    def __init__(self,name,bases,dic):
        self.obj = None
​
    def __call__(self, *args, **kwargs):
        if self.obj:
            return self.obj
        obj = type.__call__(self,*args,**kwargs)
        self.obj = obj
        return self.obj
​
​
class Student(metaclass=SingleMetaClass):
    def __init__(self,name,age):
        self.name = name
        self.age = age
​
    def say(self):
        print("my name is %s"%self.name)
​
​
s1 = Student("lee",18)
print(s1)
​
s2 = Student("lee",18)
print(s2)
​
class Teacher(metaclass=SingleMetaClass):
    def __init__(self,name,age):
        self.name = name
        self.age = age
​
    def say(self):
        print("my name is %s"%self.name)
​
t = Teacher("lee",18)
print(t)
​
​
# <__main__.Student object at 0x000001F8D96AD320>
# <__main__.Student object at 0x000001F8D96AD320>
# <__main__.Teacher object at 0x000001F8D96AD3C8>


异常


什么是异常


异常是程序运行过程中发生的非正常情况,是一个错误发生时的信号


异常如果没有被正确处理的话,将导致程序被终止,这对于用户体验是非常差的,可能导致严重的后果


处理异常的目的就是提高程序的健壮性


异常的分类


python解释器在执行代码前会先检查语法,语法检查通过才会开始执行代码


1.语法检测异常 作为一个合格的程序员 是不应该出现这种低级错误


2.运行时异常



已经通过语法检测,开始执行代码,执行过程中发生异常 称之为运行时异常


异常:

TypeError: 'int' object is not subscriptable     对象不能被切片  
TypeError: 'list' object is not callable        对象不能被调用
IndexError: list index out of range             索引超出范围
TypeError: 'builtin_function_or_method' object is not iterable     对象不能被迭代
KeyError: 'xxx'      不存在这个key
FileNotFoundError: [Errno 2] No such file or directory: 'xxxxx'  文件找不到


异常的组成:

Traceback (most recent call last):
  File "F:/python8期/课堂内容/day29/11.常见异常.py", line 22, in <module>
    with open("xxxxx") as f:
FileNotFoundError: [Errno 2] No such file or directory: 'xxxxx    
Traceback    是异常追踪信息   用于展示错误发生的具体位置 以及调用的过程
其中 包括了 错误发生的模块  文件路径   行号  函数名称  具体的代码

最后一行  前面是错误的类型  
		 后面 错误的详细信息   在查找错误时 主要参考的就是详细信息

        


异常处理


异常发生后 如果不正确处理将导致程序终止,我们必须应该尽量的避免这种情况发生


重点:


必须掌握的语法

语法:

try:

可能会出现异常的代码 放到try里面

except 具体异常类型 as e:

如果真的发生异常就执行except


如何正确处理异常


  1. 当发生异常 不是立马加try 要先找出错误原因并解决它


  2. try 仅在 即使你知道为什么发生错误 ,但是你却无法避免



    例如 你明确告诉用户 需要一个正确文件路径 然而用户依然传入了错误的路径




    如 socket 双方都要使用管道 ,但是如果一方有由于某些原因强行关闭了 ,即使你知道原因也无法避免出错 那就只能try 保证程序正常结束


    总结一句话:能不加try 就不加try


自定义异常类


当系统提供异常类不能准确描述错误原因时 就可以自定义异常类


继承自Exception即可

class  MyException(Exception):
    pass
 


主动抛出异常:


什么时候需要主动抛出异常



当我们做功能的提供者,给外界提供一个功能接口



但是使用者不按照相应的方式来使用,或者参数类型不正确等原因,导致功能无法正常执行时,就应该主动抛出异常


主动抛出异常使用raise 关键字


后面可以跟任何Exception的子类 或是 对象

raise MyException
raise MyException("错误具体原因!")


断言assert


断言 其实可以理解为断定的意思


即非常肯定某个条件是成立的


条件是否成立其实可以使用if来判断


其存在的目的就是 为了简化if 判断而生的


转载于:https://www.cnblogs.com/lice-blog/p/10930690.html