内网穿透 内网穿透原理图:
但是这种技术妨碍了 ISP 收互联网企业的带宽费用,因此 ISP 不会喜欢这种技术。
代码:https://github.com/dropsong/py_webServer
GIL(全局解释器锁) Python 语言和 GIL 没有关系。仅仅是由于历史原因 在 Cpython 虚拟机(解释器),难以移除 GIL。
GIL:全局解释器锁。每个线程在执行的过程都需要先获取 GIL ,保证同一时刻只有一个线程可以执行代码。
线程释放 GIL 锁的情况: 在 IO 操作等可能会引起阻塞的 system call 之前,可以暂时释放 GIL,但在执行完毕后,必须重新获取 GIL,Python 3.x 使用计时器(执行时间达到阈值后,当前线程释放GIL)或 Python 2.x tickets 计数达到 100
Python 使用多进程是可以利用多核的 CPU 资源的。
多线程爬取比单线程性能有提升 ,因为遇到 IO 阻塞会自动释放 GIL 锁
私有化
xx
: 公有变量
_x
: 单前置下划线,私有化属性或方法,from somemodule import *
禁止导入,类对象和子类可以访问
__xx
:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到)
__xx__
:双前后下划线,用户名字空间的魔法对象或属性。例如:__init__
,不要自己发明这样的名字
xx_
:单后置下划线,用于避免与 Python 关键词的冲突
模块导入问题 不可变类型的全局变量用 from 方式导入的时候,相当于一份拷贝,在一个文件中修改,对另一个文件中的值没有影响。
多继承的解决方案 总结:
使用 super()
使用 *args
、**kwargs
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 print ("******多继承使用 super().__init__ 发生的状态******" )class Parent (object ): def __init__ (self, name, *args, **kwargs ): print ('parent 的 init 开始被调用' ) self.name = name print ('parent 的 init 结束被调用' ) class Son1 (Parent ): def __init__ (self, name, age, *args, **kwargs ): print ('Son1 的 init 开始被调用' ) self.age = age super ().__init__(name, *args, **kwargs) print ('Son1 的 init 结束被调用' ) class Son2 (Parent ): def __init__ (self, name, gender, *args, **kwargs ): print ('Son2 的 init 开始被调用' ) self.gender = gender super ().__init__(name, *args, **kwargs) print ('Son2 的 init 结束被调用' ) class Grandson (Son1, Son2): def __init__ (self, name, age, gender ): print ('Grandson 的 init 开始被调用' ) super ().__init__(name, age, gender) print ('Grandson 的 init 结束被调用' ) print (Grandson.__mro__) gs = Grandson('grandson' , 12 , '男' ) print ('姓名:' , gs.name)print ('年龄:' , gs.age)print ('性别:' , gs.gender)print ("******多继承使用 super().__init__ 发生的状态******\n\n" )''' ******多继承使用 super().__init__ 发生的状态****** (<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>) Grandson 的 init 开始被调用 Son1 的 init 开始被调用 Son2 的 init 开始被调用 parent 的 init 开始被调用 parent 的 init 结束被调用 Son2 的 init 结束被调用 Son1 的 init 结束被调用 Grandson 的 init 结束被调用 姓名: grandson 年龄: 12 性别: 男 ******多继承使用 super().__init__ 发生的状态****** '''
静态方法和类方法 类属性、实例属性 实例属性属于对象,类属性属于类。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Province (object ): country = '中国' def __init__ (self, name ): self.name = name obj = Province('山东省' ) print (obj.name)Province.country ''' 山东省 '''
实例属性需要通过对象来访,类属性通过类访问。
类属性在内存中只保存一份,实例属性在每个对象中都要保存一份。
实例方法、静态方法、类方法 例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Foo (object ): def __init__ (self, name ): self.name = name def ord_func (self ): """ 定义实例方法,至少有一个 self 参数 """ print ('实例方法' ) @classmethod def class_func (cls ): """ 定义类方法,至少有一个 cls 参数 """ print ('类方法' ) @staticmethod def static_func (): """ 定义静态方法 ,无默认参数""" print ('静态方法' ) f = Foo("中国" ) f.ord_func() Foo.class_func() Foo.static_func() ''' 实例方法 类方法 静态方法 '''
property 属性 一种懒人方法,可以不用写括号。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Foo : def func (self ): print ('I am func' ) @property def prop (self ): print ('I am prop' ) foo_obj = Foo() foo_obj.func() foo_obj.prop ''' I am func I am prop '''
property 属性可以用来实现只能读不能写的属性。 (想必这就是莽蛇语言的独到之处了吧)
但是也可以有别的方法去写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Goods : @property def price (self ): print ('@property' ) @price.setter def price (self, value ): print ('@price.setter' ) @price.deleter def price (self ): print ('@price.deleter' ) obj = Goods() obj.price obj.price = 123 del obj.price ''' @property @price.setter @price.deleter '''
使用类属性的方式创建 property 属性 时,经典类和新式类无区别:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Foo : def get_bar (self ): return 'laowang' BAR = property (get_bar) obj = Foo() reuslt = obj.BAR print (reuslt)''' laowang '''
上面这种写法可以拓展为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class Foo (object ): def get_bar (self ): print ("getter..." ) return 'laowang' def set_bar (self, value ): """必须两个参数""" print ("setter..." ) return 'set value' + value def del_bar (self ): print ("deleter..." ) return 'laowang' BAR = property (get_bar, set_bar, del_bar, "description..." ) obj = Foo() obj.BAR obj.BAR = "alex" desc = Foo.BAR.__doc__ print (desc)del obj.BAR ''' getter... setter... description... deleter... '''
使用 property 升级 getter 和 setter 方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Money (object ): def __init__ (self ): self.__money = 0 def getMoney (self ): return self.__money def setMoney (self, value ): if isinstance (value, int ): self.__money = value else : print ("error:不是整型数字" ) money = property (getMoney, setMoney) a = Money() a.money = 100 print (a.money) ''' 100 '''
魔法属性 call 对象后面加括号,触发执行。
1 2 3 4 5 6 7 8 9 10 11 12 class Foo : def __init__ (self ): pass def __call__ (self, *args, **kwargs ): print ('__call__' ) obj = Foo() obj() ''' __call__ '''
莽蛇语言魅力时刻。
with 与“上下文管理器” 场景:应用程序打开某些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(断开)该资源。
普通版:
1 2 3 4 def m1 (): f = open ("output.txt" , "w" ) f.write("python 之禅" ) f.close()
这样写有一个潜在的问题,如果在调用 write 的过程中,出现了异常进而导致后续代码无法继续执行,close 方法无法被正常调用,因此资源就会一直被该程序占用者释放。那么该如何改进代码呢?
进阶版:
1 2 3 4 5 6 7 8 def m2 (): f = open ("output.txt" , "w" ) try : f.write("python 之禅" ) except IOError: print ("oops error" ) finally : f.close()
缺点是写起来太麻烦。
高级版:
1 2 3 def m3 (): with open ("output.txt" , "r" ) as f: f.write("Python 之禅" )
一种更加简洁、优雅的方式就是用 with 关键字 。类似于 C++ 的 RAII 。
上下文管理器 的实现和魔法属性 __enter__()
和 __exit__()
有关,和 C++ 原理上差不多,但是碍于 Python 混乱的语法,稍微显得麻烦一点,此处略去。
实现上下文管理器的另外方式: Python 还提供了一个 contextmanager 的装饰器,更进一步简化了上下文管理器的实现方式。通过 yield 将函数分割成两部分,yield 之前的语句在 __enter__
方法中执行,yield 之后的语句在 __exit__
方法中执行。紧跟在 yield 后面的值是函数的返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from contextlib import contextmanager@contextmanager def my_open (path, mode ): try : f = open (path, mode) yield f except Exception as result: print (result) finally : f.close() with my_open('out.txt' , 'w' ) as f: f.write("hello , the simplest context manager" )
MySQL 知识补充 绕个弯补充一下,之后会可能会用到。
SQL 注入 例子:
用字符串拼接的方式去进行 sql 是非常危险的。
解决方法,直接传到 execute()
中,不要分两步写:
1 2 params = [find_name] count = cs1.execute('select * from goods where name=%s' , params)
账户管理 在生产环境下操作数据库时,绝对不可以使用 root 账户连接,而是创建特定的账户,授予这个账户特定的操作权限。
MySQL 的账户可以分为以下几种:
服务实例级账号:启动了一个 mysql,即为一个数据库实例;如果某用户如 root,拥有服务实例级分配的权限,那么该账号就可以删除所有的数据库、连同这些库中的表
数据库级别账号:对特定数据库执行增删改查的所有操作
数据表级别账号:对特定表执行增删改查等所有操作
字段级别的权限:对某些表的特定字段进行操作
存储程序级别的账号:对存储程序进行增删改查的操作
注册新用户及之后的一些操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 mysql> select user from mysql.user; + | user | + | mysql.infoschema | | mysql.session | | mysql.sys | | root | + 4 rows in set (0.00 sec)mysql> create user 'yanfei' @'localhost' identified by '123' ; Query OK, 0 rows affected (0.02 sec) mysql> grant select on akashi.* to 'yanfei' @'localhost' ; Query OK, 0 rows affected (0.01 sec) mysql> select user from mysql.user; + | user | + | mysql.infoschema | | mysql.session | | mysql.sys | | root | | yanfei | + 5 rows in set (0.00 sec)
使用 sudo mysql -u yanfei -p
进入之后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 mysql> show databases; + | Database | + | akashi | | information_schema | | performance_schema | + 3 rows in set (0.00 sec)mysql> use akashi; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with - A Database changed mysql> show tables; + | Tables_in_akashi | + | goods | | miaochuan | | user | + 3 rows in set (0.00 sec)mysql> select * from goods limit 5 ; + | id | name | cate_name | brand_name | price | is_show | is_saleoff | + | 1 | r510vc 15.6 英寸笔记本 | 笔记本 | 华硕 | 3399.000 | 0x01 | 0x00 | | 2 | g150th 15.6 英寸游戏本 | 游戏本 | 雷神 | 8499.000 | 0x01 | 0x00 | | 3 | x550cc 15.6 英寸笔记本 | 笔记本 | 华硕 | 2799.000 | 0x01 | 0x00 | | 4 | x240 超极本 | 超级本 | 联想 | 4880.000 | 0x01 | 0x00 | | 5 | u330p 13.3 英寸超极本 | 超级本 | 联想 | 4299.000 | 0x01 | 0x00 | + 5 rows in set (0.00 sec)mysql> insert into goods values (6 , 非常厉害的笔记本, 笔记本 ,联想, 3333.0 ); ERROR 1142 (42000 ): INSERT command denied to user 'yanfei' @'localhost' for table 'goods'
主从配置 主机操作 在主机上配置账户:
1 2 3 4 create user 'backup' @'192.168.19.129' identified by '123' ;# 192.168 .19 .129 是从机的 IP GRANT REPLICATION SLAVE ON * .* TO 'backup' @'192.168.19.129' ;flush privileges;
修改 mysql 的配置:
1 2 3 4 5 6 7 sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf log_bin = /var/log/mysql/mysql-bin.log server-id = 1 expire_logs_days = 1 max_binlog_size = 100M binlog_do_db = test
重启 mysql 服务(重启之前确保 test 数据库是存在的):
1 sudo service mysql restart
查看二进制日志是否生成:
1 show variables like 'log_%' ;
从机操作 修改 mysql 的配置:
1 sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
添加:
1 2 3 server-id = 2 expire_logs_days = 10 max_binlog_size = 100M
重启服务:
1 sudo service mysql restart
配置主从库之间的通信:
从机上设置从库(129 机)与主库(130 机)进行通信。先把从机的 test 数据库清空,模板:
1 2 3 4 5 6 CHANGE MASTER TO MASTER_HOST= 'master_host_name' , MASTER_USER= 'replication_user_name' ,#backup MASTER_PASSWORD= 'replication_password' , #123 MASTER_LOG_FILE= 'recorded_log_file_name' , # mysql- bin.000056 MASTER_LOG_POS= recorded_log_position; # 155
作为一个可以参考的实例:
1 2 3 4 5 6 CHANGE MASTER TO MASTER_HOST='192.168.19.130', MASTER_USER='backup', MASTER_PASSWORD='123', MASTER_LOG_FILE='mysql-bin.000149', MASTER_LOG_POS=155;
START SLAVE;
开启主从同步线程(关闭用 stop slave
)。
show slave status\G
,一个可能的输出是:
针对多个数据库,设置为:
迷你 web 框架 本节实现一个 mini frame web 框架。功能类似一个迷你的 Django .
定义 WSGI 接口 WSGI 接口定义非常简单,它只要求 Web 开发者实现一个函数,就可以响应 HTTP 请求。我们来看一个最简单的 Web 版本的“Hello World!”:
1 2 3 def application (environ, start_response ): start_response('200 OK' , [('Content-Type' , 'text/html' )]) return 'Hello World!'
上面的 application()
函数就是符合 WSGI 标准的一个 HTTP 处理函数,它接收两个参数:
environ
:一个包含所有 HTTP 请求信息的 dict 对象。
start_response
:一个发送 HTTP 响应的函数。
application()
函数必须由 WSGI 服务器来调用。我们现在做的 web 服务器项目,就是一个既能解析静态网页,又能解析动态网页的服务器。
web 框架传递的字典的一个实际例子(不需要掌握):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 { 'HTTP_ACCEPT_LANGUAGE': 'zh-cn', 'wsgi.file_wrapper': <built-infunctionuwsgi_sendfile>, 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'uwsgi.version': b'2.0.15', 'REMOTE_ADDR': '172.16.7.1', 'wsgi.errors': <_io.TextIOWrappername=2mode='w'encoding='UTF-8'>, 'wsgi.version': (1,0), 'REMOTE_PORT': '40432', 'REQUEST_URI': '/', 'SERVER_PORT': '8000', 'wsgi.multithread': False, 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'HTTP_HOST': '172.16.7.152: 8000', 'wsgi.run_once': False, 'wsgi.input': <uwsgi._Inputobjectat0x7f7faecdc9c0>, 'SERVER_PROTOCOL': 'HTTP/1.1', 'REQUEST_METHOD': 'GET', 'HTTP_ACCEPT_ENCODING': 'gzip,deflate', 'HTTP_CONNECTION': 'keep-alive', 'uwsgi.node': b'ubuntu', 'HTTP_DNT': '1', 'UWSGI_ROUTER': 'http', 'SCRIPT_NAME': '', 'wsgi.multiprocess': False, 'QUERY_STRING': '', 'PATH_INFO': '/index.html', 'wsgi.url_scheme': 'http', 'HTTP_USER_AGENT': 'Mozilla/5.0(Macintosh;IntelMacOSX10_12_5)AppleWebKit/603.2.4(KHTML,likeGecko)Version/10.1.1Safari/603.2.4', 'SERVER_NAME': 'ubuntu' }
实战 如果请求是不是 html,是动态请求,后缀是 .py
,那如何处理呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 import socketimport reimport multiprocessingimport timeclass WSGIServer (object ): def __init__ (self ): self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) self.tcp_server_socket.bind(("" , 7890 )) self.tcp_server_socket.listen(128 ) def service_client (self, new_socket ): """为这个浏览器返回数据""" request = new_socket.recv(1024 ).decode("utf-8" ) request_lines = request.splitlines() print ("" ) print (">" * 20 ) print (request_lines) file_name = "" ret = re.match (r"[^/]+(/[^ ]*)" , request_lines[0 ]) if ret: file_name = ret.group(1 ) if file_name == "/" : file_name = "/index.html" if not file_name.endswith(".py" ): try : f = open ("./html" + file_name, "rb" ) except : response = "HTTP/1.1 404 NOT FOUND\r\n" response += "\r\n" response += "------file not found-----" new_socket.send(response.encode("utf-8" )) else : html_content = f.read() f.close() response = "HTTP/1.1 200 OK\r\n" response += "\r\n" new_socket.send(response.encode("utf-8" )) new_socket.send(html_content) else : header = "HTTP/1.1 200 OK\r\n" header += "\r\n" body = "hahahah %s " % time.ctime() response = header + body new_socket.send(response.encode("utf-8" )) new_socket.close() def run_forever (self ): """用来完成整体的控制""" while True : new_socket, client_addr = self.tcp_server_socket.accept() p = multiprocessing.Process(target=self.service_client, args=(new_socket,)) p.start() new_socket.close() self.tcp_server_socket.close() def main (): """控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行""" wsgi_server = WSGIServer() wsgi_server.run_forever() if __name__ == "__main__" : main()
效果:
上面的代码已经接下来的几个版本,参考代码:https://github.com/dropsong/py_webServer/tree/master/web_mini_frame
闭包 可以在函数里面定义函数,例子:
1 2 3 4 5 6 7 8 9 10 11 12 def line6 (k, b ): def create_line (x ): print (k*x+b) return create_line l1 = line6(2 , 3 ) l1(3 ) ''' 9 '''
函数、匿名函数、闭包、对象当做实参时有什么区别?
匿名函数能够完成基本的简单功能 ,传递是这个函数的引用只有功能(lambda)
普通函数能够完成较为复杂的功能 ,传递是这个函数的引用只有功能
闭包能够将较为复杂的功能,传递是这个闭包中的函数以及数据 ,因此传递是功能+数据(相对于对象,占用空间少 )
对象能够完成最为复杂的功能,传递是很多数据+很多功能 ,因此传递是功能+数据
闭包引用了外部函数的局部变量,若外部函数的局部变量没有及时释放,会消耗内存。
闭包中对变量的修改:
1 2 3 4 5 6 7 8 9 10 11 def test1 (): x = 200 def test2 (): print ("----1----x=%d" % x) x = 100 print ("----2----x=%d" % x) return test2 t1 = test1() t1()
因为我们打印 x 时,没有提前定义,需要在打印之前增加 nonlocal x
,使用外部函数的变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def test1 (): x = 200 def test2 (): nonlocal x print ("----1----x=%d" % x) x = 100 print ("----2----x=%d" % x) return test2 t1 = test1() t1() ''' ----1----x=200 ----2----x=100 '''
装饰器 需求场景 初创公司有 N 个业务部门,基础平台部门负责提供底层的功能,如:数据库操作、redis 调用、监控 API 等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def f1 (): print ('f1' ) def f2 (): print ('f2' ) def f3 (): print ('f3' ) def f4 (): print ('f4' ) f1() f2() f3() f4() f1() f2() f3() f4()
目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制 ,即:执行功能前,先进行验证。
解决方案 遵循开放封闭原则 ,对拓展开放,对修改闭合。使用装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 def w1 (func ): def inner (): func() return inner @w1 def f1 (): print ('f1' ) @w1 def f2 (): print ('f2' ) @w1 def f3 (): print ('f3' ) @w1 def f4 (): print ('f4' )
语法 基本用法 实际例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def set_func (func ): def call_func (): print ('----permission check1----' ) print ('----permission check2----' ) func() return call_func @set_func def test1 (): print ('----test1-----' ) test1() ''' ----permission check1---- ----permission check2---- ----test1----- '''
关于一些原理上的细节:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def set_func (func ): print ('--- 开始进行装饰 ---' ) def call_func (): print ('----permission check1----' ) print ('----permission check2----' ) func() return call_func @set_func def test1 (): print ('----test1-----' ) ''' --- 开始进行装饰 --- ''' ''' 即使没有调用 test1() , 这段代码也相当于执行了: test1 = set_func(test1) 因此会有输出 '''
传递参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def set_func (func ): print ('--- 开始进行装饰 ---' ) def call_func (a ): print ('----permission check1----' ) print ('----permission check2----' ) func(a) return call_func @set_func def test1 (num ): print ('----test1----- %d' % num) test1(3 ) ''' --- 开始进行装饰 --- ----permission check1---- ----permission check2---- ----test1----- 3 '''
多个装饰器装饰同一个函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 def add_first (func ): print ("---开始进行装饰权限 1 的功能---" ) def call_func (*args, **kwargs ): print ("---这是权限验证 1----" ) return func(*args, **kwargs) return call_func def add_second (func ): print ("---开始进行装饰权限 2 的功能---" ) def call_func (*args, **kwargs ): print ("---这是权限验证 2----" ) return func(*args, **kwargs) return call_func @add_first @add_second def test1 (): print ('----test1-----' ) test1() ''' ---开始进行装饰权限 2 的功能--- ---开始进行装饰权限 1 的功能--- ---这是权限验证 1---- ---这是权限验证 2---- ----test1----- ''' ''' 一种解释: s = add_second(test1) f = add_first(s) '''
一个更具体的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 def makeBold (fn ): def wrapped (): return "<b>" + fn() + "</b>" return wrapped def makeItalic (fn ): def wrapped (): return "<i>" + fn() + "</i>" return wrapped @makeBold def test1 (): return "hello world-1" @makeItalic def test2 (): return "hello world-2" @makeBold @makeItalic def test3 (): return "hello world-3" print (test1())print (test2())print (test3())''' <b>hello world-1</b> <i>hello world-2</i> <b><i>hello world-3</i></b> '''
装饰器(decorator)功能有很多,具体来说:
引入日志,在执行某个函数前或者函数后记录日志
函数执行时间统计
执行函数前预备处理
执行函数后清理功能
权限校验等场景
缓存
等
被装饰的函数带返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from time import ctime, sleepdef timefun (func ): def wrapped_func (): print ("%s called at %s" % (func.__name__, ctime())) func() return wrapped_func @timefun def foo (): print ("I am foo" ) @timefun def getInfo (): return '----hahah---' foo() sleep(1 ) foo() ret=getInfo() print (ret)''' foo called at Sun Oct 27 22:30:36 2024 I am foo foo called at Sun Oct 27 22:30:37 2024 I am foo getInfo called at Sun Oct 27 22:30:37 2024 None '''
注意,上面的代码没有拿到返回值,应该修改为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from time import ctime, sleepdef timefun (func ): def wrapped_func (): print ("%s called at %s" % (func.__name__, ctime())) return func() return wrapped_func @timefun def foo (): print ("I am foo" ) @timefun def getInfo (): return '----hahah---' foo() sleep(1 ) foo() ret=getInfo() print (ret)''' foo called at Sun Oct 27 22:35:25 2024 I am foo foo called at Sun Oct 27 22:35:26 2024 I am foo getInfo called at Sun Oct 27 22:35:26 2024 ----hahah--- '''
一般情况下为了让装饰器更通用,都可以加上 return .
装饰器带参数 装饰器带参数,在原有装饰器的基础上,设置外部变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 from time import sleepdef timefun_arg (pre="hello" ): def timefun (func ): def wrapped_func (): print ("%s called at %s" % (func.__name__, pre)) return func() return wrapped_func return timefun @timefun_arg("wangdao" ) def foo (): print ("I am foo" ) @timefun_arg("python" ) def too (): print ("I am too" ) foo() sleep(2 ) foo() too() sleep(2 ) too() ''' foo called at wangdao I am foo foo called at wangdao I am foo too called at python I am too too called at python I am too '''
类装饰器 前期提要:
1 2 3 4 5 6 7 8 9 10 class Test : def __call__ (self, *args, **kwds ): print ('I am call' ) t = Test() t() ''' I am call '''
装饰器函数其实是这样一个接口约束,它必须接受一个 callable 对象作为参数,然后返回一个 callable 对象。在 Python 中一般 callable 对象都是函数,但也有例外。只要某个对象重写了 __call__()
方法,那么这个对象就是 callable 的。
一个具体的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Test : def __init__ (self, func ): self.__func = func def __call__ (self, *args, **kwds ): print ('I am call' ) self.__func(*args, **kwds) @Test def foo (name ): print ('I am foo {}' .format (name)) foo('akashi' ) ''' I am call I am foo akashi '''
装饰后的注释问题 问题演示和解决方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 def my_decorator (func ): def wper (*args, **kwargs ): '''decorator''' print ('Calling decorated function...' ) return func(*args, **kwargs) return wper @my_decorator def example (): """Docstring""" print ('Called example function' ) print (example.__name__, example.__doc__) ''' wper decorator ''' from functools import wrapsdef my_new_decorator (func ): @wraps(func ) def wper (*args, **kwargs ): '''decorator''' print ('Calling decorated function...' ) return func(*args, **kwargs) return wper @my_new_decorator def example1 (): """I am example1""" print ('Called example1 function' ) print (example1.__name__, example1.__doc__) ''' example1 I am example1 '''
迷你 web 框架(续) 本节概要: 之前,去替换 html 模板中的变量时,替换的内容是随意写的,现在要替换的内容是从 mysql 数据库中查出来的,并把装饰器应用到框架里。
version 6 在之前的 version 5 基础上,作如下改变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 import timeimport reURL_FUNC_DICT = dict () def route (url ): def set_func (func ): URL_FUNC_DICT[url] = func return set_func @route("/index.py" ) def index (): with open ("./templates/index.html" , encoding="utf-8" ) as f: content = f.read() my_stock_info = "哈哈哈,我是本月最佳员工。。。。" content = re.sub(r"\{%content%\}" , my_stock_info, content) return content @route("/center.py" ) def center (): with open ("./templates/center.html" , encoding="utf-8" ) as f: content = f.read() my_stock_info = "这里是从mysql查询出来的数据。。。" content = re.sub(r"\{%content%\}" , my_stock_info, content) return content def application (environ, start_response ): start_response('200 OK' , [('Content-Type' , 'text/html;charset=utf-8' )]) file_name = environ['PATH_INFO' ] return URL_FUNC_DICT[file_name]()
修改要点:
伪静态、静态、动态 目前开发的网站其实都是动态网站,只是 URL 上有些区别,一般 URL 分为静态 URL、动态 URL、伪静态 URL 。
静态 URL 类似于 /news/2012-5-18/110.html
,一般称为真静态 URL,每个网页有真实的物理路径 ,也就是真实存在服务器里的。
网站打开速度快
网址结构比较友好,利于记忆
如果是中大型网站,则产生的页面特别多,不好管理
有利于 SEO
动态 URL 类似于 /NewsMore.asp?id=5
或者 /DaiKuan.php?id=17
,带有?号的 URL,一般称为动态网址,每个 URL 只是一个逻辑地址 ,并不是真实物理存在服务器硬盘里的。
适合中大型网站,修改页面很方便
因为要进行运算,所以打开速度稍慢,不过这个可以忽略不计,服务器缓存技术可以解决该问题
URL 结构稍稍复杂,不利于记忆
搜索引擎已经能够很好地理解动态 URL,所以对 SEO 没有什么减分的影响(特别复杂的 URL 结构除外)
伪静态 URL 类似于 /course/74.html
,和真静态 URL 类似。通过伪静态规则把动态 URL 伪装成静态网址,也是逻辑地址 ,不存在物理地址。
URL 比较友好,利于记忆
适合大中型网站,是个折中方案
设置麻烦,服务器要支持重写规则
增加服务器负担,速度变慢(虽然可以忽略)
可能造成动态 URL 和静态 URL 都被搜索引擎收录(可以用 robots 禁止掉动态地址)
关于伪静态规则,一个具体的例子:
version 7 实现伪静态,作如下修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import reURL_FUNC_DICT = dict () def route (url ): def set_func (func ): URL_FUNC_DICT[url] = func return set_func @route("/index.html" ) def index (): with open ("./templates/index.html" , encoding="utf-8" ) as f: content = f.read() my_stock_info = "哈哈哈哈 这是你的本月名称....." content = re.sub(r"\{%content%\}" , my_stock_info, content) return content @route("/center.html" ) def center (): with open ("./templates/center.html" , encoding="utf-8" ) as f: content = f.read() my_stock_info = "这里是从mysql查询出来的数据。。。" content = re.sub(r"\{%content%\}" , my_stock_info, content) return content def application (env, start_response ): start_response('200 OK' , [('Content-Type' , 'text/html;charset=utf-8' )]) file_name = env['PATH_INFO' ] try : func = URL_FUNC_DICT[file_name] return func() except Exception as ret: return "产生了异常:%s" % str (ret)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 import socketimport reimport multiprocessingimport timeimport sysclass WSGIServer (object ): def __init__ (self, port, app, static_path ): self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) self.tcp_server_socket.bind(("" , port)) self.tcp_server_socket.listen(128 ) self.application = app self.static_path = static_path def service_client (self, new_socket ): """为这个客户端返回数据""" request = new_socket.recv(1024 ).decode("utf-8" ) if request: request_lines = request.splitlines() print ("" ) print (">" *20 ) print (request_lines) file_name = "" ret = re.match (r"[^/]+(/[^ ]*)" , request_lines[0 ]) if ret: file_name = ret.group(1 ) if file_name == "/" : file_name = "/index.html" if not file_name.endswith(".html" ): try : f = open (self.static_path + file_name, "rb" ) except : response = "HTTP/1.1 404 NOT FOUND\r\n" response += "\r\n" response += "------file not found-----" new_socket.send(response.encode("utf-8" )) else : html_content = f.read() f.close() response = "HTTP/1.1 200 OK\r\n" response += "\r\n" new_socket.send(response.encode("utf-8" )) new_socket.send(html_content) else : env = dict () env['PATH_INFO' ] = file_name body = self.application(env, self.set_response_header) header = "HTTP/1.1 %s\r\n" % self.status for temp in self.headers: header += "%s:%s\r\n" % (temp[0 ], temp[1 ]) header += "\r\n" response = header+body new_socket.send(response.encode("utf-8" )) new_socket.close() def set_response_header (self, status, headers ): self.status = status self.headers = [("server" , "mini_web v8.8" )] self.headers += headers def run_forever (self ): """用来完成整体的控制""" while True : new_socket, client_addr = self.tcp_server_socket.accept() p = multiprocessing.Process(target=self.service_client, args=(new_socket,)) p.start() new_socket.close() self.tcp_server_socket.close() def main (): """控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行""" if len (sys.argv) == 3 : try : port = int (sys.argv[1 ]) frame_app_name = sys.argv[2 ] except Exception as ret: print ("端口输入错误。。。。。" ) return else : print ("请按照以下方式运行:" ) print ("python3 xxxx.py 7890 mini_frame:application" ) return ret = re.match (r"([^:]+):(.*)" , frame_app_name) if ret: frame_name = ret.group(1 ) app_name = ret.group(2 ) else : print ("请按照以下方式运行:" ) print ("python3 xxxx.py 7890 mini_frame:application" ) return with open ("./web_server.conf" ) as f: conf_info = eval (f.read()) sys.path.append(conf_info['dynamic_path' ]) frame = __import__ (frame_name) app = getattr (frame, app_name) wsgi_server = WSGIServer(port, app, conf_info['static_path' ]) wsgi_server.run_forever() if __name__ == "__main__" : main()
本次修改总结: 将字典里存放的 key 作了修改。另外模板里的 html 文件也作了相应修改(没有展示),将链接指向了 /index.html
和 /center.html
。
version 8 从数据库查询数据,替换模板中的变量。
准备数据的过程:
1 2 3 create database stock_db charset= utf8;use stock_db source / home/ zhiyue/ Downloads/ stock_db.sql
(这个 sql 文件不重要,我们这里只是演示)
表中的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 mysql> select * from focus; + | id | note_info | info_id | + | 2 | 你确定要买这个? | 36 | | 3 | 利好 | 37 | | 9 | | 88 | | 10 | | 89 | | 13 | | 1 | + 5 rows in set (0.00 sec)mysql> select * from info limit 5 ; + | id | code | short | chg | turnover | price | highs | time | + | 1 | 000007 | 全新好 | 10.01 % | 4.40 % | 16.05 | 14.60 | 2019 -07 -18 | | 2 | 000036 | 华联控股 | 10.04 % | 10.80 % | 11.29 | 10.26 | 2019 -07 -20 | | 3 | 000039 | 中集集团 | 1.35 % | 1.78 % | 18.07 | 18.06 | 2019 -06 -28 | | 4 | 000050 | 深天马A | 4.38 % | 4.65 % | 22.86 | 22.02 | 2017 -07 -19 | | 5 | 000056 | 皇庭国际 | 0.39 % | 0.65 % | 12.96 | 12.91 | 2017 -07 -20 | + 5 rows in set (0.00 sec)
现在,我们希望实现如下的效果:
代码参见:https://github.com/dropsong/py_webServer/tree/master/web_mini_frame/version8
version 9 让路由支持正则,实现增删改功能。
https://github.com/dropsong/py_webServer/tree/master/web_mini_frame/version9
日志 例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import logginglogging.basicConfig(level=logging.WARNING, format ='%(asctime)s - %(filename)s[line:%(lineno)d] -%(levelname)s: %(message)s' ) logging.debug('这是 loggging debug message' ) logging.info('这是 loggging info message' ) logging.warning('这是 loggging a warning message' ) logging.error('这是 an loggging error message' ) logging.critical('这是 loggging critical message' ) ''' 2017-11-06 23:07:35,725 - log1.py[line:9] - WARNING: 这是 loggging a warning message 2017-11-06 23:07:35,725 - log1.py[line:10] - ERROR: 这是 an loggging err or message 2017-11-06 23:07:35,725 - log1.py[line:11] - CRITICAL: 这是 loggging critical message '''
写到文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 import logginglogging.basicConfig(level=logging.WARNING, filename='log.txt' , filemode='w' , format ='%(asctime)s - %(filename)s[line:%(lineno)d] -%(levelname)s: %(message)s' ) logging.debug('这是 loggging debug message' ) logging.info('这是 loggging info message' ) logging.warning('这是 loggging a warning message' ) logging.error('这是 an loggging error message' ) logging.critical('这是 loggging critical message' )
既把日志输出到控制台,又写入文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import logginglogger = logging.getLogger() logger.setLevel(logging.INFO) logfile = './log.txt' fh = logging.FileHandler(logfile, mode='a' ) fh.setLevel(logging.DEBUG) ch = logging.StreamHandler() ch.setLevel(logging.WARNING) formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s" ) fh.setFormatter(formatter) ch.setFormatter(formatter) logger.addHandler(fh) logger.addHandler(ch) logger.debug('这是 logger debug message' ) logger.info('这是 logger info message' ) logger.warning('这是 logger warning message' ) logger.error('这是 logger error message' ) logger.critical('这是 logger critical message' )
元类 C, C++ 中无此设计。
我们想做一个类,这个类是用代码生成的。
类也是对象 类的本质仍然是一个对象,于是可以做如下操作:
将它赋值给一个变量
拷贝它
为它增加属性
将它作为函数参数进行传递
下面展示自由的 Python 语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class ObjectCreator (object ): pass my_object = ObjectCreator() print (my_object)print (ObjectCreator) def echo (obj ): print (obj) echo(ObjectCreator) print (hasattr (ObjectCreator, 'new_attribute' ))ObjectCreator.new_attribute = 'foo' print (hasattr (ObjectCreator, 'new_attribute' ))val = ObjectCreator print (val)print (type (1 ))print (type (my_object))print (type (ObjectCreator))''' <__main__.ObjectCreator object at 0x7f15b9e8b050> <class '__main__.ObjectCreator'> <class '__main__.ObjectCreator'> False True <class '__main__.ObjectCreator'> <class 'int'> <class '__main__.ObjectCreator'> <class 'type'> '''
创建类出来的那个类,叫元类 。
使用 type 创建类 type 还有一种完全不同的功能,动态地创建类。
type 可以接受一个类的描述作为参数,然后返回一个类。(要知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在 Python 中是为了保持向后兼容性)
用法如下:
1 type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 Test2 = type ('Test2' , (), {}) print (Test2)Foo = type ('Foo' , (), {'bar' :True }) print (Foo.bar)FooChild = type ('FooChild' , (Foo,), {}) print (FooChild.bar)def echo_bar (self ): print (self.bar) FooChild = type ('FooChild' , (Foo,), {'echo_bar' : echo_bar}) my_foo = FooChild() my_foo.echo_bar() ''' <class '__main__.Test2'> True True True '''
一个更完整的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class A (object ): num = 100 def print_b (self ): print (self.num) @staticmethod def print_static (): print ("----haha-----" ) @classmethod def print_class (cls ): print (cls.num) B = type ("B" , (A,), {"print_b" : print_b, "print_static" : print_static,"print_class" : print_class}) b = B() b.print_b() b.print_static() b.print_class() ''' 100 ----haha----- 100 '''
当我们写下如下的代码时:
Python 做了如下的操作:
Foo 中有 __metaclass__
这个属性吗?如果是,Python 会通过 __metaclass__
创建一个名字为 Foo 的类(对象)
如果 Python 没有找到 __metaclass__
,它会继续在 Bar(父类)中寻找 __metaclass__
属性,并尝试做和前面同样的操作。
如果 Python 在任何父类中都找不到 __metaclass__
,它就会在模块层次中去寻找 __metaclass__
,并尝试做同样的操作。
如果还是找不到 __metaclass__
,Python 就会用内置的 type 来创建这个类对象。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 def upper_attr (class_name, class_parents, class_attr ): new_attr = {} for name,value in class_attr.items(): if not name.startswith("__" ): new_attr[name.upper()] = value return type (class_name, class_parents, new_attr) class Foo (object , metaclass=upper_attr): bar = 'bip' print (hasattr (Foo, 'bar' ))print (hasattr (Foo, 'BAR' ))f = Foo() print (f.BAR)''' False True bip '''
上面的代码中 metaclass 是用了一个函数,接下来使用一个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 class UpperAttrMetaClass (type ): def __new__ (cls, class_name, class_parents, class_attr ): new_attr = {} for name, value in class_attr.items(): if not name.startswith("__" ): new_attr[name.upper()] = value return type (class_name, class_parents, new_attr) class Foo (object , metaclass=UpperAttrMetaClass): bar = 'bip' print (hasattr (Foo, 'bar' ))print (hasattr (Foo, 'BAR' ))f = Foo() print (f.BAR)''' False True bip '''
“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” (Python 界的领袖 Tim Peters)
元类实现 ORM ORM 是 Django 的核心思想,“Object Relational Mapping”,即对象-关系映射,简称 ORM。(JAVA 也是 ORM)
一句话理解:创建一个实例对象,用创建它的类名当做数据表名,用创建它的类属性对应数据表的字段,当对这个实例对象操作时,能够对应 MySQL 语句。
需求场景 我们在之前的 Django 开发中已经看到,我们写的类属性“好像”变成了对象属性(这使我们写代码更方便了),但这是怎么回事呢?
所谓的 ORM 就是让开发者在操作数据库的时候,能够像操作对象时通过 xxxx.属性=yyyy
一样简单,这是开发 ORM 的初衷。
本节完成一个和 insert 类似的 ORM ,简单地展示一下原理。
实例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 class ModelMetaclass (type ): def __new__ (cls, name, bases, attrs ): mappings = dict () for k, v in attrs.items(): if isinstance (v, tuple ): print ('Found mapping: %s ==> %s' % (k, v)) mappings[k] = v for k in mappings.keys(): attrs.pop(k) attrs['__mappings__' ] = mappings attrs['__table__' ] = name return type .__new__(cls, name, bases, attrs) class User (metaclass=ModelMetaclass): uid = ('uid' , "int unsigned" ) name = ('username' , "varchar(30)" ) email = ('email' , "varchar(30)" ) password = ('password' , "varchar(30)" ) def __init__ (self, **kwargs ): for name, value in kwargs.items(): setattr (self, name, value) def save (self ): fields = [] args = [] for k, v in self.__mappings__.items(): fields.append(v[0 ]) args.append(getattr (self, k, None )) sql = 'insert into %s (%s) values (%s)' % (self.__table__, ',' .join(fields), ',' .join([str (i) for i in args])) print ('SQL: %s' % sql) u = User(uid=12345 , name='Michael' , email='test@orm.org' , password='my-pwd' ) print (u.__dict__) u.save() ''' Found mapping: uid ==> ('uid', 'int unsigned') Found mapping: name ==> ('username', 'varchar(30)') Found mapping: email ==> ('email', 'varchar(30)') Found mapping: password ==> ('password', 'varchar(30)') {'uid': 12345, 'name': 'Michael', 'email': 'test@orm.org', 'password': 'my-pwd'} SQL: insert into User (uid,username,email,password) values (12345,Michael,test@orm.org,my-pwd) '''
一点微小的改进(增加了类型校验):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 class ModelMetaclass (type ): def __new__ (cls, name, bases, attrs ): mappings = dict () for k, v in attrs.items(): if isinstance (v, tuple ): print ('Found mapping: %s ==> %s' % (k, v)) mappings[k] = v for k in mappings.keys(): attrs.pop(k) attrs['__mappings__' ] = mappings attrs['__table__' ] = name return type .__new__(cls, name, bases, attrs) class User (metaclass=ModelMetaclass): uid = ('uid' , "int unsigned" ) name = ('username' , "varchar(30)" ) email = ('email' , "varchar(30)" ) password = ('password' , "varchar(30)" ) def __init__ (self, **kwargs ): for name, value in kwargs.items(): setattr (self, name, value) def save (self ): fields = [] args = [] for k, v in self.__mappings__.items(): fields.append(v[0 ]) args.append(getattr (self, k, None )) args_temp = list () for temp in args: if isinstance (temp, int ): args_temp.append(str (temp)) elif isinstance (temp, str ): args_temp.append("""'%s'""" % temp) sql = 'insert into %s (%s) values (%s)' % (self.__table__, ',' .join(fields), ',' .join(args_temp)) print ('SQL: %s' % sql) u = User(uid=12345 , name='Michael' , email='test@orm.org' , password='my-pwd' ) print (u.__dict__)u.save()
模拟 ORM 中的继承:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 class ModelMetaclass (type ): def __new__ (cls, name, bases, attrs ): mappings = dict () for k, v in attrs.items(): if isinstance (v, tuple ): print ('Found mapping: %s ==> %s' % (k, v)) mappings[k] = v for k in mappings.keys(): attrs.pop(k) attrs['__mappings__' ] = mappings attrs['__table__' ] = name return type .__new__(cls, name, bases, attrs) class Model (object , metaclass=ModelMetaclass): def __init__ (self, **kwargs ): for name, value in kwargs.items(): setattr (self, name, value) def save (self ): fields = [] args = [] for k, v in self.__mappings__.items(): fields.append(v[0 ]) args.append(getattr (self, k, None )) args_temp = list () for temp in args: if isinstance (temp, int ): args_temp.append(str (temp)) elif isinstance (temp, str ): args_temp.append("""'%s'""" % temp) sql = 'insert into %s (%s) values (%s)' % (self.__table__, ',' .join(fields), ',' .join(args_temp)) print ('SQL: %s' % sql) class User (Model ): uid = ('uid' , "int unsigned" ) name = ('username' , "varchar(30)" ) email = ('email' , "varchar(30)" ) password = ('password' , "varchar(30)" ) u = User(uid=12345 , name='Michael' , email='test@orm.org' , password='my-pwd' ) print (u.__dict__)u.save() ''' Found mapping: uid ==> ('uid', 'int unsigned') Found mapping: name ==> ('username', 'varchar(30)') Found mapping: email ==> ('email', 'varchar(30)') Found mapping: password ==> ('password', 'varchar(30)') {'uid': 12345, 'name': 'Michael', 'email': 'test@orm.org', 'password': 'my-pwd'} SQL: insert into User (uid,username,email,password) values (12345,'Michael','test@orm.org','my-pwd') '''
接口类与抽象类 没什么好说的,和 C++ 笔记里的东西差不多,这里只放几个例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class payment : def pay (self,money ): e=Exception('缺少编写 pay 方法' ) raise e class Alipay (payment ): def paying (self, money ): print ('支付宝支付了' ) def pay (payment, money ): payment.pay(money) p = Alipay() pay(p, 200 ) ''' Traceback (most recent call last): File "/home/zhiyue/Documents/1030work/test.py", line 16, in <module> pay(p, 200) File "/home/zhiyue/Documents/1030work/test.py", line 13, in pay payment.pay(money) File "/home/zhiyue/Documents/1030work/test.py", line 4, in pay raise e #手动抛异常 ^^^^^^^ Exception: 缺少编写 pay 方法 '''
然而这样的代码还是可以改进,我们希望在编写代码的时候就能发现问题,而不是调用的时候才报错,为此引出抽象类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from abc import abstractmethod, ABCMetaclass Payment (metaclass= ABCMeta): @abstractmethod def pay (self,money ): pass class Alipay (Payment ): def paying (self, money ): print ('支付宝支付了' ) p = Alipay() ''' TypeError: Can't instantiate abstract class Alipay with abstract method pay '''
原因和 C++ 中的一致。