├── .gitattributes ├── .pyup.yml ├── backup ├── default │ ├── HTTPS.md │ ├── About Me.md │ ├── CSRF token 工作原理.md │ ├── Python 中谨慎使用 copy.deepcopy().md │ ├── 极速上手写一个 Flask 扩展.md │ └── 游标分页与传统分页.md ├── Java │ └── Java 集合.md ├── CS │ ├── 计算机浮点数运算误差.md │ └── 编程风格札记.md ├── Flask │ ├── Flask 源码阅读(一)准备知识.md │ ├── SharedDataMiddleware 中间件, Flask 文件访问服务..md │ ├── Flask 源码阅读(五)附:上下文.md │ ├── 极速上手写一个 Flask 扩展.md │ ├── Flask 源码阅读(二)创建应用以及请求钩子.md │ ├── Flask 源码阅读(三)路由.md │ └── Flask 源码阅读(四)应用启动流程.md ├── DB │ └── MySQL 游标分页与传统分页.md └── Python │ ├── Python 面试题整理[实习].md │ └── Python 实习面试题整理.md ├── LICENSE.md ├── README.md └── auto_backup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | *.* linguist-language=Markdown 2 | -------------------------------------------------------------------------------- /.pyup.yml: -------------------------------------------------------------------------------- 1 | # autogenerated pyup.io config file 2 | # see https://pyup.io/docs/configuration/ for all available options 3 | 4 | schedule: every week 5 | update: insecure 6 | -------------------------------------------------------------------------------- /backup/default/HTTPS.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: HTTPS 3 | category: default 4 | date: 2017-10-15T05:35:09Z 5 | url: https://github.com/x1ah/Blog/issues/16 6 | --- 7 | 8 | https://my.oschina.net/nearzk/blog/485652 -------------------------------------------------------------------------------- /backup/Java/Java 集合.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Java 集合 3 | category: Java 4 | date: 2017-03-15T03:51:23Z 5 | url: https://github.com/x1ah/Blog/issues/8 6 | --- 7 | 8 | ![](http://ww1.sinaimg.cn/large/005NaGmtly1fdndsjxjdwj31gw0rogq3) -------------------------------------------------------------------------------- /backup/CS/计算机浮点数运算误差.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 计算机浮点数运算误差 3 | category: CS 4 | date: 2017-05-16T14:40:24Z 5 | url: https://github.com/x1ah/Blog/issues/11 6 | --- 7 | 8 | 实际上,根据 IEEE 浮点标准,计算机只能表示可以写成 `n × 2^m` 的格式这些数,当某些小数不能写成该形式时,即开始舍入,亦或是产生常见的 9 | ```python 10 | >>> 0.65 - 0.6 11 | 0.050000000000000044 12 | ``` -------------------------------------------------------------------------------- /backup/default/About Me.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About Me 3 | category: default 4 | date: 2017-03-08T08:35:26Z 5 | url: https://github.com/x1ah/Blog/issues/6 6 | --- 7 | 8 | ## 关于博客 9 | - 主要是懒,就直接在 Issues 内写了,记的都是笔记,还可能有很多错误 10 | 11 | ## 联系 12 | - 邮箱 `x1ahgxq#gmail.com` 13 | - [QQ](http://i.imgur.com/s4mteP9.jpg) 14 | - [WeChat](http://i.imgur.com/jgR9Qjc.jpg) 15 | - [Telegram](https://telegram.me/x1ahh) -------------------------------------------------------------------------------- /backup/default/CSRF token 工作原理.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CSRF token 工作原理 3 | category: default 4 | date: 2017-06-24T09:50:55Z 5 | url: https://github.com/x1ah/Blog/issues/14 6 | --- 7 | 8 | >[CSRF:跨站请求伪造](https://zh.wikipedia.org/zh/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0) 9 | 10 | -------- 11 | 12 | # 防御 13 | ## CSRF Token 14 | 工作流程如下:后端服务器生成表单时加入一个 token 字段,这个字段可根据情况调整生成算法,一般是随机字符串,用户提交表单时,该字段一并提交,后端检验 token 是否一致。攻击者可以伪造表单,但是无法在一个 session 内伪造 token。 -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 x1ah 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | - 2017-12-24T04:14:50Z: [Python 中谨慎使用 copy.deepcopy()](https://github.com/x1ah/Blog/issues/18) 2 | - 2017-10-28T13:40:16Z: [极速上手写一个 Flask 扩展](https://github.com/x1ah/Blog/issues/17) 3 | - 2017-10-15T05:35:09Z: [HTTPS](https://github.com/x1ah/Blog/issues/16) 4 | - 2017-08-20T12:18:42Z: [MySQL 游标分页与传统分页](https://github.com/x1ah/Blog/issues/15) 5 | - 2017-06-24T09:50:55Z: [CSRF token 工作原理](https://github.com/x1ah/Blog/issues/14) 6 | - 2017-05-16T14:40:24Z: [计算机浮点数运算误差](https://github.com/x1ah/Blog/issues/11) 7 | - 2017-04-08T14:50:16Z: [Python 面试题整理[实习]](https://github.com/x1ah/Blog/issues/9) 8 | - 2017-03-15T03:51:23Z: [Java 集合](https://github.com/x1ah/Blog/issues/8) 9 | - 2017-03-11T15:25:10Z: [Flask 源码阅读(五)附:上下文](https://github.com/x1ah/Blog/issues/7) 10 | - 2017-03-08T08:35:26Z: [About Me](https://github.com/x1ah/Blog/issues/6) 11 | - 2017-03-04T02:46:00Z: [Flask 源码阅读(四)应用启动流程](https://github.com/x1ah/Blog/issues/5) 12 | - 2017-03-01T13:15:13Z: [Flask 源码阅读(三)路由](https://github.com/x1ah/Blog/issues/4) 13 | - 2017-03-01T13:09:57Z: [Flask 源码阅读(二)创建应用以及请求钩子](https://github.com/x1ah/Blog/issues/3) 14 | - 2017-02-25T16:03:49Z: [Flask 源码阅读(一)准备知识](https://github.com/x1ah/Blog/issues/2) 15 | - 2017-01-25T13:17:34Z: [SharedDataMiddleware 中间件, Flask 文件访问服务.](https://github.com/x1ah/Blog/issues/1) 16 | -------------------------------------------------------------------------------- /backup/Flask/Flask 源码阅读(一)准备知识.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Flask 源码阅读(一)准备知识 3 | category: Flask 4 | date: 2017-02-25T16:03:49Z 5 | url: https://github.com/x1ah/Blog/issues/2 6 | --- 7 | 8 | ### 首先为什么要看 flask 源码? 9 | - 加强 flask 基础,熟悉 flask 的各种用法以及设计哲学 10 | - 还能学习大牛们怎么写代码,一些技巧 11 | 12 | ### flask 简介 13 | [flask](http://flask.pocoo.org/) 是一个易扩展,使用简单的微Python web开发**框架**,这里需要区别 `Web framework` 和 `Web Server`,一个是框架一个是服务器,在使用 `flask` 开发时,常常都需要直接运行项目来测试以及方便开发,实际上这也是启动了一个 `Web Server`,但是这只是一个简单的开发服务器,并不能用到生产环境中,生产环境大多用 [uWSGI](https://uwsgi-docs.readthedocs.io/),[Gunicorn](http://gunicorn.org/),[Tornado](http://www.tornadoweb.org/en/stable/)之类。 14 | 15 | ### flask 周边 16 | `flask` 的两个主要依赖分别是 [werkzeug](https://github.com/pallets/werkzeug) 和 [Jinja](https://github.com/pallets/jinja),并且 100% 兼容 [WSGI](http://legacy.python.org/dev/peps/pep-0333/)。其中 `Jinja` 是模板引擎,如果只是用来写 API 服务器大可不必关心 `Jinja`,而 `werkzeug` 便是 `flask` 的关键了,并且可以说 `flask` 是一个 `werkzeug` 的一个封装,来实现 Python 程序(Web应用)和 Web 服务器,服务器和客户端的交互。而 `WSGI` 便是 Python 程序和 Web 服务器之间交互的一个接口标准,或者说“协议”。所以需要先了解了解 `WSGI` 是个怎么回事儿。关于 `WSGI`,有很多文章已经介绍/解读过了,便不再造轮子了,下面几篇都是挺不错的几篇文章: 17 | - 首先是 [PEP333](http://legacy.python.org/dev/peps/pep-0333/),在这里面有详细的关于 WSGI 标准的介绍。 18 | - 除此之外还有 [YangZ_405](http://my.csdn.net/YangZ_XX) 的这篇 [Python的WSGI简介](http://blog.csdn.net/yangz_xx/article/details/37508909) 也很不错,建议仔细阅读。 19 | 20 | -------------------------------------------------------------------------------- /backup/default/Python 中谨慎使用 copy.deepcopy().md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Python 中谨慎使用 copy.deepcopy() 3 | category: default 4 | date: 2017-12-24T04:14:50Z 5 | url: https://github.com/x1ah/Blog/issues/18 6 | --- 7 | 8 | >在一次做 Benchmark 的时候,发现无论如何调代码,最后总是不尽人意,之后用 `pstats` 分析发现 `copy.deepcopy` 占了很大一部分运行时间,于是挑出来看了下,让其与 `dict.copy` 比较下,惊了 9 | ![image](https://user-images.githubusercontent.com/14919255/34324313-51777d70-e8a9-11e7-9296-109bd4461be6.png) 10 | 11 | 12 | ## 性能比较 13 | ```python 14 | in [1]: import string 15 | In [2]: def test_copy(): 16 | ...: d = {} 17 | ...: for i, c in enumerate(string.ascii_letters): 18 | ...: d[i] = c 19 | ...: return d.copy() 20 | ...: 21 | ...: 22 | 23 | In [3]: def test_deepcopy(): 24 | ...: d = {} 25 | ...: for i, c in enumerate(string.ascii_letters): 26 | ...: d[i] = c 27 | ...: return copy.deepcopy(d) 28 | ...: 29 | 30 | In [4]: %timeit -r 100 r = test_deepcopy() 31 | 96.4 µs ± 4.13 µs per loop (mean ± std. dev. of 100 runs, 10000 loops each) 32 | 33 | In [5]: %timeit -r 100 r = test_copy() 34 | 6.55 µs ± 98.4 ns per loop (mean ± std. dev. of 100 runs, 100000 loops each) 35 | ``` 36 | 37 | 96 - 6 的差异,惊+1 38 | 39 | ## 解决 40 | - 在无需使用深拷贝时,适当的使用 浅拷贝代替。 41 | - 序列化后再反序列化 42 | - 实现 `__deepcopy__` 方法 43 | 44 | ## Tips 45 | http://www.algorithmdog.com/slow-python-deepcopy -------------------------------------------------------------------------------- /backup/CS/编程风格札记.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 编程风格札记 3 | category: CS 4 | date: 2017-06-16T14:38:51Z 5 | url: https://github.com/x1ah/Blog/issues/13 6 | --- 7 | 8 | -[Google Python 代码规范](http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules/) 9 | - 项目名使用 小写单词,由 `-` 连接,如 `daily-scripts`,`my-blog-article` 10 | - Python 模块名,文件名使用下划线连接,如 `login_manager.py`,`login_ext/` 11 | - 四个空格缩进,JavaScript,CSS,HTML 使用两个空格 12 | - __目录名采用小写字母,下划线连接__ 13 | - 一行不超过 80 个字符,多行字符串使用括号连接,如 14 | ```python 15 | text = ("this is test text this is test text this is test tex" 16 | "this is test text this is test text this is test text" 17 | "this is test text this is test text this is test text ") 18 | ``` 19 | - 关于(大)括号换行,如: 20 | ```python 21 | def test(*args): 22 | pass 23 | 24 | # 建议 25 | test_lambda_function = lambda arg_one, arg_two, arg_three: test( 26 | 'arg_one', 'arg_two', 'arg_three', 'arg_four' 27 | ) 28 | 29 | # 不换行(不建议) 30 | test_lambda_function = lambda arg_one, arg_two, arg_three: test('arg_one', 'arg_two', 31 | 'arg_three', 'arg_four') 32 | ``` 33 | - 类名,异常名,常量名使用全大写,常量名可以使用下划线连接,包名,函数名,变量名使用小写字母,下划线连接 34 | - 模块导入顺序:标准库,空一行,第三方库,空一行,自编写库 35 | - 定义类前后各两行空行,方法前后空一行,函数上下空一行,etc 36 | - 注释: 37 | ```python 38 | def test(first, second): 39 | """description of the function 40 | 41 | Args: 42 | first: first arguments 43 | second: second arguments 44 | Returns: 45 | there return nothing 46 | example: 47 | "test" 48 | Raise: 49 | MyException 50 | """ 51 | return "hello world" 52 | ``` -------------------------------------------------------------------------------- /auto_backup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding:utf-8 3 | 4 | import os 5 | 6 | import requests 7 | 8 | 9 | def all_issues(): 10 | api = "https://api.github.com/repos/x1ah/Blog/issues" 11 | res = requests.get(api).json() 12 | for issue in res: 13 | yield issue 14 | 15 | 16 | def backup(issue): 17 | title = issue.get('title') 18 | # 默认一篇博客只打一个标签 19 | label = issue.get('labels') 20 | label = label[0].get('name') if label else 'default' 21 | url = issue.get('html_url') 22 | create_at = issue.get('created_at') 23 | body = issue.get('body') 24 | head_text = """--- \r\ntitle: {title} \r\ncategory: {label} \r\ndate: {time} \r\nurl: {url} \r\n---\r\n 25 | """.format(title=title, label=label, time=create_at, url=url) 26 | backup_path = os.path.join('backup', label) 27 | if not os.path.isdir(backup_path): 28 | os.mkdir(backup_path) 29 | path = os.path.join(backup_path, title) + '.md' 30 | print("备份 {} ing".format(path)) 31 | with open(path, 'w') as mkd: 32 | mkd.write(head_text + body) 33 | 34 | 35 | def add_to_readme(issue): 36 | with open('README.md', 'a') as readme: 37 | readme.write('- {t}: [{name}]({url})\n'.format( 38 | t=issue.get('created_at'), 39 | name=issue.get('title'), 40 | url=issue.get('html_url') 41 | )) 42 | 43 | 44 | def run(): 45 | issues = all_issues() 46 | try: 47 | os.remove('README.md') 48 | except: 49 | pass 50 | for issue in issues: 51 | backup(issue) 52 | add_to_readme(issue) 53 | try: 54 | os.system('git add .') 55 | os.system('git commit -m\'update {m}\''.format(issue[0].get('title'))) 56 | os.system('git push origin master') 57 | except: 58 | pass 59 | 60 | 61 | if __name__ == "__main__": 62 | run() 63 | 64 | -------------------------------------------------------------------------------- /backup/Flask/SharedDataMiddleware 中间件, Flask 文件访问服务..md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SharedDataMiddleware 中间件, Flask 文件访问服务. 3 | category: Flask 4 | date: 2017-01-25T13:17:34Z 5 | url: https://github.com/x1ah/Blog/issues/1 6 | --- 7 | 8 | [SharedDataMiddleware](http://werkzeug.pocoo.org/docs/0.11/middlewares/#werkzeug.wsgi.SharedDataMiddleware) 是 [Werkzeug](http://werkzeug.pocoo.org/docs/0.11/) 内的一个中间件,顾名思义,用来 share data 共享数据的。经常我们能看见一些软件的安装方式是这样的: 9 | 10 | ```shell 11 | $ curl -L https://raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | bash 12 | ``` 13 | 14 | 通过 URL 直接就能访问服务器上的文件,那么我们也可以通过 Flask 和 ShareDataMiddleware 很简单的来实现一个这样的小功能,下面看一个 Demo: 15 | 16 | ```python 17 | #!/usr/bin/env python 18 | # encoding: utf-8 19 | # demo.py 20 | 21 | # official document: http://flask.pocoo.org/docs/0.10/patterns/fileuploads/ 22 | 23 | from flask import Flask, send_from_directory 24 | from werkzeug import SharedDataMiddleware 25 | 26 | app = Flask(__name__) 27 | 28 | app.config["FILE_PATH"] = "." # The path want handdle 29 | 30 | def prev_file(filename): 31 | return send_from_directory(app.config.get("FILE_PATH"), filename) 32 | 33 | app.add_url_rule("/file/", "prev_file", build_only=True) 34 | 35 | app.wsgi_app = SharedDataMiddleware( 36 | app.wsgi_app, 37 | { 38 | "/file": app.config.get("FILE_PATH") 39 | }, 40 | cache = False 41 | # tips: http://stackoverflow.com/questions/11515804/zombie-shareddatamiddleware-on-python-heroku 42 | ) 43 | 44 | 45 | if __name__ == "__main__": 46 | app.run() 47 | ``` 48 | 49 | 当运行脚本之后,便可以直接通过访问 `http://localhost:8000/file/` 来直接访问该目录下的文件内容了。 50 | 51 | 除此之外,在 Flask 源码(0.1)内也有一段使用了SharedDataMiddleware,源码如下: 52 | 53 | ```python 54 | class Flask(object): 55 | # balabala 56 | def __init__(self, package_name): 57 | if self.static_path is not None: 58 | self.url_map.add(Rule(self.static_path + '/', 59 | build_only=True, endpoint='static')) 60 | if pkg_resources is not None: 61 | target = (self.package_name, 'static') 62 | else: 63 | target = os.path.join(self.root_path, 'static') 64 | self.wsgi_app = SharedDataMiddleware(self.wsgi_app, { 65 | self.static_path: target 66 | }) 67 | ``` 68 | 69 | 这段代码就是默认的为static静态文件提供访问服务,使得我们不用再去关心如何管理静态文件。 -------------------------------------------------------------------------------- /backup/Flask/Flask 源码阅读(五)附:上下文.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Flask 源码阅读(五)附:上下文 3 | category: Flask 4 | date: 2017-03-11T15:25:10Z 5 | url: https://github.com/x1ah/Blog/issues/7 6 | --- 7 | 8 | 先完全从 Flask 源码来观察上下文机制是怎样实现的 9 | 10 | ------------------------------------------------------ 11 | 12 | 在 `Flask.wsgi_app` 方法内,前面解释过,是在一个请求上下文内进行一系列调度。在这个方法内的调用堆栈如下: 13 | ``` 14 | with self.request_context(environ) 15 | _RequestContext(self, environ) 16 | ``` 17 | 实际上 ` _RequestContext` 就是一个自定义的上下文管理器,它的源码如下: 18 | ```python 19 | class _RequestContext(object): 20 | def __init__(self, app, environ): 21 | self.app = app 22 | self.url_adapter = app.url_map.bind_to_environ(environ) 23 | self.request = app.request_class(environ) 24 | self.session = app.open_session(self.request) 25 | self.g = _RequestGlobals() 26 | self.flashes = None 27 | 28 | def __enter__(self): 29 | _request_ctx_stack.push(self) 30 | 31 | def __exit__(self, exc_type, exc_value, tb): 32 | if tb is None or not self.app.debug: 33 | _request_ctx_stack.pop() 34 | ``` 35 | 36 | ---------------------- 37 | 38 | 在这里,可以先提提上下文管理器,实际开发中,经常能用到的对文件 I/O 会这样写: 39 | ```python 40 | with open("foo.txt", 'w') as f: 41 | f.write("hello world") 42 | ``` 43 | 这样写的好处是不用我们自行去调用 `f.close()`,它会自动关闭,那么如何自己实现一个?实际上实现了 `__enter__` 和 `__exit__` 方法的类就可以作文一个上下文管理器。如下例子: 44 | ```python 45 | class mycontext(object): 46 | def __init__(self, foo): 47 | self.foo = foo 48 | def __enter__(self): 49 | print("__enter__") 50 | print(self.foo) 51 | def __exit__(self, *args): 52 | print(self.foo) 53 | print("__exit__") 54 | 55 | # 运行 56 | >>> with mycontext("mycontext"): 57 | print("in mycontext") 58 | >>> # output 59 | __enter__ 60 | mycontext 61 | in mycontext 62 | mycontext 63 | __exit__ 64 | ``` 65 | 可见,`__enter__` 和 `__exit__` 分别在 `with` 语句块前后执行。而上面的 ` _RequestContext` 类就是这样的一个上下文管理器。首先将自己(Request Context)推入 ` _request_ctx_stack` 栈中。在调度结束后又将其弹出栈。所以通过这样进栈出栈的方式,每次取栈顶作为当前请求上下文来实现隔离,而关于这个 ` _request_ctx_stack` 又是什么样的一种存在,我的理解如下: 66 | 它是一种 `Local Stack`,何为 `Local Stack`?就是对线程独立,甚至可以不妨将其看成是个映射表,键是线程 ID, 而值便是对应栈元素。同样可以应用于 **应用上下文**,应用上下文也是这样的原理,每次取栈顶元素,在推入请求上下文时会检查当前应用上下文栈是否为空,为空会自动推入当前应用上下文。 67 | 除了这些,其还有很多细节体现,没能仔细去往深处研究,个中细节也不能一一列出,然而,有一篇博客给了我很好的思路。TonySeek 的 [Flask 的 Context 机制](https://blog.tonyseek.com/post/the-context-mechanism-of-flask/)。 68 | -------------------------------------------------------------------------------- /backup/default/极速上手写一个 Flask 扩展.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 极速上手写一个 Flask 扩展 3 | category: default 4 | date: 2017-10-28T13:40:16Z 5 | url: https://github.com/x1ah/Blog/issues/17 6 | --- 7 | 8 | >假设我是一个接口贩子,专门提供各种各样的 API 给我的客户们,主要这些 API 后端是用 Python + Flask 实现的。我需要管理和监控我的这些 API 们,看看哪些更受欢迎,哪些响应慢,哪些需要改进,于是我想给我的后端服务做个 Dashboard,能看到上面那些数据,并且想把这个东西抽象出来,以后我还能卖其他 API,还能用在我的其他项目上,于是乎打算做成一个插件。 9 | 10 | 先假装有一个存数据的客户端,👇 11 | ```python 12 | class APIDogClient: 13 | '''APIDog: 收集接口服务的各种信息,请求耗时,请求路径,请求 IP 等等等等''' 14 | def __init__(self, secret_key): 15 | self.host = 'x.x.x.x' # 假装我有一些配置需要初始化 16 | self.port = 'xxx' 17 | self.secret_key = secret_key 18 | self.secret_id = 'xxx' 19 | self.bucket = [] 20 | 21 | def storge(self, data): 22 | # clean data 23 | self.bucket.append(data) 24 | ``` 25 | 26 | 现在可以开始着手插件了,给插件取名为 `flask-APIDog`,第一代打算收集每次请求的路径,每个请求的耗时,每次请求的 IP。每次请求都这些数据一起发送到我的 APIDog 服务端存起来,并展示到 Dashboard 上。 27 | ```python 28 | import time 29 | 30 | from flask import Flask, request, current_app 31 | 32 | try: 33 | from flask import _app_ctx_stack as stack 34 | except ImportError: 35 | from flask import _request_ctx_stack as stack 36 | 37 | 38 | class APIDog: 39 | '''Flask-APIDog extendsion''' 40 | def __init__(self, app=None): 41 | self.app = app 42 | if app: 43 | self.init_app(app) 44 | self.api_dog = APIDogClient(self.app.config.get('api_dog_secret_key', '')) 45 | 46 | def init_app(self, app=None): 47 | self.app = app 48 | self.api_dog = APIDogClient(self.app.config.get('api_dog_secret_key', '')) 49 | app.before_request(self._before_request) 50 | app.after_request(self._after_request) 51 | 52 | def _before_request(self): 53 | ctx = stack.top 54 | ctx._api_dog_data = { 55 | 'request_begin_time': time.time() 56 | } 57 | 58 | def _after_request(self, response): 59 | ctx = stack.top 60 | api_request_begin_time = ctx._api_dog_data.get('request_path', time.time()) 61 | request_time = time.time() - api_request_begin_time 62 | api_data = { 63 | 'request_time': request_time, 64 | 'request_path': request.path, 65 | 'request_location': request.args.get('location', ''), 66 | 'remote_address': request.remote_addr 67 | } 68 | self.api_dog.storge(api_data) 69 | ``` 70 | 71 | 👆 是第一版的 `flask-APIDog` 的代码,很简单的一些处理, Flask 扩展一般(官方建议)提供一个 `init_app` 方法,用于在实例化插件类后初始化 Flask APP,实际上这里只是简单的给 Flask APP 的 `before_request`,`after_request` 两个钩子函数提供两个具体流程,`_app_ctx_stack.top` 是 Flask 72 | 里面上下文的概念,意思是取当前应用。更甚至完全可以直接用 `before_request`,`after_request` 装饰两个函数来实现上面那些功能,但是后面迭代版本会越来越复杂,直接写在项目里很容易污染现有代码,抽象成插件不仅提高了鲁棒性,还符合 Flask 的插件系统理念。 -------------------------------------------------------------------------------- /backup/Flask/极速上手写一个 Flask 扩展.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 极速上手写一个 Flask 扩展 3 | category: Flask 4 | date: 2017-10-28T13:40:16Z 5 | url: https://github.com/x1ah/Blog/issues/17 6 | --- 7 | 8 | >假设我是一个接口贩子,专门提供各种各样的 API 给我的客户们,主要这些 API 后端是用 Python + Flask 实现的。我需要管理和监控我的这些 API 们,看看哪些更受欢迎,哪些响应慢,哪些需要改进,于是我想给我的后端服务做个 Dashboard,能看到上面那些数据,并且想把这个东西抽象出来,以后我还能卖其他 API,还能用在我的其他项目上,于是乎打算做成一个插件。 9 | 10 | 先假装有一个存数据的客户端,👇 11 | ```python 12 | class APIDogClient: 13 | '''APIDog: 收集接口服务的各种信息,请求耗时,请求路径,请求 IP 等等等等''' 14 | def __init__(self, secret_key): 15 | self.host = 'x.x.x.x' # 假装我有一些配置需要初始化 16 | self.port = 'xxx' 17 | self.secret_key = secret_key 18 | self.secret_id = 'xxx' 19 | self.bucket = [] 20 | 21 | def storge(self, data): 22 | # clean data 23 | self.bucket.append(data) 24 | ``` 25 | 26 | 现在可以开始着手插件了,给插件取名为 `flask-APIDog`,第一代打算收集每次请求的路径,每个请求的耗时,每次请求的 IP。每次请求都这些数据一起发送到我的 APIDog 服务端存起来,并展示到 Dashboard 上。 27 | ```python 28 | import time 29 | 30 | from flask import Flask, request, current_app 31 | 32 | try: 33 | from flask import _app_ctx_stack as stack 34 | except ImportError: 35 | from flask import _request_ctx_stack as stack 36 | 37 | 38 | class APIDog: 39 | '''Flask-APIDog extendsion''' 40 | def __init__(self, app=None): 41 | self.app = app 42 | if app: 43 | self.init_app(app) 44 | self.api_dog = APIDogClient(self.app.config.get('api_dog_secret_key', '')) 45 | 46 | def init_app(self, app=None): 47 | self.app = app 48 | self.api_dog = APIDogClient(self.app.config.get('api_dog_secret_key', '')) 49 | app.before_request(self._before_request) 50 | app.after_request(self._after_request) 51 | 52 | def _before_request(self): 53 | ctx = stack.top 54 | ctx._api_dog_data = { 55 | 'request_begin_time': time.time() 56 | } 57 | 58 | def _after_request(self, response): 59 | ctx = stack.top 60 | api_request_begin_time = ctx._api_dog_data.get('request_path', time.time()) 61 | request_time = time.time() - api_request_begin_time 62 | api_data = { 63 | 'request_time': request_time, 64 | 'request_path': request.path, 65 | 'request_location': request.args.get('location', ''), 66 | 'remote_address': request.remote_addr 67 | } 68 | self.api_dog.storge(api_data) 69 | ``` 70 | 71 | 👆 是第一版的 `flask-APIDog` 的代码,很简单的一些处理, Flask 扩展一般(官方建议)提供一个 `init_app` 方法,用于在实例化插件类后初始化 Flask APP,实际上这里只是简单的给 Flask APP 的 `before_request`,`after_request` 两个钩子函数提供两个具体流程,`_app_ctx_stack.top` 是 Flask 72 | 里面上下文的概念,意思是取当前应用。更甚至完全可以直接用 `before_request`,`after_request` 装饰两个函数来实现上面那些功能,但是后面迭代版本会越来越复杂,直接写在项目里很容易污染现有代码,抽象成插件不仅提高了鲁棒性,还符合 Flask 的插件系统理念。 73 | 74 | 75 | 使用方法也与一般的 Flask 插件一致,👇 76 | ```python 77 | from flask import Flask 78 | from flask_apidog import APIDog 79 | 80 | 81 | app = Flask('test_flask_extend') 82 | api_dog = ApiDog() 83 | api_dog.init_app(app) 84 | 85 | @app.route("/") 86 | def index(): 87 | return "hello api dog" 88 | 89 | 90 | if __name__ == "__main__": 91 | app.run() 92 | ``` 93 | 94 | 这样之后,APP 的每个请求都会记录请求耗时,请求 IP,请求路径并转发到 APIDog 后端服务,这样一个简单的 Flask 插件大致就完成了。 -------------------------------------------------------------------------------- /backup/Flask/Flask 源码阅读(二)创建应用以及请求钩子.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Flask 源码阅读(二)创建应用以及请求钩子 3 | category: Flask 4 | date: 2017-03-01T13:09:57Z 5 | url: https://github.com/x1ah/Blog/issues/3 6 | --- 7 | 8 | >看优秀项目的源码有个技巧,那就是从最初的版本开始看,这样能更快的理解作者的思想,所以,下面的例子以及源码均为 flask 0.1 版本的源码,flask 0.1 版本代码总共也才600+行,非常短小精悍。 9 | 10 | ### 从例子开始 11 | 下面给一个特别简单的例子,从而来理解一个Web程序是如何启动并工作的 12 | ```python 13 | #!/usr/bin/python 14 | #coding: utf-8 15 | 16 | # demo.py 17 | 18 | from flask import Flask, g, request 19 | 20 | app = Flask(__name__) 21 | 22 | @app.before_request 23 | def preprocess(): 24 | print("======preprocess======") 25 | 26 | @app.after_request 27 | def afterprocess(response): 28 | global times 29 | print("=====afterprocess====== ") 30 | return response 31 | 32 | @app.errorhandler(404) 33 | def page_not_found(e): 34 | return "404 Not Found" 35 | 36 | @app.route("/", methods=["GET"]) 37 | def index(): 38 | g.user = 'test' 39 | headers = request.headers 40 | return """ 41 | Hello world!
42 | g.user = {0}
43 | headers={1} 44 | """.format(g.user, headers) 45 | 46 | if __name__ == "__main__": 47 | app.run(host="0.0.0.0", port=8888) 48 | ``` 49 | 50 | 先一句一句看,首先执行了`app = Flask(__name__)`,这过程发生了什么?怎么就创建了一个 app ?先看看 `Flask` 类里面到底有什么内容,部分解释如下 51 | 52 | ```python 53 | class Flask(object): 54 | request_class = Request # 请求对象,继承于werkzeug.Request类,并做了一些定制化 55 | response_class = Response # 返回对象,是继承于werkzeug.Response 类,可以看作就是 werkzeug.Response 56 | static_path = '/static' # 静态文件目录 57 | secret_key = None # 默认secret_key 为空 58 | def __init__(self, package_name): 59 | self.debug = True # 默认开启 debug 模式 60 | self.package_name = package_name # 包名,也就是传入的__name__ 61 | self.root_path = _get_package_path(self.package_name) # app 的根目录 62 | self.view_functions = {} # 视图函数 63 | self.error_handlers = {} # 也就是我们上面写过的错误处理函数 64 | self.before_request_funcs = [] # 同样的,里面用来存before_request装饰过的函数,顾名思义是在每次请求前执行的,这里还有一个小的注意点 65 | self.after_request_funcs = [] # 同理用after_request装饰过的函数,在后面版本更多用teardown_request了,在每次请求结束后执行 66 | self.url_map = Map() # 这里存放我们的URL映射关系 67 | 68 | if self.static_path is not None: 69 | self.url_map.add(Rule(self.static_path + '/', 70 | build_only=True, endpoint='static')) 71 | if pkg_resources is not None: 72 | target = (self.package_name, 'static') 73 | else: 74 | target = os.path.join(self.root_path, 'static') 75 | self.wsgi_app = SharedDataMiddleware(self.wsgi_app, { 76 | self.static_path: target 77 | }) 78 | ``` 79 | 80 | 这里就用到了前面谈到的 `werkzeug` 里的 [SharedDataMiddleware中间件](https://github.com/x1ah/Blog/issues/1),初始化了我们的静态文件,以至于我们不用去在针对静态目录,为了访问静态文件而特意写一个视图函数处理,而只需要在同目录下建一个`static/`目录,这就方便多了。 81 | 82 | 83 | 之后便是这些请求钩子了,`before_request`, `after_request`, `errorhandler`,做的工作也仅仅是把这些装饰过的函数加到前面的列表/字典的,等待调用。具体实现如下 84 | 85 | ```python 86 | class Flask(object): 87 | 88 | # ... 89 | 90 | def before_request(self, f): 91 | """Registers a function to run before each request.""" 92 | self.before_request_funcs.append(f) 93 | return f 94 | 95 | def after_request(self, f): 96 | """Register a function to be run after each request.""" 97 | self.after_request_funcs.append(f) 98 | return f 99 | def errorhandler(self, code): 100 | def decorator(f): 101 | self.error_handlers[code] = f 102 | return f 103 | return decorator 104 | ``` 105 | 106 | 这些钩子函数都是用来干嘛的? 107 | - `before_request` 在每次请求前执行,也可用来请求拦截,在后面会谈到 108 | - `after_request` 在每次请求完成后执行 109 | - 在后面的 flask 版本甚至还有 `before_first_request`, `teardown_request`之类。分别为第一次请求前,每次请求后无论出错不出错都执行 110 | - `errorhandler`,这其实不应该叫做钩子,这是错误处理,其带的`code`参数正好对应 `HTTP` 状态码,对不同的状态码执行不同的错误处理逻辑 111 | ------------------------------- 112 | 113 | 于此,我们便创建了一个 `Web application`,正如前面谈 `WSGI` 说道,应用程序必须是可调用的,并且带两个位置参数,所以,在 Flask 类里面实现了 `__call__(self, environ, start_response)` 如下,恰好符合 `WSGI` 标准, 114 | 115 | ```python 116 | class Flask(object): 117 | # ... 118 | def __call__(self, environ, start_response): 119 | return self.wsgi_app(environ, start_response) 120 | ``` -------------------------------------------------------------------------------- /backup/default/游标分页与传统分页.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 游标分页与传统分页 3 | category: default 4 | date: 2017-08-20T12:18:42Z 5 | url: https://github.com/x1ah/Blog/issues/15 6 | --- 7 | 8 | >现在很多后端服务使用的翻页方式都是使用传统的 `limit`, `offset` 分页,但是这样的分页方式一旦偏移量大了,便会产生一些性能问题以及数据重复或缺失问题。 9 | 10 | # 传统分页 11 | 12 | 通常在分页时(以 HTTP 协议 GET 方法举例),会携带两个可选参数,`limit`和`offset`。或者是携带一个 `page` 参数,如果是 `limit`,`offset` 参数,在后端从数据库获取数据时,便是直接的使用 `LIMIT` 关键字查询,如下 13 | ```mysql 14 | mysql> SELECT * FROM TEST_TABLE LIMIT offset, limit; 15 | mysql> ;;或 16 | mysql> SELECT * FROM TEST_TABLE LIMIT limit, OFFSET offset; 17 | ``` 18 | 使用 `page` 参数控制翻页的实现方式与上面大同小异,无非是 `offset = (page-1) * size` 19 | 这样完全可以实现翻页,但是在数据量和偏移量上去之后,查询性能便会直线下降。 20 | 21 | ### 数据缺失 22 | 23 | 除了性能问题,上面提到的数据缺失,假设有这样情况,先前表里有10条数据,如下 24 | 25 | ```shell 26 | +---------------------------+ 27 | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 28 | +------------------- 29 | || 参数:limit=3&offset=0,得到 30 | 31 | +-----------+ 32 | | 1 | 2 | 3 | 33 | +-----------+ 34 | 35 | || 删除一条数据(2) 36 | 37 | +-----------------------+ 38 | | 1 | 3 | 4 | 5 | 6 | 7 | 39 | +-----------------------+ 40 | 41 | || 参数:limit=3&offset=3 42 | 43 | 期望:在上次取出 1,2,3 的基础上,这次取出4,5,6 44 | 实际:取出的是 5,6,7,这就导致4这条记录没有被取到 45 | ``` 46 | 47 | ### 数据重复 48 | 49 | 还是上面的例子 50 | 51 | ```shell 52 | +---------------------------+ 53 | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 54 | +---------------------------+ 55 | 56 | || 参数:limit=3&offset=0,得到 57 | 58 | +-----------+ 59 | | 1 | 2 | 3 | 60 | +-----------+ 61 | || 插入一条数据(8) 62 | 63 | +-------------------------------+ 64 | | 1 | 8 | 2 | 3 | 4 | 5 | 6 | 7 | 65 | +-------------------------------+ 66 | 67 | || 参数:limit=3&offset=3 68 | 69 | 期望:在 1,2,3 的基础上取出4,5,6,(这里甚至取出的结果很奇怪) 70 | 实际:3,4,5,与第一次取出的1,2,3 重复了 3 这条数据 71 | ``` 72 | 73 | # 游标分页 74 | 75 | 游标分页是通过 `cursor` 和 `size` 这样的类似两个参数来控制翻页,简单的 SQL 语句如下 76 | 77 | ```sql 78 | mysql> select * from data where id>cursor limit size 79 | ``` 80 | 81 | 这样再来看上面的两个问题,第一个参数变为 `cursor=3&size=3`,结果为 4,5,6,符合预期 82 | 第二个参数取出结果为 4,5,6,也符合预期。看来数据缺失和数据重复的问题得到了解决 🎉 83 | 84 | # 性能对比 85 | >用 Docker 启动了一个 MySQL 镜像的容器。MySQL 镜像为 DockerHub 最近版。查询时间为三次查询取平均值 86 | 87 | ``` 88 | $ docker run --name cursor_paged -e MYSQL_ROOT_PASSWORD=x1ah -itd mysql:latest 89 | $ mysql -d 127.0.0.1 -P 3306 -u root -p 90 | mysql> select version(); 91 | +-----------+ 92 | | version() | 93 | +-----------+ 94 | | 5.7.19 | 95 | +-----------+ 96 | 1 row in set (0.00 sec) 97 | ``` 98 | 99 | 测试的表结构如下 100 | ```sql 101 | mysql> desc data; 102 | +---------+------------------+------+-----+---------+----------------+ 103 | | Field | Type | Null | Key | Default | Extra | 104 | +---------+------------------+------+-----+---------+----------------+ 105 | | id | int(10) unsigned | NO | PRI | NULL | auto_increment | 106 | | content | varchar(128) | NO | | NULL | | 107 | +---------+------------------+------+-----+---------+----------------+ 108 | ``` 109 | 110 | 向表内事先插入 3000000 条记录 111 | 112 | ```sql 113 | mysql> delimiter // 114 | mysql> create procedure fake_data(in num int) 115 | -> begin 116 | -> declare string char(62) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; 117 | -> declare rand_str char(2); 118 | -> declare i int default 0; 119 | -> while i set rand_str=concat(substring(string,1+floor(rand()*61),1),substring(string,1+floor(rand()*61),1)); 121 | -> set i=i+1; 122 | -> insert into data (content) values (rand_str); 123 | -> end while; 124 | -> end; 125 | -> // 126 | mysql> call fake_data(3000000); 127 | Query OK, 1 row affected (24 min 42.63 sec) 128 | ``` 129 | 130 | ## 使用 `limit`,`offset` 翻页 131 | 132 | ```shell 133 | mysql> select * from data limit 2000000, 10; 134 | +---------+---------+ 135 | | id | content | 136 | +---------+---------+ 137 | | 2000001 | RP | 138 | | 2000002 | m8 | 139 | | 2000003 | tN | 140 | | 2000004 | rE | 141 | | 2000005 | MQ | 142 | | 2000006 | GI | 143 | | 2000007 | oG | 144 | | 2000008 | 37 | 145 | | 2000009 | iU | 146 | | 2000010 | xL | 147 | +---------+---------+ 148 | 10 rows in set (0.36 sec) 149 | ``` 150 | 151 | ## 使用游标翻页 152 | 153 | ```shell 154 | mysql> select * from data where id > 2000000 limit 10; 155 | +---------+---------+ 156 | | id | content | 157 | +---------+---------+ 158 | | 2000001 | RP | 159 | | 2000002 | m8 | 160 | | 2000003 | tN | 161 | | 2000004 | rE | 162 | | 2000005 | MQ | 163 | | 2000006 | GI | 164 | | 2000007 | oG | 165 | | 2000008 | 37 | 166 | | 2000009 | iU | 167 | | 2000010 | xL | 168 | +---------+---------+ 169 | 10 rows in set (0.00 sec) 170 | ``` 171 | 172 | 这里的时间直接忽略为0 了,可见比使用 `limit offset` 要快不是一点点 173 | 174 | # 附 175 | 举一个游标翻页例子🌰 ,通常在设计接口时,并不会直接把 `cursor` 值直接明文暴露,而是服务端使用某些加密算法,像 base64 之类的,使用加密后的结果在服务端和客户端进行传递。在服务端拿到 `cursor` 参数时,会进行解码,得到可被接受的值后,再去数据库取,在取数据时会特意多取一个,以此结果长度和期望长度相比,便可得知是否还有下一页,如果存在下一页,将下一页的游标经过编码之后再返回给客户端,以此便实现了游标翻页。 176 | 177 | 178 | -------------------------------------------------------------------------------- /backup/Flask/Flask 源码阅读(三)路由.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Flask 源码阅读(三)路由 3 | category: Flask 4 | date: 2017-03-01T13:15:13Z 5 | url: https://github.com/x1ah/Blog/issues/4 6 | --- 7 | 8 | 在我们请求某个 URL 时,应用程序是如何对不同的 URL 作出不同的响应的?在 Flask 里面又是怎么实现的?在前面看见 Flask 类里面初始化了一个实例变量 `url_map`,这是个 `werkzeug.routing.Map` 对象,实际上可以把它看作是个 **路由映射规则** ,类似于字典这样的数据结构,里面存的都是 Web 应用的各种路由规则,供我们使用。 9 | 10 | 在 Flask 里面`route`装饰器估计是最常用最熟悉的了,下面就来看看这个神奇的东西背后都有哪些幺娥子,先找到了`Flask.route`方法实现,如下 11 | ```python 12 | class Flask(object) 13 | # ... 14 | def route(self, rule, **options): 15 | """ 16 | By default a variable part 17 | in the URL accepts any string without a slash however a different 18 | converter can be specified as well by using ````. 19 | =========== =========================================== 20 | `int` accepts integers 21 | `float` like `int` but for floating point values 22 | `path` like the default but also accepts slashes 23 | =========== =========================================== 24 | 25 | An important detail to keep in mind is how Flask deals with trailing 26 | slashes. The idea is to keep each URL unique so the following rules 27 | apply: 28 | 29 | 1. If a rule ends with a slash and is requested without a slash 30 | by the user, the user is automatically redirected to the same 31 | page with a trailing slash attached. 32 | 2. If a rule does not end with a trailing slash and the user request 33 | the page with a trailing slash, a 404 not found is raised. 34 | 35 | 36 | The :meth:`route` decorator accepts a couple of other arguments 37 | as well: 38 | 39 | :param rule: the URL rule as string 40 | :param methods: a list of methods this rule should be limited 41 | to (``GET``, ``POST`` etc.). By default a rule 42 | just listens for ``GET`` (and implicitly ``HEAD``). 43 | :param subdomain: specifies the rule for the subdoain in case 44 | subdomain matching is in use. 45 | :param strict_slashes: can be used to disable the strict slashes 46 | setting for this rule. See above. 47 | :param options: other options to be forwarded to the underlying 48 | :class:`~werkzeug.routing.Rule` object. 49 | """ 50 | 51 | def decorator(f): 52 | self.add_url_rule(rule, f.__name__, **options) 53 | self.view_functions[f.__name__] = f 54 | return f 55 | return decorator 56 | ``` 57 | 这里还能观察到一点小技巧:如果添加的路由规则由`/`结尾,当用户请求该URL时会自动重定向至带`/`的URL,相反如果添加的没有`/`结尾,当请求`/`结尾的URL时直接返回404,还说明了这个版本的路由匹配规则里`converter`一共有三种类系,`int`,`float`,`path`。实际上发现,在`route`内部也是通过`Flask.add_url_rule` 方法来实现路由规则添加,(*所以我们大可以使用 `add_url_rule` 来实现与`route`装饰器一样的功能,但是,为了使代码更 `Pythonic` 更简洁,兼顾可读性,在两种方法都可行的情况下更建议使用`route`装饰器来添加路由规则*)。跟踪到 `Flask.add_url_rule`: 58 | ```python 59 | class Flask(object): 60 | # ... 61 | def add_url_rule(self, rule, endpoint, **options): 62 | """Connects a URL rule. Works exactly like the :meth:`route` 63 | decorator but does not register the view function for the endpoint. 64 | 65 | Basically this example:: 66 | 67 | @app.route('/') 68 | def index(): 69 | pass 70 | 71 | Is equivalent to the following:: 72 | 73 | def index(): 74 | pass 75 | app.add_url_rule('index', '/') 76 | app.view_functions['index'] = index 77 | 78 | :param rule: the URL rule as string 79 | :param endpoint: the endpoint for the registered URL rule. Flask 80 | itself assumes the name of the view function as 81 | endpoint 82 | :param options: the options to be forwarded to the underlying 83 | :class:`~werkzeug.routing.Rule` object 84 | """ 85 | options['endpoint'] = endpoint 86 | options.setdefault('methods', ('GET',)) 87 | self.url_map.add(Rule(rule, **options)) 88 | ``` 89 | 除了给了一些常规用法外,最关键的是方法的最后一句 `self.url_map.add(Rule(rule, **options))`,所以最后都是添加到 `url_map` 里面,那么这个 `url_map` 和 `Rule` 又是什么东西?跟踪到 `werkzeug.routing`,他们的代码都相对比较长,可以看看这里 [Rule](https://github.com/pallets/werkzeug/blob/master/werkzeug/routing.py#L483L917),[Map](https://github.com/pallets/werkzeug/blob/master/werkzeug/routing.py#L1103L1357),先直接看看他们的 demo 吧。 90 | ```python 91 | >>> m = Map([ 92 | ... Rule('/', endpoint="index"), 93 | ... Rule('/about/', endpoint="/about"), 94 | ... Rule('/help', endpoint="help") 95 | ... ]) 96 | >>> c = m.bind("example.com", '/') 97 | >>> c.build('index') 98 | '/' 99 | >>> c.match("/") 100 | ('index', {}) 101 | >>> c.match('/about') 102 | Traceback (most recent call last): 103 | File "", line 1, in 104 | File "/usr/local/lib/python2.7/dist-packages/werkzeug/routing.py", line 1524, in match 105 | safe='/:|+') + '/', query_args)) 106 | werkzeug.routing.RequestRedirect: 301: Moved Permanently 107 | >>> c.match('/help') 108 | ('help', {}) 109 | >>> m.add(Rule('/view//', endpoint="view")) 110 | >>> c.match('/view/22/33') 111 | ('view', {'post': 22, 'page': 33}) 112 | >>> 113 | ``` 114 | 从这几个例子里面便可以看见 `werkzeug` 的 `Map`,`Rule` 主要功能了,先添加路由规则,再绑定,之后便可以进行匹配了。重现了前面提到的 URL 带不带反斜杠问题。 `werkzeug` 的路由过程最后都是转换到 `endpoint`,再返回给用户。关于路由暂时先说这些,后面还有一些更重要的细节。 115 | 欲知后事如何,请听下回分解。 -------------------------------------------------------------------------------- /backup/Flask/Flask 源码阅读(四)应用启动流程.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Flask 源码阅读(四)应用启动流程 3 | category: Flask 4 | date: 2017-03-04T02:46:00Z 5 | url: https://github.com/x1ah/Blog/issues/5 6 | --- 7 | 8 | >结束了这些小细节,现在开始来把这些全部串起来,看看一个 Web 应用是怎么启动的,中间又触发了什么,先后顺序又是怎么样的,一个请求过来如何响应,以及 Flask 的几个特色点 9 | 10 | 在 WSGI 协议里面就已经提到过,应用程序必须是一个可调用对象,并且带两个参数,对应到 Python 里面,可以有三种实现方式: 11 | - 可以是函数 12 | - 可以是一个实例,它的类实现了__call__方法 13 | - 可以是一个类,这时候,用这个类生成实例的过程就相当于调用这个类 14 | 在 Flask 里面采用了第二种,也就是 Flask 类实现了 `__call__` 方法。同时 WSGI 约定其必须返回一个可迭代对象,故先看看 `__call__` 的实现: 15 | ```python 16 | class Flask(object): 17 | # ... 18 | def __call__(self, environ, start_response): 19 | """Shortcut for :attr:`wsgi_app`""" 20 | return self.wsgi_app(environ, start_response) 21 | ``` 22 | 可见,其实是返回了一个 `WSGI` APP,跟着看看 `wsgi_app(environ, start_response)` 这个方法的实现: 23 | ```python 24 | class Flask(object): 25 | def wsgi_app(self, environ, start_response): 26 | with self.request_context(environ): 27 | rv = self.preprocess_request() # 调度before_request装饰函数 28 | if rv is None: 29 | rv = self.dispatch_request() # 调度请求,调度视图函数 30 | response = self.make_response(rv) 31 | response = self.process_response(response) # 调度after_request 装饰函数 32 | return response(environ, start_response) 33 | ``` 34 | 这段代码便是一次收到一次请求,Flask 的处理逻辑,详细解释一下,在 Flask 里面有 **上下文** 的概念,通俗的解释就是应用启动了便有了一个 **应用上下文** ,上下文可以抽象理解为一个隔离环境,必须在这个隔离环境下才能执行相关任务。同理,一个请求过来了 Flask 又给了你一个 **请求上下文**,意思就是只有在这个”请求隔离环境“内才能处理你的事务,而且,多个请求上下文执行任务是互不干扰的。暂时就先解释这些,后面在作详细阐述。 35 | 从上面代码来看,首先是在一个 **请求上下文** 内,`rv` 便是 `before_request` 列表里面逐个执行的首个返回结果。跟着,来到了 `preprocess_request()` 函数 36 | ```python 37 | class Flask(object): 38 | # ... 39 | def preprocess_request(self): 40 | for func in self.before_request_funcs: 41 | rv = func() 42 | if rv is not None: 43 | return rv 44 | ``` 45 | *这里有个小技巧,很明显可以看出来,一旦某个函数有返回值,便会直接将返回结果传给 `rv`,然后不再去调用视图函数,直接构造 `response` ,之后无论请求哪个路由,都直接返回这个结果。这也就是我们常说的 **请求拦截** 了。所以,我们在实际开发中 `before request` 函数里很少提供返回值* 46 | 47 | 我们这里没有给返回值,于是来到了 `rv = self.dispatch_request()`,调度请求,根据URL调用视图函数。再来看看 `dispatch_request()`方法: 48 | ```python 49 | ```python 50 | class Flask(object): 51 | # ... 52 | def dispatch_request(self): 53 | try: 54 | endpoint, values = self.match_request() 55 | return self.view_functions[endpoint](**values) 56 | except HTTPException, e: 57 | handler = self.error_handlers.get(e.code) 58 | if handler is None: 59 | return e 60 | return handler(e) 61 | except Exception, e: 62 | handler = self.error_handlers.get(500) 63 | if self.debug or handler is None: 64 | raise 65 | return handler(e) 66 | ``` 67 | 这里有 Flask 的 HTTP 错误处理逻辑,先捕捉 `HTTPException` 错误,如果捕捉到了便通过 HTTP 状态码调用我们之前对应的 `error_handlers` 函数。并返回结果构造 `response` 对象,再调用 `after request` 函数。并且,在 `after request` 函数里面是可以修改 `response` 的(所以我们的 `after_request` 函数是需要带一个 `response` 形参的)。并且返回修改后的 `response`,实现如下: 68 | ```python 69 | class Flask(object): 70 | # ... 71 | def process_response(self, response): 72 | session = _request_ctx_stack.top.session 73 | if session is not None: 74 | self.save_session(session, response) 75 | for handler in self.after_request_funcs: 76 | response = handler(response) 77 | return response 78 | ``` 79 | 直接取栈顶会话作为当前会话。 80 | 81 | -------------------------------------------------- 82 | 83 | 再回到 `app.run()`, 84 | ```python 85 | class Flask(object): 86 | # ... 87 | def run(self, host='localhost', port=5000, **options): 88 | from werkzeug import run_simple 89 | if 'debug' in options: 90 | self.debug = options.pop('debug') 91 | options.setdefault('use_reloader', self.debug) 92 | options.setdefault('use_debugger', self.debug) 93 | return run_simple(host, port, self, **options) 94 | ``` 95 | 默认参数里面给了默认的服务器就是本机,端口为 5000。首先判断了是否开了 debug 模式,因为在`werkzeug` 的 `run_simple()` 里面是没有 `debug` 参数的,而是以 `use_reloader` 参数来实现相似的功能,故先在 `options` 里 `pop` 出 `debug` 键值对,再赋值给 `use_reloader`,然后再调用 `run_simple()`,再跟着来到了 `werkzeug` 的源码,它的 `run_simple()` 实现如下: 96 | ```python 97 | def run_simple(hostname, port, application, use_reloader=False, 98 | extra_files=None, threaded=False, processes=1): 99 | """ 100 | Start an application using wsgiref and with an optional reloader. 101 | """ 102 | def inner(): 103 | srv = make_server(hostname, port, application, threaded, 104 | processes) 105 | try: 106 | srv.serve_forever() 107 | except KeyboardInterrupt: 108 | pass 109 | 110 | if os.environ.get('WERKZEUG_RUN_MAIN') != 'true': 111 | print '* Running on http://%s:%d/' % (hostname or '0.0.0.0', port) 112 | if use_reloader: 113 | # Create and destroy a socket so that any exceptions are raised before we 114 | # spawn a separate Python interpreter and loose this ability. 115 | test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 116 | test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 117 | test_socket.bind((hostname, port)) 118 | test_socket.close() 119 | run_with_reloader(inner, extra_files or []) 120 | else: 121 | inner() 122 | ``` 123 | 124 | 这个 `inner` 函数便是运行开发服务器,等待我们的请求的东西。接下来的 `if` 判断便是我们每次启动开发服务器打印的第一条日志。` * Running on http://0.0.0.0:5000/`,后面的 `if use_reloader` 便是 `debug` 模式的关键所在,**这点有些不明白,什么时候创建销毁 这个套接字,有什么意义?** 125 | 然后在 `run_with_reloader` 里面开一个线程运行 `inner()`,等待请求。这便是整个流程。 126 | 127 | ------------------------------------ 128 | 129 | ### 小结 130 | 整合上面的各点,再来捋一捋这整个过程 131 | ![](http://ww1.sinaimg.cn/large/005NaGmtly1fde33m2ou2j30nf0p5wgm) 132 | 133 | --------------------- 134 | 135 | 上面这些只是特别简单的捋了一遍,这个小部分就到这里了。 -------------------------------------------------------------------------------- /backup/DB/MySQL 游标分页与传统分页.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: MySQL 游标分页与传统分页 3 | category: DB 4 | date: 2017-08-20T12:18:42Z 5 | url: https://github.com/x1ah/Blog/issues/15 6 | --- 7 | 8 | >现在很多后端服务使用的翻页方式都是使用传统的 `limit`, `offset` 分页,但是这样的分页方式一旦偏移量大了,便会产生一些性能问题以及数据重复或缺失问题。 9 | 10 | # 传统分页 11 | 12 | 通常在分页时(以 HTTP 协议 GET 方法举例),会携带两个可选参数,`limit`和`offset`。或者是携带一个 `page` 参数,如果是 `limit`,`offset` 参数,在后端从数据库获取数据时,便是直接的使用 `LIMIT` 关键字查询,如下 13 | ```mysql 14 | mysql> SELECT * FROM TEST_TABLE LIMIT offset, limit; 15 | mysql> ;;或 16 | mysql> SELECT * FROM TEST_TABLE LIMIT limit, OFFSET offset; 17 | ``` 18 | 使用 `page` 参数控制翻页的实现方式与上面大同小异,无非是 `offset = (page-1) * size` 19 | 这样完全可以实现翻页,但是在数据量和偏移量上去之后,查询性能便会直线下降。 20 | 21 | ### 数据缺失 22 | 23 | 除了性能问题,上面提到的数据缺失,假设有这样情况,先前表里有10条数据,如下 24 | 25 | ```shell 26 | +---------------------------+ 27 | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 28 | +------------------- 29 | || 参数:limit=3&offset=0,得到 30 | 31 | +-----------+ 32 | | 1 | 2 | 3 | 33 | +-----------+ 34 | 35 | || 删除一条数据(2) 36 | 37 | +-----------------------+ 38 | | 1 | 3 | 4 | 5 | 6 | 7 | 39 | +-----------------------+ 40 | 41 | || 参数:limit=3&offset=3 42 | 43 | 期望:在上次取出 1,2,3 的基础上,这次取出4,5,6 44 | 实际:取出的是 5,6,7,这就导致4这条记录没有被取到 45 | ``` 46 | 47 | ### 数据重复 48 | 49 | 还是上面的例子 50 | 51 | ```shell 52 | +---------------------------+ 53 | | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 54 | +---------------------------+ 55 | 56 | || 参数:limit=3&offset=0,得到 57 | 58 | +-----------+ 59 | | 1 | 2 | 3 | 60 | +-----------+ 61 | || 插入一条数据(8) 62 | 63 | +-------------------------------+ 64 | | 1 | 8 | 2 | 3 | 4 | 5 | 6 | 7 | 65 | +-------------------------------+ 66 | 67 | || 参数:limit=3&offset=3 68 | 69 | 期望:在 1,2,3 的基础上取出4,5,6,(这里甚至取出的结果很奇怪) 70 | 实际:3,4,5,与第一次取出的1,2,3 重复了 3 这条数据 71 | ``` 72 | 73 | # 游标分页 74 | 75 | 游标分页是通过 `cursor` 和 `size` 这样的类似两个参数来控制翻页,简单的 SQL 语句如下 76 | 77 | ```sql 78 | mysql> select * from data where id>cursor limit size 79 | ``` 80 | 81 | 这样再来看上面的两个问题,第一个参数变为 `cursor=3&size=3`,结果为 4,5,6,符合预期 82 | 第二个参数取出结果为 4,5,6,也符合预期。看来数据缺失和数据重复的问题得到了解决 🎉 83 | 84 | # 性能对比 85 | >用 Docker 启动了一个 MySQL 镜像的容器。MySQL 镜像为 DockerHub 最近版。查询时间为三次查询取平均值 86 | 87 | ``` 88 | $ docker run --name cursor_paged -e MYSQL_ROOT_PASSWORD=x1ah -itd mysql:latest 89 | $ mysql -d 127.0.0.1 -P 3306 -u root -p 90 | mysql> select version(); 91 | +-----------+ 92 | | version() | 93 | +-----------+ 94 | | 5.7.19 | 95 | +-----------+ 96 | 1 row in set (0.00 sec) 97 | ``` 98 | 99 | 测试的表结构如下 100 | ```sql 101 | mysql> desc data; 102 | +---------+------------------+------+-----+---------+----------------+ 103 | | Field | Type | Null | Key | Default | Extra | 104 | +---------+------------------+------+-----+---------+----------------+ 105 | | id | int(10) unsigned | NO | PRI | NULL | auto_increment | 106 | | content | varchar(128) | NO | | NULL | | 107 | +---------+------------------+------+-----+---------+----------------+ 108 | ``` 109 | 110 | 向表内事先插入 3000000 条记录 111 | 112 | ```sql 113 | mysql> delimiter // 114 | mysql> create procedure fake_data(in num int) 115 | -> begin 116 | -> declare string char(62) default 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; 117 | -> declare rand_str char(2); 118 | -> declare i int default 0; 119 | -> while i set rand_str=concat(substring(string,1+floor(rand()*61),1),substring(string,1+floor(rand()*61),1)); 121 | -> set i=i+1; 122 | -> insert into data (content) values (rand_str); 123 | -> end while; 124 | -> end; 125 | -> // 126 | mysql> call fake_data(3000000); 127 | Query OK, 1 row affected (24 min 42.63 sec) 128 | ``` 129 | 130 | ## 使用 `limit`,`offset` 翻页 131 | 132 | ```shell 133 | mysql> select * from data limit 2000000, 10; 134 | +---------+---------+ 135 | | id | content | 136 | +---------+---------+ 137 | | 2000001 | RP | 138 | | 2000002 | m8 | 139 | | 2000003 | tN | 140 | | 2000004 | rE | 141 | | 2000005 | MQ | 142 | | 2000006 | GI | 143 | | 2000007 | oG | 144 | | 2000008 | 37 | 145 | | 2000009 | iU | 146 | | 2000010 | xL | 147 | +---------+---------+ 148 | 10 rows in set (0.36 sec) 149 | 150 | mysql> explain select * from data limit 2000000, 10\G; 151 | ***************************[ 1. row ]*************************** 152 | id | 1 153 | select_type | SIMPLE 154 | table | data 155 | partitions | 156 | type | ALL 157 | possible_keys | 158 | key | 159 | key_len | 160 | ref | 161 | rows | 2712992 162 | filtered | 100.0 163 | Extra | 164 | 165 | 1 row in set 166 | Time: 0.017s 167 | ``` 168 | 169 | ## 使用游标翻页 170 | 171 | ```shell 172 | mysql> select * from data where id > 2000000 limit 10; 173 | +---------+---------+ 174 | | id | content | 175 | +---------+---------+ 176 | | 2000001 | RP | 177 | | 2000002 | m8 | 178 | | 2000003 | tN | 179 | | 2000004 | rE | 180 | | 2000005 | MQ | 181 | | 2000006 | GI | 182 | | 2000007 | oG | 183 | | 2000008 | 37 | 184 | | 2000009 | iU | 185 | | 2000010 | xL | 186 | +---------+---------+ 187 | 10 rows in set (0.00 sec) 188 | 189 | mysql> explain select * from data where id > 2000000 limit 10\G 190 | ***************************[ 1. row ]*************************** 191 | id | 1 192 | select_type | SIMPLE 193 | table | data 194 | partitions | 195 | type | range 196 | possible_keys | PRIMARY 197 | key | PRIMARY 198 | key_len | 4 199 | ref | 200 | rows | 1356496 201 | filtered | 100.0 202 | Extra | Using where 203 | 204 | 1 row in set 205 | Time: 0.019s 206 | ``` 207 | 208 | 这里从查询时间还是从 `explain` 看,都是第二种方式性能要好,第一种直接扫全表了, 209 | 210 | # 附 211 | 举一个游标翻页例子🌰 ,通常在设计接口时,并不会直接把 `cursor` 值直接明文暴露,而是服务端使用某些加密算法,像 base64 之类的,使用加密后的结果在服务端和客户端进行传递。在服务端拿到 `cursor` 参数时,会进行解码,得到可被接受的值后,再去数据库取,在取数据时会特意多取一个,以此结果长度和期望长度相比,便可得知是否还有下一页,如果存在下一页,将下一页的游标经过编码之后再返回给客户端,以此便实现了游标翻页。 212 | 213 | 214 | -------------------------------------------------------------------------------- /backup/Python/Python 面试题整理[实习].md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Python 面试题整理[实习] 3 | category: Python 4 | date: 2017-04-08T14:50:16Z 5 | url: https://github.com/x1ah/Blog/issues/9 6 | --- 7 | 8 | >下面的内容均为最近几周找 Python 实习遇见的各种面试题,记录备用。其中有 bat 之类大厂也有十几个人的初创公司~(斜体的为遇见两次以上的。) 9 | 10 | # Python 语法 11 | - *说说你平时 Python 都用哪些库* 12 | 13 | - `==` 和 `is` 区别。 14 | - `==` 是比较两对象的值,`is` 是比较在内存中的地址(id), `is` 相当于 `id(objx) == id(objy)`。 15 | 16 | - *深拷贝和浅拷贝。* 17 | ```python 18 | # 浅拷贝操作只会拷贝被拷贝对象的第一层对象,对于更深层级的只不过是拷贝其引用,如下例中 `a[2]` 19 | # 和 `lst[2]` 这两个对象为第二层,实际上浅拷贝之后,这两个还是一个对象。深拷贝会完全的拷贝被拷 20 | # 贝对象的所有层级对象,也就是一个真正意义上的拷贝。 21 | >>> from copy import copy, deepcopy 22 | >>> lst = [1, 2, [3, 4]] 23 | >>> a, b = copy(lst), deepcopy(lst) 24 | >>> a, b 25 | ([1, 2, [3, 4]], [1, 2, [3, 4]]) 26 | >>> id(lst[2]), id(a[2]), id(b[2]) 27 | (139842737414224, 139842737414224, 139842737414584) 28 | >>> lst[0] = 10 29 | >>> a 30 | [1, 2, [3, 4]] 31 | >>> b 32 | [1, 2, [3, 4]] 33 | >>> lst[2][0] = 'test' 34 | >>> lst 35 | [10, 2, ['test', 4]] 36 | >>> a 37 | [1, 2, ['test', 4]] 38 | >>> b 39 | [1, 2, [3, 4]] 40 | ``` 41 | 42 | - `__init__` 和 `__new__`。 43 | - `__init__` 只是单纯的对实例进行某些属性的初始化,以及执行一些需要在新建对象时的必要自定义操作,无返回值。而 `__new__` 返回的是用户创建的实例,这个才是真正用来创建实例的,所以 `__new__` 是在 `__init__` 之前执行的,先创建再初始化。 44 | 45 | - *Python 2 和 Python 3 有哪些区别?* 46 | - lz 当时只是简单的说了几点: 47 | - Python2 和 Python3 的默认字符串不一样,Python3 默认为 Unicode 形式。 48 | - `raw_input()`, `input()` 49 | - 捕捉异常/错误的写法,Python2 除了后面的写法还支持:`except Exception, e`, 而 Python3 只支持 `except Exception as e` 50 | - Python3 中没有了 `xrange`, 而使用 `range` 代替它,在 Python3 中,`range` 返回的是一个可迭代对象,而不是 Python2 那样直接返回列表。 51 | - Python3 中 `map` 如果需要立即执行必须以 `list(map())` 这样的方式。 52 | - Python3 中,`print` 改成了函数,而在 Python2 中,`print` 是一个关键字。使用上有所差异。 53 | - Python3 中,`3/2 == 1.5`;Python2 中,`3/2 == 1`。 54 | - 上面知识列了几点比较常见的,这里有一篇 [Blog](http://sebastianraschka.com/Articles/2014_python_2_3_key_diff.html) 写的详细写。 55 | 56 | - 连接字符串都有哪些方式? 57 | - 格式化字符连接(`%s`) 58 | - `format` 59 | - `join` 60 | - `+` 61 | 62 | - 如何判断某个对象是什么类型? 63 | - `type(obj)` 64 | - `isinstance(obj)` 65 | 66 | - *生成器是什么?* 67 | - 一言难尽,推荐看这个 [stackoverflow 答案的翻译](https://taizilongxu.gitbooks.io/stackoverflow-about-python/content/1/README.html) 68 | 69 | - Python 中的 GIL 是什么?全称?举个例子说说其具体体现。 70 | - GIL 全称 Global Interpreter Lock(全局解释器锁),任何 Python 线程执行前,必须先获得 GIL 锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。要避免这种“现象”利用操作系统的多核优势可以有下面几种方法: 71 | - 使用 C 语言编写扩展,创建原生线程,摆脱 GIL,但是即使是扩展,在 Python 代码内,任意一条Python 代码还是会有 GIL 限制 72 | - 使用多进程代替多线程,使用多进程时,每个进程都有自己的 GIL。故不存在进程与进程之间的 GIL 限制。但是多进程不能共享内存。 73 | 74 | - `s = 'abcd', s[2] = 'e'` 运行结果是什么? 75 | - 报错,字符串是不可变对象 76 | 77 | - Python 中,`sorted` 函数内部是什么算法? 78 | - 在 [官方文档](https://docs.python.org/3/howto/sorting.html?highlight=Timsort) 里面有提到,用的是 [Timsort](https://en.wikipedia.org/wiki/Timsort) 算法 79 | 80 | - 编码是一个什么样的过程? 81 | - 编码是二进制到字符的过程 82 | 83 | - *Python 里面怎么实现协程?* 84 | - lz 当时也就简单说了下可以用 yield 关键字实现,举了个小例子,还说了用户控制调度,加上一些第三方框架,Gevent,tornado 之类的,可怜。这里安利一下 [驹哥](http://ajucs.com) 的一篇文章 [说清道明:协程是什么](http://mp.weixin.qq.com/s/TKrb0i5fF0pJdRIgGC9qQQ ) 85 | 86 | - `requests` 包新建一个 `session` 再 `get` 和普通的 `requests.get` 有什么区别?(tcp长连接) 87 | - 维持一个会话,** 建立一个tcp长连接** ,cookie 自动保存,下次请求还是一个会话。 88 | 89 | - *Python 都有哪些数据结构?可变对象,不可变对象分别有哪些?* 90 | - 可变对象:列表,字典 91 | - 字符串,数字,元组,集合 92 | 93 | - *在 Python 内,函数传参是引用传递还是值传递?* 94 | - 在 Python 内,如果形参是可变参数,更像是引用传递,如果是不可变参数,更像是值传递。面试只要说到了这个点,基本就没问题了。 95 | 96 | - 你会对你的项目写测试么?用哪些方法哪些库? 97 | - 只说了用 unitest......需要自行寻找答案。 98 | 99 | - 请新建一个只有一个元素 `1` 的列表和元组。 100 | - `lst = [1]` 101 | - `tup = (1,)` 102 | 103 | - *函数默认参数是可变对象情况。* 104 | ```python 105 | >>> def foo(a, b=[1, 2]): 106 | print(b) 107 | b.append(a) 108 | print(b) 109 | >>> val = 4 110 | >>> foo(val) 111 | # [1, 2] 112 | # [1, 2, 4] 113 | >>> foo(val) 114 | # [1, 2, 4] 115 | # [1, 2, 4, 4] 116 | # 这里可以看到,第二次执行函数时,默认参数 b 的值已经变成 `[1, 2, 4]` 了,原因是,默认参数只在第 117 | # 一次执行时会进行初始化,后面就默认使用 **初始化后的这个对象(引用)**,但是这里 b 是可变对象, 118 | #添加了一个元素还是之前的对象,所以,引用没变,不过是值变了而已。 119 | ``` 120 | 121 | 122 | 123 | - *Flask 的 Route 是怎么实现的?* 你认为 Flask 框架有什么优缺点? 124 | - 实际上在 Flask 类里面,`route` 可以简单理解为不过是把对应的路由规则作为键,装饰的视图函数作为值,存到 `werkzeug.routing.Map` 对象(可以看成是和字典类似的数据结构)里。这里是 [源码](https://github.com/pallets/flask/blob/master/flask/app.py#L1076-L1120),好理解些。这是之前写的一篇 [笔记](https://github.com/x1ah/Blog/issues/4) 125 | - Flask 优点是轻量,灵活,可高度定制,插件化。缺点也是过于轻量,功能必须通过第三方插件实现,插件质量参差不齐,也不能完全保证后期维护。 126 | - 这几点都只是个人之见,更详细标准的还需自行寻找答案。 127 | 128 | - *WSGI 是什么?uWSGI, nginx 这些都是什么用途?* 129 | - 这里有[维基百科](https://zh.wikipedia.org/wiki/Web%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3) 的解释,WSGI 就是一个通用的标准,遵守这个标准,我们能让我们的 Web 框架更加通用,编写更加简单。 130 | - uwsgi 和 Nginx 都是 Web Server,不同的是 Nginx 负责 外网请求 ---(转换)--> 内网请求,uwsgi 负责的是 内网请求 -> Python Web 程序。 131 | 132 | - nginx 和 Apache 的区别?(参考 [interview_python](https://github.com/taizilongxu/interview_python#7-apache和nginx的区别)) 133 | - nginx 相对 apache 的优点: 134 | - 轻量级,同样起web 服务,比apache 占用更少的内存及资源 135 | - 抗并发,nginx 处理请求是异步非阻塞的,支持更多的并发连接,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能 136 | - 配置简洁 137 | - 高度模块化的设计,编写模块相对简单 138 | - 社区活跃 139 | - apache 相对nginx 的优点: 140 | - rewrite ,比nginx 的rewrite 强大 141 | - 模块超多,基本想到的都可以找到 142 | - 少bug ,nginx 的bug 相对较多 143 | - 超稳定 144 | 145 | - 你部署 Python 项目时用的是 uWSGI 的哪个模式? 146 | - 默认模式 147 | - 这个应该问的可能性极小了,可翻阅 [uwsgi 文档](http://uwsgi-docs-cn.readthedocs.io/zh_CN/latest/WSGIquickstart.html) 查找更详细的资料 148 | 149 | 150 | # 数据结构,算法 151 | - 层次遍历二叉树用什么方法?(参考 152 | ```python 153 | class Node(object): 154 | def __init__(self, data, left=None, right=None): 155 | self.data = data 156 | self.left = left 157 | self.right = right 158 | 159 | btree = Node(1, Node(3, Node(7, Node(0)), Node(6)), Node(2, Node(5), Node(4))) 160 | 161 | def level_trav(tree): 162 | queue = [tree] 163 | while queue: 164 | current = queue.pop(0) 165 | print(current.data) 166 | if current.left: 167 | queue.append(current.left) 168 | if queue.right: 169 | stack.append(current.right) 170 | ``` 171 | 172 | - 非平衡二叉数如何变成平衡二叉数? 173 | - 参考 [AVL平衡二叉树详解与实现](https://segmentfault.com/a/1190000006123188) 174 | 175 | - 先,中,后序遍历二叉数。完全二叉数是什么? 176 | - 完全二叉树:深度为k有n个节点的二叉树,当且仅当其中的每一节点,都可以和同样深度k的满二叉树,序号为1到n的节点一对一对应时,称为“完全二叉树”。(摘自维基百科) 177 | - 先序:先根后左再右 178 | - 中序:先左后中再右 179 | - 后序:先左后右再根 180 | 181 | - 如何判断两个单链表是否相交于某个节点,包括 X 型,Y 型,V 型。 182 | - X 型不可能存在,一个单链表节点不存在两个不同的后继。 183 | ```python 184 | # 存在 V 型和 Y 型,如果交叉,则最后一个节点肯定是相同的,故直接从最后一个节点进行反向遍历。 185 | # 反转单链表 186 | def reverse_single_link_lst(link_lst): 187 | if not link_lst: 188 | return link_lst 189 | pre = link_lst 190 | cur = link_lst.next 191 | pre.next = None 192 | while cur: 193 | tmp = cur.next 194 | cur.next = pre 195 | pre = cur 196 | cur = tmp 197 | return pre 198 | 199 | # 寻找交叉点 200 | def point(node_a, node_b): 201 | if node_a is None or node_b is None: 202 | return None 203 | next_a, next_b = node_a, node_b 204 | while next_a or next_b: 205 | if next_a.val == next_b.val: 206 | if next_a.next and next_b.next and (next_a.next.val == next_b.next.val): 207 | next_a, next_b = next_a.next, next_b.next 208 | continue 209 | return next_a.val 210 | next_a, next_b = next_a.next, next_b.next 211 | return None 212 | 213 | # 构造单链表 214 | class Node(object): 215 | def __init__(self, value, next=None): 216 | self.val = value 217 | self.next = next 218 | 219 | a = ListNode(1, ListNode(2, ListNode(3, ListNode(4, ListNode(5))))) 220 | b = ListNode(7, ListNode(9, ListNode(4, ListNode(5)))) 221 | 222 | ra = reverse_single_link_lst(a) 223 | rb = reverse_single_link_lst(b) 224 | point(ra, rb) 225 | # output: 226 | # 4 227 | ``` 228 | 229 | - 如何判断两个单链表是否是同一个链表。 230 | - 直接判断第一个节点即可。 231 | 232 | - 单链表逆转。 233 | - 见上面判断交叉链表内的 `reverse_single_link_lst()` 函数。 234 | 235 | - *堆,栈,队列。* 236 | - [堆](https://zh.wikipedia.org/wiki/%E5%A0%86_(%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84)), [栈](https://zh.wikipedia.org/zh-hans/%E5%A0%86%E6%A0%88), [队列](https://zh.wikipedia.org/wiki/%E9%98%9F%E5%88%97) 237 | 238 | - *说说你知道的排序算法以及其时间复杂度。* 239 | ![](http://ww1.sinaimg.cn/large/005NaGmtly1fenoomcn03j30iz07874f.jpg) 240 | 241 | - *手写快速排序。画画堆排序的原理及过程。* 242 | ```python 243 | # 快速排序,lz 当时写的比较复杂,但是是最常见的写法(紧张导致有几个小bug),如下 244 | def quick_sort(lst, start, stop): 245 | if start < stop: 246 | i, j, x= start, stop, start 247 | while i < j: 248 | while (i < j) and (lst[j] > x): 249 | j -= 1 250 | if (i < j): 251 | lst[i] = lst[j] 252 | i += 1 253 | while (i < j) and (lst[i] < x): 254 | i += 1 255 | if (i < j): 256 | lst[j] = lst[i] 257 | j -= 1 258 | lst[i] = x 259 | quick_sort(lst, start, i-1) 260 | quick_sort(lst, i+1, stop) 261 | return lst 262 | ``` 263 | 264 | 之后面试官 [akun](https://github.com/akun) 大哥给了个特别简洁的写法,三路复用,地址在 [Gist](https://gist.github.com/akun/d90998068f4e1f3eb169) 265 | ```python 266 | def qsort(alist): 267 | """ 268 | quick sort(easy way, but more memory) 269 | test: python -m doctest qsort.py 270 | >>> import math 271 | >>> import random 272 | >>> size = 100 273 | >>> alist = [random.randint(0, size * 10) for i in range(size)] 274 | >>> qlist = qsort(alist) 275 | >>> alist.sort() 276 | >>> assert qlist == alist 277 | """ 278 | 279 | if len(alist) <= 1: 280 | return alist 281 | 282 | key = alist[0] 283 | left_list, middle_list, right_list = [], [], [] 284 | 285 | [{i < key: left_list, i == key: middle_list, i > key: right_list}[ 286 | True 287 | ].append(i) for i in alist] 288 | 289 | return qsort(left_list) + middle_list + qsort(right_list) 290 | ``` 291 | 292 | - 说说你所了解的加密算法,编码算法,以及压缩算法。了解 base64 的原理么? 293 | - 只说了听过 base64, md5 这几种编码。。。。。自行搜索吧,考的概率极小。 294 | 295 | # 数据库 296 | - 索引是什么原理?有什么优缺点? 297 | - 参考 [数据库索引的实现原理](http://blog.csdn.net/kennyrose/article/details/7532032) 298 | 299 | - 乐观锁和悲观锁是什么? 300 | - [深入理解乐观锁与悲观锁](http://www.hollischuang.com/archives/934) 301 | 302 | - 你为什么选择 Redis 而不是 MongoDB 或者其他的?(有个项目用了 Redis) 303 | - [redis、memcache、mongoDB有哪些区别?](https://segmentfault.com/q/1010000002588088/a-1020000002589415) 304 | 305 | - SQL 和 NoSQL 区别? 306 | - [SQL 和 NoSQL 的区别](http://www.jianshu.com/p/b32fe4fe45a3) 307 | 308 | # 网络 309 | - 从浏览器输入网址到网页渲染完毕这过程发生了什么? 310 | - [这里](https://github.com/skyline75489/what-happens-when-zh_CN) 说的非常详细,看面的岗位不同,回答的侧重点不一样。如面的 Web ,可以侧重说说 nginx -> uwsgi -> Python -> uwsgi -> nginx 这个过程,(WSGI 标准) 311 | 312 | - *TCP 三次握手四次挥手详细说下。* 313 | - [TCP协议中的三次握手和四次挥手(图解)](http://blog.csdn.net/whuslei/article/details/6667471) 314 | 315 | - 为什么是三次握手?两次不行么? 316 | - [TCP连接建立过程中为什么需要“三次握手”](http://www.cnblogs.com/techzi/archive/2011/10/18/2216751.html) 317 | 318 | - *说说 TCP 和 UDP 的区别。* 319 | - TCP(传输层) 320 | - 优点:TCP 面向连接,可靠,稳定,传输数据前需要建立连接,故有三次握手四次挥手,还有拥塞控制,重传等 321 | - 缺点:慢,占用系统资源,有确认机制,三次握手,所以容易被攻击,DDos 322 | - UDP 323 | - 优点:快,无状态传输协议 324 | - 缺点:不稳定,不可靠,容易丢包 325 | 326 | - 谈谈你对 SQL 注入、 XSS 、 CSRF 的理解。以及如何防范。 327 | - [关于XSS(跨站脚本攻击)和CSRF(跨站请求伪造)](https://cnodejs.org/topic/50463565329c5139760c34a1) 328 | - [SQL 注入](https://zh.m.wikipedia.org/zh-hans/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A),现在多数采用 ORM,以及参数化查询,很少再出现。 329 | 330 | - *说说 DNS 是什么东西。* 331 | - 根据域名寻找 主机 IP 的协议。 332 | 333 | - HTTP 是工作在七层模型的哪一层?DNS 又是哪一层?TCP 和 IP 呢? 334 | - HTTP,DNS 应用层,TCP 传输层,IP 网络层。 335 | 336 | - *说说你知道的 HTTP 方法和 状态码。* 337 | - [状态码](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81),这里只需要大概说说,以 1××,2××,3×× 这样的层面说,没有必要细到每一个状态码。 338 | - [HTTP 请求方法](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE#.E8.AF.B7.E6.B1.82.E6.96.B9.E6.B3.95) 339 | 340 | - *HTTP 的 GET 和 POST 有什么区别?* 341 | - 本质上,GET 和 POST 只不过是 **发送机制不同** 。 342 | 343 | - HTTP 和 HTTPS 的区别? 344 | - HTTPS = HTTP + SSL 345 | - HTTP 默认使用 80 端口,HTTPS 使用 443 端口。 346 | - [更详细](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE#.E4.B8.8EHTTP.E7.9A.84.E5.B7.AE.E5.BC.82) 347 | 348 | - 说说你知道的 HTTP 包头部信息里都有哪些字段。 349 | - 这个随便抓下包就知道了,就不说了~ 350 | 351 | - *HTTP 包头部信息里面的 `Host` 字段是什么作用?* 352 | - 表示当前请求服务器的主机名 353 | 354 | - 说说 cookie 里面你都知道哪些字段。 355 | - [Cookie](http://javascript.ruanyifeng.com/bom/cookie.html) 356 | 357 | - Session 是什么东西? 358 | - [Cookie/Session的机制与安全](http://harttle.com/2015/08/10/cookie-session.html) 359 | 360 | - 在写爬虫过程中,如果遇见需要加载 js 的情况你是如何处理的。 361 | - Selenium,PhantomJS 362 | 363 | - 普通匿名代理和高匿代理有什么区别? 364 | - 来自普通匿名代理的请求在服务端能看见真实 IP, 而高匿代理在服务端看不见真实 IP,只能看见代理服务器 IP。 365 | 366 | - 你知道哪些反爬措施? 367 | - 加代理,加头部,爬取速度控制,随机 UA,模拟真实用户的点击习惯去请求。 368 | 369 | # 操作系统 370 | - *进程和线程以及协程的区别?* 371 | - *多线程和多进程的区别?* 372 | - 信号量和互斥量的区别? 373 | - 堆内存是干嘛的? 374 | - 如何检验当前机器是大端模式还是小端模式? 375 | - 如何让某个程序在后台运行?(Linux) 376 | - sed, awk 用法(Linux) 377 | 378 | # 编程题 379 | 380 | - 手写二分查找,*快速排序*。 381 | - ~还有一个 SQL 语句的,一条 SQL 语句打印某张表某个 group count TOP 5。~ 382 | - Python 中正则模块 `re` 里 `match` 函数 和 `search` 函数有什么区别?举例说明。 383 | - 一条语句求 0 - 999999 内能被 7 整除的所有数的和。 384 | - 实现一个链表结构,要求其插入第一个节点,删除最后一个节点的复杂度为 O(1)。 385 | - 实现一个 `retry` 装饰器,使用如下: 386 | ```python 387 | # 可以指定重试次数,直到函数返回正确结果。 388 | @retry(retries=3) 389 | def func(*args, **kw): 390 | try: 391 | # some action 392 | return True 393 | except: 394 | return False 395 | ``` 396 | 大概可以像下面这样写, 397 | ```python 398 | from functools import wraps 399 | 400 | def retry(retries=3): 401 | def timesf(func): 402 | @wraps(func) 403 | def wrap(*args, **kw): 404 | i = 0 405 | status = True 406 | while status and i < times: 407 | status = func(*args, **kw) 408 | i += 1 409 | return status 410 | return wrap 411 | return timesf 412 | ``` 413 | 414 | - 有一个4G 的文本文件,存储的是酒店信息,每行存的是一个酒店ID,可以重复。请编写程序输出一个新文件,新文件内容为每行一条数据,每行的数据格式如下:`酒店ID + 出现次数`(最后提到了其他想法,如文件切片,bitmap 之类) 415 | - 实现一个函数,根据字典序比较两字符串大小,不允许用库函数,尽量越底层实现越好。(手写) 416 | - 实现一个函数,检验一个字符串是否符合 `xxxx-xx-xx` 这样的日期格式,注意润年,大小月,不允许用库函数,尽量越底层实现越好。(手写) 417 | - 假设有一个 9 x 9 的[数独](https://zh.wikipedia.org/wiki/%E6%95%B8%E7%8D%A8),里面只有 n 个空格没有填写数字,试讲讲你会用什么方法去填上数字,复杂度如何? 418 | - 我们是做地图相关工作的,现在给你提供一个三维的数据,数据描述的是不同时间一些地图上的一些地点坐标,分别有时间,x轴坐标,y轴坐标,请你设计一个算法,能够得到一天内地图上的 TOP 10 热点地区,地区大小也相应的自己作合适调整,开放性题目。 419 | 420 | # 概率论 421 | - 一副扑克除去大小王,共 52 张排,随机取三张扑克,求同花(三张扑克同一种花色)和顺子的概率。 422 | - 其他忘了 Orz 423 | -------------------------------------------------------------------------------- /backup/Python/Python 实习面试题整理.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: Python 实习面试题整理 4 | category: Python 5 | date: 2017-04-08T14:50:16Z 6 | url: https://github.com/x1ah/Blog/issues/9 7 | --- 8 | >下面的内容均为最近几周找 Python 实习遇见的各种面试题,记录备用。其中有 bat 之类大厂也有十几个人的初创公司~(斜体的为遇见两次以上的。) 9 | 10 | # Python 语法 11 | - *说说你平时 Python 都用哪些库* 12 | 13 | - `==` 和 `is` 区别。 14 | - `==` 是比较两对象的值,`is` 是比较在内存中的地址(id), `is` 相当于 `id(objx) == id(objy)`。 15 | 16 | - *深拷贝和浅拷贝。* 17 | ```python 18 | # 浅拷贝操作只会拷贝被拷贝对象的第一层对象,对于更深层级的只不过是拷贝其引用,如下例中 `a[2]` 19 | # 和 `lst[2]` 这两个对象为第二层,实际上浅拷贝之后,这两个还是一个对象。深拷贝会完全的拷贝被拷 20 | # 贝对象的所有层级对象,也就是一个真正意义上的拷贝。 21 | >>> from copy import copy, deepcopy 22 | >>> lst = [1, 2, [3, 4]] 23 | >>> a, b = copy(lst), deepcopy(lst) 24 | >>> a, b 25 | ([1, 2, [3, 4]], [1, 2, [3, 4]]) 26 | >>> id(lst[2]), id(a[2]), id(b[2]) 27 | (139842737414224, 139842737414224, 139842737414584) 28 | >>> lst[0] = 10 29 | >>> a 30 | [1, 2, [3, 4]] 31 | >>> b 32 | [1, 2, [3, 4]] 33 | >>> lst[2][0] = 'test' 34 | >>> lst 35 | [10, 2, ['test', 4]] 36 | >>> a 37 | [1, 2, ['test', 4]] 38 | >>> b 39 | [1, 2, [3, 4]] 40 | ``` 41 | 42 | - `__init__` 和 `__new__`。 43 | - `__init__` 只是单纯的对实例进行某些属性的初始化,以及执行一些需要在新建对象时的必要自定义操作,无返回值。而 `__new__` 返回的是用户创建的实例,这个才是真正用来创建实例的,所以 `__new__` 是在 `__init__` 之前执行的,先创建再初始化。 44 | 45 | - *Python 2 和 Python 3 有哪些区别?* 46 | - lz 当时只是简单的说了几点: 47 | - Python2 和 Python3 的默认字符串不一样,Python3 默认为 Unicode 形式。 48 | - `raw_input()`, `input()` 49 | - 捕捉异常/错误的写法,Python2 除了后面的写法还支持:`except Exception, e`, 而 Python3 只支持 `except Exception as e` 50 | - Python3 中没有了 `xrange`, 而使用 `range` 代替它,在 Python3 中,`range` 返回的是一个可迭代对象,而不是 Python2 那样直接返回列表。 51 | - Python3 中 `map` 如果需要立即执行必须以 `list(map())` 这样的方式。 52 | - Python3 中,`print` 改成了函数,而在 Python2 中,`print` 是一个关键字。使用上有所差异。 53 | - Python3 中,`3/2 == 1.5`;Python2 中,`3/2 == 1`。 54 | - 上面知识列了几点比较常见的,这里有一篇 [Blog](http://sebastianraschka.com/Articles/2014_python_2_3_key_diff.html) 写的详细写。 55 | 56 | - 连接字符串都有哪些方式? 57 | - 格式化字符连接(`%s`) 58 | - `format` 59 | - `join` 60 | - `+` 61 | 62 | - 如何判断某个对象是什么类型? 63 | - `type(obj)` 64 | - `isinstance(obj)` 65 | 66 | - *生成器是什么?* 67 | - 一言难尽,推荐看这个 [stackoverflow 答案的翻译](https://taizilongxu.gitbooks.io/stackoverflow-about-python/content/1/README.html) 68 | 69 | - Python 中的 GIL 是什么?全称?举个例子说说其具体体现。 70 | - GIL 全称 Global Interpreter Lock(全局解释器锁),任何 Python 线程执行前,必须先获得 GIL 锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。要避免这种“现象”利用操作系统的多核优势可以有下面几种方法: 71 | - 使用 C 语言编写扩展,创建原生线程,摆脱 GIL,但是即使是扩展,在 Python 代码内,任意一条Python 代码还是会有 GIL 限制 72 | - 使用多进程代替多线程,使用多进程时,每个进程都有自己的 GIL。故不存在进程与进程之间的 GIL 限制。但是多进程不能共享内存。 73 | 74 | - `s = 'abcd', s[2] = 'e'` 运行结果是什么? 75 | - 报错,字符串是不可变对象 76 | 77 | - Python 中,`sorted` 函数内部是什么算法? 78 | - 在 [官方文档](https://docs.python.org/3/howto/sorting.html?highlight=Timsort) 里面有提到,用的是 [Timsort](https://en.wikipedia.org/wiki/Timsort) 算法 79 | 80 | - 编码是一个什么样的过程? 81 | - 编码是二进制到字符的过程 82 | 83 | - *Python 里面怎么实现协程?* 84 | - lz 当时也就简单说了下可以用 yield 关键字实现,举了个小例子,还说了用户控制调度,加上一些第三方框架,Gevent,tornado 之类的,可怜。这里安利一下 [驹哥](http://ajucs.com) 的一篇文章 [说清道明:协程是什么](http://mp.weixin.qq.com/s/TKrb0i5fF0pJdRIgGC9qQQ ) 85 | 86 | - `requests` 包新建一个 `session` 再 `get` 和普通的 `requests.get` 有什么区别?(tcp长连接) 87 | - 维持一个会话,** 建立一个tcp长连接** ,cookie 自动保存,下次请求还是一个会话。 88 | 89 | - *Python 都有哪些数据结构?可变对象,不可变对象分别有哪些?* 90 | - 可变对象:列表,字典 91 | - 字符串,数字,元组,集合 92 | 93 | - *在 Python 内,函数传参是引用传递还是值传递?* 94 | - 在 Python 内,如果形参是可变参数,更像是引用传递,如果是不可变参数,更像是值传递。面试只要说到了这个点,基本就没问题了。 95 | 96 | - 你会对你的项目写测试么?用哪些方法哪些库? 97 | - 只说了用 unitest......需要自行寻找答案。 98 | 99 | - 请新建一个只有一个元素 `1` 的列表和元组。 100 | - `lst = [1]` 101 | - `tup = (1,)` 102 | 103 | - *函数默认参数是可变对象情况。* 104 | ```python 105 | >>> def foo(a, b=[1, 2]): 106 | print(b) 107 | b.append(a) 108 | print(b) 109 | >>> val = 4 110 | >>> foo(val) 111 | # [1, 2] 112 | # [1, 2, 4] 113 | >>> foo(val) 114 | # [1, 2, 4] 115 | # [1, 2, 4, 4] 116 | # 这里可以看到,第二次执行函数时,默认参数 b 的值已经变成 `[1, 2, 4]` 了,原因是,默认参数只在第 117 | # 一次执行时会进行初始化,后面就默认使用 **初始化后的这个对象(引用)**,但是这里 b 是可变对象, 118 | #添加了一个元素还是之前的对象,所以,引用没变,不过是值变了而已。 119 | ``` 120 | 121 | 122 | 123 | - *Flask 的 Route 是怎么实现的?* 你认为 Flask 框架有什么优缺点? 124 | - 实际上在 Flask 类里面,`route` 可以简单理解为不过是把对应的路由规则作为键,装饰的视图函数作为值,存到 `werkzeug.routing.Map` 对象(可以看成是和字典类似的数据结构)里。这里是 [源码](https://github.com/pallets/flask/blob/master/flask/app.py#L1076-L1120),好理解些。这是之前写的一篇 [笔记](https://github.com/x1ah/Blog/issues/4) 125 | - Flask 优点是轻量,灵活,可高度定制,插件化。缺点也是过于轻量,功能必须通过第三方插件实现,插件质量参差不齐,也不能完全保证后期维护。 126 | - 这几点都只是个人之见,更详细标准的还需自行寻找答案。 127 | 128 | - *WSGI 是什么?uWSGI, nginx 这些都是什么用途?* 129 | - 这里有[维基百科](https://zh.wikipedia.org/wiki/Web%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3) 的解释,WSGI 就是一个通用的标准,遵守这个标准,我们能让我们的 Web 框架更加通用,编写更加简单。 130 | - uwsgi 和 Nginx 都是 Web Server,不同的是 Nginx 负责 外网请求 ---(转换)--> 内网请求,uwsgi 负责的是 内网请求 -> Python Web 程序。 131 | 132 | - nginx 和 Apache 的区别?(参考 [interview_python](https://github.com/taizilongxu/interview_python#7-apache和nginx的区别)) 133 | - nginx 相对 apache 的优点: 134 | - 轻量级,同样起web 服务,比apache 占用更少的内存及资源 135 | - 抗并发,nginx 处理请求是异步非阻塞的,支持更多的并发连接,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能 136 | - 配置简洁 137 | - 高度模块化的设计,编写模块相对简单 138 | - 社区活跃 139 | - apache 相对nginx 的优点: 140 | - rewrite ,比nginx 的rewrite 强大 141 | - 模块超多,基本想到的都可以找到 142 | - 少bug ,nginx 的bug 相对较多 143 | - 超稳定 144 | 145 | - 你部署 Python 项目时用的是 uWSGI 的哪个模式? 146 | - 默认模式 147 | - 这个应该问的可能性极小了,可翻阅 [uwsgi 文档](http://uwsgi-docs-cn.readthedocs.io/zh_CN/latest/WSGIquickstart.html) 查找更详细的资料 148 | 149 | 150 | # 数据结构,算法 151 | - 层次遍历二叉树用什么方法?(参考 152 | ```python 153 | class Node(object): 154 | def __init__(self, data, left=None, right=None): 155 | self.data = data 156 | self.left = left 157 | self.right = right 158 | 159 | btree = Node(1, Node(3, Node(7, Node(0)), Node(6)), Node(2, Node(5), Node(4))) 160 | 161 | def level_trav(tree): 162 | queue = [tree] 163 | while queue: 164 | current = queue.pop(0) 165 | print(current.data) 166 | if current.left: 167 | queue.append(current.left) 168 | if queue.right: 169 | stack.append(current.right) 170 | ``` 171 | 172 | - 非平衡二叉数如何变成平衡二叉数? 173 | - 参考 [AVL平衡二叉树详解与实现](https://segmentfault.com/a/1190000006123188) 174 | 175 | - 先,中,后序遍历二叉数。完全二叉数是什么? 176 | - 完全二叉树:深度为k有n个节点的二叉树,当且仅当其中的每一节点,都可以和同样深度k的满二叉树,序号为1到n的节点一对一对应时,称为“完全二叉树”。(摘自维基百科) 177 | - 先序:先根后左再右 178 | - 中序:先左后中再右 179 | - 后序:先左后右再根 180 | 181 | - 如何判断两个单链表是否相交于某个节点,包括 X 型,Y 型,V 型。 182 | - X 型不可能存在,一个单链表节点不存在两个不同的后继。 183 | ```python 184 | # 存在 V 型和 Y 型,如果交叉,则最后一个节点肯定是相同的,故直接从最后一个节点进行反向遍历。 185 | # 反转单链表 186 | def reverse_single_link_lst(link_lst): 187 | if not link_lst: 188 | return link_lst 189 | pre = link_lst 190 | cur = link_lst.next 191 | pre.next = None 192 | while cur: 193 | tmp = cur.next 194 | cur.next = pre 195 | pre = cur 196 | cur = tmp 197 | return pre 198 | 199 | # 寻找交叉点 200 | def point(node_a, node_b): 201 | if node_a is None or node_b is None: 202 | return None 203 | next_a, next_b = node_a, node_b 204 | while next_a or next_b: 205 | if next_a.val == next_b.val: 206 | if next_a.next and next_b.next and (next_a.next.val == next_b.next.val): 207 | next_a, next_b = next_a.next, next_b.next 208 | continue 209 | return next_a.val 210 | next_a, next_b = next_a.next, next_b.next 211 | return None 212 | 213 | # 构造单链表 214 | class Node(object): 215 | def __init__(self, value, next=None): 216 | self.val = value 217 | self.next = next 218 | 219 | a = ListNode(1, ListNode(2, ListNode(3, ListNode(4, ListNode(5))))) 220 | b = ListNode(7, ListNode(9, ListNode(4, ListNode(5)))) 221 | 222 | ra = reverse_single_link_lst(a) 223 | rb = reverse_single_link_lst(b) 224 | point(ra, rb) 225 | # output: 226 | # 4 227 | ``` 228 | 229 | - 如何判断两个单链表是否是同一个链表。 230 | - 直接判断第一个节点即可。 231 | 232 | - 单链表逆转。 233 | - 见上面判断交叉链表内的 `reverse_single_link_lst()` 函数。 234 | 235 | - *堆,栈,队列。* 236 | - [堆](https://zh.wikipedia.org/wiki/%E5%A0%86_(%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84)), [栈](https://zh.wikipedia.org/zh-hans/%E5%A0%86%E6%A0%88), [队列](https://zh.wikipedia.org/wiki/%E9%98%9F%E5%88%97) 237 | 238 | - *说说你知道的排序算法以及其时间复杂度。* 239 | ![](http://ww1.sinaimg.cn/large/005NaGmtly1fenoomcn03j30iz07874f.jpg) 240 | 241 | - *手写快速排序。画画堆排序的原理及过程。* 242 | ```python 243 | # 快速排序,lz 当时写的比较复杂,但是是最常见的写法(紧张导致有几个小bug),如下 244 | def quick_sort(lst, start, stop): 245 | if start < stop: 246 | i, j, x= start, stop, start 247 | while i < j: 248 | while (i < j) and (lst[j] > x): 249 | j -= 1 250 | if (i < j): 251 | lst[i] = lst[j] 252 | i += 1 253 | while (i < j) and (lst[i] < x): 254 | i += 1 255 | if (i < j): 256 | lst[j] = lst[i] 257 | j -= 1 258 | lst[i] = x 259 | quick_sort(lst, start, i-1) 260 | quick_sort(lst, i+1, stop) 261 | return lst 262 | ``` 263 | 264 | 之后面试官 [akun](https://github.com/akun) 大哥给了个特别简洁的写法,三路复用,地址在 [Gist](https://gist.github.com/akun/d90998068f4e1f3eb169) 265 | ```python 266 | def qsort(alist): 267 | """ 268 | quick sort(easy way, but more memory) 269 | test: python -m doctest qsort.py 270 | >>> import math 271 | >>> import random 272 | >>> size = 100 273 | >>> alist = [random.randint(0, size * 10) for i in range(size)] 274 | >>> qlist = qsort(alist) 275 | >>> alist.sort() 276 | >>> assert qlist == alist 277 | """ 278 | 279 | if len(alist) <= 1: 280 | return alist 281 | 282 | key = alist[0] 283 | left_list, middle_list, right_list = [], [], [] 284 | 285 | [{i < key: left_list, i == key: middle_list, i > key: right_list}[ 286 | True 287 | ].append(i) for i in alist] 288 | 289 | return qsort(left_list) + middle_list + qsort(right_list) 290 | ``` 291 | 292 | - 说说你所了解的加密算法,编码算法,以及压缩算法。了解 base64 的原理么? 293 | - 只说了听过 base64, md5 这几种编码。。。。。自行搜索吧,考的概率极小。 294 | 295 | # 数据库 296 | - 索引是什么原理?有什么优缺点? 297 | - 参考 [数据库索引的实现原理](http://blog.csdn.net/kennyrose/article/details/7532032) 298 | 299 | - 乐观锁和悲观锁是什么? 300 | - [深入理解乐观锁与悲观锁](http://www.hollischuang.com/archives/934) 301 | 302 | - 你为什么选择 Redis 而不是 MongoDB 或者其他的?(有个项目用了 Redis) 303 | - [redis、memcache、mongoDB有哪些区别?](https://segmentfault.com/q/1010000002588088/a-1020000002589415) 304 | 305 | - SQL 和 NoSQL 区别? 306 | - [SQL 和 NoSQL 的区别](http://www.jianshu.com/p/b32fe4fe45a3) 307 | 308 | # 网络 309 | - 从浏览器输入网址到网页渲染完毕这过程发生了什么? 310 | - [这里](https://github.com/skyline75489/what-happens-when-zh_CN) 说的非常详细,看面的岗位不同,回答的侧重点不一样。如面的 Web ,可以侧重说说 nginx -> uwsgi -> Python -> uwsgi -> nginx 这个过程,(WSGI 标准) 311 | 312 | - *TCP 三次握手四次挥手详细说下。* 313 | - [TCP协议中的三次握手和四次挥手(图解)](http://blog.csdn.net/whuslei/article/details/6667471) 314 | 315 | - 为什么是三次握手?两次不行么? 316 | - [TCP连接建立过程中为什么需要“三次握手”](http://www.cnblogs.com/techzi/archive/2011/10/18/2216751.html) 317 | 318 | - *说说 TCP 和 UDP 的区别。* 319 | - TCP(传输层) 320 | - 优点:TCP 面向连接,可靠,稳定,传输数据前需要建立连接,故有三次握手四次挥手,还有拥塞控制,重传等 321 | - 缺点:慢,占用系统资源,有确认机制,三次握手,所以容易被攻击,DDos 322 | - UDP 323 | - 优点:快,无状态传输协议 324 | - 缺点:不稳定,不可靠,容易丢包 325 | 326 | - 谈谈你对 SQL 注入、 XSS 、 CSRF 的理解。以及如何防范。 327 | - [关于XSS(跨站脚本攻击)和CSRF(跨站请求伪造)](https://cnodejs.org/topic/50463565329c5139760c34a1) 328 | - [SQL 注入](https://zh.m.wikipedia.org/zh-hans/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A),现在多数采用 ORM,以及参数化查询,很少再出现。 329 | 330 | - *说说 DNS 是什么东西。* 331 | - 根据域名寻找 主机 IP 的协议。 332 | 333 | - HTTP 是工作在七层模型的哪一层?DNS 又是哪一层?TCP 和 IP 呢? 334 | - HTTP,DNS 应用层,TCP 传输层,IP 网络层。 335 | 336 | - *说说你知道的 HTTP 方法和 状态码。* 337 | - [状态码](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81),这里只需要大概说说,以 1××,2××,3×× 这样的层面说,没有必要细到每一个状态码。 338 | - [HTTP 请求方法](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE#.E8.AF.B7.E6.B1.82.E6.96.B9.E6.B3.95) 339 | 340 | - *HTTP 的 GET 和 POST 有什么区别?* 341 | - 本质上,GET 和 POST 只不过是 **发送机制不同** 。 342 | 343 | - HTTP 和 HTTPS 的区别? 344 | - HTTPS = HTTP + SSL 345 | - HTTP 默认使用 80 端口,HTTPS 使用 443 端口。 346 | - [更详细](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE#.E4.B8.8EHTTP.E7.9A.84.E5.B7.AE.E5.BC.82) 347 | 348 | - 说说你知道的 HTTP 包头部信息里都有哪些字段。 349 | - 这个随便抓下包就知道了,就不说了~ 350 | 351 | - *HTTP 包头部信息里面的 `Host` 字段是什么作用?* 352 | - 表示当前请求服务器的主机名 353 | 354 | - 说说 cookie 里面你都知道哪些字段。 355 | - [Cookie](http://javascript.ruanyifeng.com/bom/cookie.html) 356 | 357 | - Session 是什么东西? 358 | - [Cookie/Session的机制与安全](http://harttle.com/2015/08/10/cookie-session.html) 359 | 360 | - 在写爬虫过程中,如果遇见需要加载 js 的情况你是如何处理的。 361 | - Selenium,PhantomJS 362 | 363 | - 普通匿名代理和高匿代理有什么区别? 364 | - 来自普通匿名代理的请求在服务端能看见真实 IP, 而高匿代理在服务端看不见真实 IP,只能看见代理服务器 IP。 365 | 366 | - 你知道哪些反爬措施? 367 | - 加代理,加头部,爬取速度控制,随机 UA,模拟真实用户的点击习惯去请求。 368 | 369 | # 操作系统 370 | - *进程和线程以及协程的区别?* 371 | - *多线程和多进程的区别?* 372 | - 信号量和互斥量的区别? 373 | - 堆内存是干嘛的? 374 | - 如何检验当前机器是大端模式还是小端模式? 375 | - 如何让某个程序在后台运行?(Linux) 376 | - sed, awk 用法(Linux) 377 | 378 | # 编程题 379 | 380 | - 手写二分查找,*快速排序*。 381 | - ~还有一个 SQL 语句的,一条 SQL 语句打印某张表某个 group count TOP 5。~ 382 | - Python 中正则模块 `re` 里 `match` 函数 和 `search` 函数有什么区别?举例说明。 383 | - 一条语句求 0 - 999999 内能被 7 整除的所有数的和。 384 | - 实现一个链表结构,要求其插入第一个节点,删除最后一个节点的复杂度为 O(1)。 385 | - 实现一个 `retry` 装饰器,使用如下: 386 | ```python 387 | # 可以指定重试次数,直到函数返回正确结果。 388 | @retry(retries=3) 389 | def func(*args, **kw): 390 | try: 391 | # some action 392 | return True 393 | except: 394 | return False 395 | ``` 396 | 大概可以像下面这样写, 397 | ```python 398 | from functools import wraps 399 | 400 | def retry(retries=3): 401 | def timesf(func): 402 | @wraps(func) 403 | def wrap(*args, **kw): 404 | i = 0 405 | status = True 406 | while status and i < times: 407 | status = func(*args, **kw) 408 | i += 1 409 | return status 410 | return wrap 411 | return timesf 412 | ``` 413 | 414 | - 有一个4G 的文本文件,存储的是酒店信息,每行存的是一个酒店ID,可以重复。请编写程序输出一个新文件,新文件内容为每行一条数据,每行的数据格式如下:`酒店ID + 出现次数`(最后提到了其他想法,如文件切片,bitmap 之类) 415 | - 实现一个函数,根据字典序比较两字符串大小,不允许用库函数,尽量越底层实现越好。(手写) 416 | - 实现一个函数,检验一个字符串是否符合 `xxxx-xx-xx` 这样的日期格式,注意润年,大小月,不允许用库函数,尽量越底层实现越好。(手写) 417 | - 假设有一个 9 x 9 的[数独](https://zh.wikipedia.org/wiki/%E6%95%B8%E7%8D%A8),里面只有 n 个空格没有填写数字,试讲讲你会用什么方法去填上数字,复杂度如何? 418 | - 我们是做地图相关工作的,现在给你提供一个三维的数据,数据描述的是不同时间一些地图上的一些地点坐标,分别有时间,x轴坐标,y轴坐标,请你设计一个算法,能够得到一天内地图上的 TOP 10 热点地区,地区大小也相应的自己作合适调整,开放性题目。 419 | 420 | # 概率论 421 | - 一副扑克除去大小王,共 52 张排,随机取三张扑克,求同花(三张扑克同一种花色)和顺子的概率。 422 | - 其他忘了 Orz 423 | 424 | # 总结 425 | - 如果目标是大公司,多刷题,多刷题,多刷题,基础知识(操作系统,计算机网络,数据结构,算法之类)一定要劳靠,如果只是想写业务,这些将会是加分项。但是,作为一个有理想的程序猿,基础不劳,地动山摇,出来混迟早要还的。 426 | 427 | >最后顺利拿到了自己喜欢的 offer,而且个人也建议,不要一味追求大厂,选择一个自己喜欢的,技术栈符合自己的,也符合自己职业发展的公司/岗位~ --------------------------------------------------------------------------------