首页 文章 精选 留言 我的

精选列表

搜索[增量式],共10000篇文章
优秀的个人博客,低调大师

蚂蚁搬家式迁移mysql数据库

乘机房搬迁的机会,打算做一次业务整合。现有的架构是在2010年规划并运营起来的,随着时间的推移,项目也越来越多。打开nginx配置文件,有四十多行include包含存在,每一个包含就是一个项目(有些是web,有些是app)。一整个机柜,老旧的设备,负载均衡高可用架构。为保证业务一致性和降低成本,业务数据(开发的应用程序及用户上传数据)共享一套NFS;各业务共享同一套物理数据库(一台物理服务器mysql创建多个库)。随着业务和访问量的增长,这种隐患越来越令人担忧,主要表现在以下几个方面: ◆安全问题 数十个站点共享目录,以nfs方式共享给各物理服务器,这几十个项目,只要任何一个有安全漏洞存在,有心人都能进来为所欲为,让站点全部沦陷。时不时的,被人注入恶意代码,针对性的进行清除,但没多久又被注入篡改。大家心里都有数,存在漏洞的地方,不一定是被篡改的那个。但站点太多,又没有隔离,根本无法用安全工具扫描(一个站点进行扫描,平均花费一天)。 [root@web57 ~]# more /usr/local/nginx/conf/nginx.confuser www www;worker_processes 6;worker_rlimit_nofile 51200;events {use epoll;#use kqueue; #FreeBSD systemworker_connections 51200;}http {include mime.types;default_type application/octet-stream;#charset gb2312;server_names_hash_bucket_size 256;client_header_buffer_size 256k;large_client_header_buffers 4 256k;client_max_body_size 500m;…………………………………………省略若干…………………………………include vhosts/faxian.quanzhen.com.conf;include vhosts/www.quanzhen.com.conf;include vhosts/news.quanzhen.com.conf;include vhosts/s.quanzhen.com.conf;include vhosts/down.quanzhen.com.conf;include vhosts/static.quanzhen.com.conf;include vhosts/image.quanzhen.com.conf;include vhosts/3g.quanzhen.com.conf;include vhosts/mini.quanzhen.com.conf;include vhosts/xml.quanzhen.com.conf;include vhosts/mayiapi.quanzhen.com.conf;include vhosts/www.android77.com.conf;include vhosts/fahongbao.android77.com.conf;include vhosts/update.android77.com.conf;include vhosts/dev.quanzhen.com.conf;include vhosts/qr.110.cc.conf;include vhosts/110.cc.conf;include vhosts/eggserver.quanzhen.com.conf;include vhosts/apkegg.quanzhen.com.conf;include vhosts/eggserver.yidong7.cn.conf;include vhosts/www.yidong7.cn.conf;include vhosts/down.yidong7.cn.conf;include vhosts/wan.quanzhen.com.conf;include vhosts/open.quanzhen.com.conf;include vhosts/bakdown.yidong7.cn.conf ;include vhosts/hanhua.quanzhen.com.conf;include vhosts/mpk.quanzhen.com.conf;include vhosts/android.quanzhen.com.conf;include vhosts/pay.quanzhen.com.conf;include vhosts/cmstop.quanzhen.cn.conf;include vhosts/news.quanzhen.cn.conf;include vhosts/pingce.quanzhen.cn.conf;include vhosts/gonglue.quanzhen.cn.conf;include vhosts/hao.quanzhen.cn.conf;include vhosts/all.quanzhen.cn.conf;include vhosts/s.quanzhen.cn.conf;include vhosts/apkz.quanzhen.com.conf;include vhosts/ajax.quanzhen.com.conf;include vhosts/union.quanzhen.com.conf;include vhosts/mai.quanzhen.com.conf;include vhosts/blog.quanzhen.com.conf;include vhosts/guazi.quanzhen.com.conf;include vhosts/lockscreen.yidong7.cn.conf;include vhosts/dsp.pujia8.com.conf;include vhosts/3svx4haii9.quanzhen.com.conf;include vhosts/u.quanzhen.com.conf;include vhosts/bianji.quanzhen.com.conf;include vhosts/default.conf;} ◆性能问题 主要集中在数据库上边,只要有一个库出现问题,引起锁表或者其它竞争,全部相关业务都会挂起,烦不胜烦啊。想进行拆分,决策人认为,本来就满机柜了,如果再新家机器,得另租机柜,考虑到成本等其它问题,只求不出事即可。 整合的计划是,迁移部分业务到公有云上,腾出服务器后,对现有的设备进行扩充配置(拼内存、硬盘等,古旧的机器直接下架)。留下配置高的,进行虚拟化,既能减少设备数量(托管费降低),又有利于日常维护。 前边说了这么多,似乎与技术关系不大,但对于一些有遗留问题的项目,还是具有参考意义。接下来,我们就进入正题,看看我们要迁移的项目状况。要往云上迁移的数据包括网站数据及数据库数据,网站数据比较好办,rsync同步到对应的目录,而数据库相对而言,要麻烦不少。两个数据库,一个容量38G,另一个29G,不算太大,但公用的ibdata1文件却有123G,最初是尝试把这两个库,直接导入到阿里云的RDS,在进行数次操作失败后,咨询客服得到的答复是RDS暂时不支持分表的数据库。为节省成本,购买一个配置高一点的云主机(cpu 8core,内存32G,1T高效云盘),部署上mysql5.6,供两个数据库使用。 第一次尝试 预估了一下,200G的数据,贪心一把,看一次性能不能迁移完。提前几天,把云上的环境全部准备妥当(能出来测试页),运营部门把通知发下去,然后某天夜里0:30分,一些人在办公室,一些人在家里,眯着眼,庄重地在键盘敲入“screen”这几个字符。在qq群里得到一致许可,可以进行数据库导出操作以后,小弟小心翼翼地发来一条指令: [root@db-209 ~]# innobackupex --user=root --passwor='i%=KGb76' \--defaults-file=/etc/my.cnf \ --databases=“quanzhen_mobile7lockscreen quanzhen_equipment” /data/bakmysql/InnoDB Backup Utility v1.5.1-xtrabackup; Copyright 2003, 2009 Innobase Oyand Percona Ireland Ltd 2009-2012. All Rights Reserved.This software is published underthe GNU GENERAL PUBLIC LICENSE Version 2, June 1991.180618 00:30:31 innobackupex: Starting mysql with options: --defaults-file='/etc/my.cnf' --password=xxxxxxxx --user='root' --unbuffered --180618 00:30:31 innobackupex: Connected to database with mysql child process (pid=20090)180618 00:30:37 innobackupex: Connection to database server closedIMPORTANT: Please check that the backup run completes successfully. At the end of a successful backup run innobackupex prints "completed OK!".innobackupex: Using mysql Ver 14.12 Distrib 5.0.95, for redhat-linux-gnu (x86_64) using readline 5.1innobackupex: Using mysql server version Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.innobackupex: Created backup directory /data/bakmysql/2018-06-18_00-30-37180618 00:30:37 innobackupex: Starting mysql with options: --defaults-file='/etc/my.cnf' --password=xxxxxxxx --user='root' --unbuffered --180618 00:30:37 innobackupex: Connected to database with mysql child process (pid=20123)180618 00:30:39 innobackupex: Connection to database server closed180618 00:30:39 innobackupex: Starting ibbackup with command: xtrabackup_55 --defaults-file="/etc/my.cnf" --defaults-group="mysqld" --backup --suspend-at-end --target-dir=/data/bakmysql/2018-06-18_00-30-37 --tmpdir=/tmpinnobackupex: Waiting for ibbackup (pid=20132) to suspendinnobackupex: Suspend file '/data/bakmysql/2018-06-18_00-30-37/xtrabackup_suspended'xtrabackup_55 version 2.0.7 for Percona Server 5.5.16 Linux (x86_64) (revision id: 552)xtrabackup: uses posix_fadvise().xtrabackup: cd to /data/mysql_dbxtrabackup: Target instance is assumed as followings.xtrabackup: innodb_data_home_dir = ./xtrabackup: innodb_data_file_path = ibdata1:10M:autoextendxtrabackup: innodb_log_group_home_dir = ./xtrabackup: innodb_log_files_in_group = 2xtrabackup: innodb_log_file_size = 5242880>> log scanned up to (601191481892)[01] Copying ./ibdata1 to /data/bakmysql/2018-06-18_00-30-37/ibdata1>> log scanned up to (601191481892)>> log scanned up to (601191481892)>> log scanned up to (601191481892)>> log scanned up to (601191481892)>> log scanned up to (601191481892)>> log scanned up to (601191481892)>> log scanned up to (601191481892)…………………………………省略…………………………………………… 乐观估计,上午7点前,能完成整个迁移,几个人商量轮流监看进展程度,等进行完一步后叫醒休息的人,以便进行下一步。结果,到凌晨六点多,才执行完这个innobackupex,还差好几步呢,每一步都同样耗时,只能宣告迁移暂时失败,选个黄道吉日,分两次进行迁移。 第二次分拆迁移 万年历排除近期诸事不宜的日子,再摇卦选利用用神的地支,选定日志,约上相关人等,继续进行迁移。有了上一次的教训,在迁移前又对要迁移的库做了清理,删掉了一些无用的数据,省出来好几个G的空间。在源数据库,执行指令: [root@db-209 ~]#innobackupex --user=root --passwor='i%=KGb76' \ --defaults-file=/etc/my.cnf --databases=“quanzhen_equipment” /data/bakmysql/ 我交到好以后,就躺下睡觉,到凌晨三点电话响了,告知第一步完成。 [root@db-209 ~]#innobackupex --apply-log /data/bakmysql/2018-06-18_00-30-37 日志应用倒是执行的很快,回车即完。然后进行tar 打包和复制文件到目标服务器,由于租赁的出口带宽太小(总带宽30M,现在读者知道为什么要夜间访问低谷进行迁移了吧?),复制文件到目标服务花了一些时间。 目标服务器,仅仅需要安装好mysql软件,创建好目录/data/mysql_db,不需要执行数据库初始化操作,因为innobackupex导入时,要求数据目录必须为空。阿里云的配置,远比源服务器配置高,解压文件很快就完成。 检查一下mysql选项文件/etc/my.cnf,注意是选项文件。设定“—datadir=/data/mysql_db”,就可执行导入操作,指令如下: [root@msyql mysql_db]# innobackupex --defaults-file=/etc/my.cnf \ --copy-back /data/db_bk/2018-06-18_00-30-37 源数据导出时,没有把库mysql一并导出,这倒不是什么要紧的事情,反正只有一个账户需要创建。接下来,初始化数据库并创建应用帐号,具体操做如下: [root@msyql mysql_db]#cd /usr/local/mysql/[root@msyql ~]#scripts/mysql_install_db --user=mysql --datadir=/data/mysql_db[root@msyql ~]#mysqlmysql>grant all on quanzhen_equipment.* to …… 还要记得给mysql空密码消除掉。 源库与目标库,比对一下表的数量,以及随机抽取一些大表,对记录数进行比较。确认数据完整以后,一帮去调试应用,后续工作不表,没我什么事。 第三次分拆迁移 有了上一次的成功经验,这次信心满满了,不过担心还是有的,就是那个目标库导入时,要求数据目录为空。小弟在未开始时,就来征求我的意见,我担心可能会有障碍,就对他说,你只要把源站数据导出准备好,放到目标数据库,余下的我亲自搞定。 自己的选择有两个,一个是使用选项“--force-non-empty-directories”,如果不行,就再弄一个mysql实例,启用3307端口,双实例运行。先尝试第一个选项,看能不能进行下去,具体指令为: [root@msyql db_bk]# pwd/data/db_bk[root@msyql db_bk]#innobackupex --defaults-file=/etc/my.cnf --copy-back \ --force-non-empty-directories 2018-06-22_00-24-52180623 23:31:57 innobackupex: Starting the copy-back operationIMPORTANT: Please check that the copy-back run completes successfully. At the end of a successful copy-back run innobackupex prints "completed OK!".innobackupex version 2.4.11 based on MySQL server 5.7.19 Linux (x86_64) (revision id: b4e0db5)innobackupex: Can't create/write to file '/data/mysql_db/ib_logfile0' (Errcode: 17 - File exists)[01] error: cannot open the destination stream for ib_logfile0[01] Error: copy_file() failed. 悲催了,有同名文件存在,不行!直接终止运行。好吧,我把文件“ib_logfile0、ib_logfile1”挪走,再执行,还是不行,提示文件“ibdata1”存在,这可是个大家伙。虽然担心新导入的ibdata1可能不包含现有数据库相关信息,但忍不住想试一把。可能有读者会问,这样搞可能把数据库原有的数据破坏掉了,其实我想到这一层来,老早我就把整个库做了备份,买了保险的。 正全神贯注盯着屏幕查看输出,希望进展顺利,突然,qq群有消息传来,问进展如何,啥时能完成。一看时间,六点了,北方大地已经一片光明。时间来不及了,停掉进程,试试直接复制文件,不使用innobuckupex。心中没底,就去仔细比较了数据库目录与导出数据目录中的三个文件“ibdata1、ib_logfile0、ib_logfile1”,发现其大小完全相同。不管了,把现有数据库里的这几个文件搬走,从导出目录cp来着三个文件。复制完,执行mysqld_safe启动服务,失败,提示ib_logfile0无写入权限;这好办,一条chown指令而已。再执行启动mysql服务,正常。 那么数据对不对呢?我不能确定,万一不对,就再配一个mysql,导入数据,以双实例启动,后边再想法整合;阿里云购买的服务器,相互通信是内网,不会在传输上浪费太多时间。 既然服务正常,就对一下数据吧,万一运气爆棚(前几天夜里梦到自己能飞,抓住一只巨型天鹅,我美美地搂着天鹅的脖子…),数据完整可用呢!我自己悄悄对比了一阵,没差异呢,又到qq群呼叫其它人,说导入有障碍,数次不成功,后边采取了一些不确定的手段,mysql服务是起来了,请大家核实一下数据,看是否完整可用。几个程序员一阵忙碌,得到答复,数据是完整可用的。到此,我的工作完成了。 有人可能要鄙视我一番,为什么不先测试?不制定完善的流程?这个问题问得好!我数次建议决策人,准备点资源,说白了就是准备1台空闲服务器,再内网演练,就算白天也能能进行(复制数据走内网,不在用户访问的带宽),但是,没有资源给我啊,但事情又不得不做。虽然累点,折腾一番,翻过来想,咱玩悬的也获得经验,不然也没有这个文章问世,你们觉得觉呢? 道长有话说最近受邀在51CTO博客专栏出版《负载均衡高手炼成记》,依托自己十余年的IT运维经验,以实际工作经验为基础,介绍不同场景下,负载均衡的实现方式,以及负载均衡的日常维护。本专栏适合于一直徘徊在运维初级未找到入佳境之门的运维工程师,也适合对负载均衡有兴趣了解和学习的技术爱好者。跟着田道长学完这个系列课程,不仅学知识,更是涨薪、升职、进心仪公司的一大捷径!负载均衡高手炼成记

