python中的魔术方法、上下文管理

知识共享许可协议 版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons

1.可调用对象

python中一切皆对象,函数也不例外。

def foo():
    print(foo.__module__, foo.__name__)


foo()  # 等价于 foo.__call__()

函数即对象,对象foo加上(),就是调用此函数对象的__call__()方法

可调用对象
可调用对象:定义一个类,并实例化得到其实例,将实例像函数一样调用。

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __call__(self, *args, **kwargs):
        return "< Point {} {}>".format(self.x, self.y)


p = Point(4, 5)
print(p)
print(p())  # 类中定义了__call__方法,实例可以像函数一样调用


class Adder:
    def __call__(self, *args):
        ret = 0
        for x in args:
            ret += x
        self.ret = ret
        return ret


adder = Adder()
print(adder(4, 5, 6))
print(adder.ret)

练习

  定义一个斐波那契数列的类,方便调用,计算第n项;增加迭代的方法,返回容器长度,支持索引的方法。

class Fib:
    def __init__(self):
        self.items = [0, 1, 1]

    def __len__(self):
        return len(self.items)

    def __iter__(self):
        yield from self.items

    def __getitem__(self, index):
        if index < 0:
            raise KeyError('Wrong index')
        if index < len(self.items):
            return self.items[index]

        for i in range(len(self.items), index + 1):
            self.items.append(self.items[i - 2] + self.items[i - 1])
        return self.items[index]

    def __call__(self, index):
        return self[index]

    def __repr__(self):
        return "<{} {}>".format(self.__class__.__name__, self.items)


f = Fib()
print(f(5))
for x in f:
    print(x)
print(f[6])
print(len(f))

可以看出,使用类类实现菲波那切数列也是非常好的实现,还可以缓存数据,便于检索。

2.上下文管理

2.1上下文管理对象

当一个对象实现了__enter__()和__exit__()方法,它就属于上下文管理对象。
上下文管理对象
with操作的对象,必须支持上下文管理,即必须要有enter方法和exit方法
没有as子句并不影响上下文管理
with语句不会开辟新的作用域

import time


class A:  # 定义了进入和退出方法的对象叫上下文管理对象
    def __init__(self):
        print('1, init ~~~~~~~~~~~')
        time.sleep(1)
        print('2, init over')

    def __enter__(self):  # 返回值会给a; 进入和退出的方法必须同时存在,它们都是实例的,不是类的
        print('3, enter ~~~~~~~~~~~~')

    def __exit__(self, exc_type, exc_val, exc_tb):  # 默认的返回值为None
        print('6, exit ~~~~~~~~~~~~~')
        print(exc_type)  # <class 'ZeroDivisionError'>
        print(exc_val)  # division by zero
        print(exc_tb)  # <traceback object at 0x000000000221EC48>
        return 'abc'  # 有返回值时,会压制异常


with A() as a:
    print('4, enter with ~~~~~~~~~')
    import sys
    sys.exit(100)
    # 1 / 0  # 有异常时,又没有捕获并处理异常,异常下的语句不会再执行
    # 注意即便异常没有处理,__exit__方法下的语句还是要执行
    time.sleep(1)
    print('5, exit with ~~~~~~~~~~')   # 注意执行顺序 ,标注的序号就是执行色顺序

  实例化对象的时候,并不会调用enter方法,进入with语句块调用__enter__方法,然后执行语句体,最后离开with语句块的时候,调用__exit__方法。

  with语句开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。with语句并不开辟一个新的作用域。

2.2 上下文管理的安全性

  上例中,虽然出现了异常,但是enter和exit照样执行,上下文管理是安全的。
极端的例子,调用sys.exit(),它会退出当前解释器。打开python解释器,在里面敲入sys.exit(),窗口直接就关闭了,也就是说,python运行环境直接退出了。从执行结果来看, 依然执行了__exit__方法,哪怕是退出python环境。

2.2.1 with语句
import time


