zoukankan      html  css  js  c++  java
  • 面向对象:元类、异常处理(try...except...)

    元类:

     python中一切皆对象,意味着:

      1. 都可以被引用,如 x = obj

      2. 都可以被当做函数的参数传入

      3. 都可以被当做函数的返回值

      4. 都可以当做容器类的元素(列表、字典、元祖、集合),如: l = [func,time,obj,1]

    换句话说,只要能满足上述4点,它就是对象;例如,类也是对象(类也是由type实例化产生的)

    class Foo:
        pass
    
    class Bar:
        pass
    
    print(type(Foo))
    print(type(Bar))
    # 运行结果:
    # <class 'type'>
    # <class 'type'>

    定义: 产生类的类称之为元类,默认所有用class定义的类,他们的元类都是type

    定义类的两种方式:

      方式一:class方法(class方式也是利用了type这个元类)

      方式二:直接利用type这个元类的方法 

    定义类的三要素: 类名、类的基类、类的名称空间

    """利用class方法"""
    class Chinese:
        country = "China"
    
        def __init__(self,name,age):
            self.name = name
            self.age = age
    
        def talk(self):
            print("%s is talking" %self.name)
    
    """利用type自己定义一个类"""
    class_name = "Chinese"   # 类名
    class_base = (object,)   # 它的基类;元祖的形式
    
    # 类体(即 类内部的代码)
    class_body = """
    country = "China"
    
    def __init__(self,name,age):
        self.name = name
        self.age = age
    
    def talk(self):
        print("%s is talking" %self.name)
    """
    class_dict = {}  # 类的名称空间
    
    exec(class_body,globals(),class_dict)   # 执行类体的代码,并把类体的值放入到 class_dict中作为其名称空间
    print(class_dict)
    # 打印结果:
    # {'country': 'China', '__init__': <function __init__ at 0x000000D574701E18>, 'talk': <function talk at 0x000000D57487BD90>}
    
    # 定义类
    Chinese1 = type(class_name,class_base,class_dict)   # 这样就定义出了一个类
    
    print(Chinese)
    print(Chinese1)
    # 打印结果:
    # <class '__main__.Chinese'>
    # <class '__main__.Chinese'>
    
    obj = Chinese("neo",18)
    obj1 = Chinese1("egon",22)
    
    print(obj,obj.name,obj.age)
    print(obj1,obj1.name,obj1.age)
    # 打印结果:
    # <__main__.Chinese object at 0x000000F1D54B67B8> neo 18
    # <__main__.Chinese object at 0x000000F1D54B6710> egon 22

    自定义元类控制类的行为:

    利用class定义类(如上面的 Chinese)时,完整的写法其实是: class Chinese(object,metaclass = type)  # metaclass = type的含义是 元类是type ; “class Chinese:” 这行代码在执行(即定义Chinese类)时,其实是 Chinese = type(...) ,也就是 type这个元类在实例化。

    如下代码:

    """不以type作为元类,而以自己定义的类作为元类,来控制类的行为"""
    
    """第二步的分析:创建 Mymeta 元类"""
    class Mymeta(type):  # class 是调用type这个元类; type元类里面已经定义了很多的方法,而我只是重新定义其中一小部分代码,所以我创建的元类需要继承type,从而Mymeta能够调用type的其他方法
        """
        Chinese 的元类是这个Mymeta,在定义Chinese类的时候需要实例化Mymeta,
        如: Chinese = Mymeta(class_name,class_bases,class_dict)
        需要往Mymeta中传入定义类的三要素,所以 Mymeta中需要有 __init__ 方法
        """
        def __init__(self,class_name,class_bases,class_dict):  # 这个实例化的__init__ 肯定有这三个参数:class_name,class_bases,class_dict
            super(Mymeta,self).__init__(class_name,class_bases,class_dict)   # type 的 __init__ 方法中有很多的功能,自己创建的元类 Mymeta 首先需要继承type的__init__ , 同时再定义自己的方法
    
            print(class_name)
            print(class_bases)
            print(class_dict)
    
    
    """第一步的分析:不以type作为元类,而以我自己创建的类 Mymeta 作为元类;所以我需要先定义一个元类 Mymeta"""
    class Chinese(object,metaclass=Mymeta):
        country = "China"
    
        def __init__(self,name,age):
            self.name = name
            self.age = age
    
        def talk(self):
            print("%s is talking" %self.name)   # 类体代码会以字典的形式传入到class_dict中作为 Chinese类的名称空间
    
    # 运行结果:
    # Chinese
    # (<class 'object'>,)
    # {'__module__': '__main__', '__qualname__': 'Chinese', 'country': 'China', '__init__': <function Chinese.__init__ at 0x00000075D7EDBD90>, 'talk': <function Chinese.talk at 0x00000075D7EDBE18>}
    
    """执行上述代码,直接就执行了 Mymeta 中 __init__ 的三个print,是因为 class Chinese(object,metaclass=Mymeta) 就是Mymeta在进行实例化,而实例化时会自动调用 __init__"""

    补充知识点: raise TypeError("类型错误")  # raise 是主动抛出异常,程序就不往下走了

    自定义元类控制类的行为示例2:(类名首字母大写)

    class Mymeta(type):
        def __init__(self,class_name,class_bases,class_dict):
            """目标功能:如果类名的首字母没有大写,直接报错"""
            if not class_name.istitle():  # .istitle() 用于判断字符串首字母是否大写,返回bool值
                raise TypeError("类名首字母必须大写")  # 首字母没有大写,直接抛出异常,程序终止
            super(Mymeta,self).__init__(class_name,class_bases,class_dict)
    
    class chinese(metaclass=Mymeta):  # 首字母没有大写  # 元类是 Mymeta
        pass
    
    # 执行结果:
    # 直接报错
    # TypeError: 类名首字母必须大写

     自定义元类控制类的行为示例2:(类体中需要有注释且注释不能为空)

    补充知识: 类的名称空间中有一个key 是 “__doc__”,它的含义是“类里面的注释”

    class Mymeta(type):
        def __init__(self,class_name,class_base,class_dict):
            if "__doc__" not in class_dict or not class_dict["__doc__"].strip():  # class_dict 中没有"__doc__"这个key 或者 class_dict["__doc__"]为空 # 空字符串和None自带bool值为False
                raise TypeError("必须有注释,且注释不能为空")
    
            super().__init__(class_name,class_base,class_dict)
    
    class chinese(metaclass=Mymeta):
        pass
    
    # 运行结果:
    # 报错
    # TypeError: 必须有注释,且注释不能为空

     自定义元类控制类的实例化行为:

    补充知识点: __call__ 方法: 让对象(如 people)也能有 people()的方法。(此方法是给对象用的)

    class Foo:
        def __call__(self, *args, **kwargs):  # 能让对象变成一个可调用对象  # obj()能触发 __call__
            print(self)
            print(args)
            print(kwargs)
    
    obj = Foo()  # 同理,Foo()能这样调用, Foo的那个元类(type)里面也有 __call__ 方法
    obj(1,2,3,a="A",b="AB")  # obj也能用类似于类Foo()的方法
    
    # 运行结果:
    # <__main__.Foo object at 0x000000D889D0A240>
    # (1, 2, 3)
    # {'a': 'A', 'b': 'AB'}

    """
    元类type内部也应该有一个 __call__ 方法, 会在调用Foo时触发执行
    Foo(1,2,x=1) 即相当于 Foo.__call__(Foo,1,2,x=1) 同时,类的调用也是一个实例化的过程
    """

    自定义元类控制类的实例化:

    class Mymeta(type):
        def __init__(self,class_name,class_bases,class_dict):
            if not class_name.istitle():
                raise TypeError("类的首字母必须大写")
            if "__doc__" not in class_dict or not class_dict["__doc__"].strip():
                raise TypeError("类中必须有注释且注释不能为空")
         super(Mymeta,self).__init__(class_name,class_bases,class_dict)
    """现在我自己定义一个__call__ 方法""" def __call__(self, *args, **kwargs): print("=====>>") class Chinese(object,metaclass=Mymeta): """ xx """ def __init__(self,name,age): self.name = name self.age = age def talk(self): print("%s is talking" %self.name) # obj = Chinese("neo",22) # Chinese能这样调用说明Mymeta中默认有 __call__ 方法 obj = Chinese("neo",22) # Chinese("neo",22) 在实例化的过程中会自动调用我重新定义的__call__ , 即 Chinese.__call__(Chinese,"neo",22) print(obj) # 运行结果: # =====>> # None """ 正常情况下,类实例化时会有3个过程发生: 1. 先造出一个空对象 2. 根据__init__把空对象初始化 3. 得到一个返回值 """ # 所以我上面在Mymeta中自定义的__call__ 是不规范的

     下面对 Mymeta 中的 __call__ 方法进行修改,并说明类在实例化时自动调用其__init__方法的机制:

    class Mymeta(type):
        def __init__(self,class_name,class_bases,class_dict):     # 这个__init__ 控制的是 类(Mymeta)的实例化
            if not class_name.istitle():
                raise TypeError("类的首字母必须大写")
            if "__doc__" not in class_dict or not class_dict["__doc__"].strip():
                raise TypeError("类中必须有注释且注释不能为空")
            super(Mymeta,self).__init__(class_name,class_bases,class_dict)
    
        def __call__(self, *args, **kwargs):  # 它控制的是类(Chinese)的调用
            print("=====>>")
            # 第一步: 先造一个空对象obj
            obj = object.__new__(self)  # object.__new__(cls)的作用是新建一个 cls类 的对象
            # 第二步: 把空对象obj初始化
            self.__init__(obj,*args,**kwargs)  # Chinese调用自己的 __init__  # 把新建的obj这个对象传入到 Chinese类的__init__ 中 # 这行的 *args和**kwargs的含义是: __call__中的*args和**kwargs是怎么接收的,就怎么原封不动的传给这行代码中的*args和*kwargs
            # 第三步: 返回obj
            return obj
        """
        以上也是类(Chinese)在实例化时__init__被触发自动动用的原因,因为 __call__ 被自动调用,而 __call__ 中又手动调用了 类(Chinese)中的 __init__
        """
    
    class Chinese(object,metaclass=Mymeta):
        """
        Chinese中的 __init__ 在被 Mymeta 中的 __call__ 调用时,*args中的"neo"当做位置参数传给 name,**kwargs中的 age=22 当作关键字参数传给 age
        """
        def __init__(self,name,age):
            self.name = name
            self.age = age
    
        def talk(self):
            print("%s is talking" %self.name)
    
    obj = Chinese("neo",age=22)
    print(obj.__dict__)
    # 运行结果:
    # =====>>
    # {'name': 'neo', 'age': 22}

     自定义元类控制类的实例化行为的应用:

    单例模式: 如果生成的多个对象里面的属性完全一样,就没必要实例化多次,让这多个对象使用同一块内存空间就行

    单例模式实现方式一 :

    """单例模式:让实例化的的对象共用同一个内存地址"""
    
    class Mysql:
        __instance = None  # 设置一个隐藏属性,用于储存实例化的对象
        def __init__(self):
            self.host = "127.0.0.1"
            self.port = 3306
    
        @classmethod
        def singleton(cls):
            if not cls.__instance:  # __instance 为 None时,说明还没有实例化过
                obj = cls()  # 实例化得到一个对象
                cls.__instance = obj # 把实例化的对象赋值给 __instance
            return cls.__instance
    
        def con(self):
            pass
    
        def execute(self):
            pass
    
    obj1 = Mysql.singleton()
    obj2 = Mysql.singleton()
    obj3 = Mysql.singleton()
    
    print(obj1)
    print(obj2)
    print(obj3)
    
    # 运行结果:
    # <__main__.Mysql object at 0x000000E52BF366D8>
    # <__main__.Mysql object at 0x000000E52BF366D8>
    # <__main__.Mysql object at 0x000000E52BF366D8>

    单例模式实现方式二: 元类的方式

    """通过自定义元类控制类的实例化过程:单例模式的 __call__ 方法"""
    
    class Mymeta(type):
        __instance = None  # 定义一个隐藏属性用于储存实例化的对象
        def __init__(self,class_name,class_bases,class_dict):
            if not class_name.istitle():
                raise TypeError("首字母必须大写,且只能首字母大写")
            super().__init__(class_name,class_bases,class_dict)
    
        def __call__(self, *args, **kwargs):
            if not self.__instance: # 如果没有经过实例化
                obj = object.__new__(self)  # 创建一个 self 的新对象
                self.__init__(obj)  # 对新创建的 self的对象执行 Mysql中的 __init__ 方法(实例化)
                self.__instance = obj  # 把实例化后的对象赋值给 __instance
            return self.__instance
    
    
    class Mysql(metaclass=Mymeta):
        def __init__(self):
            self.host = "127.0.0.1"
            self.port = 3306
    
        def con(self):
            pass
        def execute(self):
            pass
    
    obj1 = Mysql()
    obj2 = Mysql()
    obj3 = Mysql()
    
    print(obj1)
    print(obj2)
    print(obj3)
    # 运行结果:
    # <__main__.Mysql object at 0x0000001965AC67B8>
    # <__main__.Mysql object at 0x0000001965AC67B8>
    # <__main__.Mysql object at 0x0000001965AC67B8>

    单例类:

    class Singleton:
        __instance = None
    
        def __init__(self):
            pass
    
        def __new__(cls, *args, **kwargs):
            if not cls.__instance:
                cls.__instance = object.__new__(cls)
            return cls.__instance
    a = Singleton()
    b = Singleton()
    
    print(a)
    print(b)
    
    # __new__()方法是在实例化之前被调用,返回一个空对象;然后__init__()方法对这个空对象添加独有属性(__init__没有返回值);
    # __new__() 是一个静态方法(@staticmethod)

    练习: 加工标准类型,实现一个列表,所有元素都是字符串,实现获取中间值的属性,实现授权清空元素。

    class MyList(list):
        def __init__(self,*args,auth=True):
            self.auth = auth
            for i in args:
                if not isinstance(i,str):
                    raise TypeError("only string is valid")
            super().__init__(args)
    
        @property
        def show_mid(self):
            length = len(self)
            half_length = length/2  # 相除的结果是float型
    
            if length%2 == 0:
                return [self[int(half_length)-1],self[int(half_length)]]
            else:
                return self[int(half_length)]
        def clear(self):
            if self.auth:
                super().clear()
            else:
                print("no auth to clear list")
    
    s = MyList("neo","alex","egon","jack",auth=False)
    print(s.show_mid)
    s.clear()
    print(s)

    异常处理:

    异常:就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止),在python中,错误触发的异常如下:

    错误分两种:

      1. 语法错误: 程序在执行之前python会先检测其语法,如果出现语法错误,根本过不了python解释器的语法检测,所以必须在程序执行之前就改正

      2. 逻辑错误:如:TypeError、ValueError、NameError、IndexError、KeyError、AttributeError、StopIteration等

    关于异常:

      一、 错误发生的条件如果是可以预知的,此时应该用if判断去预防异常

      二、 错误发生的条件如果是不可预知的,此时应该用异常处理机制:try ... except...

    try:  # 检测下面的代码
        f = open("a.txt","r")
        print(f.__next__(),end="")
        print(f.__next__(),end="")
        print(f.__next__(),end="")
        print(f.__next__(),end="")
        print(f.__next__(),end="")
    
        f.close()
    except StopIteration:  # 捕捉异常类型,如果上面代码的出错类型和except后面的类型一样,则执行下面的代码,程序也继续运行;但被检测的代码(try里面的代码)在抛出异常后不再往下执行
        print("
    出错了")
    
    print("====>>1")
    print("====>>2")
    
    # 运行结果:
    # 111
    # 222
    # 333
    # 出错了
    # ====>>1
    # ====>>2

    try ... except ...的详细用法:

      1. 异常类只能用来处理 指定的异常情况,如果非指定异常则无法处理

      2. 多分支:被检测的代码块抛出的异常有多种可能性,并且我们需要针对每一种异常类型都定制专门的处理逻辑

    try:
        print("===>1")
        name
        print("===>2")
        l = [1,2,3]
        l[100]
        print("===>3")
        d = {}
        d["name"]
        print("===>4")
    
    except KeyError as e: # 把异常值赋值给一个变量e
        print("--->",e)
    except IndexError as e:
        print("--->",e)
    except NameError as e:
        print("--->",e)
    
    print("test")
    
    # 运行结果:
    # ===>1
    # ---> name 'name' is not defined
    # test
    
    """多分支的except有点类似于 else的用法"""

      3. 万能异常: Exception(不包括语法错误); 使用场景: 被检测的代码块抛出的异常有多种可能性,并且我们针对所有的异常类型都只用一种处理逻辑

    把多分支的代码简写:

    try:
        print("===>1")
        name
        print("===>2")
        l = [1,2,3]
        l[100]
        print("===>3")
        d = {}
        d["name"]
        print("===>4")
    except Exception as e:
        print("异常发生啦:",e)
    
    print("===> after code")
    # 运行结果:
    # ===>1
    # 异常发生啦: name 'name' is not defined
    # ===> after code

    多分支和万能异常也能结合起来使用:

    try:
        print("===>1")
        name
        print("===>2")
        l = [1,2,3]
        l[100]
        print("===>3")
        d = {}
        d["name"]
        print("===>4")
    except NameError as e:
        print("--->",e)
    except IndexError as e:
        print("--->",e)
    except KeyError as e:
        print("--->",e)
    except Exception as e:  # 用法有点类似于 elif..., elif..., elif..., else...
        print("其他异常",e)
    
    print("test after code")
    
    # 运行结果:
    # ===>1
    # ---> name 'name' is not defined
    # test after code

      4. 其他结构:try ... except ... else...finally...

    try:
        print("===>1")
        name = "NEO"
        print("===>2")
        l = [1,2,3]
        val = l[1]
        print("===>3")
        d = {"name":"neo"}
        d_name = d["name"]
        print("===>4")
    except NameError as e:
        print("--->",e)
    except IndexError as e:
        print("--->",e)
    except KeyError as e:
        print("--->",e)
    except Exception as e:
        print("其他异常",e)
    else:  # 在被检测的代码块没有发生异常时执行else语句 # try ...except ... else ...中else的用法类似于 for 循环 else...
        print("在被检测的代码块没有发生异常时执行else语句")
    
    finally:  # 不管被检测的代码块有没有发生异常都会执行 finally语句  # finally语句属于被检测代码块的附加逻辑,即 finally语句隶属于被检测的代码块
        print("不管被检测的代码块有没有发生异常都会执行 finally语句")
    """finally语句通常在一些关于回收资源方面使用,如 finally: f.close()"""
    
    print("test after code")
    # 运行结果:
    # ===>1
    # ===>2
    # ===>3
    # ===>4
    # 在被检测的代码块没有发生异常时执行else语句
    # 不管被检测的代码块有没有发生异常都会执行 finally语句
    # test after code

      5. 主动触发异常: raise 异常类型(值)

    class People:
        def __init__(self,name,age):
            if not isinstance(name,str):
                raise TypeError("name必须是str类型")
            if not isinstance(age,int):
                raise TypeError("age 必须是int 类型")
         self.name = name
         self.age = age people
    = People("neo",22)

      6. 自定义异常类型

    class MyException(BaseException):
        def __init__(self,msg):
            super(MyException,self).__init__()
            self.msg = msg
    
        def __str__(self):  # 需要调用 __str__
            return "<%s>" %self.msg  # 需要return
    
    raise MyException("我自己的异常类型")  # raise的作用除了 追踪异常信息和异常类型  还有 print(obj)(打印异常对象)的作用,所以需要在MyException中调用 __str__
    
    # 运行结果:
    # Traceback (most recent call last):
    #   File "F:/l面向对象.py", line 1860, in <module>
    #     raise MyException("我自己的异常类型")  
    # __main__.MyException: <我自己的异常类型>

      7. 断言assert

    assert ("name" in info) and ("age" in info)  # 断言 后面的内容; 如果assert后面的内容为False,则报错:AssertionError
  • 相关阅读:
    js+servlet实现百度搜索栏下拉框的实现
    python time模块
    dajngo基础
    MySQL解决时区问题:datadrip com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone.
    数据库连接池
    超星学习通绕开鼠标监控的方法
    centos7 安装python3
    docker命令
    爬虫一
    学习python的第九天笔记
  • 原文地址:https://www.cnblogs.com/neozheng/p/8496280.html
Copyright © 2011-2022 走看看