优秀的个人博客,低调大师

分布式--Redis 内存数据库

1. Redis 内存数据库 1). Redis Redis是一个开源的,高性能,C语言开发的 ,键值对(key-value)存储数据的NOSQL数据库。 2). 基本概念 NOSQL :Not Only Sql 泛指非关系型数据库 Redis MongoDB Hbase 关系型数据库: mysql oracle sybase sqlServer 3). 数据库应用的发展历史 1:在互联网时代来临之前 ,企业的一些内部信息管理系统,一个单个的数据库的实例就可以应付系统的功能需求单数据库实例 2:随着系统访问用户的增多 ,数据量的增大,单个数据库的实例已经满足不了系统频繁读取数据(权限的验证,系统的默认配置)的需求 。缓存 (ehCache/MemCached)+ 数据库实例 3: 缓存虽然可以缓解数据库的读取压力 ,但是数据量的写入压力持续增大 ,这个时候我们需要采取数据库主从机制进行读写分离缓存 (ehCache/MemCached)+ 主从数据库 + 读写分离 4: 数据量再次增大,读写分离以后,主数据库的写库的压力出现瓶颈缓存 (ehCache/MemCached)+ 主从数据库 + 读写分离 +分库分表 5:互联网和大数据的时代的来林,关系型数据库已经不能很好的存储一些并发性大,实时性高,而且格式不固定的数据Nosql数据库+主从数据库 + 读写分离 +分库分表 4). 系统架构中的位置 图1.png 2. 安装 3. Redis的五大数据类型以及应用 String/Hash/List/Set/sorted set 字符串类型 String 列表类型 list 集合类型 set 有序集合类型 sorted set 散列类型 hash 1). K-V格式中 V是一个字符串对象 set k1 v1 get k1 del k1 incr num (自增1 如果没有设置值默认为1) decr num (自减1) incrby num 5 (自增5) decrby num 5 (自减 5) Append num 4 (追加4)返回字符串长度位数,不是数学+ 是拼接 应用场景: 秒杀商品/商品点赞 2). Hash K-V格式中 V是一个Hash类型 其实java里面的Map<String,object> 赋值命令 hset student id 1 hset student name mazaiting hset student sex boy hmset myhash username admin age 20 取值命令 hget myhash username hget myhash username age hgetall myhash 删除命令 hdel myhash username age del myhash (删除myhash) hincrby myhash age 5 (增加5) 其他命名 hexists myhash username 判断myhash中是否包含username包含返回1否则返回0 hlen myhash 获取myhash中的字段数 hkeys myhash 获取所有的 key hvals myhash 获取所有的值 应用场景: 电商网站的商品详细信息 3). list K-V格式中 V是一个list类型 其实java里面的LinkedList 把他看成一个竹子,两边是打通了 从左插入记录 lpush book 1 2 3 从右插入记录 rpush book 4 5 6 从左弹出记录 lpop book 1 从右弹出记录 lpush book 1 列表里面的记录顺序(竹子)-- 3 2 1 4 5 6 应用场景: 商品的评论 lpush mylist 1 2 3 从左插入记录 rpush mylist a b c 从右插入记录 lrange mylist 开始索引位置 结束索引位置(-1最后一个) lpop mylist 左侧弹出(相当于删除) rpop mylist 右侧弹出(相当于删除) lpush mylist 左侧插入 rpush mylist 右侧插入 lrem mylist 删除几个元素 删除哪个元素 lset mylist 索引位置 插入的值 linsert mylist before 哪个值前面 要插入的值 linsert mylist after 哪个值后面 要插入的值 rpoplpush 集合1 集合2 把集合1中取出的值放在集合2中 4). Set K-V格式中 V是一个set类型 set是无序的/set里面的元素具有唯一性、 sadd summer_friends sky big mo mike sadd maybe_friends sky big tdd world oky 应用场景: 独立IP进行网站投票 /微信朋友圈的共同好友 sadd myset a b c 添加 a b c 不能添加重复的值 srem myset a c 移除a c smembers myset 查看所有值 sismember myset a 查看集合里面是否包含a包含返回1 否则返回0 sadd myset2 a b c sadd myset3 a c 1 sdiff myset2 myset3 myset2 和myset3相差的是b 差集运算 sinter myset2 myset3 myset2 和myset3相交的是a c 交集运算 sunion myset2 myset3 并集运算 合并集合 相同的会去掉(值不能重复) scard myset 获取集合数量 srandmember myset 随机获取集合中的一个值 sdiffstore myset4 myset2 myset3 将myset2 和myset3中相差的值放到myset4里面 sinterstore myset4 myset2 myset3 将myset2 和myset3中相交的值放到myset4里面 sunionstore myset4 myset2 myset3 将myset2 和myset3中相并的值放到myset4里面 5). sorted-set 游戏排名 热点话题中应用 K-V格式中 V是一个Zset类型 zset是有序的? 怎么做到有序 应用场景: 商品的销售排行榜 zadd sales 100 huawei 99 iphone 98 vivo 97 oppo Zrange sales 0 -1 withscores 从小到大 Zrevrange sales 0 -1 withscores 从大到小 4. Java 使用Redis 1). 工具下载 Jedis下载 此处使用windows下的redis工具包,因为虚拟机连接会出现很多问题。 1). Redis-win下载 下载后解压,如下图 图3.png 2). 在上方地址栏输入cmd然后回车,这时会打开命令提示符窗口,然后输入redis-server.exe redis.windows.conf 开启redis服务器. 图4.png 3). 在上方地址栏继续输入cmd回车,输入命令``打开redis客户端 图5.png 2). 连接 public class RedisJava { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); System.out.println("服务正在运行: " + jedis.ping()); jedis.close(); } } 3). 测试 public class RedisJava { public static void main(String[] args) { Jedis jedis = new Jedis("127.0.0.1", 6379); System.out.println("服务正在运行: " + jedis.ping()); jedis.set("name", "linghaoyu"); System.out.println(jedis.get("name"));; jedis.close(); } } 打印结果: 图6.png 5. 其他 1). Redis HyperLogLog命令 Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。基数:数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。 图7.png 2). Redis 订阅 I. Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。Redis 客户端可以订阅任意数量的频道。 下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系: 图8.png 当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端: 图9.png II. 实例 订阅 subscribe redisChat 图10.png 发布消息 publish redisChat "Redis is a great caching technique" 图11.png 3). Redis 事务 Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证: 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。 一个事务从开始到执行会经历以下三个阶段: 开始事务。 命令入队。 执行事务。 示例: 127.0.0.1:6379> multi OK 127.0.0.1:6379> set book-name "Mastering C++ in 21 days" QUEUED 127.0.0.1:6379> get book-name QUEUED 127.0.0.1:6379> sadd tag "C++" "Programming" "Matering Series" QUEUED 127.0.0.1:6379> smembers tag QUEUED 127.0.0.1:6379> exec 1) OK 2) "Mastering C++ in 21 days" 3) (integer) 3 4) 1) "Matering Series" 2) "C++" 3) "Programming" 图12.png 序号 命令及描述 1 DISCARD 取消事务,放弃执行事务块内的所有命令。 2 EXEC 执行所有事务块内的命令。 3 MULTI 标记一个事务块的开始。 4). Redis 桌面管理 代码下载