class A:  # 定义了进入和退出方法的对象叫上下文管理对象
    def __init__(self):
        print('1, init ~~~~~~~~~~~')
        time.sleep(1)
        print('2, init over')

    def __enter__(self):  # 返回值会给a; 进入和退出的方法必须同时存在,它们都是实例的,不是类的
        print('3, enter ~~~~~~~~~~~~')

    def __exit__(self, exc_type, exc_val, exc_tb):  # 默认的返回值为None
        print('6, exit ~~~~~~~~~~~~~')
        print(exc_type)  # <class 'ZeroDivisionError'>
        print(exc_val)  # division by zero
        print(exc_tb)  # <traceback object at 0x000000000221EC48>
        return 'abc'  # 有返回值时,会压制异常


with A() as a:
    print('4, enter with ~~~~~~~~~')
    # import sys
    # sys.exit(100)
    # 1 / 0  # 有异常时,又没有捕获并处理异常,异常下的语句不会再执行
    # 注意即便异常没有处理,__exit__方法下的语句还是要执行
    time.sleep(1)
    print('5, exit with ~~~~~~~~~~')   # 注意执行顺序 ,标注的序号就是执行色顺序


b = A()
with b as c:  # ——> c = b.__enter__()即c为b调用__enter__方法的返回值
    print(1, b)
    print(2, c)  # None ——> __enter__方法的默认返回值为None
    print(b is c)  # False
    print(b == c)  # False
    print(id(b), id(c))

  with语法,会调用with后对象的__enter__方法,如果有as,则将该方法的返回值赋给as子句的变量。

2.2.2 方法的参数

__enter__方法没有其他参数;
__exit__方法有三个参数:
exit(self, exc_type, exc_value, exc_tb)

这三个参数都与异常相关,如果该上下文退出时,没有异常,这三个参数都是None,如果有异常,参数意义如下:
exc_type: 异常类型
exc_val:异常的值
exc_tb: 异常的追踪信息
__exit__返回一个等效为True的值,则压制异常,否则继续向外抛出异常。

练习

为加法函数计时:
方法1、使用装饰器显示该函数的执行时长
方法2、使用上下文管理方法来显示该函数的执行时长

import time
from datetime import datetime
from functools import wraps, update_wrapper


class Timeit:
    """This is Timeit class"""
    def __init__(self, fn):
        self.fn = fn
        self.__doc__ = fn.__doc__  # 设置实例的属性
        self.__name__ = fn.__name__

    def __enter__(self):
        self.start = datetime.now()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        delta = (datetime.now() - self.start).total_seconds()
        print("{} took {}s in class".format(self.fn.__name__, delta))


def timeit(fn):

    @wraps(fn)
    def wrapper(*args, **kwargs):
        start = datetime.now()
        ret = fn(*args, **kwargs)
        delta = (datetime.now() - start).total_seconds()
        print("{} took {}s in dec".format(fn.__name__, delta))
        return ret
    return wrapper


@timeit
def add(x, y):
    """this is add function"""
    time.sleep(2)
    return x + y


add(4, 5)


with Timeit(add) as t:  # ——> t = Timeit(add)实例化
    add(4, 5)

print(add.__doc__)  # this is add function
print(add.__name__)  # add

对上述的代码稍加改造,可以使用可调用对象实现计时,还可以把类作为装饰器使用。

import time
from datetime import datetime
from functools import wraps, update_wrapper


class Timeit:
    """This is Timeit class"""
    def __init__(self, fn):
        self.fn = fn
        # self.__doc__ = fn.__doc__  # 设置实例的属性
        # self.__name__ = fn.__name__
        # update_wrapper(self, fn)
        wraps(fn)(self)  # 解决文档字符串的问题,三种方法都可以

    def __enter__(self):
        self.start = datetime.now()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        delta = (datetime.now() - self.start).total_seconds()
        print("{} took {}s in class".format(self.fn.__name__, delta))

    def __call__(self, *args, **kwargs):
        start = datetime.now()
        ret = self.fn(*args, **kwargs)
        delta = (datetime.now() - start).total_seconds()
        print("{} took {}s in class".format(self.fn.__name__, delta))
        return ret


