关于__setitem__,__getitem__,delitem__以及__slots__,迭代器原理,上下文管理协议还有元类

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于__setitem__,__getitem__,delitem__以及__slots__,迭代器原理,上下文管理协议还有元类相关的知识,希望对你有一定的参考价值。

关于__setitem__,__getitem__,delitem__

类似于以前的学过的__setattr__,__getattr__...

不同之处在于item结尾的是用于对象以字典添加的形势添加,查看和删除属性的时候才会触发,如下例子:

class Foo(object):
    # __slots__=[‘x‘,‘y‘]
    def __setitem__(self, key, value):
        print(我在写入)
        self.__dict__[key]=value
    def __getitem__(self, item):
        print(我在返回值)
        return self.__dict__[item]
    def __delitem__(self, key):
        print(我在删除值)
        del self.__dict__[key]

a=Foo()
a[x]=1
print(a[x])
del a[x]

 

接下来我们来看看__slots__:

__slots__有两个作用
作用一:
__slots__=[‘x‘,‘y‘]
如果在类中定义了以上的属性那么就限制了实例化对象的添加成员

如下例子:

class Foo(object):
    __slots__=[x,y,z]
    def run(self):
        print(from run)

a=Foo()
a.x=10
a.y=20
a.z=12
a.w=13

通过运行我们发现当我们尝试设置w属性的时候就报错了

作用二:
我们在类中添加__slots__后执行 对象.__dict__发现对象不在产生dict了,也就是说对象不再独立开辟对象自己的命名空间,由此可以看出在类中限制好对象的成员后,不仅可以限制对象添加成员还可以以此节约内存空间的目的

class Foo(object):
    __slots__=[x,y,z]
    def run(self):
        print(from run)

a=Foo()
print(a.__dict__)
b=Foo()
print(b.__dict__)

可能你现在还无法想象,加入我们每实例化一个对象就会产生一个dict,如果有成百上前的对象就会产生成百上千的独立命名空间,这样就会浪费很多的内存空间

 

迭代器的原理:

你有想过迭代器是如何实现的吗,假设我们要自定义一个range函数,这个时候我们就需要用到__next__和__iter__了

我们都是知道只要是包含__iter__方法的就是一个可迭代对象,执行__next__方法就会返回一个值,那么我们模拟下range函数

class Range(object):
    def __init__(self,start,stop,jump=0):
        self.start=start
        self.stop=stop
        self.jump=jump
        if self.jump>0:
            self.jump=self.jump-1
    def __iter__(self):
        return self
    def __next__(self):
        n=self.start
        if self.start>self.stop-1:
            raise StopIteration
        self.start+=(1+self.jump)
        return n
for i in Range(0,9,2):
    print(i)

我们自定义了一个Range类用来模拟range迭代器,运行后发现可以正常运行,当然如果想要百分百模拟我们还有一些地方需要完善

 

关于__del__:

这个函数有点特殊,我们还是直接说说它的原理吧。在类中定义好__del__后,它会在被python解释器的垃圾回收机制,在类没有被任何调用或者被对象指向的时候,就会被python垃圾回收机制清理来节约内存,这个时候__del__内部的代码就会执行我们来试试

import time
class Foo(object):
    def __del__(self):
        print(我要被销毁了)
a=Foo()

我们发现在程序运行结束后就会执行__del__代码,可能这样看的还不是很清楚,我们再来看看

import time
class Foo(object):
    def __del__(self):
        print(我要被销毁了)
a=Foo()
del a
time.sleep(5)

我们导入了一个time模块在程序运行结束之前先睡5秒,睡5秒之前先把对象a给删除,我们发现不用等程序结束运行就会执行__del__代码,如果你不信还可以先把del a给注销试试

 

 

上下文管理协议__enter__和__exit__:

我们打开文件读取和写入的时候会用上with这个语句,它会在文件读取完毕的时候自动关闭文件,但你有想过这是如何实现的吗?