优秀的个人博客,低调大师

python函数式编程之生成器

在前面的学习过程中,我们知道,迭代器有两个好处: 一是不依赖索引的统一的迭代方法 二是惰性计算,节省内存 但是迭代器也有自己的显著的缺点,那就是 不如按照索引取值方便 一次性,只能向后取值,不能向前取值 所以我们还需要学习另外一种对象,那已经生成器 1.什么是生成器 如果一个函数体内部包含yield关键字,该函数就是生成器函数,执行该函数就得到一个生成器对象 2.得到生成器 先来看下面的代码 def foo(): print("first...") yield print("second...") yield print("third...") g=foo() print(g) 根据上面生成器的定义:函数体内部包含yield关键字,则该函数就是生成器函数,则上面的函数执行结果就是一个生成器对象 执行上面的代码,查看程序执行结果 <generator object foo at 0x0000000001DF2BF8> 可以看出:上面的函数执行的结果g就是一个生成器对象,上面的函数foo就是一个生成器函数 3.生成器的内置方法 修改上面的代码,调用dir方法查看生成器中包含的方法 def foo(): print("first...") yield print("second...") yield print("third...") g=foo() print(dir(g)) 打印生成器内部的方法,可以看到打印的结果 ['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw'] 在这些结果中,可以看到有__iter__方法和__next__方法,由此可以判断出生成器的本质就是迭代器 ,生成器是迭代器的一种 4.判断生成器是否是迭代器 修改上面的代码 def foo(): print("first...") yield print("second...") yield print("third...") g=foo() from collections import Iterable print(isinstance(g,Iterable)) 查看打印结果 True 上面的两个例子都可以证明:生成器的本质是迭代器,生成器就是迭代器的一种 5.生成器的 __iter__方法和__next__方法 既然生成器的本质是迭代器,那么调用生成器的 __iter__方法和__next__方法,得到的结果会是什么呢 修改上面的代码 def foo(): print("first...") yield print("second...") yield print("third...") g=foo() print(g) print(g.__iter__()) g.__next__() 程序执行结果 <generator object foo at 0x0000000001DF2BF8> <generator object foo at 0x0000000001DF2BF8> first... 从上面的程序的执行结果可以看出:直接打印生成器g和调用生成器g.__iter__方法,得到的结果都是生成器对象g 在内存中的地址 调用g.__next__方法,实际上就是从生成器g中取出一个值,执行一次g.__next__方法,触发一次生成器的取值操作,这个过程在上面的代码中表现为foo函数的向下执行过程 从上面的程序的执行结果中可以看到,只执行了foo函数的第一个print函数,并没有执行第二个和第三个print函数。 通常对函数来说,函数开始执行以后直到return语句,函数才会停止执行 在这里执行一次g.__next__方法,foo函数中执行了一行代码,遇到yield就停止了,在这里yield好像起到了return的作用。 实际上,yield关键字的功能之一就是起到返回的作用 上面的程序执行遇到yield,本次g.__next__方法执行完毕。 在函数的执行过程中,如果函数的return语句有返回值,则函数的执行完成就得到return语句的返回值, 如果return没有定义返回值或者函数中没有定义return语句,则函数的执行结果默认为None 修改上面的代码,打印__next__方法的执行结果 def foo(): print("first...") yield print("second...") yield print("third...") g=foo() print(g.__next__()) 程序执行结果 first... None 可以看到,调用__next__方法时,yield后没接任何参数时,yield默认的返回值也是None 6.yield后面接返回值 那如果在yield关键字后接一个返回值,程序执行结果会是怎么样的呢 修改上面的代码,在yield关键字后接一个返回值,看程序的执行结果 def foo(): print("first...") yield 1 print("second...") yield 2 print("third...") g=foo() print(g.__next__()) 程序执行结果 first... 1 从上面的程序的执行结果可以看出,yield会把其后面接的数返回,作为__next__方法的执行结果 7.yield与return的不同点 在函数中,不管一个函数中定义了多少个return语句,函数在执行到第一个return语句的时候就会中止,其后面的语句将不会被继续执行 而对于yield来说,每调用一次__next__方法,程序会从开始向下执行,直到遇到yield语句,程序暂停,等到第二次调用__next__方法,程序会从上次暂停的地方继续向下执行,直到遇到下一个yield或者程序执行完成 在上面的例子里,是使用yield把函数foo变成一个生成器,执行foo函数时,并不会立即执行foo函数,而是先得到生成器g,当调用一次`g.__next__`方法时,函数foo开始向下执行,遇到yield时,程序暂停,当下一次调用`g.__next__`方法时,函数foo继续从上一次暂停的地方开始向下执行,直到遇到yield暂停 8.生成器的StopIteration 修改程序,多次调用__next__方法,查看程序的执行结果 def foo(): print("first...") yield print("second...") yield print("third...") g=foo() print(g) print(g.__iter__()) g.__next__() print('*'*30) g.__next__() print('#'*30) g.__next__() 程序执行结果 <generator object foo at 0x0000000001DF2BF8> <generator object foo at 0x0000000001DF2BF8> first... ****************************** second... ############################## third... Traceback (most recent call last): File "E:/py_code/test.py", line 28, in <module> g.__next__() StopIteration 从上面程序的执行结果可以看出,每调用一次生成器的__next__方法,会得到一个返回值,就相当于从迭代器中取一个值。 如果程序在执行过程中,没有得到返回值,这就说明迭代器的最后一个值已经被遍历完成了,所以此时再调用__next__方法,程序就会抛出异常 9.生成器的for循环遍历 在前面的学习中已经知道,生成器本质上就是一个迭代器。既然是迭代器,那么当然可以使用for循环来遍历生成器 修改上面的例子,使用for循环来遍历生成器 def foo(): print("first...") yield 1 print("second...") yield 2 print("third...") g=foo() for i in g: print(i) print("*"*30) 查看程序的执行结果 first... 1 ****************************** second... 2 ****************************** third... 在上面的例子里,每执行一次for循环,就相当于是执行一次g.__next__方法,yield会返回其后所接的数字,所以for循环前两次的执行结果都是print函数和yield后接的数字 for循环执行到第三次的时候,执行完print函数,程序会抛出StopIteration异常,但是StopIteration的异常会被for循环捕捉到,所以for循环执行第三次只执行了print语句 10.总结: yield关键字的功能: 与return的功能类似,都可以返回值,但不一样的地方在于一个函数中可以多次调用yield来返回值 为函数封装好了`__iter__方法`和`__next__方法`,把函数的执行结果变成了迭代器 `遵循迭代器的取值方式(obj.__next__())`,触发的函数的执行,函数暂停与再继续都由yield保存 11.示例:使用yield模拟linux中的命令:tail -f | grep 'error' | grep '404' 代码如下: import time def tail(file_path, encoding='utf-8'): with open(file_path, encoding=encoding) as f: f.seek(0, 2) while True: line = f.readline() if line: yield line else: time.sleep(0.5) def grep(lines, pattern): for line in lines: if pattern in line: yield line g1 = tail('a.txt') g2 = grep(g1, 'error') g3 = grep(g2, '404') for i in g3: print(i)