# def timeit(fn):
#
#     @wraps(fn)
#     def wrapper(*args, **kwargs):
#         start = datetime.now()
#         ret = fn(*args, **kwargs)
#         delta = (datetime.now() - start).total_seconds()
#         print("{} took {}s in dec".format(fn.__name__, delta))
#         return ret
#     return wrapper
#
#
# @timeit
# def add(x, y):
#     """this is add function"""
#     time.sleep(2)
#     return x + y


# add(4, 5)


@Timeit  # add = Timeit(add)实例化,add为Timeit 的实例
def add(x, y):
    """this is a function"""
    time.sleep(1)
    return x + y


add(4, 5)  # add took 1.014002s in class

# with Timeit(add) as t:  # ——> t = Timeit(add)
#     # add(4, 5)
#     t(5, 6)  # 实例调用
#
print(add.__doc__)  # this is add function
print(add.__name__)  # add
2.3 上下文应用场景
  1. 增强功能: 在代码执行的前后增加代码,一增强其功能,类似装饰器的功能。
  2. 资源管理:打开了资源需要关闭,例如文件对象、网络连接、数据库连接等。
  3. 权限验证:在执行代码之前,做权限的验证,在__enter__中处理。
2.4 contextlib.contextmanager

contextlib.contextmanager
  它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现__enter__和__exit__方法。对下面的函数有要求:必须有yield,也就是这个函数必须返回一个生成器,且只有一个yield 值。也就是这个装饰器接收一个生成器对象作为参数。

from contextlib import contextmanager


@contextmanager  # 上下文管理的装饰器
def a():
    print('enter~~~~~~')  # 进入代码
    try:
        yield 123
    except ZeroDivisionError:
        pass
    finally:  # 退出的代码放在finally语句中
        print('exit~~~~~~~~')  # 退出代码


with a() as f:  # f = a().__enter__()
    print('f = ', f)  # f =  123
    print(1 / 0)
    print('with over~~~~')


print('+++++++++++++++')

当yield发生处为生成器函数增加了上下文管理。这是为函数增加上下文机制的方式。

  • 把yield之前的当做__enter__方法执行
  • 把yield之后的当做__exit__方法执行
  • 把yield的值作为__enter__方法的返回值
import contextlib
import time
import datetime


@contextlib.contextmanager
def add(x, y):  # 为生成器函数增加了上下文管理
    start = datetime.datetime.now()
    try:
        time.sleep(2)
        yield x + y
    finally:
        delta = (datetime.datetime.now() - start).total_seconds()
        print(delta)


with add(4, 5) as f:  # f = x + y
    print(f)

   总结:如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方法,如果业务复杂,用类的方式加__enter__和__exit__方法方便。

3.反射

运行时,区别于编译时,指的是程序被加载到内存中执行的时候。==反射,reflection,指的是运行时获取类型定义的信息。==简单说,在python中,能够通过一个对象,找出其type、class、attribute或method的能力,称为反射或自省。具有反射能力的函数有type()、isinstance、callable()、dir()、getattr()等

3.1反射相关的函数和方法

属性函数

from functools import update_wrapper


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    # def show(self):
    #     print(self.x, self.y)

    def __repr__(self):
        return "<Point {} {}>".format(self.x, self.y)
    
    __str__ = __repr__


p1 = Point(4, 5)
print(p1.x, p1.y)
p1.x = 20
print(p1.__dict__)
p1.z = 100
p1.__dict__['x'] = 200
print(p1.__dict__)

print(dir(p1))
print(sorted(p1.__dir__()))


p2 = Point(4, 5)
print(p2.x)
print(getattr(p2, 'y', 2000))  # 2000为缺省值
# print(getattr(p2, 'z'))
if not hasattr(p2, 'z'):
    print(getattr(p2, 'z', 3000))  # 3000为缺省值

setattr(p2, 'z', 3000)
print(p2.__dict__)  # {'x': 4, 'y': 5, 'z': 3000}
# print(getattr(p2, '__dict__'))


