{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 9.8 将装饰器定义为类的一部分\n", "* 问题:想在类中定义装饰器,并作用在其他的函数上\n", "* 方案:在类中定义装饰器首先要确定它的使用方法,是作为一个实例方法还是作为一个类方法" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "from functools import wraps\n", "class A:\n", " #作为一个实例方法\n", " def decorator1(self,func):\n", " @wraps(func)\n", " def wrapper(*args, **kwargs):\n", " print(\"decorator 1\")\n", " return func(*args,**kwargs)\n", " return wrapper\n", " #作为一个类方法\n", " @classmethod\n", " def decorator2(cls, func):\n", " @wraps(func)\n", " def wrapper(*args, **kwargs):\n", " print(\"decorator 2\")\n", " return func(*args,**kwargs)\n", " return wrapper" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "#作为实列使用\n", "a = A()\n", "@a.decorator1\n", "def spam():\n", " print('spam')\n", "#作为类方法使用\n", "@A.decorator2\n", "def grok():\n", " print(\"grok\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 在类中定义装饰器很奇怪,但是标准库中有很多这种方式,@property装饰器实际上是一个类,他内部定义了三个方法getter()、setter()、deleter()每个方法都是装饰器" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "class Person:\n", " first_name = property()\n", " @first_name.getter\n", " def first_name(self):\n", " return self._first_name\n", " \n", " @first_name.setter\n", " def first_name(self,value):\n", " if not isinstance(value,str):\n", " raise TypeError(\"Expected a string\")\n", " self._first_name = value" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 9.9 将装饰器定义为类\n", "* 问题:想使用一个装饰器去包装函数,但是希望返回一个可以调用的实例。需要让装饰器可以同时工作在类定义的内部和外部\n", "* 方案:为了将装饰器定义成一个实例,需要确保它实现了\\_\\_call\\_\\_()、\\_\\_get\\_\\_()方法" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "import types\n", "from functools import wraps\n", "class Profiled:\n", " def __init__(self,func):\n", " wraps(func)(self)\n", " self.ncalls = 0\n", " def __call__(self,*args, **kwargs):\n", " self.ncalls += 1\n", " return self.__wrapped__(*args, **kwargs)\n", " def __get__(self, instance, cls):\n", " if instance is None:\n", " return self\n", " else:\n", " return types.MethodType(self,instance)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 接下来你可以将它作为一个装饰器使用,在类内或者外面都可以" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [], "source": [ "@Profiled\n", "def add(x, y):\n", " return x + y\n", "\n", "class Spam:\n", " @Profiled\n", " def bar(self, x):\n", " print(self, x)" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "add(2,3)" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "9" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "add(4,5)" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "add.ncalls" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<__main__.Spam object at 0x00B65350> 1\n" ] } ], "source": [ "s = Spam()\n", "s.bar(1)" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<__main__.Spam object at 0x00B65350> 2\n" ] } ], "source": [ "s.bar(2)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<__main__.Spam object at 0x00B65350> 10\n" ] } ], "source": [ "s.bar(10)" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "3" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Spam.bar.ncalls" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 也可以使用闭包和nonlocal变量来实现装饰器" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "import types\n", "from functools import wraps\n", "def profiled(func):\n", " ncalls = 0\n", " @wraps(func)\n", " def wrapper(*args,**kwargs):\n", " nonlocal ncalls\n", " ncalls += 1\n", " return func(*args, **kwargs)\n", " wrapper.ncalls = lambda: ncalls\n", " return wrapper" ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "5" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "@profiled\n", "def add(x,y):\n", " return x+y\n", "add(2,3)" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "7" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "add(3,4)" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "add.ncalls()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 9.10 为类和静态方法提供装饰器\n", "* 问题:想要给类或者静态方法提供装饰器\n", "* 方案:在@classmethod或者@staticmethod之前定义" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import time\n", "from functools import wraps\n", "def timethis(func):\n", " @wraps(func)\n", " def wrapper(*args, **kwargs):\n", " start = time.time()\n", " r = func(*args,**kwargs)\n", " end = time.time()\n", " print(end-start)\n", " return r\n", " return wrapper\n", " " ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "class Spam:\n", " @timethis\n", " def instance_method(self, n ):\n", " print(self, n)\n", " while n > 0:\n", " n -= 1\n", " @classmethod\n", " @timethis\n", " def class_method(cls,n):\n", " print(cls,n)\n", " while n > 0:\n", " n -= 1\n", " \n", " @staticmethod\n", " @timethis\n", " def static_method(n):\n", " print(n)\n", " while n > 0:\n", " n -= 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 装饰后的类和静态方法可以正常工作,但是增加了计时功能" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "<__main__.Spam object at 0x00B79510> 10000000\n", "0.7324073314666748\n" ] } ], "source": [ "s = Spam()\n", "s.instance_method(10000000)" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " 1000000\n", "0.12408709526062012\n" ] } ], "source": [ "Spam.class_method(1000000)" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "10000000\n", "0.8057591915130615\n" ] } ], "source": [ "Spam.static_method(10000000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 注意:在使用的时候上面代码中@staticmethod和@timethis的顺序不能调换" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 如果想要定义一个抽象类方法,可以使用下面的那样:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [], "source": [ "from abc import ABCMeta, abstractmethod\n", "class A(metaclass=ABCMeta):\n", " @classmethod\n", " @abstractmethod\n", " def method(cls):\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 上面的两个@也是不可以交换顺序的" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 9.11 装饰器为被包装函数增加参数\n", "* 问题:想要给被包装的函数增加额外的参数,但是不可以改变该函数的现有调用规则\n", "* 方案:可以使用关键字参数来给被包装的函数增加额外的参数" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [], "source": [ "from functools import wraps\n", "def optional_debug(func):\n", " @wraps(func)\n", " def wrapper(*args, debug=False, **kwargs):\n", " if debug:\n", " print('Calling ', func.__name__)\n", " return func(*args,**kwargs)\n", " return wrapper\n", "@optional_debug\n", "def spam(a,b,c):\n", " print(a,b,c)" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 2 3\n" ] } ], "source": [ "spam(1,2,3)" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Calling spam\n", "1 2 3\n" ] } ], "source": [ "spam(1,2,3,debug=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 通过装饰器给被包装的函数增加参数并不常见,但有事该方法可以避免代码的重复" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": [ "def a(x,debug=False):\n", " if debug:\n", " print(\"calling a\")\n", "def b(x,y,z,debug=False):\n", " if debug:\n", " print(\"calling b\")\n", "def c(x,y,debug=False):\n", " print(\"calling c\")" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "# 我们可以将上述代码重写为:\n", "from functools import wraps\n", "import inspect\n", "def optional_debug(func):\n", " if 'debug' in inspect.getfullargspec(func).args:\n", " raise TypeError(\"debug argument already defined\")\n", " @wraps(func)\n", " def wrapper(*args, debug=False, **kwargs):\n", " if debug:\n", " print(\"calling \",func.__name__)\n", " return func(*args,**kwargs)\n", " return wrapper\n", "\n", "@optional_debug\n", "def a(x):\n", " pass\n", "@optional_debug\n", "def b(x,y,z):\n", " pass\n", "@optional_debug\n", "def c(x,y):\n", " pass" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 9.12 使用装饰器扩充类的功能\n", "* 问题:想通过反省或者重写类定义来修改其部分i行为,但是不想使用继承\n", "* 方案:使用类装饰器" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": [ "def log_getattribute(cls):\n", " orig_getattribute = cls.__getattribute__\n", " def new_getattribute(self, name):\n", " print(\"getting : \",name)\n", " return orig_getattribute(self, name)\n", " cls.__getattribute__ = new_getattribute\n", " return cls\n", "\n", "@log_getattribute\n", "class A:\n", " def __init__(self, x):\n", " self.x = x\n", " def spam(self):\n", " pass" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "getting : x\n" ] }, { "data": { "text/plain": [ "22" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a = A(22)\n", "a.x" ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "getting : spam\n" ] } ], "source": [ "a.spam()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 9.13 使用元类控制实例的创建\n", "* 问题:想要通过改变实例创建的方式来实现单例、缓存、等特性\n", "* 方案:如下" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [], "source": [ "# 我们知道python创建的类可以像函数一样调用它来创建实例\n", "class Spam:\n", " def __init__(self,name):\n", " self.name = name\n", "a = Spam(\"Guido\")\n", "b = Spam(\"Diana\")" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": [ "# 假设我们不想让任何人创建该类的实例\n", "class NoInstance(type):\n", " def __call__(self,*args,**kwargs):\n", " raise TypeError(\"can't instance directly\")\n", "class Spam(metaclass=NoInstance):\n", " @staticmethod\n", " def grok(x):\n", " print(\"Spam.grok\")" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Spam.grok\n" ] } ], "source": [ "#这样我们只能调用该类的静态方法,而不能进行实例化\n", "Spam.grok(42)" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "can't instance directly", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0ms\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mSpam\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;32m\u001b[0m in \u001b[0;36m__call__\u001b[1;34m(self, *args, **kwargs)\u001b[0m\n\u001b[0;32m 2\u001b[0m \u001b[1;32mclass\u001b[0m \u001b[0mNoInstance\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtype\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 3\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0m__call__\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"can't instance directly\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 5\u001b[0m \u001b[1;32mclass\u001b[0m \u001b[0mSpam\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmetaclass\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mNoInstance\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 6\u001b[0m \u001b[1;33m@\u001b[0m\u001b[0mstaticmethod\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n", "\u001b[1;31mTypeError\u001b[0m: can't instance directly" ] } ], "source": [ "s = Spam()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 假如你仅仅想要创建一个实例(单例模式):" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [], "source": [ "class Singleton(type):\n", " def __init__(self, *args,**kwargs):\n", " self.__instance = None\n", " super().__init__(*args,**kwargs)\n", " def __call__(self,*args, **kwargs):\n", " if self.__instance is None:\n", " self.__instance = super().__call__(*args, **kwargs)\n", " return self.__instance\n", " else:\n", " return self.__instance\n", "class Spam(metaclass = Singleton):\n", " def __init__(self):\n", " print(\"creating Spam\")" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "creating Spam\n" ] } ], "source": [ "a = Spam()" ] }, { "cell_type": "code", "execution_count": 38, "metadata": {}, "outputs": [], "source": [ "b = Spam()" ] }, { "cell_type": "code", "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 39, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a == b" ] }, { "cell_type": "code", "execution_count": 40, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 40, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a is b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 假设想要缓存实例" ] }, { "cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": [ "import weakref\n", "class Cached(type):\n", " def __init__(self, *args, **kwargs):\n", " super().__init__(*args, **kwargs)\n", " self.__cache = weakref.WeakValueDictionary()\n", " def __call__(self, *args):\n", " if args in self.__cache:\n", " return self.__cache[args]\n", " else:\n", " obj = super().__call__(*args)\n", " self.__cache[args] = obj\n", " return obj\n", "class Spam(metaclass=Cached):\n", " def __init__(self,name):\n", " print(\"creating Spam({!r})\".format(name))\n", " self.name = name\n", " " ] }, { "cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "creating Spam('Guodo')\n" ] } ], "source": [ "a = Spam('Guodo')" ] }, { "cell_type": "code", "execution_count": 48, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "creating Spam('Diana')\n" ] } ], "source": [ "b = Spam(\"Diana\")" ] }, { "cell_type": "code", "execution_count": 49, "metadata": {}, "outputs": [], "source": [ "c = Spam(\"Guodo\")" ] }, { "cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 50, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#注意上面并没有重新创建\n", "a is c" ] }, { "cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 51, "metadata": {}, "output_type": "execute_result" } ], "source": [ "a is b" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# 9.14 捕获类的属性定义顺序\n", "* 问题:想要自动的记录一个类中属性和方法的定义顺序\n", "* 方案:利用元类" ] }, { "cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [], "source": [ "from collections import OrderedDict\n", "class Typed:\n", " _expected_type = type(None)\n", " def __init__(self,name=None):\n", " self._name = name\n", " def __set__(self, instance, value):\n", " if not isinstance(value, self._expected_type):\n", " raise TypeError('type Error')\n", " instance.__dict__[self._name] = value\n", "\n", "class Integer(Typed):\n", " _expected_type = int\n", "class Float(Typed):\n", " _expected_type = float\n", "class String(Typed):\n", " _expected_type = str\n", "class OrderMeta(type):\n", " def __new__(cls, clsname, bases, clsdict):\n", " d = dict(clsdict)\n", " order = []\n", " for name, value in clsdict.items():\n", " if isinstance(value, Typed):\n", " value._name = name\n", " order.append(name)\n", " d['_order'] = order\n", " return type.__new__(cls, clsname, bases, d)\n", " @classmethod\n", " def __prepare__(cls, clsname, bases):\n", " return OrderedDict()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* 下面将使用上面代码将一个类实例的数据序列化为一个csv数据" ] }, { "cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [], "source": [ "class Structure(metaclass=OrderMeta):\n", " def as_csv(self):\n", " return ','.join(str(getattr(self,name)) for name in self._order)\n", "class Stock(Structure):\n", " name = String()\n", " shares = Integer()\n", " price = Float()\n", " def __init__(self, name, shares, price):\n", " self.name = name\n", " self.shares = shares\n", " self.price = price" ] }, { "cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'google'" ] }, "execution_count": 59, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s = Stock(\"google\",100, 234.19)\n", "s.name" ] }, { "cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'google,100,234.19'" ] }, "execution_count": 60, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s.as_csv()" ] }, { "cell_type": "code", "execution_count": null, "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.6.4" } }, "nbformat": 4, "nbformat_minor": 2 }