内网穿透
内网穿透原理图:

但是这种技术妨碍了 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
例子:
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 开始被调用')
# 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
# 而 super 只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
# super(Grandson, self).__init__(name, age, gender)
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__ 发生的状态******
'''
静态方法和类方法
类属性、实例属性
实例属性属于对象,类属性属于类。
例子:
class Province(object):
# 类属性
country = '中国'
def __init__(self, name):
# 实例属性
self.name = name
# 创建一个实例对象
obj = Province('山东省')
# 直接访问实例属性
print(obj.name)
# 直接访问类属性
Province.country
'''
山东省
'''
实例属性需要通过对象来访,类属性通过类访问。
类属性在内存中只保存一份,实例属性在每个对象中都要保存一份。
实例方法、静态方法、类方法
例子:
class Foo(object):
def __init__(self, name):
self.name = name
def ord_func(self):
""" 定义实例方法,至少有一个 self 参数 """
# print(self.name)
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 属性
一种懒人方法,可以不用写括号。
例子:
class Foo:
def func(self):
print('I am func')
# 定义 property 属性
@property
def prop(self):
print('I am prop')
foo_obj = Foo()
foo_obj.func() # 调用实例方法
foo_obj.prop # 调用 property 属性
'''
I am func
I am prop
'''
property 属性可以用来实现只能读不能写的属性。 (想必这就是莽蛇语言的独到之处了吧)
但是也可以有别的方法去写:
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 # 自动执行 @price.setter 修饰的 price 方法,并将 123 赋值给方法的参数
del obj.price # 自动执行 @price.deleter 修饰的 price 方法
'''
@property
@price.setter
@price.deleter
'''
使用类属性的方式创建 property 属性时,经典类和新式类无区别:
class Foo:
def get_bar(self):
return 'laowang'
BAR = property(get_bar)
obj = Foo()
reuslt = obj.BAR # 自动调用 get_bar 方法,并获取方法的返回值
print(reuslt)
'''
laowang
'''
上面这种写法可以拓展为:
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 # 自动调用第一个参数中定义的方法:get_bar
obj.BAR = "alex" # 自动调用第二个参数中定义的方法:set_bar 方法,并将“alex”当作参数传入
desc = Foo.BAR.__doc__ # 自动获取第四个参数中设置的值:description...
print(desc)
del obj.BAR # 自动调用第三个参数中定义的方法:del_bar 方法
'''
getter...
setter...
description...
deleter...
'''
使用 property 升级 getter 和 setter 方法:
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 设置值时调用 setMoney,当获取值时调用 getMoney
money = property(getMoney, setMoney)
a = Money()
a.money = 100 # 调用 setMoney 方法
print(a.money) # 调用 getMoney 方法
'''
100
'''
魔法属性 call
对象后面加括号,触发执行。
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__''
'''
__call__
'''
莽蛇语言魅力时刻。
with 与“上下文管理器”
场景:应用程序打开某些资源并执行完业务逻辑之后,必须做的一件事就是要关闭(断开)该资源。
普通版:
def m1():
f = open("output.txt", "w")
f.write("python 之禅")
f.close()
这样写有一个潜在的问题,如果在调用 write 的过程中,出现了异常进而导致后续代码无法继续执行,close 方法无法被正常调用,因此资源就会一直被该程序占用者释放。那么该如何改进代码呢?
进阶版:
def m2():
f = open("output.txt", "w")
try:
f.write("python 之禅")
except IOError:
print("oops error")
finally:
f.close()
缺点是写起来太麻烦。
高级版:
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 后面的值是函数的返回值。
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() 中,不要分两步写:
params = [find_name]
count = cs1.execute('select * from goods where name=%s', params)
账户管理
在生产环境下操作数据库时,绝对不可以使用 root 账户连接,而是创建特定的账户,授予这个账户特定的操作权限。
MySQL 的账户可以分为以下几种:
- 服务实例级账号:启动了一个 mysql,即为一个数据库实例;如果某用户如 root,拥有服务实例级分配的权限,那么该账号就可以删除所有的数据库、连同这些库中的表
- 数据库级别账号:对特定数据库执行增删改查的所有操作
- 数据表级别账号:对特定表执行增删改查等所有操作
- 字段级别的权限:对某些表的特定字段进行操作
- 存储程序级别的账号:对存储程序进行增删改查的操作
注册新用户及之后的一些操作:
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 进入之后:
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'
主从配置
主机操作
在主机上配置账户:
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 的配置:
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 数据库是存在的):
sudo service mysql restart
查看二进制日志是否生成:
show variables like 'log_%';
从机操作
修改 mysql 的配置:
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
添加:
server-id = 2
expire_logs_days = 10
max_binlog_size = 100M
重启服务:
sudo service mysql restart
配置主从库之间的通信:

从机上设置从库(129 机)与主库(130 机)进行通信。先把从机的 test 数据库清空,模板:
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
作为一个可以参考的实例:
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!”:
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 框架传递的字典的一个实际例子(不需要掌握):
{
'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,那如何处理呢?
#!/usr/bin/python
# author luke
import socket
import re
import multiprocessing
import time
class WSGIServer(object):
def __init__(self):
# 1. 创建套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定
self.tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
self.tcp_server_socket.listen(128)
def service_client(self, new_socket):
"""为这个浏览器返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">" * 20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
# print("*"*50, file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 返回http格式的数据,给浏览器
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()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response body发送给浏览器
new_socket.send(html_content)
else:
# 2.2 如果是以.py结尾,那么就认为是动态资源的请求
header = "HTTP/1.1 200 OK\r\n"
header += "\r\n"
body = "hahahah %s " % time.ctime()
response = header + body
# 发送response给浏览器
new_socket.send(response.encode("utf-8"))
# 关闭套接
new_socket.close()
def run_forever(self):
"""用来完成整体的控制"""
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = self.tcp_server_socket.accept()
# 5. 为这个客户端服务
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
闭包
可以在函数里面定义函数,例子:
def line6(k, b):
def create_line(x):
print(k*x+b)
return create_line
# l1 是 create_line 函数,函数中 k 的值是 1,b 的值是 2
l1 = line6(2, 3)
l1(3)
'''
9
'''
函数、匿名函数、闭包、对象当做实参时有什么区别?
- 匿名函数能够完成基本的简单功能,传递是这个函数的引用只有功能(lambda)
- 普通函数能够完成较为复杂的功能,传递是这个函数的引用只有功能
- 闭包能够将较为复杂的功能,传递是这个闭包中的函数以及数据,因此传递是功能+数据(相对于对象,占用空间少)
- 对象能够完成最为复杂的功能,传递是很多数据+很多功能,因此传递是功能+数据
闭包引用了外部函数的局部变量,若外部函数的局部变量没有及时释放,会消耗内存。
闭包中对变量的修改:
# 这个代码会报错
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 ,使用外部函数的变量。
# 可以正常运行
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 等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:
############### 基础平台提供的功能如下 ###############
def f1():
print('f1')
def f2():
print('f2')
def f3():
print('f3')
def f4():
print('f4')
############### 业务部门 A 调用基础平台提供的功能 ###############
f1()
f2()
f3()
f4()
############### 业务部门 B 调用基础平台提供的功能 ###############
f1()
f2()
f3()
f4()
目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。
解决方案
遵循开放封闭原则,对拓展开放,对修改闭合。使用装饰器:
def w1(func):
def inner():
# 验证 1
# 验证 2
# 验证 3
func()
return inner
@w1
def f1():
print('f1')
@w1
def f2():
print('f2')
@w1
def f3():
print('f3')
@w1
def f4():
print('f4')
语法
基本用法
实际例子:
# 装饰器一定是内部有闭包的函数
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-----
'''
关于一些原理上的细节:
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)
因此会有输出
'''
传递参数
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
'''
多个装饰器装饰同一个函数
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)
'''
一个更具体的例子:
# 定义函数:完成包裹数据
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)功能有很多,具体来说:
- 引入日志,在执行某个函数前或者函数后记录日志
- 函数执行时间统计
- 执行函数前预备处理
- 执行函数后清理功能
- 权限校验等场景
- 缓存
- 等
被装饰的函数带返回值
from time import ctime, sleep
def 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
'''
注意,上面的代码没有拿到返回值,应该修改为:
from time import ctime, sleep
def timefun(func):
def wrapped_func():
print("%s called at %s" % (func.__name__, ctime()))
return func() # modified
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 .
装饰器带参数
装饰器带参数,在原有装饰器的基础上,设置外部变量。
from time import sleep
def 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
# 下面的装饰过程
# 1. 调用 timefun_arg("wangdao")
# 2. 将步骤 1 得到的返回值,即 time_fun 返回, 然后 time_fun(foo)
# 3. 将 time_fun(foo)的结果返回,即 wrapped_func
# 4. 让 foo = wrapped_fun,即 foo 现在指向 wrapped_func
@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
'''
类装饰器
前期提要:
class Test:
def __call__(self, *args, **kwds):
print('I am call')
t = Test()
t()
'''
I am call
'''
装饰器函数其实是这样一个接口约束,它必须接受一个 callable 对象作为参数,然后返回一个 callable 对象。在 Python 中一般 callable 对象都是函数,但也有例外。只要某个对象重写了 __call__() 方法,那么这个对象就是 callable 的。
一个具体的例子:
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))
# t = Test(foo)
# t()
foo('akashi')
'''
I am call
I am foo akashi
'''
装饰后的注释问题
问题演示和解决方法:
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
'''
wper decorator
'''
###############################
from functools import wraps
def my_new_decorator(func):
@wraps(func) # focus this
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__) # example Docstring
'''
example1 I am example1
'''
迷你 web 框架(续)
本节概要: 之前,去替换 html 模板中的变量时,替换的内容是随意写的,现在要替换的内容是从 mysql 数据库中查出来的,并把装饰器应用到框架里。
version 6
在之前的 version 5 基础上,作如下改变。
# mini_frame.py
#!/usr/bin/python
# author luke
import time
import re
URL_FUNC_DICT = dict()
def route(url):
def set_func(func):
URL_FUNC_DICT[url] = func
# def call_func(*args, **kwargs):
# return func(*args, **kwargs)
# return call_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):
# 由mini_frame框架添加响应码和头部
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
file_name = environ['PATH_INFO']
# file_name = "/index.py"
return URL_FUNC_DICT[file_name]() # 加括号是为了执行
修改要点:
- 使用字典替换 if 判断
- 使用装饰器实现路由功能
伪静态、静态、动态
目前开发的网站其实都是动态网站,只是 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
实现伪静态,作如下修改。
# mini_frame.py
import re
URL_FUNC_DICT = dict()
def route(url):
def set_func(func):
# URL_FUNC_DICT["/index.py"] = index
URL_FUNC_DICT[url] = func
# def call_func(*args, **kwargs):
# return func(*args, **kwargs)
# return call_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']
# file_name = "/index.py"
# if file_name == "/index.py":
# return index()
# elif file_name == "/center.py":
# return center()
# else:
# return 'Hello World! 我爱你中国....'
try:
func = URL_FUNC_DICT[file_name]
return func()
# return URL_FUNC_DICT[file_name]()
except Exception as ret:
return "产生了异常:%s" % str(ret)
# web_server.py
import socket
import re
import multiprocessing
import time
# import dynamic.mini_frame
import sys
class WSGIServer(object):
def __init__(self, port, app, static_path):
# 1. 创建套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定
self.tcp_server_socket.bind(("", port))
# 3. 变为监听套接字
self.tcp_server_socket.listen(128)
self.application = app
self.static_path = static_path
def service_client(self, new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
if request:
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
# print("*"*50, file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 返回http格式的数据,给浏览器
# 2.1 如果请求的资源不是以.html结尾,那么就认为是静态资源(css/js/png,jpg等)
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()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response ic.mini_frame.applicationbody发送给浏览器
new_socket.send(html_content)
else:
# 2.2 如果是以.py结尾,那么就认为是动态资源的请求
env = dict() # 这个字典中存放的是web服务器要传递给 web框架的数据信息
env['PATH_INFO'] = file_name
# {"PATH_INFO": "/index.py"}
# body = dynamic.mini_frame.application(env, self.set_response_header)
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
# 发送response给浏览器
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:
# 4. 等待新客户端的链接
new_socket, client_addr = self.tcp_server_socket.accept()
# 5. 为这个客户端服务
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]) # 7890
frame_app_name = sys.argv[2] # mini_frame:application
except Exception as ret:
print("端口输入错误。。。。。")
return
else:
print("请按照以下方式运行:")
print("python3 xxxx.py 7890 mini_frame:application")
return
# mini_frame:application
ret = re.match(r"([^:]+):(.*)", frame_app_name)
if ret:
frame_name = ret.group(1) # mini_frame
app_name = ret.group(2) # application
else:
print("请按照以下方式运行:")
print("python3 xxxx.py 7890 mini_frame:application")
return
with open("./web_server.conf") as f:
conf_info = eval(f.read())
# 此时 conf_info是一个字典里面的数据为:
# {
# "static_path":"./static",
# "dynamic_path":"./dynamic"
# }
sys.path.append(conf_info['dynamic_path'])
# import frame_name --->找frame_name.py
frame = __import__(frame_name) # 返回值标记这 导入的这个模板
app = getattr(frame, app_name) # 此时app就指向了 dynamic/mini_frame模块中的application这个函数
# print(app)
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
从数据库查询数据,替换模板中的变量。
准备数据的过程:
create database stock_db charset=utf8;
use stock_db
source /home/zhiyue/Downloads/stock_db.sql
(这个 sql 文件不重要,我们这里只是演示)
表中的数据:
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
日志
例子:
import logging
logging.basicConfig(level=logging.WARNING,
format='%(asctime)s - %(filename)s[line:%(lineno)d] -%(levelname)s: %(message)s')
# 开始使用 log 功能
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
'''
写到文件:
import logging
logging.basicConfig(level=logging.WARNING,
filename='log.txt',
filemode='w',
format='%(asctime)s - %(filename)s[line:%(lineno)d] -%(levelname)s: %(message)s')
# 开始使用 log 功能
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')
既把日志输出到控制台,又写入文件:
import logging
# 第一步,创建一个 logger
logger = logging.getLogger()
logger.setLevel(logging.INFO) # Log 等级总开关
# 第二步,创建一个 handler,用于写入日志文件
logfile = './log.txt'
fh = logging.FileHandler(logfile, mode='a') # open 的打开模式这里可以进行参考
fh.setLevel(logging.DEBUG) # 输出到 file 的 log 等级的开关
# 第三步,再创建一个 handler,用于输出到控制台
ch = logging.StreamHandler()
ch.setLevel(logging.WARNING) # 输出到 console 的 log 等级的开关
# 第四步,定义 handler 的输出格式
formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
fh.setFormatter(formatter)
ch.setFormatter(formatter)
# 第五步,将 logger 添加到 handler 里面
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 语法:
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 中是为了保持向后兼容性)
用法如下:
type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
例子:
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 类中的 echo_bar 属性,指向了上面定义的函数
FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
my_foo = FooChild()
my_foo.echo_bar()
'''
<class '__main__.Test2'>
True
True
True
'''
一个更完整的例子:
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
'''
metaclass 属性
当我们写下如下的代码时:
class Foo(Bar):
pass
Python 做了如下的操作:
- Foo 中有
__metaclass__这个属性吗?如果是,Python 会通过__metaclass__创建一个名字为 Foo 的类(对象) - 如果 Python 没有找到
__metaclass__,它会继续在 Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。 - 如果 Python 在任何父类中都找不到
__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。 - 如果还是找不到
__metaclass__,Python 就会用内置的 type 来创建这个类对象。
例子:
# 效果:将 Foo 的属性 bar 改成了 BAR
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
#调用 type 来创建一个类,这里的返回值给了 Foo
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 是用了一个函数,接下来使用一个类:
class UpperAttrMetaClass(type):
# __new__ 是在__init__之前被调用的特殊方法
# __new__是用来创建对象并返回之的方法
# 而__init__只是用来将传入的参数初始化给对象
# 你很少用到__new__,除非你希望能够控制对象的创建
# 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
# 如果你希望的话,你也可以在__init__中做些事情
# 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
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
# 方法 1:通过'type'来做类对象的创建
return type(class_name, class_parents, new_attr)
# 方法 2:复用 type.__new__方法
# 这就是基本的 OOP 编程,没什么魔法
# return type.__new__(cls, class_name, class_parents, new_attr)
# python3 的用法
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 ,简单地展示一下原理。
实例
# 这个类的工作:将 User 类的类属性删除,然后增加一些东西返回
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
mappings = dict()
# 判断是否需要保存
for k, v in attrs.items():
# 判断是否是指定的 StringField 或者 IntegerField 的实例对象
if isinstance(v, tuple):
print('Found mapping: %s ==> %s' % (k, v)) # 日志
mappings[k] = v
# example:
# k: uid
# v: ('uid', "int unsigned")
# 删除这些已经在字典中存储的属性
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 可以给对象新增属性
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)
# 非常魔幻的写法。在 C++ 的构造函数中,你不会需要将 uid=, name= 这些写出来,
# 这里 Python 写出来,是直接传给了 __init__
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)
'''
一点微小的改进(增加了类型校验):
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
mappings = dict()
# 判断是否需要保存
for k, v in attrs.items():
# 判断是否是指定的 StringField 或者 IntegerField 的实例对象
if isinstance(v, tuple):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v
# 删除这些已经在字典中存储的属性
for k in mappings.keys():
attrs.pop(k)
# 将之前的 uid/name/email/password 以及对应的对象引用、类名字
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)")
# 当指定元类之后,以上的类属性将不在类中,而是在__mappings__属性指定的字典中存储
# 以上 User 类中有
# __mappings__ = {
# "uid": ('uid', "int unsigned")
# "name": ('username', "varchar(30)")
# "email": ('email', "varchar(30)")
# "password": ('password', "varchar(30)")
# }
# __table__ = "User"
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 中的继承:
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
mappings = dict()
# 判断是否需要保存
for k, v in attrs.items():
# 判断是否是指定的 StringField 或者 IntegerField 的实例对象
if isinstance(v, tuple):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v
# 删除这些已经在字典中存储的属性
for k in mappings.keys():
attrs.pop(k)
# 将之前的 uid/name/email/password 以及对应的对象引用、类名字
attrs['__mappings__'] = mappings # 保存属性和列的映射关系
attrs['__table__'] = name # 假设表名和类名一致
return type.__new__(cls, name, bases, attrs)
# return super().__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++ 笔记里的东西差不多,这里只放几个例子。
class payment:
def pay(self,money):
e=Exception('缺少编写 pay 方法')
raise e #手动抛异常
class Alipay(payment):
# 这里类的方法不是一致的 pay,导致后面调用的时候找不到 pay
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 方法
'''
# Alipay 没有 pay 方法,走到父类的 pay 方法后报错
然而这样的代码还是可以改进,我们希望在编写代码的时候就能发现问题,而不是调用的时候才报错,为此引出抽象类。
from abc import abstractmethod, ABCMeta
class 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++ 中的一致。