setattr(Point, 'XYZ', 1000)   # 给类动态增加属性
print(Point.__dict__)

if not hasattr(Point, 'show'):
    # 为类动态的增加方法
    setattr(Point, 'show', lambda self: print(self.x, self.y))

p3 = Point(4, 5)
p3.show()
Point.show(p3)

p4 = Point(5, 6)
if not hasattr(p4, 'showy'):
    setattr(p4, 'showy', lambda self: print(self.y))  # 给实例动态的增加方法,但是没有绑定,就是要手动注入实例,不推荐这么增加方法
    # TypeError: <lambda>() missing 1 required positional argument: 'self'
    # setattr(p4, 'showy', lambda: print(123))
p4.showy(p4)  # 6
# p4.showy()  # 123
print(p4.show)  # <bound method <lambda> of <__main__.Point object at 0x0000000002209748>>;即会将第一参数绑定为实例
print(p4.showy)  # <function <lambda> at 0x0000000002260BF8>

p5 = Point(10, 10)
p6 = Point(4, 5)

setattr(Point, '__add__', lambda self, other: self.__class__(self.x + other.x, self.y + other.y))
print(p5 + p6)  # <Point 14 15>

练习

命令分发器:通过名称找对应的函数执行
思路:名称找对象的方法

# 命令分发器
class Dispatcher:
    def __init__(self):
        pass

    def reg(self, name, fn):
        setattr(self, name, fn)

    def run(self):
        while True:
            cmd = input('>>>').strip()
            if cmd == "quit":
                break
            getattr(self, cmd, lambda: print('Unknown cmd {}'.format(cmd)))()


dis = Dispatcher()
dis.reg('ls', lambda: print('ls'))
dis.run()

3.2反射相关的魔术方法

3.2.1 getattr()
class Base:
    n = 0


class Point(Base):
    z = 6

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def show(self):
        print(self.x, self.y)

    def __getattr__(self, item):
        return "missing {}".format(item)


p1 = Point(4, 5)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.t)  # 实例没有t属性

  实例属性会按照继承关系找,如果找不到,就会执行__getattr__()方法,如果没有这个方法,就会抛出AttributeError异常表示找不到属性。
查找属性的顺序为:
instance.dict --> instance.class.dict --> 继承的祖先类(直到object)的__dict__,找不到 --> 调用__getattr__()。

3.2.2 setattr()
class Base:
    n = 0


class Point(Base):
    z = 6

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def show(self):
        print(self.x, self.y)

    def __getattr__(self, item):
        print(item, '~~~~~~~~~~~~')
        print(type(item))  # item是字符串
        return "missing {}".format(item)

    def __setattr__(self, key, value):
        print('setattr {}={}'.format(key, value))
        # self.__dict__[key] = value  # 操作实例字典,将属性赋值放在字典中
        # super().__setattr__(key, value)  # 调用父类object的__setattr__方法
        # self.key = value  # 无限递归
        # setattr(self, key, value)  # 无限递归
        

p1 = Point(4, 5)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.t)  # 实例没有t属性
p1.x = 50  # setattr x=50;实例通过点号设置属性,就会调用__setattr__()
print(p1.x)  # missing x
print(p1.__dict__)  # {}
p1.__dict__['x'] = 60
print(p1.__dict__)  # {'x': 60}
print(p1.x)

  实例通过点号设置属性,例如self.x = x属性赋值,就会调用__setattr__()方法,属性要加到__dict__字典中,就需要自己完成。setattr()方法,可以拦截对实例属性的增加、修改操作,如果要设置生效,需要自己操作实例的字典。

class Base:
    n = 0


