魔术方法

在Python中,被双下滑线包围起来的方法(定义在类中)叫魔术方法(Magic Method),比如我们最常见的__init__()就是一个典型的魔术方法

魔术方法通常都是满足一定的条件时自动调用,魔术方法可以实现一些特殊的功能

Python语言参考手册中的“Data Model”(https://docs.python.org/3/reference/datamodel.html)一章列出了83个魔术方法

1 构造、初始化和析构

__new__()  # 总是在初始化方法被执行前执行——实际上是在当类的实例被创建的同时执行(而初始化方法则是之后的故事了),有返回值且返回该类的一个实例

__init__() # 初始化方法,最常用的,使用时注意:1)写在类里面的方法第一个参数self不要漏写,这一点对所有方法同样适用,2)__new__()是基于__new__()方法的,不需要返回值 

__del__() # 相当于Java的析构函数,在对象要被销毁前执行
class Student:

    def __new__(cls, *args, **kwargs):
        return super().__new__(cls)

    def __init__(self, filename):
        self.file = open(filename, 'r+')

    def __del__(self):
       self.file.close()

s = Student('data.txt')
print(s)

2 属性访问相关魔术方法

__getattr__(self, item) # 获取不存在的属性时,触发该方法。指定返回值,得到的属性值就是该返回值     

__getattribute__(self, item)  # 获取属性时,不论属性是否存在,调用该方法,并发属性名以字符串形式传入,指定该方法的返回值就是 获取的属性的值 。  该方法与__getattr__()  同时存在,该方法优先级更高。 触发该方法内异常

__setattr__(self, item, value)  # 设置属性的值时触发  

__delattr__()  # 删除属性的值时触发
class Student:

    name = '丹丹'
    age = 10

    def __getattribute__(self, item):
        return super().__getattribute__(item)

    def __getattr__(self, item):
        return '不存在的属性'

    def __setattr__(self, key, value):
        # super().__setattr__(key, value)
        self.__dict__[key] = value

    def __delattr__(self, item):
        # super().__delattr__(item)
        self.__dict__.pop(item)


s = Student()

print(s)
s.name = '莉莉'
s.name = 'lili'
del s.name
print(s.name)
print(s.address)

3 对象具有列表字典特性

__setitem__(self, item, value)  # obj['key'] = value 时自动触发
__getitem__(self, item)         # 取值 obj['key'] 时自动触发
__delitem__(self, item)         # del obj['key'] 删除key时自动触发
class Student:
    def __getitem__(self, item):
        if item in self.__dict__:
            return self.__dict__[item]
        else:
            return None

    def __setitem__(self, key, value):
        self.__dict__[key] = value

    def __delitem__(self, key):
        self.__dict__.pop(key)



s = Student()
s['name'] = '丹丹'
print(s['name'])
del s['name']
print(s['name'])
# 斐波那锲数列
class Fib:
   def __getitem__(self, item):
       a, b = 1, 1
       for x in range(item):
           a, b = b, a + b
       return a


f = Fib()
print(f[1])
print(f[4])


# 升级版 斐波那锲数列
class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L


f = Fib()

print(f[4])
print(f[4:10])

4 对象 字符串形式输出

__str__()     # str(obj) 或者 print(obj)输出, 触发,要求返回字符串
__repr__()    # repr(obj)或者交互式解释器 触发,要求返回字符串
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)

5 对象变为迭代器

__iter__()    # 使对象可以被 for...in 迭代,返回一个迭代器对象
__next__()    # 返回下一次迭代的值, 两个通常成对出现
# 简单的迭代器  无限自然数
class Foo:
    def __init__(self,x):
        self.x=x

    def __iter__(self):
        return self

    def __next__(self):
        n=self.x
        self.x+=1
        return self.x

f=Foo(3)
for i in f:
    print(i)
# 模拟range功能
class Range:
    def __init__(self,start,stop,step):
        self.start = start
        self.stop = stop
        self.step = step

    def __next__(self):
        if self.start >= self.stop:
            raise StopIteration
        x=self.start
        self.start += self.step
        return x

    def __iter__(self):
        return self

for i in Range(1,7,3): 
    print(i)
# 朕最喜欢的 斐波那锲数列
class Fib:
    def __init__(self):
        self._a=1
        self._b=1

    def __iter__(self):
        return self

    def __next__(self):
        self._a,self._b=self._b,self._a + self._b
        return self._a


for i in f1:
    if i > 100:
        break
    print('%s ' %i,end='')

5 把对象当函数调用

__call__()   # 把对象当函数调用时自动触发
class Foo:
    def __call__(self, *args, **kwargs):

        print('__call__')


obj = Foo() 
obj()       # 执行 __call__

6 把with语句用于对象

__enter__()  # 出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
__exit__()  # with中代码块执行完毕时执行

上下文管理协议,即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(f,f.name)

__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) #------------------------------->不会执行

如果__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



with Open('a.txt') as f:
    print('=====>执行代码块')
    raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->会执行

作用:

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

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

7 __slots__

  1. __slots__是什么:

    是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)

  2. __dict__属性:

    使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)

  3. __slots__的作用 :

    __dict__会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ 当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。

    使用__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名。基于这个特性,它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具。

  4. 注意事项:

    __slots__的很多特性都依赖于普通的基于__dict__的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到 的用作数据结构的类上义__slots__比如在程序中需要创建某个类的几百万个实例对象 。

class Foo:
    __slots__='x'


f1=Foo()
f1.x=1
f1.y=2#报错
print(f1.__slots__) #f1不再有__dict__

class Bar:
    __slots__=['x','y']

n=Bar()
n.x,n.y=1,2
n.z=3#报错

results matching ""

    No results matching ""