• python之路--面向对象(三)


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

      isinstance(obj,cls)检查是否obj是否是类 cls 的对象。由于Python中一切都是类,所以也可以用来判断是否为字典,字符串,列表等类型

    #应用一:判断是否为该类的对象
    class Foo(object):
        pass
    obj = Foo()
    print(isinstance(obj, Foo))  #True
    #应用二 :判断数据类型
    print(isinstance('hahah',str)) #True
    print(isinstance('hahah',dict)) # False

      issubclass(sub, super)检查sub类是否是 super 类的派生类。issubclass两个参数都必须为类。

    class A:
        pass
    
    class B(A):
        pass
    class C:
        pass
    
    print(issubclass(B,A))  #True    
    print(issubclass(C,A))  #False

    二 反射

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

      四个可以实现自省的函数(getattr,hasattr,setattr,delattr)

    class People:
        x=1
        def run(self):
            print('run')
    
    print(hasattr(People,'x'))   #True
    #判断object中有没有一个name字符串对应的方法或属性
    #hasattr(*args, **kwargs)  第一个参数为对象,第二个为属性或者方法
    #------------------》《-------------------------------------
    
    print(getattr(People,'x'))   #1
    #获取属性对应的值或者内存地址
    # getattr(object, name, default=None),如果不存在,则返回default的值
    #------------------》《-------------------------------------
    
    setattr(People,'x',2);print(People.x)   #2
    #设置修改属性
    #setattr(x, y, v) x为对象,y为属性,v为要赋予的值
    #------------------》《-------------------------------------
    
    delattr(People,'x')
    print(People.__dict__)   #{'run': <function People.run at 0x000000000221B8C8>,}
    #删除属性或者方法
    四个自省函数的使用
    import sys
    this_module=sys.modules[__name__]
    print(this_module)#this_module为当前模块名
    获取当前模块名

      为什么用反射之反射的好处: 

      好处一:实现可插拔机制

        有俩程序员,一个lili,一个是egon,lili在写程序的时候需要用到egon所写的类,但是egon去跟女朋友度蜜月去了,还没有完成他写的类,lili想到了反射,使用了反射机制lili可以继续完成自己

      的代码,等egon度蜜月回来后再继续完成类的定义并且去实现lili想要的功能。

        总之反射的好处就是,可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然

      后后期再去实现接口的功能

    class FtpClient:
        'ftp客户端,但是还么有实现具体的功能'
        def __init__(self,addr):
            print('正在连接服务器[%s]' %addr)
            self.addr=addr
    #from module import FtpClient
    f1=FtpClient('192.168.1.1')
    if hasattr(f1,'get'):
        func_get=getattr(f1,'get')
        func_get()
    else:
        print('---->不存在此方法')
        print('处理其他的逻辑')

    好处二:动态导入模块(基于反射当前模块成员)

    #字符串导入模块的两种方式
    #__import__内置函数,不需要导入其他模块。官方不推荐平时使用过程中利用该方法
    a=__import__('tmp')
    a.test()
    
    #通过导入模块来完成,官方推荐使用该方法
    import importlib
    mo =importlib.import_module('tmp')
    mo.test()

    三 __setattr__,__delattr__,__getattr__

    class People:
        country='china'
        def __init__(self,name):
            self.name=name   #触发__setattr__方法
    
        def __getattr__(self, item):   #类的属性不存在时,才会触发
            print('----> from getattr:你找的属性不存在')
            print(item,type(item))   #y <class 'str'>  item为属性名。
    
        def __setattr__(self, key, value):
            '''
            因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,
            就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
            '''
            print('this is setattr')
            # self.key=value #这就无限递归了,你好好想想
            self.__dict__[key]=value #应该使用它
    
        def __delattr__(self, item):    #item 为属性名,为字符串类型
            print('this is del')
            # del self.item #无限递归了
            self.__dict__.pop(item)
    #本质上都是在对名称空间做操作
    
    p1=People('test')
    p1.name='test1'  #同样也会触发__setattr__方法
    del p1.name  #触发__delatty__
    三个内嵌方法的使用

    四 __setitem__,__getitem,__delitem__

      这三种内嵌函数与__setattr__,__delattr__,__getattr__的区别是,只有使用类似字典格式进行操作时,才会触发__setitem__,__delitem__, __getitem__。如下:

    class People:
        country='china'
        def __init__(self,name):
            self.name=name
    
        def __getitem__(self, item):   #类的属性不存在时,才会触发
            print('----> from getitem:你找的属性不存在')
            print(item,type(item))   #y <class 'str'>  item为属性名。
    
        def __setitem__(self, key, value):
            '''
            因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,
            就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
            '''
            print('this is setattr')
            # self.key=value #这就无限递归了,你好好想想
            self.__dict__[key]=value #应该使用它
    
        def __delitem__(self, item):    #item 为属性名,为字符串类型
            print('this is del')
            # del self.item #无限递归了
            self.__dict__.pop(item)
    #本质上都是在对名称空间做操作
    
    '''
    p1=People('test')
    p1.name='test1'    
    del p1.name
    以上三种方法虽然都会进行操作,但是都无法触发__setitem__,__delitem__, __getitem__
    是触发了object的__setattr__,__delattr__,__getattr__
    '''
    p1=People('test')
    del p1['name']
    print(p1.__dict__)
    setitem,getitem,delitem

    五 二次加工标准类型(包装)

      包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)

      1.利用继承和派生来实现

    class List(list):
        def append(self, object):
            if not isinstance(object,int):
                raise TypeError('只能添加整型')
            #self.append(object) 造成死循环
            print(super().append)
            super().append(object)
        def insert(self, index, object):
            if isinstance(object,int):
                raise TypeError('只能是字符串类型')
            super().insert(index,object)
    l=List([1,2,3,4,5])
    l.append(6)
    l.insert(2,'aa')
    print(l)
    数据二次加工

      2.利用授权来完成二次加工

    import time
    class Open:
        def __init__(self,filename,m='r',encode='utf-8'):
            self.f=open(filename,mode=m,encoding=encode)
    
        def write(self,contains):
            now = time.strftime("%y-%m-%d %X")
            self.f.write("%s %s
    "%(now,contains))
    
        def __getattr__(self, item):
            fun=getattr(self.f,item)
            if not fun:
                raise AttributeError('没有该方法')
            return fun
    
    f=Open('a.txt','w')  #
    f.write('hahahha')
    
    f=Open('a.txt')  #
    print(f.readlines())
    文件写操作改写

      授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。

      实现授权的关键点就是覆盖__getattr__方法

    #_*_coding:utf-8_*_
    __author__ = 'Linhaifeng'
    #我们来加上b模式支持
    import time
    class FileHandle:
        def __init__(self,filename,mode='r',encoding='utf-8'):
            if 'b' in mode:
                self.file=open(filename,mode)
            else:
                self.file=open(filename,mode,encoding=encoding)
            self.filename=filename
            self.mode=mode
            self.encoding=encoding
    
        def write(self,line):
            if 'b' in self.mode:
                if not isinstance(line,bytes):
                    raise TypeError('must be bytes')
            self.file.write(line)
    
        def __getattr__(self, item):
            return getattr(self.file,item)
    
        def __str__(self):
            if 'b' in self.mode:
                res="<_io.BufferedReader name='%s'>" %self.filename
            else:
                res="<_io.TextIOWrapper name='%s' mode='%s' encoding='%s'>" %(self.filename,self.mode,self.encoding)
            return res
    f1=FileHandle('b.txt','wb')
    # f1.write('你好啊啊啊啊啊') #自定制的write,不用在进行encode转成二进制去写了,简单,大气
    f1.write('你好啊'.encode('utf-8'))
    print(f1)
    f1.close()
    文件操作支持b模式

    六 __getattribute__

     __getattribute__不管属性存不存在,都会执行
    class Foo:
        def __init__(self,x):
            self.x=x
    
        def __getattribute__(self, item):
            print('不管是否存在,我都会执行')
    
    f1=Foo(10)
    f1.x
    f1.xxxxxx
    
    输出结果:
    不管是否存在,我都会执行
    不管是否存在,我都会执行
    __getattribute__
    #_*_coding:utf-8_*_
    __author__ = 'Linhaifeng'
    
    class Foo:
        def __init__(self,x):
            self.x=x
    
        def __getattr__(self, item):
            print('执行的是我')
            # return self.__dict__[item]
        def __getattribute__(self, item):
            print('不管是否存在,我都会执行')
            raise AttributeError('哈哈')
    
    f1=Foo(10)
    f1.x
    f1.xxxxxx
    
    #当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError

    七 __str__,__repr__,__format__

      改变对象的字符串显示__str__,__repr__, repr() 输出对 Python比较友好,而str()的输出对用户比较友好。

      自定制格式化字符串__format__

      str()触发__str__,repr()触发repr,format()触发__format__

    #_*_coding:utf-8_*_
    __author__ = 'Linhaifeng'
    format_dict={
        'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型
        'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址
        'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名
    }
    class School:
        def __init__(self,name,addr,type):
            self.name=name
            self.addr=addr
            self.type=type
    
        def __repr__(self):
            return 'School(%s,%s)' %(self.name,self.addr)
        def __str__(self):
            return '(%s,%s)' %(self.name,self.addr)
    
        def __format__(self, format_spec):
            # if format_spec
            if not format_spec or format_spec not in format_dict:
                format_spec='nat'
            fmt=format_dict[format_spec]
            return fmt.format(obj=self)
    
    s1=School('oldboy1','北京','私立')
    print('from repr: ',repr(s1))
    print('from str: ',str(s1))
    print(s1)
    
    '''
    str函数或者print函数--->obj.__str__()
    repr或者交互式解释器--->obj.__repr__()
    如果__str__没有被定义,那么就会使用__repr__来代替输出
    注意:这俩方法的返回值必须是字符串,否则抛出异常
    '''
    print(format(s1,'nat'))
    print(format(s1,'tna'))
    print(format(s1,'tan'))
    print(format(s1,'asfdasdffd'))
    View Code

    八 __slots__

      1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
      2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
      3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
    当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个
    字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给
    实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
      4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该
    只在那些经常被使用到的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
    关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具
    class Foo:
        __slots__ = 'x'
    
    
    f1 = Foo()
    f1.x = 1
    print(f1.__dict__)  # 报错,f1不再有__dict__,只能通过__slots来查看定义的属性
    
    #---------------------分割线---------------------------
    class Bar:
        __slots__ = ['x', 'y']
    
    n = Bar()
    n.x, n.y = 1, 2
    n.z = 3  # 报错
    '''
    __slots__ 只定义了x,y变量,所以所有的对象的名称空间只能有这两个属性,
    无法再新增新的属性
    '''
    class Foo:
        __slots__=['name','age']
    
    f1=Foo()
    f1.name='alex'
    f1.age=18
    print(f1.__slots__)
    
    f2=Foo()
    f2.name='egon'
    f2.age=19
    print(f2.__slots__)
    
    print(Foo.__dict__)
    #f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存
    验证对象无字典

    九 __next__和__iter__实现迭代器协议

    class Range:
        def __init__(self,start=0,end=None,n=1):
            self.start=start
            self.end=end
            self.n=n
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if not self.end:
                self.end=self.start
                self.start = 0
            x=self.start
            if x<self.end:
                self.start+=self.n
                return x
            else:
                raise StopIteration('这已经是最后一个值了')
    
    f=Range(2,10,2)
    
    for i in f:
        print(i)
    from collections import Iterable,Iterator   #Iterator可以用来判断是否为生成器.
                                                #Iterable用来 判断对象是否可迭代。
    print(isinstance(f,Iterator))  #True
    模拟range函数

     十 __doc__

    class Foo:
        '我是描述信息'
        pass
    
    print(Foo.__doc__)
    class Foo:
        '我是描述信息'
        pass
    
    class Bar(Foo):
        pass
    print(Bar.__doc__) #该属性无法继承给子类

    十一 __module__和__class__

      __module__ 表示当前操作的对象在那个模块

      __class__     表示当前操作的对象的类是什么

       在tmp.py模块下,创建一个类,然后再pra.py中导入tmp.py

    class C:
        def __init__(self):
            self.name = 'test'
    tmp.py
    from tmp import *
    
    c=C()
    print(c.__class__)  #<class 'tmp.C'>
    print(c.__module__)  #tmp
    pra.py

    十二  __del__

      析构方法,当对象在内存中被释放时,自动触发执行。()

      注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的

    import time
    class Foo:
    
        def __del__(self):
            print('执行我啦')
    
    f1=Foo()
    del f1   #删除f1,即释放f1和他对应的内存关联。内存被回收,触发析构函数。
    time.sleep(2)
    print('------->')
    
    输出结果:
    执行我啦
    ------->
    简单示例
    import time
    class Foo:
    
        def __del__(self):
            print('执行我啦')
    
    f1=Foo()
    #del f1   #此时,不主动删除f1,那么需要等程序执行完毕,释放掉内存时,f1内存被回收
                #才会触发析构函数
    time.sleep(2)
    print('------->')
    
    输出结果:
    ------->
    执行我啦
    进一步探讨

    十三 __enter__和__exit__

      我们知道,在操作文件时可以使用with来写代码块,如下:

    with open('a.txt') as f: #等于f=open('a.txt')
        f.read()

      上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法.

    class Open:
        def __init__(self,name):
            self.name=name
    
        def __enter__(self):
            print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
            # return self
        def __exit__(self, exc_type, exc_val, exc_tb):
            print('with中代码块执行完毕时执行我啊')
    
    
    with Open('a.txt') as f:
        print('=====>执行代码块')
    
    输出结果:
    出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
    =====>执行代码块
    with中代码块执行完毕时执行我啊
    with的基础构造

      __exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

    class Open:
        def __init__(self,name):
            self.name=name
    
        def __enter__(self):
            print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print('with中代码块执行完毕时执行我啊')
            print(exc_type)
            print(exc_val)
            print(exc_tb)
    
    
    
    with Open('a.txt') as f:
        print('=====>执行代码块')
        raise AttributeError('***着火啦,救火啊***')
    print('0'*100) #------------------------------->不会执行
    
    
    输出结果:
    Traceback (most recent call last):
      File "D:/Users/wujy1/PycharmProjects/oldboy/类/练习/pra.py", line 562, in <module>
        raise AttributeError('***着火啦,救火啊***')
    AttributeError: ***着火啦,救火啊***
    出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
    =====>执行代码块
    with中代码块执行完毕时执行我啊
    <class 'AttributeError'>
    ***着火啦,救火啊***
    <traceback object at 0x00000000027A1088>
    异常信息

      如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

    class Open:
        def __init__(self,name):
            self.name=name
    
        def __enter__(self):
            print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            print('with中代码块执行完毕时执行我啊')
            print(exc_type)
            print(exc_val)
            print(exc_tb)
            return True  #返回为True,则忽略异常
    
    
    
    with Open('a.txt') as f:
        print('=====>执行代码块')
        raise AttributeError('***着火啦,救火啊***')
    print('0'*100) #------------------------------->正常执行
    忽略异常
    class Open:
        def __enter__(self):
            return self.f
    
        def __init__(self,filename,m='r',encode='utf-8'):
            self.f=open(filename,mode=m,encoding=encode)
    
        def write(self,item):
            self.f.write(item)
    
        def __getattr__(self, item):
            return  getattr(self.f,item)
    
        def __exit__(self, exc_type, exc_val, exc_tb):
            self.f.close()
            return True  #不抛异常
    
        def __str__(self):
            return 'hahahah'
    with Open('a.txt','w') as f:  #f=Open('a.txt','w')
         l=['1','2','3']
         f.writelines(l)
    模拟with

    用途或者说好处:

    1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预

    2.在需要管理一些资源比如文件,网络连接和锁的编程环境中(数据库链接),可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处。

    十四 __call__  

      对象后面加括号,触发执行。

    注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

    class Foo:
    
        def __init__(self):
            pass
        
        def __call__(self, *args, **kwargs):
    
            print('__call__')
    
    
    obj = Foo() # 执行 __init__
    obj()       # 执行 __call__
    View Code

    十五 metaclass

      exec:三个参数    exec(object,globals,locals)

      参数一:字符串形式的命令

      参数二:全局作用域

      参数三:局部作用域

      exec会在指定的局部作用域内执行字符串内的代码,除非明确地使用global关键字

    g={
        'x':1,
        'y':2,
        'teachers':{'egon','alex','yuanhao','wupeiqi'}
    }
    l={'birds':[1,2,3]}
    
    # exec("""
    # def func():
    #     global x
    #     x='egon'
    # func()
    # """,g,l)
    # print(g)
    # print(l)
    
    exec("""
    global x
    x='egon'
    """,g,l)
    print(g)
    print(l)
    exec

      一.类也是对象

      python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例),因而我们可以将类当作一个对象去使用,同样满足第一类对象的概念,可以:

    • 把类赋值给一个变量

    • 把类作为函数参数进行传递

    • 把类作为函数的返回值

    • 在运行时动态地创建类

    #type函数可以查看类型,也可以用来查看对象的类,二者是一样的
    class A:
        pass
    
    a=A()
    print(type(A))  #<class 'type'>
    print(type(a))  #<class '__main__.A'>

      二.什么是元类  

      元类是类的类,是类的模板

      元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为

      元类的实例化的结果为我们用class定义的类,正如类的实例为对象(a对象是A类的一个实例,A类是 type 类的一个实例)

      type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象。(type---类----对象)

                  

    三 创建类的两种方式

      1.使用class关键字

    class People:
        country='China'
        def run(self):
            print('run')
    
    '''People类包含元素有:类名,名称空间,父类(py3中默认为object)'''

      2.手动模拟class创建类的过程:将创建类的步骤拆分开,手动去创建

      准备工作:

      创建类主要分为三部分

        1 类名

        2 类的父类

        3 类体(名称空间)

    def run(self):
        print('run')
    
    class_name='People'    #类名
    class_bases=(object,)   #父类
    
    class_body='''
    country='china'
    def run(self):
        print('run')
    '''

      步骤一(先处理类体->名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典

    class_dict={}
    exec(class_body,globals(),class_dict)
    print(class_dict)
    '''
    {'country': 'china', 'run': <function run at 0x00000000021DB8C8>}
    '''

      步骤二:调用元类type(也可以自定义)来产生类Chinense

    People=type(class_name,class_bases,class_dict)
    print(People)
    print(type(People))
    print(isinstance(People,type))
    '''
    <class '__main__.People'>
    <class 'type'>
    True

      我们看到,type 接收三个参数:

    • 第 1 个参数是字符串 ‘People,表示类名

    • 第 2 个参数是元组 (object, ),表示所有的父类

    • 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法

      补充:若People类有继承,即class People(Bar):.... 则等同于type('People',(Bar,),{})

      我们也可以直接定义命名空间字典,省去使用exec转化的一步。代码如下:

    def run(self):
        print('run')
    
    class_name='People'    #类名
    class_bases=(object,)   #父类
    class_dict={'country': 'china', 'run':run}
    People=type(class_name,class_bases,class_dict)
    print(People)
    print(type(People))
    print(isinstance(People,type))
    '''
    <class '__main__.People'>
    <class 'type'>
    True
    '''

       4.一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的创建,工作流程是什么)

      所以类实例化的流程都一样,与三个方法有关:(大前提,任何名字后加括号,都是在调用一个功能,触发一个函数的执行,得到一个返回值)

      1.p=People(),会调用产生People的类内的__call__方法,People()的结果即__call__的结果
      2.调用__call__方法的过程中,先调用People.__new__,得到obj,即实例化的对象,但是还没有初始化
      3.调用__call__方法的过程中,如果People.__new__()返回了obj,再调用People.__init__,将obj传入,进行初始化(否则不调用People.__init__)
        总结:
    __new__更像是其他语言中的构造函数,必须有返回值,返回值就实例化的对象
    __init__只是初始化函数,必须没有返回值,仅仅只是初始化功能,并不能new创建对象

       前提注意:

      1. 在我们自定义的元类内,__new__方法在产生obj时用type.__new__(cls,*args,**kwargs),用object.__new__(cls)抛出异常:TypeError: object.__new__(Mymeta) is not safe, use type.__new__()

      2. 在我们自定义的类内,__new__方法在产生obj时用object.__new__(self)

    元类控制创建类:

    class Mymeta(type):
        def __init__(self):
            print('__init__')
    
        def __new__(cls, *args, **kwargs):
            print('__new__')
    
        def __call__(self, *args, **kwargs):
            print('__call__')
    
    class Foo(metaclass=Mymeta):
        pass
    
    print(Foo)
    '''
    打印结果:
    __new__
    None
    '''
    
    '''
    分析Foo的产生过程,即Foo=Mymeta(),会触发产生Mymeta的类内的__call__,即元类的__call__:
        Mymeta加括号,会触发父类的__call__,即type.__call__
        在type.__call__里会调用Foo.__new__
        而Foo.__new__内只是打印操作,没有返回值,因而Mymeta的结果为None,即Foo=None
    '''
    class Mymeta(type):
        def __init__(self):
            print('__init__')
    
        def __new__(cls, *args, **kwargs):
            # print('__new__')
            obj=type.__new__(cls,*args,**kwargs)
            return obj
    
        def __call__(self, *args, **kwargs):
            print('__call__')
    
    
    Foo=Mymeta()
    class Foo(metaclass=Mymeta):
        pass
    
    print(Foo)
    '''
    基于上述表达,我们改写了__new__,返回obj,但是抛出异常:
    TypeError: __init__() takes 1 positional argument but 4 were given
    
    '''
    
    
    #改写
    class Mymeta(type):
        def __init__(self,name,bases,dic):
            print('__init__')
    
        def __new__(cls, *args, **kwargs):
            print('__new__')
            obj=type.__new__(cls,*args,**kwargs)
            return obj
    
        def __call__(self, *args, **kwargs):
            print('__call__')
    
    
    # Foo=Mymeta()
    class Foo(metaclass=Mymeta):
        pass
    
    print(Foo)
    class Mymeta(type):
        def __init__(self,name,bases,dic):
            for key,value in dic.items():
                if key.startswith('__'):
                    continue
                if not callable(value):
                    continue
                if not value.__doc__:
                    raise TypeError('%s 必须有文档注释' %key)
            type.__init__(self,name,bases,dic)
    
        def __new__(cls, *args, **kwargs):
            # print('__new__')
            obj=type.__new__(cls,*args,**kwargs)
            return obj
    
        def __call__(self, *args, **kwargs):
            # print('__call__')
            pass
    
    # Foo=Mymeta()
    class Foo(metaclass=Mymeta):
        def f1(self):
            'from f1'
            pass
    
        def f2(self):
            pass
    
    '''
    抛出异常
    TypeError: f2 必须有文档注释
    '''
    所有类都必须有__doc__
    #元类总结
    class Mymeta(type):
        def __init__(self,name,bases,dic):
            print('===>Mymeta.__init__')
    
    
        def __new__(cls, *args, **kwargs):
            print('===>Mymeta.__new__')
            return type.__new__(cls,*args,**kwargs)
    
        def __call__(self, *args, **kwargs):
            print('aaa')
            obj=self.__new__(self)
            self.__init__(self,*args,**kwargs)
            return obj
    
    class Foo(object,metaclass=Mymeta):
        def __init__(self,name):
            self.name=name
        def __new__(cls, *args, **kwargs):
            return object.__new__(cls)
    
    '''
    需要记住一点:名字加括号的本质(即,任何name()的形式),都是先找到name的爹,然后执行:爹.__call__
    
    而爹.__call__一般做两件事:
    1.调用name.__new__方法并返回一个对象
    2.进而调用name.__init__方法对儿子name进行初始化
    '''
    
    '''
    class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行
    Foo=Mymeta('foo',(...),{...})
    因此我们可以看到,只定义class就会有如下执行效果
    ===>Mymeta.__new__
    ===>Mymeta.__init__
    实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta('Foo',(...),{...})操作,
    遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的爹type,然后执行type.__call__(...)方法
    于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化
    '''
    
    '''
    obj=Foo('egon')
    的原理同上
    '''
    
    '''
    总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了
    1.谁后面跟括号,就从谁的爹中找__call__方法执行
    type->Mymeta->Foo->obj
    Mymeta()触发type.__call__
    Foo()触发Mymeta.__call__
    obj()触发Foo.__call__
    2.__call__内按先后顺序依次调用儿子的__new__和__init__方法
    '''
    元类总结

    补充知识点:

       __eq__ 和__hash__

        

    class People(object):
        def __init__(self,name,age,sex,height):
            self.name=name
            self,age=age
            self.sex=sex
            self.height=height
    a=People('test1',23,'man',183)
    b=People('test2',25,'woman',183)
    c=People('test3',24,'man',183)
    d=People('test4',23,'woman',183)
    e=People('test2',40,'woman',183)
    f=People('test1',29,'man',183)
    l=[a,b,c,d,e,f]

      如上代码所示,有个People类,并且有6个对象。当对象的name和sex相同时,我们认为他是同一个对象。如果我们要对列表l进去去重,将同样的对象去除时,可以运用__eq__和__hash__来简单的实现该功能。

      改写如下:

    class People(object):
        def __init__(self,name,age,sex,height):
            self.name=name
            self.age=age
            self.sex=sex
            self.height=height
        def __hash__(self):
            return hash((self.name,self.sex))
        def  __eq__(self, other):
            if self.sex==other.sex and self.name==other.name:
                return True
    new_l=set(l)
    for i in new_l:
        print i.name,i.age
    
    结果:
        test3 24
        test2 25
        test1 23
        test4 23

      

      从上面的示例,我们可以指定__eq__方法是用来判断来个值是否相等,__hash__ 设置了hash对象哪些值的内容。这两者设置完成后,可以直接使用集合来进行去重。如果仅仅是判断大小是否相等,可以只设置__eq__

  • 相关阅读:
    npm version patch
    nginx 操作
    基于 Vue CLI 组件库封装,按需加载实现
    nginx 配置文件路径获取
    Laravel 生产资源路由并指定模型
    base.js,通用js方法,Js方法封装
    jquery.params.js,Jquery获取页面参数,js获取页面参数
    layui使用,LayUI select不显示,LayUI文件上传,Layui自定义校验规则
    Layer弹窗消息封装,Layer消息提示封装,Layer使用
    Html跨域js封装,前端页面跨域js,postMessage实现跨域
  • 原文地址:https://www.cnblogs.com/white-small/p/7081809.html
Copyright © 2020-2023  润新知