class Point(Base):
    z = 6
    d = {}

    def __init__(self, x, y):
        # self.x = x
        # self.y = y
        # setattr(self, 'x', x)
        # setattr(self, 'y', y)
        self.__dict__['x'] = x
        self.__dict__['y'] = y

    def show(self):
        print(self.x, self.y)

    def __getattr__(self, item):
        print(item, '~~~~~~~~~~~~')
        # print(type(item))  # item是字符串
        # return "missing {}".format(item)
        return self.d[item]

    def __setattr__(self, key, value):
        print('setattr {}={}'.format(key, value))
        if key not in self.__dict__.keys():
            self.d[key] = value
        else:
            self.__dict__[key] = value  # 操作实例字典,将属性赋值放在字典中
        # super().__setattr__(key, value)  # 调用父类object的__setattr__方法
        # self.key = value  # 无限递归
        # setattr(self, key, value)  # 无限递归

    def __delattr__(self, item):
        print('del attribute {}'.format(item))


p2 = Point(4, 5)
# del p2.x
# del p2.z
print(p2.__dict__)  # {'x': 4, 'y': 5}
print(p2.x, p2.y)
p2.tt = 2000
print(p2.tt)  # tt ~~~~~~~~~~~~ 2000
3.2.3 delattr()
class Point:
    Z = 5

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __delattr__(self, item):  # 该方法会阻止通过实例来删除属性
        print('Can not del {}'.format(item))


p = Point(12, 2)
del p.x  # Can not del x
del p.Z  # Can not del Z
p.z = 17  
del p.z  # Can not del z
print(Point.__dict__)
print(p.__dict__)  # {'x': 12, 'y': 2, 'z': 17},通过实例删除属性并没有实现
del Point.Z
print(Point.__dict__)  # 类的字典中Z属性被删除了

== 可以阻止通过实例来删除属性的操作,但是通过类依然可以删除类属性。==

3.2.4 getattribute()
class Base:
    n = 0


class Point(Base):
    z = 6
    d = {'tt': '20000'}

    def __init__(self, x, y):
        self.x = x
        self.y = y
        # setattr(self, 'x', x)
        # setattr(self, 'y', y)
        # self.__dict__['x'] = x
        # self.__dict__['y'] = y

    def show(self):
        print(self.x, self.y)

    def __getattribute__(self, item):  # 默认的返回值为None
        # print(type(item))
        # print(item)
        # return 1
        # return super().__getattribute__(item)
        # return object.__getattribute__(self, item)
        raise AttributeError('tt')

    def __getattr__(self, item):
        print(item, '~~~~~~~~~~~~')
        # print(type(item))  # item是字符串
        # return "missing {}".format(item)
        # return self.d[item]
        return __class__.d[item]  # 注意不能写成self.d[item],否则会无限递归

    # def __setattr__(self, key, value):
    #     print('setattr {}={}'.format(key, value))
    #     if key not in self.__dict__.keys():
    #         self.d[key] = value
    #     else:
    #         self.__dict__[key] = value  # 操作实例字典,将属性赋值放在字典中
    #     # super().__setattr__(key, value)  # 调用父类object的__setattr__方法
    #     # self.key = value  # 无限递归
    #     # setattr(self, key, value)  # 无限递归

    def __delattr__(self, item):
        print('del attribute {}'.format(item))


p3 = Point(4, 5)
# print(p3.__dict__)  # {'x': 4, 'y': 5}
# print(p3.x)   # 实例访问的第一道关是__getattribute__方法
print(p3.tt)  # 20000

  实例的所有属性访问,第一个都会调用__getattribute__方法,它阻止了属性的查找,该方法应该返回(计算后的)值或抛出一个AttributeError异常。

  • 它的return值将作为属性查找的结果
  • 如果抛出AttributeError异常,则会直接调用__getattr__方法,因为表示属性没有找到。
      getattribute__方法中为了避免在该方法中无限递归,它的实现则永远调用基类的同名方法以访问需要的任何属性,例如object.getattribute(self, name)。
    注意,除非你明确知道__getattriabute__方法用来做什么,否则不要使用它。
    总结:
    魔术方法
    属性查找顺序:
    实例调用__getattribute
    () --> instance.dict() --> instance.class.dict() --> 继承的祖先类直到object.dict() --> 调用__getattr__()

猜你喜欢

转载自blog.csdn.net/sqsltr/article/details/90517618