我们来看看他们的定义和执行顺序

class Foo(object):
    def __enter__(self):
        print(from enter)
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(from exit)
        print(exc_type,exc_type)
        print(exc_val,exc_val)
        print(exc_tb,exc_tb)
with Foo():
    print(Foo)

发现会优先执行enter方法,最后执行exit方法,他们到底是什么呢

class Foo(object):
    def __enter__(self):
        print(from enter)
        return 23233
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(from exit)
        print(exc_type,exc_type)
        print(exc_val,exc_val)
        print(exc_tb,exc_tb)
with Foo()as a:
    print(a)
    print(Foo)
    raise TypeError(类型错误)

发现enter的作用可以用来返回值后被as后面的变量名给接受值,而exit的那几个参数可以用于接受错误信息

,当在exit中加入return Ture后发现会忽略这些错误。

知道他们的用处后我们来模拟一个用类自定义的日志文件写入的open函数

import time
class Open:
    def __init__(self,filepath,m=r,encoding=utf8):
        self.io=open(filepath,mode=m,encoding=encoding)
        self.filepath=filepath
        self.mode=m
        self.encoding=encoding
    def write(self,line):
        t=time.strftime(%y-%m-%d %x)
        self.io.write(%s %s%(t,line))
    def __getattr__(self,item):
        return getattr(self.io,item)
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.io.close()

with Open(001.txt,w)as f:
    f.write(xxxxxx)
    f.seek(0)
with Open(001.txt,r)as r:
    print(r.read())

完美,还有比这更完美的了吗

 

元类

什么是元类,通过__dict__发现其实类也是由一个字典组成的,既然类是由字典组成的我们能不能自定义一个类呢?

name=cris
def run():
    print(runing)
def add():
    print(add)
cls=type(func,(object),{run:run,add:add})

我们可以通过这种定义元类的方式来定义一个cls类,来试试能否正常调用

name=cris
def run():
    print(runing)
def add():
    print(add)
cls=type(func,(object,),{run:run,add:add})
cls.add()
cls.run()
print(cls.__name__)

type有三个参数,第一个是类名,第二个是基础关系,第三个是函数字典

 

通过type我们还可以定制自己的元类:

元类起始就是类的类,可以用来控制类的行为,如果一个父类继承元类,二子类以metaclass方式继承父类,那么就可以在父类中控制类的行为,比如我们限制子类必须写doc文档

class Foo(type):
    def __init__(self,cls_name,base,dict):
        for key in dict:
            if not callable(dict[key]):continue
            if not dict[key].__doc__:
                raise TypeError(没有写函数文档这是不允许的)

class Foo1(metaclass=Foo):
    def __init__(self,name):
        self.name=name
    def run(self):
        print(thin is run func)

a=Foo1(cris)

这个时候我们在子类中没有写函数文档就会抛出异

 

实际上在我们实例化一个类的对象的时候就是在调用元类的call方法,我只是知道有这么个操作具体原理我也不明

class Foo(type):
    def __init__(self,cls_name,base,dict):
        pass
    def __call__(self, *args, **kwargs):
        obj=self.__new__(self)
        self.__init__(obj, *args, **kwargs)
        return obj

class Foo1(metaclass=Foo):
    def __init__(self,name):
        self.name=name
    def run(self):
        print(thin is run func)

a=Foo1(cris)
a.name

 

以上是关于关于__setitem__,__getitem__,delitem__以及__slots__,迭代器原理,上下文管理协议还有元类的主要内容,如果未能解决你的问题,请参考以下文章

__setitem__,__getitem,__delitem__

__getitem__ __setitem__ __delitem__ 使用

python学习之__getitem__,__setitem__,__delitem__

python - __setitem__/__getitem__/__delitem__类的内置方法

__getitem__\__setitem__\__delitem__

为啥当我使用 [:] 时我的子类的 __getitem__ 和 __setitem__ 没有被调用?