优秀的个人博客,低调大师

python函数式编程之装饰器(二)

以前用装饰器,都是定义好了装饰器后,使用@装饰器名的方法写入被装饰函数的正上方 在这里,定义的装饰器都是没有参数的 在定义装饰器的函数的时候,没有在括号里定义参数,这就叫做无参装饰器 既然有无参装饰器,那么当然也就会有有参装饰器 有参装饰器的定义和使用 定义一个普通的装饰器 db_path = "db.txt" login_dic = { "user": None, "status": None, } def auth(func): def wrapper(*args, **kwargs): if login_dic['user'] and login_dic['status']: res = func(*args, **kwargs) return res name = input("your name:").strip() password = input("your password:").strip() with open(db_path, "r", encoding="utf-8") as f: user_dic = eval(f.read()) if name in user_dic and password == user_dic[name]: print("login ok") login_dic['user'] = name login_dic['status'] = True res = func(*args, **kwargs) return res else: print("login error") return wrapper @auth def index(): print("welcome to index page") @auth def home(name): print("welcome to %s home page" % name) index() home() 上面的例子是一个典型的基于文件的认证方式的装饰器 在生产环境中,用户的认证方式可能有很多种,比如还有LDAP和数据库的认证方式 如果想把上面的代码修改,添加基于不同认证方式的用户认证 db_path = "db.txt" login_dic = { "user": None, "status": None, } def deco(auth_type): def auth(func): def wrapper(*args, **kwargs): if auth_type == "file": if login_dic['user'] and login_dic['status']: res = func(*args, **kwargs) return res name = input("your name:").strip() password = input("your password:").strip() with open(db_path, "r", encoding="utf-8") as f: user_dic = eval(f.read()) if name in user_dic and password == user_dic[name]: print("login ok") login_dic['user'] = name login_dic['status'] = True res = func(*args, **kwargs) return res else: print("login error") elif auth_type == 'ldap': print("ldap认证方式") elif auth_type == "sql": print("数据库的认证方式") else: print("不知道的认证方式") return wrapper return auth 有参装饰器的调用 @deco(auth_type="file") def index(): print("welcome to index page") @deco(auth_type="abc") def home(name): print("welcome to %s home page" % name) index() home() 在上面的例子里,deco(auth_type="file")这个函数的执行以后就得到了auth函数,但是现在得到的auth函数跟以前的auth函数是不一样的,现在的auth函数内部添加了认证方式这样一个参数, 这样添加auth_type参数后的结果就相当于加在index函数和home函数正上方的是auth函数,这样就为最开始时的auth函数添加了一个参数 函数中的路由功能 先定义3个函数,现在想做的是只需要用户输入字符串,就执行对应的函数 可以通过为每个函数添加装饰器的功能,把普通函数添加到函数字典中,然后到函数字典中找到输入字符串对应的函数地址,加括号就可以运行了 类似于 func_dic = {"f1": f1, "f2": f2, "f3": f3} def f1(): print("f1 func") def f2(): print("f2 func") def f3(): print("f3 func") 这里就要使用到有参装饰器 定义有参装饰器 def make_func_dic(key): def deco(func): func_dic[key] = func return deco 用装饰器make_func_dic装饰函数 func_dic = {} def make_func_dic(key): def deco(func): func_dic[key] = func return deco @make_func_dic("f1") # 等同于deco(f1) def f1(): print("f1 func") @make_func_dic("f2") # 等同于deco(f2) def f2(): print("f2 func") @make_func_dic("f3") # 等同于deco(f3) def f3(): print("f3 func") 打印func_dic,得到结果 {'f1': <function f1 at 0x0000000002D2A510>, 'f2': <function f2 at 0x0000000002D2A598>, 'f3': <function f3 at 0x0000000002D2A620>} 可以看到func_dic字典中,每个字符串对应的是相应字符串的函数的内存地址 这样想运行f1函数,只需要把func_dic中的"f1"的值加括号运行就可以了 然后使用户输入字符串,运行字符串对应的函数 while True: cmd=input(">>: ").strip() if cmd in func_dic: func_dic[cmd]() 这样就可以得到想要的结果了 这样做的好处:只需要输入对应的字符串,就可以执行对应的函数,不需要使用if和elif对输入的字符串进行一条一条的判断了 这就是使用装饰器在代码级别达到函数的路由功能

