zoukankan      html  css  js  c++  java
  • day 26

    什么是元类

    首先要明确一个大前提一切皆对象

    类也是对象,可以把一个类当成普通对象来使用,比如存储到列表中,或者作为参数传给函数等等...

    对象是如何产生的? 通过类实例化产生的

    类对象 是由type实例化产生的。

    我们可以手动调用type来实例化产生一个类:

     

    type(类名(class_name),父类元组(object,),名称空间字典(name_dict) #返回一个新的类

    type(对象) #将会返回这个对象的类型

     

     

    一个类由三个部分组成

    1.类的名称  :class_name

    2.类的父类们 : 传入父类元组(object,)

    3.类的名称空间   名称空间字典(name_dict)

    # 模拟解释器创建类对象
    def test1(a):
        print(a)
    
    def test2(self,b):
        print(self,b)
    
    class_name = "C"
    bases = (object,)
    name_dict = {"name":"jack","test1":test1,"test2":test2}
    
    C = type(class_name,bases,name_dict)   通过type加括号来里面加入类名,父类元组和名称空间在用一个变量来接收
                            就可以模拟解释器来生成类对象。
    # print(C) c1 = C() # print(c1) c1.test2(100)

    exec 与 eval

      exec用于执行字符串形式的python代码 只要符合python都能执行 ,并且可以指定将执行产生的名字放入某个名称空间

    eval 用于执行简单的表达式,不能有任何的特殊语法

    class_text = """
    class A:
        def test(self):
            print(self)
    """
    loca2 = {}
    exec(class_text,None,loca2)
    print(loca2)
    #eval(class_text)  #报错

    最终可以总结出元类是用于产生类的类,元类翻译为metaclass。

    我们在定义元类时 尽量在类名后添加MetaClass 方便阅读

    用来干啥

    当我们需要高度定制类时,如限制类名必须大写开头等等...

    就需要使用元类,但是元类type中的代码 无法被修改 ,只能创建新的元类(继承自type) 通过覆盖__init__来完成对类的限制。

    使用元类

    class MyMetaClass(type):
        pass
    
    # 使用自定义元类
    class Person(metaclass=MyMetaClass):
        pass

    __init__方法 (重点)

    实例化对象时会自动执行类中的__init__方法, 类也是对象 ,在实例化类对象时会自动执元类中的__init__方法

    会将类本身作为第一个对象传入,并且传入类的三个必要参数,类的名字,父类们,名称空间。

    案例:限制类名首字母必须大写,并控制类中方法名必须小写:

    class MyMetaClass(type):
        def __init__(self,class_name,bases,name_dict):
    
            super().__init__(class_name,bases,name_dict)
            # 类名必须首字母大写  否则直接抛出异常
            if not class_name.istitle():
                print("类名必须大写 傻x!")
                raise Exception
    
            # 控制类中方法名必须全部小写
            for k in name_dict:
                if str(type(name_dict[k])) == "<class 'function'>":
                    if not k.islower():
                        raise Exception
        pass
    
    # 会自动调用其元类中的 __init__ 方法传入 类对象本身 类名称 父类们  名称空间
    class Student(object,metaclass=MyMetaClass): # MyMetaClass("Student",(object,),{})
        NAME = 10
        def say(self):
            print("SAY")
        pass
    # 需求: 要求每个类必须包含__doc__属性   __doc__ 用于访问一个对象的注释信息
    
    
    # 你要控制类的创建  那就自定义元类 覆盖__init__
    class DocMeatClass(type):
    
        def __init__(self,class_name,bases,name_dict):
            super().__init__(class_name,bases,name_dict)
            # if not("__doc__" in name_dict and name_dict["__doc__"]):
          判断名称空间字典内是否有__doc__属性,并且要保证values值不为空,如果没有满足设定要求
          就打印出错误信息
    # raise Exception if not self.__doc__: raise Exception class Person(metaclass=DocMeatClass): pass



    __new__方法

    元类中的new方法会在创建类对象时执行,并且先于init方法,作用是创建一个类对象。

    class A(metaclass=MyMetaClass):

    pass

    1.执行MyMetaClass的__new__方法 拿到一个类对象

    2.执行MyMetaClass的__init__ 方法 传入类对象以及其他的属性 ,进行初始化

    注意:如果覆盖了__new__ 一定也要调用type中的__new__并返回执行结果

     

    使用__new__定制元类与__init__定制元类的区别:

    生成元类的时候会首先调用__new__生成一个类的空对象,再调用__init__方法来对类的方法和属性进行初始化操作。

    在__new__中亦可以对元类进行限制,如果对性能要求高的话 可以选在在new中完成定制 如果发现有问题,就不用创建类对象了。

     

    __call__方法(重点)

    元类中的 call方法会在调用类时执行,可以用于控制对象的创建过程,我们可以通过__call__方法来对对象的生成加以限制,达到调教的效果,如可以设定传入的参数只要是字符串类型就必须是大写。

    这里需要注意的是:__new__,__init__ 是只有在生成类对象时才会执行,如果只是这样的话,并不会有__call__参与进去,即,只有在调用类来生成对象的才会执行。

    下面就是__call__使用的案例,控制对象传入字符串参数的大小写。

    class MyMeta(type):
    
        # 获得某个类的实例
        def __call__(self, *args, **kwargs):
            print("call")
            # return super().__call__(*args,**kwargs)
            new_args = []
            for i in args:
                if isinstance(i,str):
                    new_args.append(i.upper())
                else:
                    new_args.append(i)
            return super().__call__(*new_args,**kwargs)
    
    
    
    # 注意注意注意:  __new__  __init__ 是创建类对象时才会执行
    # __call__ 类对象要产生实例时执行
    
    class Student(metaclass=MyMeta):
        def __init__(self,name,gender,age):
            self.name = name
            self.gender = gender
            self.age = age
    
    s = Student("jack","woman",18)
    print(s.age)
    print(s.gender)
    这里是将传入的参数进行一个遍历循环,将是字符串形式的参数通过upper()方法变成大写放入一个
    空列表中,在将其他参数一起加入列表中,最后将列表打散之后通过调用的__call__方法,代替原先
    的参数列表,返回给外界。


    元类实现单例模式

    什么是单例:  某个类如果只有一个实例对象,那么该类成为单例类。

     

    单例的好处:  当某个类的所有对象特征和行为完全一样时,避免重复创建对象,浪费资源。

    用元类实现单例就是首先设置一个obj属性为空,当有第一组参数出传入时,就将这组参数赋值给

    obj 属性,同一个类中不可修改。

     

     

    ```python
    class SingletonMetaClass(type):
        #创建类时会执init 在这为每个类设置一个obj属性 默认为None
        def __init__(self,a,b,c):
            super().__init__(a,b,c)
            self.obj = None
        
        # 当类要创建对象时会执行 该方法
        def __call__(self, *args, **kwargs):
             # 判断这个类 如果已经有实例了就直接返回 从而实现单例
            if self.obj:
                return self.obj
    
            # 没有则创建新的实例并保存到类中
            obj = type.__call__(self,*args,**kwargs)
            self.obj = obj
            return obj
    ```

    异常

    什么是异常

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

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

    处理异常的目的就是让程序越来越稳定,更加健壮。

    异常的分类

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

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

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

     

    在python解释器 中常见的报错信息入下:

     

    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 是异常追踪信息 ,用于展示错误发生的具体位置, 以及调用的过程。
    其中 包括 :错误发生的模块,  文件路径,   行号,  函数名称,  具体的代码。

    最后一行 前面是错误的类型 ,后面是错误的详细信息,在查找bug并修改时主要是参考最后的详细信息。

    异常处理

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

    重点:

    必须掌握的语法

    语法:

    try:

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

    except 具体异常类型 as e:

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

    如何正确处理异常

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

    2. try 仅在 即使你知道为什么发生错误 ,但是你却无法避免 例如 你明确告诉用户 需要一个正确文件路径 然而用户依然传入了错误的路径

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

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

     

    主动抛出异常:

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

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

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

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

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

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

     

     

    断言assert

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

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

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

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

     

  • 相关阅读:
    java核心学习(十六) javaIO框架---Process类的流,读写其他进程
    java核心学习(十五) IO框架---重定向标准输入输出
    java核心学习(十四) IO框架---推回输入流
    java核心学习(十三) IO框架---转换流和缓冲流
    java核心学习(十二) IO框架---理解IO流
    递推+矩阵快速幂 HDU 2065
    树形DP hdu1520
    二分图之最小路径覆盖 HDU1151
    二分图之最小独立集 HDU 2768
    最短路 POJ2267
  • 原文地址:https://www.cnblogs.com/1624413646hxy/p/10931795.html
Copyright © 2011-2022 走看看