{ "cells": [ { "cell_type": "markdown", "id": "92d19e69", "metadata": {}, "source": [ "| [02_advanced/06_装饰器.ipynb](https://github.com/shibing624/python-tutorial/blob/master/02_advanced/06_装饰器.ipynb) | Decorator装饰器 |[Open In Colab](https://colab.research.google.com/github/shibing624/python-tutorial/blob/master/02_advanced/06_装饰器.ipynb) |\n", "\n", "# 装饰器:Decorator\n", "如果你有一批变量想统一按一个规则处理,并且需要缩减代码,你需要函数。\n", "\n", "如果你有一批函数想统一按一个规则处理,并且需要缩减代码,你需要装饰器(Decorator)\n", "\n", "理清下面2点:\n", "\n", "函数\n", "- 接受参数\n", "- 做点事情\n", "- 返回结果\n", "\n", "\n", "装饰器\n", "- 接受函数作为参数\n", "- 做点事情\n", "- 返回一个函数\n", "\n", "\n", "用 @ 来使用装饰器\n", "\n", "使用 @ 符号来将某个函数替换为装饰符之后的函数:\n", "\n", "例如这个函数:" ] }, { "cell_type": "code", "execution_count": 25, "id": "ab40e597", "metadata": {}, "outputs": [], "source": [ "def dec(f):\n", " print('I am decorating function', id(f))\n", " return f" ] }, { "cell_type": "code", "execution_count": 26, "id": "2d5dc64a", "metadata": {}, "outputs": [], "source": [ "def foo(x):\n", " print(x) # I am decorating function 45206384" ] }, { "cell_type": "code", "execution_count": 27, "id": "02d198a1", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "I am decorating function 140410365416640\n" ] } ], "source": [ "foo = dec(foo)" ] }, { "cell_type": "markdown", "id": "1e1b3e56", "metadata": {}, "source": [ "可以替换为:" ] }, { "cell_type": "code", "execution_count": 28, "id": "4d903f7d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "I am decorating function 140410366082496\n" ] } ], "source": [ "@dec\n", "def foo(x):\n", " print(x)" ] }, { "cell_type": "markdown", "id": "70d1d89d", "metadata": {}, "source": [ "### 例子\n", "定义两个装饰器函数,一个将原来的函数值加一,另一个乘二:" ] }, { "cell_type": "code", "execution_count": 29, "id": "1107d480", "metadata": {}, "outputs": [], "source": [ "def plus_one(f):\n", " def new_func(x):\n", " return f(x) + 1\n", "\n", " return new_func" ] }, { "cell_type": "code", "execution_count": 30, "id": "c091bc23", "metadata": {}, "outputs": [], "source": [ "def times_two(f):\n", " def new_func(x):\n", " return f(x) * 2\n", "\n", " return new_func" ] }, { "cell_type": "markdown", "id": "8625804c", "metadata": {}, "source": [ "定义函数,先乘二再加一:" ] }, { "cell_type": "code", "execution_count": 31, "id": "a84188e2", "metadata": {}, "outputs": [], "source": [ "@plus_one\n", "@times_two\n", "def foo(x):\n", " return int(x)" ] }, { "cell_type": "code", "execution_count": 32, "id": "ea5f6fd0", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "b = foo(2)\n", "b # 5" ] }, { "cell_type": "markdown", "id": "0f1bf966", "metadata": {}, "source": [ "## 修饰器工厂\n", "decorators factories 是返回修饰器的函数\n", "\n", "它的作用在于产生一个可以接受参数的修饰器,\n", "\n", "例如我们想将 函数 输出的内容写入一个文件去,可以这样做:" ] }, { "cell_type": "code", "execution_count": 33, "id": "befb9986", "metadata": {}, "outputs": [], "source": [ "def super_loud(filename):\n", " fp = open(filename, 'w')\n", "\n", " def loud(f):\n", " def new_func(*args, **kw):\n", " fp.write(str(args))\n", " fp.writelines('\\n')\n", " fp.write('calling with' + str(args) + str(kw))\n", " # 确保内容被写入\n", " fp.flush()\n", " fp.close()\n", " rtn = f(*args, **kw)\n", " return rtn\n", "\n", " return new_func\n", "\n", " return loud" ] }, { "cell_type": "code", "execution_count": 44, "id": "4fd6baa5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "100\n" ] } ], "source": [ "@super_loud('test.txt')\n", "def foo(x):\n", " print(x)\n", "\n", "\n", "# 调用 foo 就会在文件中写入内容:\n", "foo(100)" ] }, { "cell_type": "code", "execution_count": 45, "id": "f43e0965", "metadata": {}, "outputs": [], "source": [ "import os\n", "os.remove('test.txt')" ] }, { "cell_type": "markdown", "id": "a54aee1f", "metadata": {}, "source": [ "## @classmethod 装饰器\n", "在 Python 标准库中,有很多自带的装饰器,\n", "\n", "例如 classmethod 将一个对象方法转换了类方法:" ] }, { "cell_type": "code", "execution_count": 35, "id": "37f2970d", "metadata": {}, "outputs": [], "source": [ "class Foo(object):\n", " @classmethod\n", " def bar(cls, x):\n", " print('the input is', x)\n", "\n", " def __init__(self):\n", " pass" ] }, { "cell_type": "markdown", "id": "9b6dca6f", "metadata": {}, "source": [ "类方法可以通过 类名.方法 来调用:" ] }, { "cell_type": "code", "execution_count": 36, "id": "150420db", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "the input is 10\n" ] } ], "source": [ "Foo.bar(10)" ] }, { "cell_type": "markdown", "id": "adba89fd", "metadata": {}, "source": [ "## @property 装饰器\n", "有时候,我们希望像 Java 一样支持 getters 和 setters 的方法,\n", "\n", "这时候就可以使用 property 装饰器:" ] }, { "cell_type": "code", "execution_count": 37, "id": "a7d0c3dc", "metadata": {}, "outputs": [], "source": [ "class Foo(object):\n", " def __init__(self, data):\n", " self.data = data\n", "\n", " @property\n", " def x(self):\n", " return self.data\n" ] }, { "cell_type": "markdown", "id": "ae91216c", "metadata": {}, "source": [ "此时可以使用 .x 这个属性查看数据(不需要加上括号):" ] }, { "cell_type": "code", "execution_count": 38, "id": "2017109a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "22\n" ] } ], "source": [ "foo = Foo(22)\n", "print(foo.x)" ] }, { "cell_type": "markdown", "id": "27b3c443", "metadata": {}, "source": [ "这样做的好处在于,这个属性是只读的:\n", "\n", "foo.x = 1 会报错\n", "\n", "\n", "如果想让它变成可读写,可以加上一个装饰符 @x.setter:" ] }, { "cell_type": "code", "execution_count": 39, "id": "d3776ec2", "metadata": {}, "outputs": [], "source": [ "class Foo(object):\n", " def __init__(self, data):\n", " self.data = data\n", "\n", " @property\n", " def x(self):\n", " return self.data\n", "\n", " @x.setter\n", " def x(self, value):\n", " self.data = value" ] }, { "cell_type": "code", "execution_count": 40, "id": "45c72448", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "1000" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "foo = Foo(1000)\n", "foo.x" ] }, { "cell_type": "code", "execution_count": 42, "id": "c238a25e", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2222" ] }, "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ "foo.x = 2222\n", "foo.x" ] }, { "cell_type": "markdown", "id": "6b06a6e0", "metadata": {}, "source": [ "## 应用:定时器\n", "\n", "要求:写一个定时器功能,要求监控一个执行程序,超时则报警。\n", "\n", "如何完成?" ] }, { "cell_type": "code", "execution_count": 55, "id": "db796406", "metadata": {}, "outputs": [], "source": [ "\n", "import signal\n", "import time\n", "\n", "\n", "def set_timeout(num, callback):\n", " def wrap(func):\n", " def handle(signum, frame): # 收到信号 SIGALRM 后的回调函数,参数1是信号的数字,参数2是the interrupted stack frame.\n", " raise RuntimeError\n", "\n", " def to_do(*args, **kwargs):\n", " try:\n", " signal.signal(signal.SIGALRM, handle) # 设置信号和回调函数\n", " signal.alarm(num) # 设置 num 秒的闹钟\n", " print('start alarm signal.')\n", " r = func(*args, **kwargs)\n", " print('close alarm signal.')\n", " signal.alarm(0) # 关闭闹钟\n", " return r\n", " except RuntimeError as e:\n", " callback()\n", "\n", " return to_do\n", "\n", " return wrap\n", "\n", "\n", "def after_timeout(): # 超时后的处理函数\n", " print(\"do something after timeout.\")\n", " raise RuntimeError\n", "\n", "\n", "@set_timeout(2, after_timeout) # 限时 2 秒超时\n", "def connect(): # 要执行的函数\n", " time.sleep(2.4) # 函数执行时间,写大于2的值,可测试超时\n", " return \"完成\"\n", "\n", "class Demo:\n", " @set_timeout(2, after_timeout)\n", " def conn(self):\n", " time.sleep(3)\n", " return \"ok\"" ] }, { "cell_type": "markdown", "id": "d2de7ccd", "metadata": {}, "source": [ "试一下:" ] }, { "cell_type": "code", "execution_count": 56, "id": "b48a9f03", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "start alarm signal.\n", "do something after timeout.\n", "err\n" ] } ], "source": [ "try:\n", " a = connect()\n", " print(a)\n", "except Exception as e:\n", " a = 'err'\n", " print(a)\n" ] }, { "cell_type": "code", "execution_count": 58, "id": "d77a4854", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "start alarm signal.\n", "close alarm signal.\n", "ok\n" ] } ], "source": [ "b = Demo()\n", "try:\n", " c = b.conn()\n", " print(c)\n", "except RuntimeError as e:\n", " print('run time err.')" ] }, { "cell_type": "markdown", "id": "191f8a51", "metadata": {}, "source": [ "如果不超时:" ] }, { "cell_type": "code", "execution_count": 59, "id": "9a8c240b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "start alarm signal.\n", "close alarm signal.\n", "ok\n" ] } ], "source": [ "class Demo:\n", " @set_timeout(2, after_timeout)\n", " def conn(self):\n", " time.sleep(1)\n", " return \"ok\"\n", " \n", "b = Demo()\n", "try:\n", " c = b.conn()\n", " print(c)\n", "except RuntimeError as e:\n", " print('run time err.')" ] }, { "cell_type": "markdown", "id": "91f17f28", "metadata": {}, "source": [ "本节完。" ] }, { "cell_type": "code", "execution_count": null, "id": "041101e8", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" } }, "nbformat": 4, "nbformat_minor": 5 }