优秀的个人博客,低调大师

python函数式编程之装饰器(一)

1.开放封闭原则 简单来说,就是对扩展开放,对修改封闭 在面向对象的编程方式中,经常会定义各种函数。 一个函数的使用分为定义阶段和使用阶段,一个函数定义完成以后,可能会在很多位置被调用 这意味着如果函数的定义阶段代码被修改,受到影响的地方就会有很多,此时很容易因为一个小地方的修改而影响整套系统的崩溃, 所以对于现代程序开发行业来说,一套系统一旦上线,系统的源代码就一定不能够再改动了。 然而一套系统上线以后,随着用户数量的不断增加,一定会为一套系统扩展添加新的功能。 此时,又不能修改原有系统的源代码,又要为原有系统开发增加新功能,这就是程序开发行业的开放封闭原则,这时就要用到装饰器了。 2.什么是装饰器?? 装饰器,顾名思义,就是装饰,修饰别的对象的一种工具。 所以装饰器可以是任意可调用的对象,被装饰的对象也可以是任意可调用对象 3.装饰器的作用 在不修改被装饰对象的源代码以及调用方式的前提下为被装饰对象添加新功能 原则: 1.不修改被装饰对象的源代码 2.不修改被装饰对象的调用方式 目标: 为被装饰对象添加新功能 4.装饰器的定义和使用 来看下面的代码: import time import random def index(): time.sleep(random.randrange(1,5)) print("welcome to index page") index() index函数的作用是程序在随机睡眠1到5秒之后,打印一句话 现在想为index函数添加一个新功能:统计index函数的运行时间,该怎么做呢?? 修改index函数如下: import time import random def index(): start_time=time.time() time.sleep(random.randrange(1,5)) print("welcome to index page") end_time=time.time() print("cost time: %s" %(end_time - start_time)) index() 运行程序,执行结果如下: welcome to index page cost time: 2.000999927520752 可以看到,为index函数添加新功能确实实现了,但是却违反了开放封闭原则。 在符合开放封闭原则的前提下,如果想为index函数添加新功能,此时就要使用装饰器了 修改代码 import time import random def index(): time.sleep(random.randrange(1,5)) print("welcome to index page") def timmer(): def inner(): start_time=time.time() index() end_time=time.time() print("run time: %s " %(end_time-start_time)) return inner f=timmer() f() 运行程序,查看执行结果 welcome to index page run time: 1.0 从程序执行结果可以看出,index函数的运行时间已经被统计出来了 但是查看源码可以知道,index函数的源码确实没有被修改,但是index的调用方式被修改了 而且还有一个问题就是,timmer这个装饰器只能被用来装饰index这个函数,如果以后想统计别的函数的运行时间,又要重新定义别的装饰器,这样也太不灵活了。 修改上面的代码 import time import random def timmer(func): def inner(): start_time=time.time() func() end_time=time.time() print("run time: %s " %(end_time-start_time)) return inner def index(): time.sleep(random.randrange(1,5)) print("welcome to index page") index=timmer(index) index() 运行程序,查看程序执行结果 welcome to index page run time: 4.0 可以看到,index函数的源代码没有被修改,index函数的调用方式也没有改变,但是依然为index函数添加了统计时间的功能,这里使用的就是装饰器了。 来分析下上面代码的执行流程: 1.导入time和random模块,定义index函数和timmer函数 2.把原始的index函数的内存地址作为参数传给timmer函数。 3.timmer函数内部嵌套定义一个函数inner,然后返回inner函数的内存地址 4.timmer函数执行完成,返回timmer函数的内部函数inner的内存地址,然后把inner的内存地址赋值给index变量 5.index是inner函数的内存地址,index变量加括号运行,实际上就是在运行inner函数 6.运行inner函数,定义程序开始时间。 7.执行timmer函数的变量func,在第2步知道,func这个变量就是index的内存地址,所以这里实际上是执行被装饰过后的index函数 8.index函数执行完成,定义程序的终止时间 9.统计并打印整个程序的执行过程中所花费的时间 这就是装饰器装饰index函数的执行流程 5.装饰器的简化使用 现在我又有另外一个函数home,现在我也想统计home函数的运行时间,可以把代码修改如下 import time import random def timmer(func): def inner(): start_time=time.time() func() end_time=time.time() print("run time: %s " %(end_time-start_time)) return inner def index(): time.sleep(random.randrange(1,5)) print("welcome to index page") def home(): time.sleep(random.randrange(1,5)) print("welcome to home page") index=timmer(index) index() home=timmer(home) home() 运行程序,执行结果如下 welcome to index page run time: 3.0 welcome to home page run time: 4.0 可以看到,每次调用统计程序运行时间的装饰器timmer,都要先把被调用的函数的函数名作为参数传给timmer装饰器 然后再把timmer装饰器的执行结果赋值给被调用的函数名本身,最后才能调用被装饰的函数,太麻烦了有没有?? 其实python中的装饰器可以简化成下面的格式 import time import random def timmer(func): def inner(): start_time=time.time() func() end_time=time.time() print("run time: %s " %(end_time-start_time)) return inner @timmer def index(): time.sleep(random.randrange(1,5)) print("welcome to index page") @timmer def home(): time.sleep(random.randrange(1,5)) print("welcome to home page") index() home() 程序执行结果 welcome to index page run time: 2.0 welcome to home page run time: 4.0 可以看出,使用@加装饰器名添加到被装饰对象的上方的方式也可以为一个函数添加装饰器中定义的功能 6.多个装饰器的定义与调用 在上面的例子里,定义并调用了一个统计程序运行时间的装饰器timmer, 如果现在想为index函数添加一个用户认证的功能,可以定义一个名为auth的装饰器 import time import random def auth(func): def wrapper(): while True: user=input("Input your username>>>:").strip() pwd=input("Input your password>>>:").strip() if user== "abcd" and pwd == "abcd1234": print("login successful") func() break else: print("login error") return wrapper @auth def index(): time.sleep(random.randrange(1,5)) print("welcome to index page") index() 运行程序 Input your username>>>:abcd # 先输入错误的用户名和密码 Input your password>>>:1234 login error # 提示用户输入错误,登录失败 Input your username>>>:abcd # 让用户再次输入用户名和密码 Input your password>>>:abcd1234 login successful # 登录成功 welcome to index page # 执行index函数 从程序执行结果可以看出,用户登录密码验证的装饰器auth已经定义并被成功调用了 如果想为index函数添加用户认证的功能,又想统计index函数执行时间的功能,在使用装饰器的情况下该怎么调用呢 import time import random def timmer(func): def inner(): start_time=time.time() func() end_time=time.time() print("run time: %s " %(end_time-start_time)) return inner def auth(func): def wrapper(): while True: user=input("Input your username>>>:").strip() pwd=input("Input your password>>>:").strip() if user== "abcd" and pwd == "abcd1234": print("login successful") func() break else: print("login error") return wrapper @timmer @auth def index(): time.sleep(2) print("welcome to index page") index() 在上面的代码里,为index函数添加了两个装饰器,现在有一个问题,就是这两个装饰器究竟哪个先被调用,哪个后被调用呢?? 来分析一下, 如果timmer装饰器先被调用,那么程序就会先执行timmer装饰器,然后再执行auth装饰器,提示输入用户名和密码, 这样一来timmer装饰器统计的时间就会包括输入用户名和密码的时间,这个时间会远远大于index函数睡眠的2秒种; 如果auth装饰器先被调用,timmer装饰器后被调用,那么timmer装饰器统计的运行时间就应该只包括index函数的执行时间值应该在2秒多一点点的时间范围内 运行程序,先输入错误的用户名和密码以使用程序的执行时间加长 Input your username>>>:abcd Input your password>>>:abcd login error Input your username>>>:abcd Input your password>>>:abcd1234 login successful welcome to index page run time: 12.759000062942505 从程序的执行结果可以知道,程序是先运行timmer装饰器,然后才运行auth装饰器,所以timmer统计的时间就包括了用户认证的时间,所以timmer统计到的程序运行时间远远大于index睡眠的2秒钟 所以这里得出一个结论: 当一个函数同时被两个装饰器装饰时,加上函数最上面的装饰器先执行,加在下面的装饰器先装饰 把上面例子里的timmer装饰器和auth装饰器位置互换一下 import time import random def timmer(func): def inner(): start_time=time.time() func() end_time=time.time() print("run time: %s " %(end_time-start_time)) return inner def auth(func): def wrapper(): while True: user=input("Input your username>>>:").strip() pwd=input("Input your password>>>:").strip() if user== "abcd" and pwd == "abcd1234": print("login successful") func() break else: print("login error") return wrapper @auth @timmer def index(): time.sleep(2) print("welcome to index page") index() 运行index函数,依然先输入错误的用户名和密码,增加用户认证的时间 Input your username>>>:abcd Input your password>>>:abcd login error Input your username>>>:abcd Input your password>>>:abcd1234 login successful welcome to index page run time: 2.0 可以看到,这次timmer统计到的时间只包含index函数的运行时间,不包含用户进行认证的时间 来分析一下上面例子中,index函数被timmer装饰器和auth装饰器装饰的代码装饰流程 @auth # index=auth(timmer(index)) @timmer # index=timmer(index) def index(): time.sleep(2) print("welcome to index page") 在上面得出结论,一个函数同时被两个装饰器时,加在下面的装饰器先装饰 1.timmer装饰器装饰原始的index,可以写成:index=timmer(index) 2.在timmer装饰器中,timmer装饰器实际上是返回inner的内存地址,所以在这里,index=inner 3.timmer装饰器装饰完成后,由auth装饰器来装饰,此时可以写成index=auth(index), 4.这里auth括号里的index已经不再是原始index函数,而是已经被timmer装饰过后的index了,所以index=auth(timmer(index)) 5.又因为timmer装饰的结果等于inner函数的内存地址,所以:index=auth(inner) 至此,两个装饰器的装饰过程已经知道了,来看程序的执行过程 6.程序先执行auth装饰器,进入用户认证,请用户输入用户名和密码 7.用户输入正确的用户名和密码后,开始执行func函数,也已经上面分析的inner函数 8.timmer装饰器先定义程序的开始运行时间,然后运行func函数,也就是原生的index函数 9.index函数先睡眠2秒,然后执行print语句,再定义程序的结束时间 10.最后统计并打印程序的运行时间,至此程序运行完毕。 所以这里用户输入用户名和密码的时间不会被timmer装饰器统计在内 7.被装饰函数参数的设置与定义 先来看一段代码 import time def timmer(func): def inner(): start_time=time.time() func() end_time=time.time() print("run time: %s " %(end_time-start_time)) return inner @timmer def index(): time.sleep(2) print("welcome to index page") @timmer def home(name): time.sleep(3) print("welcome to %s home page" % name) 如上所示,home函数添加了一个参数,而index函数并没有参数 按照正常的函数的定义与调用方式,调用index函数和home函数的方式应该是下面这种形式 index() home("python") 然后我们运行程序就会发现,程序抛出了异常 File "E:\python_learn\py_code\test.py", line 28, in <module> home("python") TypeError: inner() takes 0 positional arguments but 1 was given 说个异常说明inner函数不需要位置参数,但是我们给了一个位置参数 回到timmer装饰器定义的部分,可以看到,timmer装饰器的内部函数确实没有定义参数 这样一来,timmer装饰器只能用于装饰没有参数的函数了, 我们可以在timmer装饰器定义的时候为inner函数添加一个参数 import time def timmer(func): def inner(name): start_time=time.time() func(name) end_time=time.time() print("run time: %s " %(end_time-start_time)) return inner @timmer def index(): time.sleep(2) print("welcome to index page") @timmer def home(name): time.sleep(3) print("welcome to %s home page" % name) index() home("python") 但是这样一来,timmer装饰器装饰index函数的时候又会抛出异常,因为index函数没有参数 File "E:\python_learn\py_code\test.py", line 27, in <module> index() TypeError: inner() missing 1 required positional argument: 'name' 在不知道被装饰函数的参数个数的情况下,即被装饰函数的参数可变长,且形式不固定的时候, 可以使用*args和**kwargs,把上面的代码修改 import time def timmer(func): def inner(*args,**kwargs): start_time=time.time() func(*args,**kwargs) end_time=time.time() print("run time: %s " %(end_time-start_time)) return inner @timmer def index(): time.sleep(2) print("welcome to index page") @timmer def home(name): time.sleep(3) print("welcome to %s home page" % name) index() home("python") 再次运行程序,查看运行结果 welcome to index page run time: 2.0 welcome to python home page run time: 3.0 由上可知,在不知道被装饰函数的参数个数时,可以使用*args和**kwargs来表示任意长度任意形式的参数 8.被装饰函数的返回值 修改上面的代码,为home函数定义一个返回值,分别打印index函数和home函数的返回值 import time def timmer(func): def inner(*args,**kwargs): start_time=time.time() func(*args,**kwargs) end_time=time.time() print("run time: %s " %(end_time-start_time)) return inner @timmer def index(): time.sleep(2) print("welcome to index page") @timmer def home(name): time.sleep(3) print("welcome to %s home page" % name) return("home func") index_res=index() print(index_res) home_res=home("python") print(home_res) 运行程序,可以看到 welcome to index page run time: 2.0 None welcome to python home page run time: 3.0 None 可以看到,home函数中定义的返回值并没有被打印出来,显示的值为None 因为这里执行的home函数不是原始定义的home函数,而是wrapper函数的执行结果 因为wrapper函数并没有定义返回值,所以执行被装饰后的home函数并没有打印出返回值 修改代码,在timmer装饰器中定义并返回被装饰函数执行的返回值 import time def timmer(func): def inner(*args,**kwargs): start_time=time.time() res=func(*args,**kwargs) end_time=time.time() print("run time: %s " %(end_time-start_time)) return res return inner @timmer def index(): time.sleep(2) print("welcome to index page") @timmer def home(name): time.sleep(3) print("welcome to %s home page" % name) return("home func") index_res=index() print(index_res) home_res=home("python") print(home_res) 再次执行函数,查看执行结果 welcome to index page run time: 2.0 None welcome to python home page run time: 3.0 home func 可以看来,原始home函数中定义的返回值被打印出来了 结论: 如果被装饰函数没有定义返回值,timmer装饰器装饰后的返回值为None 而如果被装饰函数定义了返回值,则timmer装饰器装饰后则返回被装饰函数的返回值 9.wraps内置方法的作用 查看一个函数的帮助文档有两种方法 func_name.__doc__ help(func_name) 先来看一个例子,定义timmer装饰器和index函数,并且都添加了帮助文档 import time def timmer(func): def inner(*args,**kwargs): 'wrapper inner function' start_time=time.time() res=func(*args,**kwargs) end_time=time.time() print("run time: %s " %(end_time-start_time)) return res return inner def index(): 'index function' time.sleep(2) print("welcome to index page") 在index没有被timmer装饰前,来查看index的帮助文档 print(index.__doc__) 程序运行结果 index function 然后为index添加timmer装饰器,再次查看index函数的帮助文档 import time def timmer(func): def inner(*args,**kwargs): 'wrapper inner function' start_time=time.time() res=func(*args,**kwargs) end_time=time.time() print("run time: %s " %(end_time-start_time)) return res return inner @timmer def index(): 'index function' time.sleep(2) print("welcome to index page") print(index.__doc__) 程序运行结果 wrapper inner function 可以看到,在为index函数添加装饰器后,index函数的帮助文档变成装饰器timmer内部函数的帮助文档了 换句话说,就是原始index函数内部的数据被装饰器timmer修改了 怎么样才能在保留原始被装饰函数的数据的前提下,为函数添加新功能呢??就是python内置的wraps装饰器 导入wraps装饰器,修改上面的代码,为timmer的内部函数添加wraps装饰器,然后再次查看被装饰函数的帮助文档 import time from functools import wraps def timmer(func): @wraps(func) def inner(*args,**kwargs): 'wrapper inner function' start_time=time.time() res=func(*args,**kwargs) end_time=time.time() print("run time: %s " %(end_time-start_time)) return res return inner @timmer def index(): 'index function' time.sleep(2) print("welcome to index page") print(index.__doc__) 运行程序,执行结果如下 index function 可以看到,index函数即使添加了装饰器,其内部的原始数据仍然没有被装饰器修改 从上面的示例可以看出,wraps装饰器的作用就是保留被装饰对象的原始数据信息

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册