diff --git a/README.md b/README.md index fa7d9f1..8f1fdc2 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,1153 @@ -![](http://image.iswbm.com/image-20200607140915244.png) +# 从零到一的 Python 学习路线 -

- Build Status - - - - -

+我在我的个人公众号(Python编程时光)分享过非常多的 Python 干货,由于公众号是个十分封闭的生态,读过之后,就没什么人会记住它了。不像网站那样有搜索引擎会给它们持续的曝光,历久弥香。 +我自认为在我公众号里,发布的文章质量是非常高的,为了不让这些干货沉入海底,我开了这个仓库,方便有需要的人进行索引,择需阅读。 -## [项目主页](http://python.iswbm.com/) -在线阅读:[Python 编程时光](http://python.iswbm.com/) -![](http://image.iswbm.com/20200607130051.png) +目前目录更新内容至 2021/3/20 发的文章。 -## 文章结构 +## 01. 基础系列 -![](http://image.iswbm.com/20200607131339.png) +### 1.1 基础必学 +1、[盘点 Python 高手都写不出来的几个错误](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485974&idx=1&sn=6a6a2fb8bc5c2acd300ebecdf625086c&chksm=e8866af4dff1e3e2c528594310475edaf2839d7b09048270acbb0dccc124581eaa65b123d393&scene=27#wechat_redirect) +2、[Python基础|深入闭包与变量作用域](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485039&idx=1&sn=23557ac2640819b568a426b2db4df69c&scene=21#wechat_redirect) + +3、[Python基础|类方法的强制重写与禁止重写](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485037&idx=1&sn=8f4838b5bc919631c5cb642120b010c2&scene=21#wechat_redirect) + +4、[Python基础|多继承与Mixin设计模式](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485032&idx=1&sn=35e1c7014bc3f668cc4b48d9c318f1f9&scene=21#wechat_redirect) + +5、[Python基础|理解元组存在的意义](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485038&idx=1&sn=d0c7ab3fc20b299e4a0b9f6b414e4070&scene=21#wechat_redirect) + +6、[你知道 Python里的「单分派泛函数」?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484938&idx=1&sn=061fdb00ccc499aa0cfc304852adb143&scene=21#wechat_redirect) + +7、[类型注解的福音,提高Python代码可读性](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484901&idx=1&sn=8f506cb46edb94dfb668525399f30f9e&scene=21#wechat_redirect) + +8、[写几个 Python 进阶必备函数](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484973&idx=1&sn=451d381fa3021e14b514cc15907ce0c3&scene=21#wechat_redirect) + +9、[Python 字符串连接,哪种的效率最高?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485016&idx=1&sn=88497c09c8785b656ae1fee1406827dc&scene=21#wechat_redirect) + +10、[深入理解Python中的上下文管理器](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484950&idx=1&sn=9d78e469f8f190ef0484842b0391bec9&scene=21#wechat_redirect) + +11、[秒杀市面 90% 的 Python 入门教程 (上)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484852&idx=1&sn=2362e034c2740d9f53c3cc99a6908cbd&scene=21#wechat_redirect) + +12、[秒杀市面 90% 的 Python 入门教程 (中)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484856&idx=1&sn=881ee5d8154a85479d71ca4594f90142&scene=21#wechat_redirect) + +13、[秒杀市面 90% 的 Python 入门教程 (下)](https://mp.weixin.qq.com/s?__biz=MzU4OTUwMDE1Mw==&mid=2247485231&idx=1&sn=fa7146937f51110eb7356154bc2e2282&scene=21#wechat_redirect) + +14、[检验你 Python 基本功的 17 个骚操作](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484863&idx=2&sn=ff11a17ad82938b3a4f83ee5fbc2e497&scene=21#wechat_redirect) + +25、[和import说再见,这个库教你怎么偷懒](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485325&idx=2&sn=90c6dfbdbb36f63db72d1c0af67c1115&scene=21#wechat_redirect) + +16、[如何修改 CentOS 6.x 上默认Python 版本](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484881&idx=1&sn=ecb99878d3e2fca2bedc808b1e7aae9c&scene=21#wechat_redirect) + +17、[写 Python 时你要避免的十个错误](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484885&idx=1&sn=6acba3bf872fe1309308d9ef6cde53ce&scene=21#wechat_redirect) + +18、[看完这篇,你也是字符编码大神!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488475&idx=2&sn=17ca56435158c6c19134e30bda6e2b2d&chksm=e8867339dff1fa2f99cb0d85d99a2befa3689951778fbef8f2e0bad1042ae0c4813b99860c33&scene=27#wechat_redirect) + +19、[大白话解释什么是 Python Launcher?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485268&idx=1&sn=9cea766f1e1a1f6278fca4665fc33a87&scene=21#wechat_redirect) + +20、[13条Python2.x和3.x的区别,你知道几条?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485044&idx=1&sn=f38e2036317893be8388900dde3077e8&scene=21#wechat_redirect) + +21、[Python基础|新式类和经典类的区别?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485033&idx=1&sn=8b6357d5a66f9a06347bf0922ae11946&scene=21#wechat_redirect) + +22、[Python 中有 3 个不可思议的返回功能](https://mp.weixin.qq.com/s/dSky88bOCPgYDprzJhnlbg) + +23、[太干了!一张图整理了 Python 所有内置异常](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490040&idx=2&sn=59578093d82362c023ce305b2fc55103&chksm=e886791adff1f00c0a76154c7c7101d39b151891cc07b33dfe2523c95d9e38250c3684f674bb&scene=27#wechat_redirect) + +24、[有了这篇文章, Python 中的编码不再是噩梦](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486174&idx=1&sn=ceb21deb2ff3ce750117c23f414416e3&chksm=e8866a3cdff1e32a1fcf07880803a7a3c350cc40d612bbd4f34b84cb06bf39250932f1ba5f05&scene=27#wechat_redirect) + +25、[ 别笑!Python 新手这五大坑你躲不过](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485573&idx=1&sn=362e7c5e73c27942e8250375257620e2&chksm=e8866867dff1e17140dc043b5dd33eec0e94e1616eab7e4b415bb767883d46d58a7243fb198f#rd) + +26、[Python 3.9 发布,字典的合并操作符终于来了](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485825&idx=1&sn=04075357936423097f692fa19857c3aa&chksm=e8866963dff1e075707b605fb1352f7760fb1b41040bdda4dec6b83370af37e009424d24f65e&token=2013245174&lang=zh_CN#rd) + +27、[Python 3 入门,看这篇就够了(超全整理)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492484&idx=1&sn=7985df89a647c271a9c99b1a25f87cb4&chksm=e8858366dff20a7062c54d79cfc85945d4ae91e96c0a0dcb8383d24680c6eeb80d68a6f2b718&scene=27#wechat_redirect) + +28、[掌握 Python 中下划线的 5 个潜规则](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492133&idx=2&sn=ba4631b32f8fb4cd018a68b5411771b1&chksm=e88582c7dff20bd1c417c97e6798ba5bcacc90b55161976d7529d12f1850e413ede66110cbe3&scene=27#wechat_redirect) + +29、[还傻傻分不清什么是方法,什么是函数?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491545&idx=2&sn=193bb88089cc7583989db44707aeca90&chksm=e8867f3bdff1f62d159dca793efc1c937b1c7a135b6f25ff9d77d8d1b819c7ddc66b3ba1f8d0&scene=27#wechat_redirect) + +30、[Python 如何像 awk一样分割字符串?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491382&idx=2&sn=6e3824fa540fe7225d0cd1e7180559b6&chksm=e8867fd4dff1f6c2d2c024b6a07f616794c5b89a1b2b08ab05d029e5de1a59ff751bb03a6561&scene=27#wechat_redirect) + +31、[一篇文章带你剖析Python 字节流处理神器struct](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491241&idx=2&sn=889bf712f4115a27f321e791b3862405&chksm=e8867e4bdff1f75dc9e1e2374c3302753c3a6a28b42436e6e2550aa8ac528652b2561ad2ca48&scene=27#wechat_redirect) + +32、[OrderedDict 是如何保证 Key 的插入顺序的?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486136&idx=2&sn=81fd4039611666917ae8801bd73fe921&chksm=e8866a5adff1e34c603500e97c4b7e1cecb7a1fb43e01c6ff5a794d526d54e5a02b35240d2db&scene=27#wechat_redirect) + +33、[字典访问不存在的key 时,如何才能不报错?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490809&idx=2&sn=a256a0d8b86bae3a7dc65f7a88e4ab39&chksm=e8867c1bdff1f50d1da0e11d2dca288b085064062ecb8f68891b0bd48b8a1fca4f8ca594fc22&scene=27#wechat_redirect) + +34、[如何使用 Python 执行 js 代码?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491161&idx=2&sn=d89bbc4c214563807427703aa584a26d&chksm=e8867ebbdff1f7ad301b58ac567faec95be2d8f5be921b23ea5d3722d377c2926f32ff7b68ac&scene=27#wechat_redirect) + +35、[Python 代码覆盖率工具 - Coverage](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489752&idx=2&sn=b115d9377feb0129985295e5a3c8ec5e&chksm=e886783adff1f12c7bb14c55560f06f6a5327d6beee9bba38b9965784baf197918ae3898e95d&scene=27#wechat_redirect) + +36、[超赞!100 道让你练习 Python 基础的题目](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488558&idx=1&sn=58a53b85a7e49989794d8d0e9885f335&chksm=e88674ccdff1fdda61609dc4aa811dc6a52a819fe56da3bd0480f9f21ae248c94d8f8f3f8798&scene=27#wechat_redirect) + +37、[一道 3 行代码的 Python面试题,我懵逼了一天](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486901&idx=1&sn=73fe3a92ac2639706d9573a0eaffd48f&chksm=e8866d57dff1e441d6ffad3c4f00b053db39df1cec4e6cf69992641b359cc96448f7f3068918&scene=27#wechat_redirect) + +38、[学习 Python 的指南大纲,从基础到核心知识全都有](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486246&idx=1&sn=58665e2be95f71415ac3e2c1c5f4f690&chksm=e8866bc4dff1e2d212a4d93c0c83cd39cd534f64a8e113b208f96a6156e75ef652ee1ac32270&scene=27#wechat_redirect) + +39、[Python 中常见的配置文件写法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496552&idx=2&sn=cc57aee69875c855fcc7e7d4098dda44&chksm=e885938adff21a9cb0cd1ee105af9001ca2a597ae37fba35c76de13eb2a69033133a866950d6&scene=27#wechat_redirect) + +40、[Python 的 \__name__ 变量,到底是个什么东西?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496153&idx=3&sn=44fbf9d2c5bf1c629dab6abc1f429a1b&chksm=e885913bdff2182d69117d5f85ede8eed726f1bf00192cb270dc4dfd6e20a1e51638c6520bee&scene=27#wechat_redirect) + +### 1.2 基础库 + +1、[Python时间模块,超实用总结!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493317&idx=2&sn=b821b2765cfcdf7da8858b962f092980&chksm=e8858627dff20f31b14090d0ff178002ea0544196d8d63e595f5f6787f8e95d60df292f80bb4&scene=27#wechat_redirect) + +2、[用 Python 玩转正则表达式,这篇讲得太好了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493207&idx=2&sn=ca2c44f056ce22d132089d200951ee6d&chksm=e88586b5dff20fa3db38273731c3521a278c09ff81570d01512f203d280d253ae1f3d5fc1799&scene=27#wechat_redirect) + +3、[打基础一定要吃透这12类 Python 内置函数](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486198&idx=1&sn=efea0e57956188eeafb163d7bb2448c4&chksm=e8866a14dff1e3022f361245b182a30fbbf40550f3e8dc4456e1acf260aa73d35a20c010559d&scene=27#wechat_redirect) + +4、[8个超好用的Python内置函数,提升效率必备!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485943&idx=2&sn=ecb57c8dbf0e4965d4842cc6f614da64&chksm=e8866915dff1e0035ded3974097c1f0e00561962eeb0498c633fdf664cd429749aa6e6eca806&scene=27#wechat_redirect) + +5、[用 Python 操作 Redis,看这一篇就够了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492907&idx=2&sn=f44d9ef0e624b150c080dd075974e540&chksm=e88585c9dff20cdf6ed8b913312602838fbdffcade584412e43326d11f0a1cc41bd4ffbce94c&scene=27#wechat_redirect) + +6、[超全!我把 Python 的 200个标准库整理出来了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492755&idx=1&sn=c0f99bb14839677d228d029a564d457c&chksm=e8858471dff20d67bc162863a6193998f39aae3e1103a5b3015ceb79f4cea1dde4fda5d2cff3&scene=27#wechat_redirect) + +7、[一篇文章掌握 Python 内置 zip() 的全部内容](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491227&idx=2&sn=61efa9b45c112a25be348a94a9ac8151&chksm=e8867e79dff1f76fcc3858afdfd4e68aef0a5d0430f0420aacd2045aac86d3c31ff88bac464d&scene=27#wechat_redirect) + +8、[通过“四不要”,掌握 Python 的 Lambda 函数](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493501&idx=2&sn=696cb2ef4c3fcc6713f148d42f6eb0be&chksm=e885879fdff20e897b71e791caa6a6dbfb783c22a3ea7bf9c4cfb8406b668e57914c1c209707#rd) + +9、[原来 collections 这么好用!!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493894&idx=2&sn=3f16362bfa3ec2ecf9f7318cc50e2f95&chksm=e88589e4dff200f25c4c03294f59716431c57a65161f6c364d610950650b24a7cb4b037345e4#rd) + +10、[使用 Python 打印漂亮的表格,这两项基本功你可会?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494135&idx=2&sn=f055f78da070dc71c2593540f3272988&chksm=e8858915dff20003c05bc328b09a8cdb8dda3e37118201fb2eb317c1296e788aa1f50c8edf45&scene=27#wechat_redirect) + +11、[73个f-string的例子,帮你吃透字符串格式化](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497032&idx=2&sn=dc4761acf90fa7ff71833f0e7f952189&chksm=e88595aadff21cbca930dfa432106b6645b01ae1485d09f1d860a5779311b1d9c1bf23123146&scene=27#wechat_redirect) + +12、[一学就会的 Python 时间转化总结(超全)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496446&idx=1&sn=98c71ceefb10fb049ec33c3c1aa2bc6c&chksm=e885921cdff21b0adf359a9b75e5a44033a6cfa86a05cb6ed9e19f07c6b73b6cf97c4e3cd3c1&scene=27#wechat_redirect) + +13、[适合新手的 SQLAlchemy 上手教程](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498470&idx=2&sn=d0ee4a5367ad5195bcb8377ea6fc6101&chksm=e8859a04dff21312e8172de1e1986d41b4cf60d653d923954f48e111258a0e6019fc1bb50c1a&scene=27#wechat_redirect) + +14、[不服不行,Python 操作 JSON 的门道也这么多~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498220&idx=1&sn=249796b68dadafc9f85600fb03f8b0ae&chksm=e885990edff21018ad853a24abcd4a7c6780cc1740daddcd622bb233cdaa6d4834468284ac96&scene=27#wechat_redirect) + +15、[没想到吧?这货比 open 更适合读取文件](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498035&idx=1&sn=66e076de5448b41f252127b1f12a6d7e&chksm=e88599d1dff210c7f6f470b7917cf6b099febc93af25740562b2e2e0ec1ea42968128d26186b&scene=27#wechat_redirect) + +16、[Python输出简洁美观的文本化表格](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497768&idx=2&sn=2c49bcdf0264481f75658db0c0dbc54c&chksm=e88598cadff211dc7f140bc8c0d59d88169fde365567c82bcd8f7a8dccb4081072da83d88df0&scene=27#wechat_redirect) + +17、[使用 Python 操作 MySQL,这篇文章别错过~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497264&idx=2&sn=b11ff1be91e3297575d9eb9de3465257&chksm=e88596d2dff21fc4e404e0df21d096426df4eae33d6143bd851244cbf00adc3dc9072e9ac1a6&scene=27#wechat_redirect) + +18、[一篇文章教你如何用 Python 记录日志](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497049&idx=2&sn=0bb3de6705dd1dcef5352c2ff37991d5&chksm=e88595bbdff21cad17c69ee31955572ff64fed9ea38a8ef0a63472e89c97bc027d89101b8b57&scene=27#wechat_redirect) + +### 1.3 代码案例 + +1、[15个Pythonic的代码示例](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485036&idx=1&sn=24de1996a63bf25b0c0deec782f688cf&scene=21#wechat_redirect) + +2、[常见 Python 简洁代码的样例](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484823&idx=2&sn=4459a7edc4a4989068b22b31c76f2c92&scene=21#wechat_redirect) + +3、[每天花 30 秒,就可以练习的 Python 小技巧!](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484861&idx=1&sn=e5fa97570b3c180e5a5edc753fc62669&scene=21#wechat_redirect) + +4、[精心整理!9个 Python 实用案例分享](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492921&idx=2&sn=800c245cf392dfed915c618c68a5fe80&chksm=e88585dbdff20ccdd85dc21989092ab3e028dd2e3bcfb8f349bc3a7df99b299b6f8a43b57371&scene=27#wechat_redirect) + +5、[瞧瞧,这样的代码才叫 Pythonic](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493439&idx=1&sn=2ed198c261ee285f2e3c1cbdf5c78541&chksm=e88587dddff20ecb24804e6b191b690d7950584104b5ed94b2f1e6a043a24909dfe418e64df6#rd) + +6、[瞧瞧,这样的『函数』才叫 Pythonic](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493550&idx=1&sn=cd5cf944c3e3ac3f2b567def827de2d4&chksm=e885874cdff20e5aeb438aab72e2f87329a403024bb0cae3308e61d5f9cc9a15609da6d8b7dd#rd) + +7、[这样的奇技淫巧不可取,切记切记](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493635&idx=2&sn=cc61ae5798c278c52a07e3e7129a5bb1&chksm=e88588e1dff201f7c4997d9fe8004d86973833bbe91ded31fbf93cdf00d1ec22f19f8cff825c&token=58703854&lang=zh_CN#rd) + +8、[别这样直接运行Python命令,否则电脑等于“裸奔”](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494114&idx=1&sn=202d3492ae48d73431173b76caa64345&chksm=e8858900dff2001626f47454f8dee79679f43b74701c7730e1c499f4c895edafca5aa68e280f&scene=27#wechat_redirect) + +9、[6 个例子教你重构 Python 代码](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494309&idx=2&sn=e1cffffa71b5cc76434b6771cfcc0bae&chksm=e8858a47dff20351575b1cd29e35a51b30c7859c9dffbf816df883bc7ccee9ea15aa5eaad812&scene=27#wechat_redirect) + +10、[25 条很棒的 Python 一行代码](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496971&idx=2&sn=9196437b9976a382b277c7780f8aa04b&chksm=e88595e9dff21cffc32c8da9d2a5d59ab40d7e1d1d32f9193687c35cba490919c624d04d7ef6&scene=27#wechat_redirect) + +11、[Python 中的 EAFP 和 LBYL 代码风格到底是什么?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496204&idx=2&sn=5aa0cca75c6b59bd5f1b6f9cd1bfe07e&chksm=e88592eedff21bf857a0d53a1c3515e5e4ad32c5ae728218b6dedf63fcd02a9750a9f66387d4&scene=27#wechat_redirect) + +12、[3000 字教你学会最地道的 Python 编程风格](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494858&idx=1&sn=9386de992ce9f9b08d2d6d2721584a39&chksm=e8858c28dff2053ec9ca59d1d4244c119ca4363b6914e87b3ef250a54bb8a82c611ab437763e&scene=27#wechat_redirect) + +13、[再来 6 个例子教你重构 Python 代码](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494838&idx=2&sn=eeaf56eeaf9cc72f1340233bcd88b5a7&chksm=e8858c54dff20542f4712be4bd185f2e27e78e09bb77acb97e2be2d4c1329553be4abe25a3e2&scene=27#wechat_redirect) + +14、[如何在Python里面实现链式调用?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498592&idx=2&sn=19b2f757b9887a67b0d2cfb8b0cb924e&chksm=e8859b82dff21294793e2b9a4eac9c375509796044d964aeb24c0448f9ec16b4cf3cad0102a9&scene=27#wechat_redirect) + +15、[Python 怎么捕获警告?(注意:不是捕获异常)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498517&idx=1&sn=2540f928893ffecc88a7aa86c330de91&chksm=e8859bf7dff212e1d8425ccff1c2dd043b5d611b291c4a5db729d7416189571d99e2c8d4fdee&scene=27#wechat_redirect) + +16、[a is b 为 True,a == b 一定为 True 吗?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497362&idx=1&sn=993ad8a3f86d467db967ea13d52175cc&chksm=e8859670dff21f66c76d2c4e0c61db9adda56824eba878be40e8b4ed8409c6922c8526f818fc&scene=27#wechat_redirect) + +## 02. 进阶系列 + +### 2.1 进阶必学 + +1、[描述符:其实你不懂我(一)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484934&idx=1&sn=ef8b30ffe2467e5736036bd083b340ca&scene=21#wechat_redirect) + +2、[描述符:我无处不在!(二)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484927&idx=1&sn=8a3d673ee20bd418d93a249380ca8e76&scene=21#wechat_redirect) + +3、[Python静态方法其实暗藏玄机](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484911&idx=1&sn=a16846030c3589c912aec9efdf4f7f80&scene=21#wechat_redirect) + +4、[全面深入理解 Python 面向对象](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485208&idx=1&sn=61bf8d3ec81fa85b0bc9de6e5f5d6611&scene=21#wechat_redirect) + +5、[几个使用装饰器的小技巧](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484930&idx=1&sn=ed731e32b6e95e7d83d74a544f97142a&scene=21#wechat_redirect) + +6、[围观大神是如何用 Python 处理文件的?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497532&idx=1&sn=bed756631d9c94c2eb2d57e0b29f6ab6&chksm=e88597dedff21ec899e90126cbe219be5fc738229bf69a02c490435c4ed57cbe32636198bb44&scene=27#wechat_redirect) + +7、[Python进阶开发|元类编程](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485080&idx=1&sn=45e6d9995e4469d8b7bce2f800ac0f9b&scene=21#wechat_redirect) + +8、[Python进阶开发之网络编程](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485078&idx=1&sn=e0a3855d959c178b8c01bd196a048dce&scene=21#wechat_redirect) + +9、[Python 进阶:深入 GIL (上篇)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484875&idx=2&sn=8780a349c60522ad1b7aa20a3fe7a19c&scene=21#wechat_redirect) + +10、[没掌握好这24条,别说Python慢。](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484910&idx=1&sn=df8fb32a840a28954614b09bce730b72&scene=21#wechat_redirect) + +11、[花了两个星期,我终于把WSGI整明白了](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484919&idx=1&sn=bd7d2bc0ab8a41110d5d93e44ad20b1f&scene=21#wechat_redirect) + +12、[源码解读|Flask 上下文核心机制](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484943&idx=1&sn=ca846404d30a2ec775ab7db5df73aa8e&scene=21#wechat_redirect) + +13、[说说几个 Python 内存分配时的小秘密](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484853&idx=2&sn=edf7c3225d119291fae5d05820f55aac&scene=21#wechat_redirect) + +14、[写了三年代码,还是不懂 Python 世界的规则](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484886&idx=1&sn=f46917f6e23f8961c912606a281a354d&scene=21#wechat_redirect) + +15、[如何保护你写的 Python 代码?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484858&idx=1&sn=e18a1c89f14d4e4043833e3ff5cc8d32&scene=21#wechat_redirect) + +16、[27 个问题,告诉你 Python 为什么如此设计?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484862&idx=1&sn=89500433bc174dfc0530eebea2fb91d1&scene=21#wechat_redirect) + +17、[精心整理 30 个Python代码实现的常用功能](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485147&idx=2&sn=c5b871e1cebde6b311609f6de703f2e3&scene=21#wechat_redirect) + +18、[高手之路:从零开始打造一个Web服务器](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484891&idx=1&sn=ed12d175452c2efedeefb1635063177c&scene=21#wechat_redirect) + +19、[从0到1:全面理解 RPC 远程调用](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484915&idx=1&sn=427b9dda155e4201c2f939c3919def91&scene=21#wechat_redirect) + +20、[一篇 Python 函数式编程指南](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484834&idx=1&sn=559bac4ff85f7082bc989a6fde04d3f3&scene=21#wechat_redirect) + +21、[没看完这11 条,别说你精通 Python 装饰器](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484900&idx=1&sn=3997a2a377577e3d16a9b7f8e6a5ea53&scene=21#wechat_redirect) + +22、[程序卡住了?教你如何调试已在运行的程序](https://mp.weixin.qq.com/s/QC-Pc-0iifaVKOfsiNTYmA) + +23、[字符串在Python内部是如何省内存的](https://mp.weixin.qq.com/s/jp_I82fnr0-3cd6ioZcoGQ) + +24、[巧用 traceback 定位 Python 内存泄漏](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485751&idx=1&sn=8a054a575767c103c830a6b3ef5f73c8&chksm=e88669d5dff1e0c3ae1c7c32f35a8f46a3a2e0a637fb4b947566f417dd8f4419bec636c8a667#rd) + +25、[非常全的通俗易懂 Python 魔法方法指南(上)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485551&idx=1&sn=4c0983f22269a113bcdf83690e5e2b20&chksm=e886688ddff1e19b9ad230128a67ee1a9ee1eced0720c14b5d48f68943be10b1b85b23d8ca2d#rd) + +26、[非常全的通俗易懂 Python 魔法方法指南(下)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485555&idx=1&sn=0a218b796e651b451a17112e22790d07&chksm=e8866891dff1e18771a9392da7f509732244ebc4d1a6e2427acd39ee8b59b146e3d4961a2a62#rd) + +27、[教你如何阅读 Python 开源项目代码](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485451&idx=1&sn=e15856352c18297770c67d9df66ec3b0&chksm=e88668e9dff1e1ff0f6a39f3885f4126232213149ac09daa1fc0ec9e0de2095bd11fb727d63a#rd) + +28、[这个 Python 知识点,90% 的人都得挂~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490984&idx=1&sn=135b7b187d8d41b3c10179dbaedaf77e&chksm=e8867d4adff1f45cd90fe2609fe7d1840b07fab8cf19cb7406ebd038c31df0028e611077d80e&scene=27#wechat_redirect) + +29、[一篇长文学懂 PyTorch](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489158&idx=1&sn=2e2e88565131d11271aea61b0bedf182&chksm=e8867664dff1ff728ee30e1baab0d7df0139fc67e2514ffcaebda6d50e5d08bd2866ad7dee35&scene=27#wechat_redirect) + +30、[学了这么久,你知道Python是如何运作的吗?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488458&idx=1&sn=23040c906e83462c4f64039cba6e3dfb&chksm=e8867328dff1fa3e149248e53762444b1abc72df395a597639357b5525d16b1b6db242339d20&scene=27#wechat_redirect) + +31、[求你了,别再使用 pprint 打印字典了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486288&idx=1&sn=7647e1ee63c49c14472baec19fb79b87&chksm=e8866bb2dff1e2a4d5b85ae9db1e8f4d8dda839643fb02b9e0b7f7fe6143cb627773e56d44bf&scene=27#wechat_redirect) + +32、[Python实现RabbitMQ中6种消息模型](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486017&idx=1&sn=9b95896abee7a345e3abee1ac4f2edab&chksm=e8866aa3dff1e3b547ef5062c30516b2d54a051fd4a07d85078223ea673af1400be1a6c56ad5&scene=27#wechat_redirect) + +33、[想写好面向对象的代码,这几篇一定要看(下)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485938&idx=1&sn=a725785ef8000868b7cb85606ee754d3&chksm=e8866910dff1e006f195d6ea0d66f4b4f1fe5a9b97debc18ae4b04a239a347d342fbc67b44e4&scene=27#wechat_redirect) + +34、[想写好面向对象的代码,这几篇一定要看(中)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485926&idx=1&sn=ce148af333d9f377d8a6fb9577bbf57c&chksm=e8866904dff1e012ec1bb8f418c43cca7954979bd1481707761b29d1e632ace6369efd50210b&scene=27#wechat_redirect) + +35、[想写好面向对象的代码,这几篇一定要看(上)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485913&idx=1&sn=74b34bd95d513a94201ad05604c4a05f&chksm=e886693bdff1e02dd2079cb90458aac914e5cb832aec59677da8b2f85200baaa57e804a4b58e&scene=27#wechat_redirect) + +36、[教你 10 分钟构建一套 RESTful API 服务](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488534&idx=2&sn=d593dbf907217ff4dea1daf636062778&chksm=e88674f4dff1fde26b1a1074a0a0c78be0630a34b4aed705755946bd8cfcde1a23e0df916392&scene=27#wechat_redirect) + +37、[要搞懂元类(metaclasses),这篇文章一定要看](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493280&idx=2&sn=d1a0aa45b9ecd03bd940328bc031458c&chksm=e8858642dff20f54ecbe8d03aebe2dacdc1fe0fdb1daa3855e09f565f05b711c9aa4eabd9ddb&scene=27#wechat_redirect) + +38、[弄懂这 6 个问题,拿下 Python 生成器!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493273&idx=2&sn=89bd3d18e7837f59c41d2f021047a1f9&chksm=e885867bdff20f6d0d72d463a255d2a51225321f8bc6115523411e540305e669a24580e58d78&scene=27#wechat_redirect) + +39、[`[]` 与 list() 哪个快?为什么快?快多少呢?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493457&idx=2&sn=67d576d2aeab16d4ad9c1b491e10deaf&chksm=e88587b3dff20ea501789da4c67560e21708f89032732eaca544e22c65780ea249e98e6ab50e#rd) + +40、[连 Python 生成器的原理都解释不了,还敢说 Python 用了 5 年?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493530&idx=1&sn=5096c680a63a57b1f55d1a9d372ec302&chksm=e8858778dff20e6e257fcacfb644bc4736afac692ab19f41717f4cdc084eb43951aba0912187#rd) + +41、[Python 列表去重的4种方式及性能对比](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493746&idx=2&sn=218b1a28af72347363557bdaac32d016&chksm=e8858890dff20186a8fefe81e11a1aa64e787a75b3930ecb6e4421e132a8322dbc5fe3b81b30#rd) + +42、[第一次把 Python 的切片理解得如此透彻](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493987&idx=2&sn=ca90bbc53a31c0ccc84fea4869fd1b81&chksm=e8858981dff20097a5167ef9d60fe4c54479b6f50b85188ffc43490c03b8cb2bec8464975793#rd) + +43、[为什么继承 Python 内置类型会出问题?!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494268&idx=2&sn=67ea6f78e7add1d43174f601626d1cb5&chksm=e8858a9edff20388c08f6e3f9c8ce9db6969174fc3674bf04d65cf0fbcda48c08217de5d622b&scene=27#wechat_redirect) + +44、[Python最会变魔术的魔术方法,竟能"大变活人"](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496126&idx=2&sn=083eafff3facad62a0660061990c7387&chksm=e885915cdff2184aa79584c1e46d3b40bc928eeb732a856e9ff26e7864a3fb1fb81749d73364&scene=27#wechat_redirect) + +45、[说说 Python 的内置电池,你学过吗?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495322&idx=2&sn=7271490d820c6a48b9ee7d090c1c88bd&chksm=e8858e78dff2076e4b5651811652d2da47d2a1db07eda9362120b51f83da7b65aca58d1ad3a6&scene=27#wechat_redirect) + +46、[恶补了 Python 装饰器的六种写法,你随便问~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494951&idx=1&sn=43b65fade87940be918ff1ff066b9ca8&chksm=e8858dc5dff204d3571414164e63a1de715499418805477d012104fa12e6a2577ef5fa58e6a8&scene=27#wechat_redirect) + +47、[Python 从业十年的程序员,写的万字经验分享](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494942&idx=1&sn=84efbced575838b9b8e169e8c59383b7&chksm=e8858dfcdff204eaf7d251ddf1ea69655bd5eb69da596a4970c845f912c2ac9e3f2f18e5e7b5&scene=27#wechat_redirect) + +48、[Python 优化机制 "常量折叠" 是究竟是怎么回事?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497951&idx=2&sn=e54c8412c056e98a34a4ce622915221d&chksm=e885983ddff2112b359c3e2a3856357a3fcccc24e5825fa0a6b972d0f5c64662df363c8d705e&scene=27#wechat_redirect) + + +### 2.2 包的管理 + +1、[最全的 pip 使用指南,50% 你可能没用过](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484827&idx=1&sn=df0923856c820e10baca20c9873b336b&scene=21#wechat_redirect) + +2、[花了两天,终于把 Python 的 setup.py 给整明白了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497217&idx=1&sn=acce69f1f39f2688056fd72a9d7044bf&chksm=e88596e3dff21ff5bf1c0e6f8a26e218458aa079d987810811f2d1ebd3704cdb1f35881229c8&scene=27#wechat_redirect) + +3、[深入探讨 Python 的import机制:实现远程导入模块](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484838&idx=1&sn=1e6fbf5d7546902c6965c60383f7b639&scene=21#wechat_redirect) + +4、[盘点 Python 依赖库管理的工具:pip、pipreqs、pigar、pip-tools、pipdeptree](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485120&idx=1&sn=170ef1173eabc5bed28c724e19d6c0ac&scene=21#wechat_redirect) + +5、[如何使用 pyenv 运行Python的多个版本?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486239&idx=2&sn=863e80589f5eb9b2daa9701f6b494997&chksm=e8866bfddff1e2ebc244354238aedfa9291aa27ad8f7e466213236369dae9800b29b5766c0cf&scene=27#wechat_redirect) + +6、[如何管理 Python 的虚拟环境?超全讲解virtualenv的使用](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485049&idx=1&sn=c16383d6cc91a7ed8254e344d994f101&scene=21#wechat_redirect) + +7、[一款让Python开发效率提升50%的工具包](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489006&idx=1&sn=eead45b3c34777085465f9854a433e36&chksm=e886750cdff1fc1ae2791adba1231e045630417f9b4185217817183aa1f260815acabcd39363&scene=27#wechat_redirect) + +8、[记 Python “用户环境”的一次完美应用](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486191&idx=1&sn=e556df305ce1df4c4969d2fdbe884cfa&chksm=e8866a0ddff1e31b2ca8ca8fbbe0355fb8c24376d715224ef2176e613b5601764be0193e3aa4&scene=27#wechat_redirect) + +9、[学了半天,import 到底在干啥?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493413&idx=2&sn=770c9bb7104dfb8c53c544f546cf7aa4&chksm=e88587c7dff20ed143cb93b4cd1b0f64af8b9c849fd9d531e58a9ba27a449d4a28cd96091b40&scene=27#wechat_redirect) + +10、[非常干货:Python 探针实现原理](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493427&idx=1&sn=d9e0c646a98cf2b54b5724ebc3262255&chksm=e88587d1dff20ec71ed33c5165bbdec377eea89307da0f924784cff0f4c01c05a5f3247de50e&token=58703854&lang=zh_CN#rd) + +11、[如何编码检查依赖关系是否有循环依赖](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496866&idx=2&sn=a25b46ef4aa67a007612ef53bc832811&chksm=e8859440dff21d5647027fdcdad642c3f9781f140f7ecb47f975678eb74b8321d4b65ec076cc&scene=27#wechat_redirect) + +12、[关于包导入,这三个知识点太多人不知道了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496204&idx=1&sn=cf788dfc35b7377ed70369aafba19c47&chksm=e88592eedff21bf872d8b6c5ded642ea5e2f2f55dcd31331da9bcd13f265cde7fee2f65ca8f5&scene=27#wechat_redirect) + +13、[手把手教你发布 Python 项目开源包](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494964&idx=2&sn=cac94af61d53857c2fa7362f2b4f8564&chksm=e8858dd6dff204c02b5235eb0a705e9193d3c5107369a87d35753bcb2e7f80020ceaccfbb9e9&scene=27#wechat_redirect) + +14、[有人在代码里下毒!慎用 pip install 命令](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494847&idx=1&sn=822e027acb42d6ab882687fc515f14ae&chksm=e8858c5ddff2054b80c6eda85f27331472ad78bcc11ca980cf03cae0824255666b0518afb2cc&scene=27#wechat_redirect) + +15、[简化 Python 函数调用的 3 种技巧](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494847&idx=2&sn=bec329103690975870e79fc00abdeba7&chksm=e8858c5ddff2054bdbddfb145caf763aab5130c3939d39a737cb3bd42934f7a74b8d3fb0e06d&scene=27#wechat_redirect) + +16、[如何 Import 自定义的 Python 模块?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494821&idx=2&sn=646396ea0a67ab3eed7b0f0652e647dd&chksm=e8858c47dff2055109d74b13de70efe7a5083fd1a99a1323043e932694a572291d00359bec15&scene=27#wechat_redirect) + +17、[解锁装包新姿势,这个场景下 pip 真的难用~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494820&idx=1&sn=24ceaa11376cbfa5a4d48adc076603db&chksm=e8858c46dff20550d0124ef1bf1e481e14ffcf485e8c49aa77f6f9541f6163850538e4af05f9&scene=27#wechat_redirect) + +18、[Python 的 import 居然这么有料](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497490&idx=1&sn=8667466d2dcaee300e990b0d4462e485&chksm=e88597f0dff21ee6787df0506a0c4b354df6b960c2c8dbf349ead6e43557230ae81fa0c661d7&scene=27#wechat_redirect) + +### 2.3 性能优化 + +1、[Python高效代码实践:性能、内存和可用性](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485161&idx=1&sn=07625f06db330897bc8bfee880ceac18&scene=21#wechat_redirect) + +2、[Python 代码的性能优化之道](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485004&idx=1&sn=8dc07a40bcac30c93874398b17a52831&scene=21#wechat_redirect) + +3、[7 个习惯帮你提升Python运行性能](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484957&idx=1&sn=9b3c644e6b1777b77ce7f84047c6d500&scene=21#wechat_redirect) + +4、[如何提升你的 Python 代码健壮性(上)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484871&idx=1&sn=985b79143d0cafd21e5263ee79afa777&scene=21#wechat_redirect) + +5、[如何提升你的 Python 代码健壮性(下)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484867&idx=1&sn=b8eab8416229dc74f1ac96e099a3df71&scene=21#wechat_redirect) + +6、[将 Python 运行速度提升 30 倍的神技巧](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484877&idx=1&sn=f6067875f1e807d19291e383f8421a3b&scene=21#wechat_redirect) + +7、[实战讲解:Python 性能分析与优化实践](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485404&idx=1&sn=486fcf898d6dd21b0feb990de3c1e08f&chksm=e886673edff1ee28b8c6e1d520b2732b2f66f4c4691ef2696b54743b1ad0769356e12a1e98b2#rd) + +8、[如何调试Python 程序的内存泄露问题](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488577&idx=2&sn=16aff9687a4a16faa492118cd5c1bfae&chksm=e88674a3dff1fdb5b80a4547cb863fcf4255f90ab2024ae8c3bbf1f270efb13333b2f7426baf&scene=27#wechat_redirect) + +9、[牛逼!一行代码让 pandas 的 apply 速度飙到极致!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493908&idx=2&sn=0413803e1a73bf632c2d5ba24c6e7ce4&chksm=e88589f6dff200e020a32578706e7c4791be700ce9f16f810d5b19dcc6a99bce2d5c7ba96b6d#rd) + +10、[3 倍性能提升!升级 Flask 到 Quart](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494567&idx=1&sn=d91202b10ab957a58c5eb70fa159fe67&chksm=e8858b45dff2025387626bac42e8441e8d0c8c290070f5fc1a885776fb6a3a06ce3467b1b8d2&scene=27#wechat_redirect) + +11、[超干分享!如何提高Python的运行速度?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494621&idx=2&sn=00c8aa00a85f8710fada38d35b2c8388&chksm=e8858b3fdff2022991a0024757c64faaed81da830cbbf1fa62b6b218d7271b0837b11922c1f0&scene=27#wechat_redirect) + +12、[程序运行慢?你怕是写的假 Python](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495235&idx=1&sn=816c6de9e1d23d65fe8c1db458426f68&chksm=e8858ea1dff207b749b64461bb06bf8818883644a4450b6400efda4d7e54d4832162448e9f68&scene=27#wechat_redirect) + +13、[超干分享!如何提高Python的运行速度?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494621&idx=2&sn=00c8aa00a85f8710fada38d35b2c8388&chksm=e8858b3fdff2022991a0024757c64faaed81da830cbbf1fa62b6b218d7271b0837b11922c1f0&scene=27#wechat_redirect) + +14、[快亦有道!让 Python 变快的 5个方案](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497409&idx=2&sn=12667ea2e03ecb9db03478b17778ffea&chksm=e8859623dff21f358e56375becfa2ea8873235c2f25a216c36ffbd7a76ce2f13c7cbc269d58d&scene=27#wechat_redirect) + +### 2.4 并发编程 + +1、[并发编程01|从性能角度来初探并发编程](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485074&idx=1&sn=a859c6ab1d9b95c30c9f8b06f9489887&scene=21#wechat_redirect) + +2、[并发编程02|创建多线程的几种方法](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485073&idx=1&sn=854ff524645247e5020d977e57d9c0e6&scene=21#wechat_redirect) + +3、[并发编程03|谈谈线程中的“锁机制”](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485069&idx=1&sn=52370a27d4a5c4541969921ada890e0b&scene=21#wechat_redirect) + +4、[并发编程04|消息通信机制/任务协调](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485068&idx=1&sn=fc0798e84e7845cd9efee1809f932f15&scene=21#wechat_redirect) + +5、[并发编程05|线程中的信息隔离](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485066&idx=1&sn=0bb9d6c82a6d062e50d09e1eefd09427&scene=21#wechat_redirect) + +6、[并发编程06|如何创建线程池](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485065&idx=1&sn=60891b67b139806cf6bf4c05f5861f02&scene=21#wechat_redirect) + +7、[并发编程07|从生成器使用入门协程](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485064&idx=1&sn=8584bb778152a12ca335970bed9fdbdc&scene=21#wechat_redirect) + +8、[并发编程08|深入理解yield from语法](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485063&idx=1&sn=0ff7a99058320ff90a6237e7e03367fb&scene=21#wechat_redirect) + +9、[并发编程09|初识异步IO框架:asyncio](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485061&idx=1&sn=9e846df1a5cb57e0bc6254dcc953e243&scene=21#wechat_redirect) + +10、[并发编程12|学习异步IO框架:asyncio](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485058&idx=1&sn=92ef1f79ce6488670ae13e5d6a1c7908&scene=21#wechat_redirect) + +11、[并发编程11|实战异步IO框架:asyncio](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485057&idx=1&sn=8bacf0f2b42de5962fbdf32796903f27&scene=21#wechat_redirect) + +12、[百万「并发」之Python异步编程(上篇)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484896&idx=2&sn=5d8458ede3440ae501d19c5ffd5f8a99&scene=21#wechat_redirect) + +13、[百万「并发」之Python异步编程(中篇)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484889&idx=2&sn=25d11042e5b2ab17dff40535d354b8f8&scene=21#wechat_redirect) + +14、[百万「并发」之Python异步编程(下篇)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484885&idx=2&sn=b9e62a9b024358aec629e876b76e0eb5&scene=21#wechat_redirect) + +15、[如何一行 Python 代码实现并行?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484826&idx=1&sn=6c5b575b7b134642077cfde2bb8b613f&scene=21#wechat_redirect) + +16、[asyncio:从原理、源码到实现](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485245&idx=1&sn=a08826c3d28037479190fd652438bcca&scene=21#wechat_redirect) + +17、[为什么说线程是CPU调度的基本单位?](https://mp.weixin.qq.com/s/c3aZ-6UzZVD3_PvVhiDPWA) + +18、[非常适合小白的 Asyncio 教程](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495734&idx=1&sn=2fb31e6fd306f19ba03b368f5924b7e8&chksm=e88590d4dff219c252b4e1126f196e0a0c395f4f8d22d58cc5c531d9dd2acad6f6e295a1fee8&scene=27#wechat_redirect) + +19、[说说 Python 里关于线程安全的那些事儿](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486279&idx=1&sn=2b6acf717b7f5cc6a2b72c62266dbe01&chksm=e8866ba5dff1e2b318c679413fd7d0ac3a69db7cab1ceda03eca26fb71f6d2837af80e2143d0&scene=27#wechat_redirect) + +20、[异步 Python 比同步 Python 快在哪里?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494549&idx=2&sn=1120b33a12bf8ab25df805cfab9afbc8&chksm=e8858b77dff20261afdf788de30c7e14f3d66ff52215b8c0db66320a118e4544283424340d4e&scene=27#wechat_redirect) + +21、[为什么 Python 多线程无法利用多核?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494567&idx=2&sn=653795446bacff2d445cabd417748756&chksm=e8858b45dff202530afc05a00469b20a0a95f53bcaefc49472240e1c905459ecfb74edbe0461&scene=27#wechat_redirect) + +22、[Flask 之父:我不觉得有异步压力](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496214&idx=2&sn=cd813e57b7e03aff0300f1f65cc583f0&chksm=e88592f4dff21be2738eaf2745cf828841fea934e986f9a4ee7d13eb9fe09701d1c13993435c&scene=27#wechat_redirect) + +23、[Python 协程的本质?原来也不过如此](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495553&idx=1&sn=d00916691bf2cddc576f1346672ef603&chksm=e8858f63dff20675238e5eee747645d022aa316bd5417004c296157aadb4de6134d6118e66e7&scene=27#wechat_redirect) + +### 2.5 实战练习 + +1、[适合 Python 新手练习的绝佳项目](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485898&idx=2&sn=36a1b1a94dece0a290652da23679cff0&chksm=e8866928dff1e03e58b95b3cd24dbc23e368b6eb2c0599b2efe814924e21ec5c01935b2e0455&token=2013245174&lang=zh_CN#rd) + +2、[4个Python实战项目,让你瞬间读懂Python!](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485852&idx=2&sn=eb3471a260cce835a755a780d6cc690d&chksm=e886697edff1e068bd0f9d456e26ee5ebe482580457951e42807e731d266c95f00c37de22fea&token=2013245174&lang=zh_CN#rd) + +### 2.6 GUI 应用 + +1、[如何在Python中编写精美图形界面?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486179&idx=1&sn=eed86ec2f44a92fadb94f237178b3f8a&chksm=e8866a01dff1e317f450d91b88e91d6912d6a1b2ac7a34ebb86b74a2abc0f912d8ced5a3baac&scene=27#wechat_redirect) + +2、[或许,这是最强大的一款Python GUI工具](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492571&idx=2&sn=6ac2c0c19d86d653acd82d5b4ef86126&chksm=e8858339dff20a2f595159499e8eb8e072b52e61521e4a1322337f174bff942134a25b3b6b9b&scene=27#wechat_redirect) + +### 2.7 自动化 + +1、[付费?是不可能的!20行Python代码实现一款永久免费PDF编辑工具](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492085&idx=1&sn=785968af86bf998ecad9b11583a23ad6&chksm=e8858117dff2080119f596c6447d9a0c7b73c6bced8306883900f2f4d9582929f9d5c26ac45d&scene=27#wechat_redirect) + +2、[带你用 Python 实现自动化群控(入门篇)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491979&idx=2&sn=7489738d3225a96767173e4b54023f95&chksm=e8858169dff2087fed0f11ad6b880c110e3530eae28ed7702343f6d81163860ed20a2f056294&scene=27#wechat_redirect) + +3、[最全总结:Python 发送邮件的几种方式](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489568&idx=2&sn=bdac42b533784bf3a8ee523da36bacba&chksm=e88678c2dff1f1d499321032e6871f2810608909121bf56aee184c6d7770d3b310f39cad7c43&scene=27#wechat_redirect) + +4、[太全了!使用 Python 转换 PDF,看这一篇总结就够了。](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488934&idx=2&sn=eed1cab8e03c908fdfe311bf4bbd91a6&chksm=e8867544dff1fc52e0d9dd62c61d36ec5a5b6847aeb9992e5423eb4e01f08995461160adbb45#rd) + +5、[Python吊打Excel?屁!那是你不会用!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487151&idx=2&sn=9c4a697758af01c086cced44b98a3380&chksm=e8866e4ddff1e75bd2f19e036c3e338edee50871364d2f185afa51cb361c83f3bbda8ce2d92f&scene=27#wechat_redirect) + +6、[收藏|Python办公自动化不得不会的十大文件操作](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495776&idx=2&sn=50e900caa96346371045145170dc9073&chksm=e8859082dff219940436120f8fa9ae329dc655e69335f967e2f926786420d9d97b1b11711100&scene=27#wechat_redirect) + +7、[再见 VBA!神器工具统一 Excel 和 Python](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495801&idx=1&sn=becae030a09a689e1f231a5c3c1ef7bd&chksm=e885909bdff2198d1d2aafaf0077a2763d796e435817b70b612095ae944489581f3bbffe0a69&scene=27#wechat_redirect) + +8、[微软最强 Python 自动化工具开源了!不用写一行代码!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494621&idx=1&sn=61d9f8b7557cb2ff517fde1a414b219d&chksm=e8858b3fdff2022954d383579b6392362e99e99af4a6309028dfa6ddd7bfc90a02c85d599b50&scene=27#wechat_redirect) + +### 2.8 网络爬虫 + +1、[想逆向我的 js 代码?先过了我的反 debug 再说吧!](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484876&idx=1&sn=ed81400f77deb2af18311f8e42e9b11b&scene=21#wechat_redirect) + +2、[教你实现一个可视化爬虫监控系统](https://mp.weixin.qq.com/s?__biz=MzU4OTUwMDE1Mw==&mid=2247484208&idx=1&sn=d04421526cf0e12089541063b4ec448d&scene=21#wechat_redirect) + +3、[估计是讲得最清楚的「异步爬虫」指南](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484929&idx=1&sn=7055414a33d7cbadaeb5645cbd5203ec&scene=21#wechat_redirect) + +4、[10 个爬虫工程师必备的工具](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484958&idx=1&sn=3422e53de4ae701a922104ad26f8e256&scene=21#wechat_redirect) + +5、[Selenium自动登录淘宝,我无意间发现了登录漏洞!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490737&idx=2&sn=6a33873b3777afd420f3c56078a88687&chksm=e8867c53dff1f5455fe54e1ee0044cc97638fbc96983f36e19938810bea4e06d5af0ba3036df&scene=27#wechat_redirect) + +6、[干!一篇文章讲了这么多爬虫的技巧](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490557&idx=2&sn=f5128b1dc827cc4a6035d46714a484b1&chksm=e8867b1fdff1f20993b024fbfaa4445763a5949174913a58d9f17ddcb0286be8b325516cb449&scene=27#wechat_redirect) + +7、[谈谈常见网站加密和混淆技术](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486218&idx=2&sn=6261fa602eb4f73365a9d372376594ed&chksm=e8866be8dff1e2fea5082dd13dc7799a1cbc829f2e41a32891db411152153cb261d01be5807b&scene=27#wechat_redirect) + +8、[初识网络爬虫之夜探老王家](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486179&idx=2&sn=3a1b880c7f1b37d095359b35ff96e9b4&chksm=e8866a01dff1e31711d7fb093a96f54eaf374b46c686b0ea4e49d6f926018ad5b6c27aebfa9d&scene=27#wechat_redirect) + +9、[不懂爬虫也能轻易爬取数据的 6 大工具](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486085&idx=1&sn=51c740e1d848f1624a5a8e582434f661&chksm=e8866a67dff1e371de89a64503591ae0c42b67cd43434fa394125f09a2f65bc23817a266ac13&scene=27#wechat_redirect) + +10、[效率提高十倍,Puppeteer 如何启动交互模式?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496566&idx=2&sn=efc444487afd08ec055ceaee414857d6&chksm=e8859394dff21a824ccc8e2679f985e94c60d6b680f6856aa7a7a9ed608c9dc1ad552f0f2b40&scene=27#wechat_redirect) + +11、[别去送死了,你这样写爬虫,早晚得进去~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495216&idx=2&sn=648a296205fb3310287d95907d5126ef&chksm=e8858ed2dff207c4c42eb8b224bfe4f68186108642afd8ad7dfddef2628ff2c8f93170dbd948&scene=27#wechat_redirect) + +12、[如何在 APP 上爬取数据?多图教程带你实操](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498688&idx=2&sn=4125d37e4f552bb0fd186fd066c07eb6&chksm=e8859b22dff21234d15301eeeaef6f61acce0f9c6020285029e61448357b6703a53a51984d12&scene=27#wechat_redirect) + +### 2.9 实用系列 + +1、[你抢不到的火车票,我帮你!](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484972&idx=1&sn=19ac660b61f046c5d419acdae7f394a6&scene=21#wechat_redirect) + +2、[30分钟教你快速搭建一个顔值超高的博客](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485045&idx=1&sn=8b250c0c174e418e2025d86f42c695b6&scene=21#wechat_redirect) + +3、[用Python写一个表白神器让你七夕脱离单身](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485021&idx=1&sn=123b39391d11e9c7160b47a4c6a3dcb1&scene=21#wechat_redirect) + +4、[用 Sphinx 搭建博客时,如何导流到公众号?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484848&idx=1&sn=80ae18e7f53a64e62ac9c0ef0c21362e&scene=21#wechat_redirect) + +5、[10 行 Python 代码写 1 个 USB 病毒](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484832&idx=1&sn=43c353856c7dd9ea2cb1a9bbcd077fa2&scene=21#wechat_redirect) + +6、[废旧 Android 手机如何改造成 Linux 服务器](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485279&idx=1&sn=7166a2c9445438eaaf63a25b598e5427&scene=21#wechat_redirect) + +7、[如何将手机打造成 Python 开发利器?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485256&idx=1&sn=2e646e28287707c67c22fbe68112e622&scene=21#wechat_redirect) + +8、[我用 Python 做了一回黑客,批量破解了朋友的网站密码](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485156&idx=1&sn=05142ae7b4bf7e97ebe042e394aa7084&scene=21#wechat_redirect) + +9、[手把手教你安装Win+Ubuntu双系统](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485030&idx=1&sn=8383a7306381f36781957b807fa93961&scene=21#wechat_redirect) + +10、[一篇文章让你的 MacBook 进入超神状态](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484906&idx=1&sn=b2c3c969e53beae53aa7be7959227b5b&scene=21#wechat_redirect) + +11、[情人节来了,教你个用 Python 表白的技巧](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485701&idx=1&sn=ef7f0b83e60f397c1839259b275575bf&chksm=e88669e7dff1e0f1317ec429cf9e70442bb3a08ec46efe9a0d06f4ec9847574ea30b53904670&token=1148998814&lang=zh_CN#rd) + +12、[“Hack” 微信实战:如何用 Python 分析微信群聊记录?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486141&idx=1&sn=8b32c57dce0f3a33127c39c9cd35509e&chksm=e8866a5fdff1e349f16f677304716e40a305e60168fe7e30361de675dad5dfa956c9d7c79089&scene=27#wechat_redirect) + +13、[5 行 Python 代码生成自定义二维码](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488795&idx=2&sn=8e03e01e861753f7ccf3adeb1f3cb848&chksm=e88675f9dff1fcef328c69b1519743fcc3ae83b80dd3d66cb934acb346781497f83c5a9051a5&scene=27#wechat_redirect) + +14、[女朋友背着我,用 Python 偷偷隐藏了她的行踪](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487175&idx=1&sn=c3ba19bdbcb6fd59b37ff04d02bce63b&chksm=e8866e25dff1e733b31386fda99271eefadc77b3e0579df4d74617b561ecf0fa0db70f9f2860&scene=27#wechat_redirect) + +15、[zip 解压炸弹? ?在 Python 面前,啥也不是..](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491841&idx=2&sn=307a29d71776f6d1c68567a16727a7e5&chksm=e88581e3dff208f55a48081cb8f951ca7d977c9b1ec3ac9aaf26bc8c53904ac0e15035e5c680&scene=27#wechat_redirect) + +16、[把你的朋友变成表情包?Python:So easy](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493765&idx=2&sn=cd3cd2a891cb3bc47956ac3bc8a06dd1&chksm=e8858867dff20171241f81274eec097f48ea23558029b42a3e7565833c8a62d20e52d2dea87e#rd) + +17、[超干!如何建立一个完美的 Python 项目?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493789&idx=1&sn=7f0f4c40e410a3cfb01706dd9c8c8fc6&chksm=e885887fdff2016930accb8ea6d4e2cb67d4d6983ca716148da7f63c62a6b96f518497ed2575&token=58703854&lang=zh_CN#rd) + +18、[10 个“疯狂”的 Python 项目创意](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493963&idx=2&sn=e4ff38bc55f4ad4d990191e81a316421&chksm=e88589a9dff200bff3e65c19744e59dd3407a48ff5b104bc7b71771762b331daca5c7772892a#rd) + +19、[使用 Python 自动化清理微信僵尸好友](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496922&idx=2&sn=3b2233563ed7a02df94ab2600fe737f3&chksm=e8859438dff21d2eb4874859a839f6c03b74cbd217a2e92eb6ece714051af11fdcb81c76c4d7&scene=27#wechat_redirect) + +20、[别再问我怎么Python打包成exe了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496522&idx=2&sn=5579b947755bcc4aa2d2dd098d4396bc&chksm=e88593a8dff21abe09ab37a9c6ee855bab17342a7397b946514acb9db281c629502709adc6ae&scene=27#wechat_redirect) + +21、[还没抢到票?试下这个用 Python 写的最新抢票神器](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496260&idx=2&sn=6f0b4ea3960b25640eb2936f65ac9e0a&chksm=e88592a6dff21bb0140bb319486321f16a41679a53f68ea8cdbe0cad3fa9fbb6996fcc2019ec&scene=27#wechat_redirect) + +22、[使用 Python 制作按键触发Windows通知的脚本](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494892&idx=2&sn=6c633c829e9e996bf6c3109911d2e20e&chksm=e8858c0edff205181ebc2eb5e907ea38b0dc0fa581a243fe57269968dc1aab147d3230c8a8cf&scene=27#wechat_redirect) + +23、[纯Python方案实现中英文全文搜索](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494874&idx=2&sn=f515724a52613a875869e6aa31ae3e78&chksm=e8858c38dff2052ede9f42592fb59446b68bb3fff84870601c36ee64b864527d25cb9619ccae&scene=27#wechat_redirect) + +24、[两行 Python 代码,精准识别一张图片的格式](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497854&idx=2&sn=ca9066d3d7e4f3a0e8dfa18429683b30&chksm=e885989cdff2118a81ecd769cf1443e252d5347a7c76e7d5b2d9c494d0afa2dbda5fbe467897&scene=27#wechat_redirect) + +25、[明哥放大招! 这下看你们还怎么搬运我的文章 ~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498344&idx=1&sn=c3707e3058f444fc42e373155f82c514&chksm=e8859a8adff2139c790e5240dd4339b5cf7493e959eba0ddff5738a2432ff287ba6488b59b18#rd) + +26、[如何在手机上配置 Python 环境](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497813&idx=2&sn=b4b09859f7b175e0a60ca7e61131d475&chksm=e88598b7dff211a124f85f82db20a3c7742dce30f7218dcd00ac393cc49ad7754b2401f8e1bd&scene=27#wechat_redirect) + +27、[在手机上运行 Python,这款工具比 QPython 还好用~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497684&idx=2&sn=62756dddcd6d8a8e29326d33b1d9e70e&chksm=e8859736dff21e20cf67f8c29e87cdcd2849f04bb9567dc2f5c2dcc6a00cbd8aeeecaace335f&scene=27#wechat_redirect) + +28、[情人节表白神器,v2.0 版本](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497554&idx=1&sn=032200c99fcb0841246cb5eed74dc73b&chksm=e88597b0dff21ea6b20f8301a3c2e2dfdf914d49f23155f81113c90b7b6135802f252481a318&scene=27#wechat_redirect) + +29、[5个无聊透顶的 Python 程序](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497441&idx=1&sn=a6e7b24b13cee4c7f580587f0303a579&chksm=e8859603dff21f158b9560b6b397b13a26d71ef6c72d47d220c65812f5c0f9d7f15a3c209fe5&scene=27#wechat_redirect) + +30、[怎样用Python制作好玩的GIF动图?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497164&idx=2&sn=cfc237768c53716f4554ea89bbca3294&chksm=e885952edff21c381eb898a203bed86a1b96eba8e1cabecabf0c896489c5fc3a0022b0be8527&scene=27#wechat_redirect) + + +## 03. 数据分析 + +### 3.1 基础库 + +1、[有关 NumPy 和数据表达的可视化介绍](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485295&idx=1&sn=6220c2ed0d73feade0c9691fbf214ba7&scene=21#wechat_redirect) + +2、[快速提升效率,这6个 Pandas 技巧一定要知道](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491909&idx=2&sn=3f3bf16784689a5e8bd5f9ef9478040f&chksm=e88581a7dff208b12e25ddaf6a047abc47878eee56f85309b6618cfd92a4dfeb2445f23e2f05#rd) + +3、[50题 讲透 matplotlib :从入门到精通](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486026&idx=1&sn=7b333759709c30fe442732b7a107e3c1&chksm=e8866aa8dff1e3be37f5169ac53bbcf71a76514e99fbbcbe458ec15aa1204d988ad44d689eb3&scene=27#wechat_redirect) + +4、[实用的 Pandas 技巧,估计 80% 的人不知道](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496209&idx=2&sn=8f8db418261326c981cd27f57cc3fc5b&chksm=e88592f3dff21be543cc302a715ea39ae8a1adacb7342f45d5bcf2eca9c2b398b05955408597&scene=27#wechat_redirect) + +5、[用Python 操作 Excel,这篇文章别错过了!(超全总结)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498065&idx=1&sn=5f2bb43d9d960c2a7330353c4ac926a8&chksm=e88599b3dff210a528c3e6aac2ea1708b7aa5e3d58c49d12d63492271396ccebee8f0a4a6eae&scene=27#wechat_redirect) + +6、[别找了,这是 Pandas 最详细教程了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497846&idx=2&sn=9483146777ac86567e3933e6d5475e5f&chksm=e8859894dff2118208b517d0d9e753fbf8b1bb83995d36cf04694825972b6a82e195d706852d&scene=27#wechat_redirect) + +7、[再见 for 循环!pandas 提速 315 倍~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497472&idx=2&sn=df1089640d2beb3607518d0ea9f3e709&chksm=e88597e2dff21ef45a7c3b25e102bc665368f628d1b76decfbeeb18a4cd1a2605737c4c8fb21&scene=27#wechat_redirect) + +8、[Python 操作 Excel 库 xlwings 常用操作详解](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497164&idx=1&sn=281a25369e47ecba1951d4e92f01d5d0&chksm=e885952edff21c38f633910f3c2aac54f8e379707e70d9a70a1cdde5bfd4b7fde145315ea54f&scene=27#wechat_redirect) + +### 3.2 数据可视化 + +1、[可视化01|一图带你入门matplotlib](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485022&idx=1&sn=76d4270a15c430217588bd9f8be8303b&chksm=e88666bcdff1efaa35c6d685f19b04e62f58e768dc9d1819bcb9f8211f65d3fd83296259d923&token=1148998814&lang=zh_CN#rd) + +2、[可视化02|详解六种可视化图表](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485020&idx=1&sn=7f82736e6a2e5442ed59c82d8e242ca4&scene=21#wechat_redirect) + +3、[可视化03|用正余弦学习matplotlib](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485019&idx=1&sn=9d44ee27fd888831e94845d6d0256deb&scene=21#wechat_redirect) + +4、[可视化04|子图与子区难点剖析](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485014&idx=1&sn=90dbdf17f049c152944a4c28642df249&scene=21#wechat_redirect) + +5、[可视化05|绘制酷炫的GIF动态图](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485007&idx=1&sn=1464dee30d006ddb5111f9827b0c4081&scene=21#wechat_redirect) + +6、[可视化06|自动生成图像视频](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485002&idx=1&sn=f09c089328eb0fe39721b73272c81214&scene=21#wechat_redirect) + +7、[可视化07|50个最有价值的图表](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484920&idx=1&sn=92eb2fd55f13a8bda75a7103a73f8d50&scene=21#wechat_redirect) + +8、[ 可视货08|利用 Flask 动态展示 Pyecharts 图表数据的几种方法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485658&idx=1&sn=f78e976889de52c0ce35e862bafacd8a&chksm=e8866838dff1e12e41b8f2dd1a43dc540ff39484ae64831332d233b5a4f7ca3b439d370ef1ed#rd) + +9、[一个没法商用,但是好玩有趣的 Python 手绘图形库!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491212&idx=2&sn=66a5fd4f672b668aee6bb2c3a01a92f6&chksm=e8867e6edff1f77813b2b59f6219d18041963e6011afa1a590925c71ebd79c7e90b03e82c606&scene=27#wechat_redirect) + +10、[如何使用 Python 绘制一套动态图形?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488616&idx=1&sn=ef094168fc5ff4c46a66b2068cb1011e&chksm=e886748adff1fd9cc9f9f3ed3b2210c9f9f2c770458b3207f9fd6b12ee37a5451e9444b41e59&scene=27#wechat_redirect) + +11、[一文学会制作 6 种炫酷的 Python 动态图](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486111&idx=2&sn=fadf51d43bc468c58ba6885db3006e7a&chksm=e8866a7ddff1e36b5815a9693150799d5925727f5a425e642fd136283ff4a58b7f19ba9da39d&scene=27#wechat_redirect) + +12、[如何使用 Python 绘制一套动态图形?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488616&idx=1&sn=ef094168fc5ff4c46a66b2068cb1011e&chksm=e886748adff1fd9cc9f9f3ed3b2210c9f9f2c770458b3207f9fd6b12ee37a5451e9444b41e59&scene=27#wechat_redirect) + +13、[超硬核的 Python 数据可视化教程](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492265&idx=2&sn=2aede70646f9ee34ad526d2d99b6549e&chksm=e885824bdff20b5d428dbb09daeae2f3e96f1362372bf6ffb16fae1f06263703379873025d43&scene=27#wechat_redirect) + +14、[收藏!最全的可视化学入门算法教程(Python实现)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491520&idx=1&sn=55804b80dcc56a3923255abf58e08968&chksm=e8867f22dff1f6340a7f61f7796a1e78bd695278a340351860efc244c8a9bbf80cc02bbfaf05&scene=27#wechat_redirect) + +15、[50 款数据可视化分析工具大集合,总有一款适合你](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490737&idx=1&sn=f447d6e27504611baf8a4ad4056c6e5a&chksm=e8867c53dff1f5450ff808baec53797bd772cdd3c0e43f2f0c278cd4b8867223d5591ab2aa7d&scene=27#wechat_redirect) + +16、[用Python画漂亮的专业插图 ?So easy!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490251&idx=1&sn=14df276f09201930fdc413d8f9a772df&chksm=e8867a29dff1f33fd0f85cf4412c42cc1145e917b3cd06e158aa31356525799033dd8c6e620f&scene=27#wechat_redirect) + +17、[太好玩了,看看我用 Pyecharts绘制的“时间轮播图”](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489657&idx=2&sn=6359a534033c0a6f21af28a7d9f8b768&chksm=e886789bdff1f18d6719558e305453cba2b6c8a55d66c9bcb6dd5678209d89c1594d87de0a87&scene=27#wechat_redirect) + +18、[实战!用60行Python代码画出30万条房产数据分析图](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489032&idx=1&sn=6b9c7281d3090783a9a486b81f749789&chksm=e88676eadff1fffcec00d3c44ee9b34496dc886417da55aeabed017d496da85d442f205c227d&scene=27#wechat_redirect) + +19、[刷爆全网的动态条形图,原来5行Python代码就能实现!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492725&idx=1&sn=964c45002220761dafcf537d5f9fe802&chksm=e8858497dff20d8175b87ddb1997b1dd71c91a03a92e60611718fdbf2bdbe3cee41d239b360d&scene=27#wechat_redirect) + +20、[这 10 个 Python 可视化动图,学会了就吹牛了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493551&idx=2&sn=dee235d803b491163f74a49f5cf0d12f&chksm=e885874ddff20e5bf639454c226f6e74e5180dbab3803db08c2bd72d67347aa52dede40da4c9#rd) + +21、[牛批了,1行python代码就可实现炫酷可视化](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494580&idx=2&sn=bf87885645377449f3a94d6d8122e1f6&chksm=e8858b56dff2024046c48ccf0ff087c42fa2608c0d0b6183ea61ffb85278a9c52138cdfbef13&scene=27#wechat_redirect) + +22、[珍藏版 | 这 30 个细节,决定了数据可视化的质量](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496866&idx=1&sn=f1ba05ed6a4db3e507f0cf0d4ce3ac59&chksm=e8859440dff21d56256e0ad9a9c739dca37fae6c40843fe98033a2e77a412a667407f36a7113&scene=27#wechat_redirect) + +23、[吹爆了这个可视化神器,上手后直接开大~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495182&idx=1&sn=d8e913a808796e6fa793d58df26d209f&chksm=e8858eecdff207fac878d4b27894b5a2c69812d6803262e430d2244443a2c23e38d91a697556&scene=27#wechat_redirect) + +24、[Pygal,可导出矢量图的Python可视化利器](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497951&idx=1&sn=7cce71d5c34fdeb89509e97817a5f0af&chksm=e885983ddff2112bc76bd9a4dbe14f407c5fe8b2ea0010b353e760b59c4476603f1d82a26eac&scene=27#wechat_redirect) + +25、[这可能是 Python里最强的绘制地图神器](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497813&idx=1&sn=c5034f6ecf03fe3ccb6771b39faa8230&chksm=e88598b7dff211a11145c44db19f50df244ab3c2b740e1d51ead51b25d7fa9643660ca5cc29a&scene=27#wechat_redirect) + +### 3.3 工具使用 + +1、[ 整理了 50个 IPython 的实用技巧](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485613&idx=1&sn=70f89faf2573b3025edc73f525f22a0a&chksm=e886684fdff1e159509d30fd0a24854da39fb5a6ae55e0ed6e40b7971d3219968006f80aad7b#rd) + +2、[ Jupyter NoteBook 的使用指南](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485024&idx=1&sn=aac0db4b2c97c7f5c2871342b936eaa2&chksm=e8866682dff1ef947ec6474b8c03f668e462232ebd0ec0f137d42e5e63b9b66a3d9e303c1caf&token=1148998814&lang=zh_CN#rd) + +3、[Jupyter Notebook最常用的五大配置技巧](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491411&idx=2&sn=88c290af7793ddc024cf2a39875b97e8&chksm=e8867fb1dff1f6a731077d749b1ea44fd558995f3448e27da9becf7b035ed777774d1b0bb0ec&scene=27#wechat_redirect) + +4、[强烈安利!这十二个 IPython 魔法命令](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488777&idx=2&sn=3b14fe7faa4d2a26b702c15ebe6b6103&chksm=e88675ebdff1fcfd471dc84c802dab2c37889d59e55711c5d521c80fdbf38291b4092b840083&scene=27#wechat_redirect) + +5、[真香!安利 6 个 Python 数据分析神器](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488619&idx=1&sn=0c6c76b9deaa4f1976dcacaaee018312&chksm=e8867489dff1fd9fa2b0ac08696c4dae1b48054ca64aefef6d2117179dbbffbfe4ae9d1a4f81&scene=27#wechat_redirect) + +6、[使用 pyecharts 打造酷炫的 BI 大屏](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487730&idx=2&sn=b2ee3058d63ebf702f3f0f9a6ca9c623&chksm=e8867010dff1f90686948269ca5e0907d726fc775029cc414285d71c0176ce859b818d2e34d5&scene=27#wechat_redirect) + +7、[酷炫的动态可视化交互大屏,用Excel就能做!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486295&idx=2&sn=2826a69402912a70955209192e761c4d&chksm=e8866bb5dff1e2a30d374d5f46afa1cb9d952a0c939701b9f8b5bc52df4169d5e2da30236505&scene=27#wechat_redirect) + +8、[像操作Excel一样玩Pandas,这款可视化神器太棒了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493689&idx=1&sn=2e614500c8359e4b87de55128fe69593&chksm=e88588dbdff201cd761e20605f10f1aa9525d6d89072cbbc9a307e35788b8a425c2044d337d4#rd) + +9、[8 个 Jupyter Notebook Tips,隐藏得太深了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494062&idx=2&sn=860539d7eae82c53791cd7a2eb005536&chksm=e885894cdff2005ae9169c7dfabe3760c19eded392ec18805f1d763ad8dce77c21150698178a&scene=27#wechat_redirect) + +10、[PyCaret:几行代码轻松搞定从数据处理到模型部署](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495402&idx=2&sn=bb0664c6ac7995dfad4f4a1268e2e374&chksm=e8858e08dff2071e121d81fa564671ba4425814687fd2df3aa32898ffb3bdbe72d303bc9d66a&scene=27#wechat_redirect) + +11、[这个 Jupyter 插件,用起来就像 Excel 一样简单](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498500&idx=2&sn=672a1da1a9957c84a19802842df5b5d9&chksm=e8859be6dff212f0754dd0a3820241be3af34d72e6dc32e7d83d1f87198bd549ccfd66c55029&scene=27#wechat_redirect) + + +## 04. 开发工具 + +1、[代码调试|无图形调试工具 - pdb](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484969&idx=1&sn=a99fc31865edc4b439707d2be6f66654&scene=21#wechat_redirect) + +2、[代码调试|远程调试图文超详细教程](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484970&idx=1&sn=9ac9c5dfcdc8c6b48a7b4c9854825734&scene=21#wechat_redirect) + +3、[优化Python开发环境的几个技巧](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485131&idx=1&sn=f1bd7b31a3624f015c74f56a8888a695&scene=21#wechat_redirect) + +4、[让你重新爱上 Windows 的小众软件](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484964&idx=1&sn=1479536416788a6c55dddef70167eb88&scene=21#wechat_redirect) + +5、[Python 的命令行参数解析工具](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484960&idx=1&sn=3456d9a05b35588b060833ceb189db6e&scene=21#wechat_redirect) + +6、[搜索神器 EveryThing,你把它的潜力用到极致了吗?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484894&idx=1&sn=59877fe818739175d3d075458594f563&scene=21#wechat_redirect) + +7、[开发工具|盘点 Xshell 的那些奇淫技巧](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485043&idx=1&sn=8c240e569c60607cd4cea8888a6aa2e1&scene=21#wechat_redirect) + +8、[告别996,全靠这个Python补全利器](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484824&idx=2&sn=8f20af371954dfd2a5c890bb5814b4c9&scene=21#wechat_redirect) + +9、[学会这21条,你离 Vim 大神就不远了!](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484902&idx=1&sn=d677261fda90b67bf5eda886447a4f77&scene=21#wechat_redirect) + +10、[用 Python 做开发,做到这些才能一直爽](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484874&idx=1&sn=abffab70b1e265ede1fd3b994dedab90&scene=21#wechat_redirect) + +11、[这款神器,能把 Python 代码执行过程看地一清二楚](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484878&idx=1&sn=9bc941ff19ec38e7c7137a22df981f27&scene=21#wechat_redirect) + +12、[如何把自己的 Python 包发布到 PYPI?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484835&idx=2&sn=f24a415d47d4122c4d6b1a6ccf254a51&scene=21#wechat_redirect) + +13、[谁说 Vim 不好用?送你一个五彩斑斓的编辑器!](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484872&idx=2&sn=ce718e511b754fe936172724a824a716&scene=21#wechat_redirect) + +14、[迄今为止,我见过最好的正则入门教程(上)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484831&idx=1&sn=077d14410db6a906c3b962f0ba18b2d4&scene=21#wechat_redirect) + +15、[迄今为止,我见过最好的正则入门教程(下)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484830&idx=1&sn=4d48b1c7f2b5234992732f8f15b868df&scene=21#wechat_redirect) + +16、[Win 平台做 Python 开发的最佳组合](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484837&idx=2&sn=68935077ae9eeb6fe4f708565aa0a9ed&scene=21#wechat_redirect) + +17、[15 款Python编辑器的优缺点,别再问我“选什么编辑器”啦!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492759&idx=1&sn=6c6e6606fd9da9d2b951bda9e8a1b223&chksm=e8858475dff20d638f4b731a44dd48fe01a68f16480bcaed2516fc7d3d56c61cc6c62afccb9d&scene=27#wechat_redirect) + +18、[用了三年的 pdb,没想到还能这么调试](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498550&idx=1&sn=d2d0ba8987509a67ba40c19f7dcfd719&chksm=e8859bd4dff212c2f4d6f07c42ae81af046aeb8611c3c869143ed7e4ebac03fd011ddc8b88e6&scene=27#wechat_redirect) + +19、[调试 Python 代码,可别再用 Print了!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497083&idx=1&sn=47fa23a2104c2e75ad71c56b6d89b84d&chksm=e8859599dff21c8f0f832e83f3e5220bef96fbba7514863e4e8fcd178818258f024744902806&scene=27#wechat_redirect) + +### 4.1 PyCharm + +1、[受用一生的高效PyCharm使用技巧(一)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484946&idx=1&sn=12a891db18e9d1233535fa4aca9a3892&scene=21#wechat_redirect) + +2、[受用一生的高效PyCharm使用技巧(二)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484942&idx=1&sn=9dbb2cfe2a277bd3519d37414fcc608e&scene=21#wechat_redirect) + +3、[受用一生的高效PyCharm使用技巧(三)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484932&idx=1&sn=347b35ccdee94895652caa86191360c8&scene=21#wechat_redirect) + +4、[受用一生的高效PyCharm使用技巧(四)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484918&idx=1&sn=66b3fd784050e5cb85109faa3d063993&scene=21#wechat_redirect) + +5、[受用一生的高的PyCharm使用技巧(五)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484914&idx=1&sn=cd1282f94f9326647b05ad9090afd2c8&scene=21#wechat_redirect) + +6、[受用一生的高效PyCharm使用技巧(六)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484907&idx=1&sn=647a75b96884b39c57664f62ead11b89&scene=21#wechat_redirect) + +7、[受用一生的高效 PyCharm 使用技巧(七)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485372&idx=1&sn=3ed61c4ccbb089358213304cd70bb0f7&chksm=e886675edff1ee487911e922279675264916dd08120f5f40113f37dc080b94c7bf9ad67719c4&token=1148998814&lang=zh_CN#rd) + +8、[代码调试|远程调试图文超详细教程](https://mp.weixin.qq.com/s?__biz=MzU4OTUwMDE1Mw==&mid=2247484304&idx=1&sn=7612f0c2fd6232723ca2733fb1e8f46d&scene=21#wechat_redirect) + +9、[手把手教你打造一个顔值超高的IDE](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485052&idx=1&sn=2e0bb1a3af4206fd2cf8e3650f695e6b&scene=21#wechat_redirect) + +10、[玩转 PyCharm ,这篇文章就够了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487629&idx=1&sn=9a19cbcce2effcc242cc8d6d33cd2762&chksm=e886706fdff1f97955e73495c0be5b104f834d569d75e9e1470417f79e1d19ff4937683484ad&scene=27#wechat_redirect) + +11、[用动画展示 Pycharm 十大实用技巧](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486118&idx=1&sn=990c35adb86919bda2f2cb82e7c5fb9e&chksm=e8866a44dff1e35200244e3d369ad6c0f736d3511dcfb086b7641339e20ae62e35b8fd60bdd9&scene=27#wechat_redirect) + +12、[太棒了!Jupyter 与PyCharm 完美融合,Jupytext 来啦!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496487&idx=1&sn=344204a2893c7bb54e9977f274c29fef&chksm=e88593c5dff21ad366d0fa3c2a1af3760f8f67b167ee8b29e016fe31adc66bb84c3e018e25ad&scene=27#wechat_redirect) + +13、[装上后这 14 个插件后,PyCharm 真的是无敌的存在](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495720&idx=1&sn=3233659fa9c975df9e95402a8c68b405&chksm=e88590cadff219dcd82f22d1d4684dda09a2db3469a0826ffe7605388fc37fb3a2c7cca49e0f&scene=27#wechat_redirect) + +14、[卸载 PyCharm!这才是 Python 小白的最理想的 IDE](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497739&idx=1&sn=2e4795f4ddc784cfa796fb59b3d0e707&chksm=e88598e9dff211ffbe2cea5f574c8ee6117f4e7cd5d4869a804422139360421e4803df270f19&scene=27#wechat_redirect) + +### 4.2 VSCode + +1、[这 21 个VSCode 快捷键,能让你的代码飞起来](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484893&idx=1&sn=421b544115efad388314ccd027761b40&scene=21#wechat_redirect) + +2、[神技巧!在浏览器中也能用 VS Code](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484859&idx=2&sn=70abd19426374271a10cbef8e1645c7a&scene=21#wechat_redirect) + +3、[生产力终极指南:用了两年,如今才算真正会用VS Code](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492595&idx=2&sn=a39a7cb70908d442a399ac80a272f90e&chksm=e8858311dff20a0782cca942c7e9f3444bb443cf816fd05d76c5f7c234930afacb6c948802bf&scene=27#wechat_redirect) + +4、[VS Code的7个开源替代品,全都知道算我输!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490251&idx=2&sn=ef0eca9da6eff8405a5a48d89a6b2e54&chksm=e8867a29dff1f33f29487864efb1389b02705d81d0198df581b5b71085ff238f24ffe99f154c&scene=27#wechat_redirect) + +5、[再见了,PyCharm](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489522&idx=1&sn=d7d45dba4a7c9e2f010d09f757639a19&chksm=e8867710dff1fe0657a16b6087ad4a50fdceb8f324bef5f950b3320e33e09ce10d1fa045827d&scene=27#wechat_redirect) + +6、[微软推出 Pylance,改善 VS Code 中的 Python 体验](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489006&idx=2&sn=58f6dbe10cba98cebab0fd19f124e4df&chksm=e886750cdff1fc1a6cfe249a2f76213fef1a789f4a5006704e33d00b9abadde489af7052a8a9&scene=27#wechat_redirect) + +7、[VS Code 连接远程服务器运行 Jupyter Notebook](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486773&idx=2&sn=75b2453513b1445d9881f5ec8c1f44d1&chksm=e8866dd7dff1e4c167b27eccec9a6469b7988d6b05010160247b3e1a549764201807212ed581&scene=27#wechat_redirect) + +8、[GitHub 发布重磅更新:你电脑上的 IDE 可以删了?!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486295&idx=1&sn=e5bd46de0616fad6105d2e46a359cd3d&chksm=e8866bb5dff1e2a37058e1cbc502863f8b8a885a493b7ef9f5e442a1b554ffeb0ae09d77026e&scene=27#wechat_redirect) + +9、[用 VS Code 写 Python,这8个扩展装上后无敌了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492505&idx=1&sn=ba354ab86500280d46038d2fa2f3572b&chksm=e885837bdff20a6dd2d4411719e44a3b88371b5006e9dc2b2580e6862f3161e70cb339240512&scene=27#wechat_redirect) + +10、[有了这个VSCode神器,从此爱上调试代码](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497025&idx=2&sn=91734e86eb03b0ad8b299fa05fb89896&chksm=e88595a3dff21cb5638a874e6ea75aefa8539eb832e239d8103fd66584d1276edbe1d616c4fb&scene=27#wechat_redirect) + +11、[用 VS Code 写 Python,这几个插件是必装的](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496552&idx=1&sn=ab85fc16d53f5d6bbcb0c030cae51402&chksm=e885938adff21a9ca75d485aab0980cb5cb7363d4eadaf0e1867dbb955c7296d1e16f7c7c367&scene=27#wechat_redirect) + +12、[出炉了! 2021 年将火爆的 10款 VSCode 扩展插件](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497928&idx=1&sn=0ec6994930f1cf8bd3a1ed8d8404eb18&chksm=e885982adff2113c94ba7c0ddc989c174daeedd159359d9289b48006e0c6b1d0aa8b87980ef1&scene=27#wechat_redirect) + +13、[神器 VS Code,超详细Python配置使用指南](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497249&idx=1&sn=00a13cb6099f487ce3f88f27923d6a3b&chksm=e88596c3dff21fd504d3e469a0eddc00f8febdeb95b7b8c7483dfad7ef84382d2cc1f3b16f2c&scene=27#wechat_redirect) + +### 4.3 好用的库 + +1、[使用 Python 远程登陆服务器的最佳实践](https://mp.weixin.qq.com/s/aRXYAP9D9rgil-0_Etb0SQ) + +2、[这么设置 Python 的环境变量,我还是第一次见](https://mp.weixin.qq.com/s/LcjIPZPSg0KbL9X-i6vigg) + +3、[太强了!Python中完美的日志解决方案](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485741&idx=1&sn=505c06669baa44873171a3eed81f32b4&chksm=e88669cfdff1e0d91635b75f54998667e04bfb05a1aebae096930bd23acdd9d35ea858861bb2#rd) + +4、[如何使用 Python 操作 Git 代码?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485723&idx=1&sn=35511b71347111c00a9e1633309efd13&chksm=e88669f9dff1e0ef7aec03f2bdbddb8408bf12a57a36add441b75e486dd72df13be7629625ab#rd) + +5、[用它5分钟以后,我放弃用了四年的 Flask](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485657&idx=1&sn=e21ddc5b670a9b667135df2ca97ec99c&chksm=e886683bdff1e12d9e1d64a7f5bfbc65ca2372711cd9b08ef36ce0cdd99811ce44ff162585df#rd) + +6、[整理了 34 个被吹爆了的Python开源框架](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485653&idx=1&sn=edd893dd711b3681aa0bacdf8ab384fd&chksm=e8866837dff1e1216f6fbf413118f839e6e1dc5ed7b45117df55f13e25670c5ffba34966abff#rd) + +7、[ 为了选出最合适的 http客户端,我做了个测评](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485651&idx=1&sn=647c22ac66e469ccc33478457dadf224&chksm=e8866831dff1e1279149ff1e3af84bef86d24c524b7b7c14e9c519cf7c4739fa8f10dcc4a8d0#rd) + +8、[凭什么 FastAPI 火成这样?看这篇文章就知道了](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485891&idx=1&sn=82bde3e83435ebb6beb7e3581f12f7b0&chksm=e8866921dff1e0376befb029e9f1d81f33b0fa55242182f8de20d2a6e6b0cce0b5efd02f9aef&token=2013245174&lang=zh_CN#rd) + +9、[要想 BUG 变得富有美感,这个库你一定要看](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485889&idx=1&sn=0258eb138dce2c71b533d340770d5d09&chksm=e8866923dff1e03548dee4f780b410c22b51e1c3ffde3ef814f6a9b94e49af941fc04c79a660&token=2013245174&lang=zh_CN#rd) + +10、[如何使用 Python 输出漂亮的表格?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485841&idx=2&sn=705c123abed5441a52f8640b65f3c400&chksm=e8866973dff1e0650e4b2e025d0e86b3aedeb7ea8f68ad560f37cb033f36660b6eee15fb3ce4&token=2013245174&lang=zh_CN#rd) + +11、[用 Python 写出来的进度条,竟如此美妙~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492108&idx=2&sn=2fa1e31f9684df6d6f3da21aa4cba6e5&chksm=e88582eedff20bf886c29d480245d835c0a3fbea49e54909abbeb703bb7149d4e3444202e97c&scene=27#wechat_redirect) + +12、[推荐一些能提高生产力的 Python 库](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491947&idx=2&sn=765ca757e0aee7fd4dd3f7779f409ff3&chksm=e8858189dff2089f74a97972c1f213291a8680a766b328a74c609f465f7dcf16c324df57a660&scene=27#wechat_redirect) + +13、[规整字符串的数据提取神器:parse 库](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491617&idx=1&sn=b77cd25d6c92f86c1492f913edcb51c7&chksm=e88580c3dff209d500525bbc81464034ca431c92603b5ec83046faaac7ca4c012034964805e0&scene=27#wechat_redirect) + +14、[微软再出神器,这次终于对 Python下手了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491407&idx=1&sn=0044bf683ddc4799ef2e7c04fb75ebf8&chksm=e8867faddff1f6bbbae3e716e7b686eeb4a5b49f19a2fb2b8743ae2e641ee529189d313f6fe8&scene=27#wechat_redirect) + +15、[这个 Python 库有点黑科技](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491241&idx=1&sn=700979655adce734f5300e7ee380d4ab&chksm=e8867e4bdff1f75d355b34ca8561844c920a2434357350e06a87c99cdbdfb094ee29e7957988&scene=27#wechat_redirect) + +16、[适合 Python 入门的 8 款强大工具!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491088&idx=2&sn=23cd3b06b8117a5d996d258dd9c0eb23&chksm=e8867ef2dff1f7e479ad5a633295f1be0fc272f6b4de2526c21129b2dee23cb517e51b508f8f&scene=27#wechat_redirect) + +17、[这些Python库虽然冷门,但功能真的很强大!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490868&idx=2&sn=7bda9c9021cd26eb43d752f6ff84dbe7&chksm=e8867dd6dff1f4c0398b427f13787c99f24cea2a7b2c7b10fbc3316b0215b3ea861f197e604f&scene=27#wechat_redirect) + +18、[那些被低估了的 Python 库,看看你用过几个?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490782&idx=1&sn=2c710eca844da200dac14ab017ffcdb3&chksm=e8867c3cdff1f52a86c291e34e458f57bd5572c6fdeb75cdb84fdfba2d9bb1731067dca7d195&scene=27#wechat_redirect) + +19、[用 Python 写出这样的进度条,刷新了我对进度条的认知](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490326&idx=1&sn=867061f4ad793e957161c30be77ef9bb&chksm=e8867bf4dff1f2e2f1cdc5af4366540d33fc0edfc52810af09b82a590fcd6ec4c5c2d7a528c6&scene=27#wechat_redirect) + +20、[少有人知的 Python "重试机制",请了解一下 tenacity ](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489274&idx=1&sn=7ddc4505f932b386ee22c7873ff5df1a&chksm=e8867618dff1ff0ea5aa55c6acfeb85944028f1bb0be7fb2697f4ec018de297579b797ce048b&scene=27#wechat_redirect) + +21、[一个极具意义的 Python 前端开发工具](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489090&idx=1&sn=1e7ee21422440dc32d7a9a40eaab6c4e&chksm=e88676a0dff1ffb6731df2996d3a4011eebdd8af4926920eefb30d086fa909943179e83d8957&scene=27#wechat_redirect) + +22、[非常实用的 Python 库,推一次火一次](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493413&idx=1&sn=2405d851af05549d5cb5f79353fdc393&chksm=e88587c7dff20ed1c2f93c9a042b293eae294f17bf13cbbebdf50682c8fd24e09e4647dbbe6b&scene=27#wechat_redirect) + +23、[新一代 Notebook 的神器出现,替代 JuPyter 指日可待](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493296&idx=2&sn=075bc9476933eec961bc2e9a7245d2d0&chksm=e8858652dff20f44a4d9cb30ad03841bc991a0efc2a63e63b04d5c8f68b3c6a67fbfa884551e&scene=27#wechat_redirect) + +24、[使用 Python 下载的 11 种姿势,一种比一种高级](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493230&idx=2&sn=8d356b9e0a9d597fbca1ef6ea2f8ca4d&chksm=e885868cdff20f9a2d220f18ecc27909c1217568728f213f492bf566578da5742190bcff4862&scene=27#wechat_redirect) + +25、[一个非常好用的 Python 魔法库](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493501&idx=1&sn=fc2575646311350cb47f6caed6102539&chksm=e885879fdff20e89c9600b6147885523fa0dbb7182fdf458c5aad5cd76ba6b415d25694f6bed#rd) + +26、[终于来了!!Pyston v2.0 发布,解决 Python 慢速的救星](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493602&idx=1&sn=3ba266f9460d94eb4d16c50bffe3ca94&chksm=e8858700dff20e1621fa34dae0b983c38fbcb5575d03f4c951c34a6d0a5f3b81e6c61fddd68a&token=58703854&lang=zh_CN#rd) + +27、[献给 Python 开发人员的 25 个最佳 GitHub 代码库!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493878&idx=1&sn=f476bbed89543828b54e7c9fcd93abbb&chksm=e8858814dff20102b366afa61e870c2d82d3f8fd332dbf39f3824867a3f8b5e7a5ad4b0bfb89#rd) + +28、[求你了,别再用 print 调试代码了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494015&idx=1&sn=dc0dd7179d00d074e3013658760fe41d&chksm=e885899ddff2008be8fde435d8ed2fb80057386aa13f8eee273fd02e8c1ca99201130881380e#rd) + +29、[爱了爱了!0.052秒打开100GB数据,这个Python开源库火爆了!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494268&idx=1&sn=db20e8232caa59512c852dcd723e3a46&chksm=e8858a9edff2038857440f061ec0cef305011d967dfd738df8187f10a2f44d90e3fd7c07e946&scene=27#wechat_redirect) + +30、[用 Hypothesis 快速测试你的 Python 代码](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494299&idx=2&sn=d8af7430ff62c90179491a097a5b43fd&chksm=e8858a79dff2036fae8f7ec05e6827c52cb27149d00a32f9a7dea9ab0e8ed1736fe4b0b3495c&scene=27#wechat_redirect) + +31、[微软最强 Python 自动化工具开源了!不用写一行代码!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494621&idx=1&sn=61d9f8b7557cb2ff517fde1a414b219d&chksm=e8858b3fdff2022954d383579b6392362e99e99af4a6309028dfa6ddd7bfc90a02c85d599b50&scene=27#wechat_redirect) + +32、[Python 中 Mock 到底该怎么玩?一篇文章告诉你(超全)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496880&idx=2&sn=88899b73e7ca5f261be2b0ab245703ac&chksm=e8859452dff21d4473f5a73b7d87a800ce177581d94028b7911c0187b3f92bd2762d03195126&scene=27#wechat_redirect) + +33、[这款 Python 版终端资源监控器,火了!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496153&idx=2&sn=8b53b63e04b2eff07a16bb8ef641aaca&chksm=e885913bdff2182dd176a5f6b1e324ed18f788830037c649915dc24df289139fd402e97c91b5&scene=27#wechat_redirect) + +34、[谁是 2020 年最强 Python 库?年度 Top 10 出炉!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496075&idx=1&sn=f05417a3c7adb638d394741a0b47c21e&chksm=e8859169dff2187ff80b3fd056160ae9d25009f7b373f82a5bc2c7d6f104a5f12ee52a623a3a&scene=27#wechat_redirect) + +35、[Python 算法模板库,Pythonista 找工作利器](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495227&idx=2&sn=c7899ab854da814888ef853023d8b74c&chksm=e8858ed9dff207cf571d1819770952180f9c882b63e62025e819fe25538fe4f3a3148d73c228&scene=27#wechat_redirect) + +36、[终于把所有的Python库,都整理出来啦!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498689&idx=1&sn=66743481223ff48bcb5c8ed257b95754&chksm=e8859b23dff212350492116b1cdc3527f969fbb62cac8309ef91932e02842f35b5f7f4d08661&scene=27#wechat_redirect) + +37、[Python 超级强大的模式匹配工具—Pampy](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498439&idx=1&sn=1613fd0fb2154bb3a9b5caefb1225b41&chksm=e8859a25dff21333272d75f14badb8491295351b0b3542db7cc1506a7462359ed297ce228710&scene=27#wechat_redirect) + +## 05. 网络基础 + +1、[点亮你的 HTTPS?原来这么简单!!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492265&idx=1&sn=46b095516bf3c0ebfc367ab64f8fd42c&chksm=e885824bdff20b5db2861a411a07653135ffbafaa1e772588bcf86cf9ad54433a0ff492355ad&scene=27#wechat_redirect) + +2、[手绘 10 张图,把 CSRF 跨域攻击、JWT 跨域认证说得明明白白的](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489610&idx=1&sn=c9442c4ad370c318ea7ebafb26208b9c&chksm=e88678a8dff1f1bec43cd601b0c0e3eaff32d168dc2ad69f6273e27f2e6d72b49b7585046057&scene=27#wechat_redirect) + +3、[网络出了问题,如何排查? 这篇文章告诉你](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488835&idx=2&sn=edac7045eb00479481365334ba198f50&chksm=e88675a1dff1fcb76f10049aa97ade7c0669fc1a8c32649a810c1d43b37c7dbe964c17b0730f&scene=27#wechat_redirect) + +4、[肝了三天,万字长文教你玩转 tcpdump,从此抓包不用愁](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488684&idx=1&sn=9a9d717a6c00f978c6575cb934c5c942&chksm=e886744edff1fd58ebb593338484013442c8c2f481497d6c5246cabce3ad43c1ee28299f9098&scene=27#wechat_redirect) + +5、[三张图彻底搞懂 iptables 和 netfilter](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488534&idx=1&sn=44856bba34b906930780535a7000a5af&chksm=e88674f4dff1fde263c119bb66a5fa879e6c07b12d50035552dbd529757bedc45a3a41823013&scene=27#wechat_redirect) + +6、[tcpdump / wireshark 抓包及分析](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488505&idx=2&sn=5e7d7a204c9eef4537fd352090086685&chksm=e886731bdff1fa0d3ba2c9a951fdbd849858a58ea630adab247f3b8b3ff7e76d7de022a8270d&scene=27#wechat_redirect) + +7、[100 个网络基础知识,看完成半个网络高手](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488490&idx=1&sn=a8725e200b3ac351ab4f0a2b6d112199&chksm=e8867308dff1fa1e1d55c89b691394902d5c7f8965063acd8551ad14269135d603f7bef22281&scene=27#wechat_redirect) + +8、[网络知识扫盲:扒开 TCP 的外衣,我看清了 TCP 的本质](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488180&idx=1&sn=09526224732ebfcccb52847f27298c70&chksm=e8867256dff1fb40c9f47bafd0e87a9237c5a9ebf33c8a3d0a598276b496d29cdaa3fbff8d26&scene=27#wechat_redirect) + +9、[网络知识扫盲:一篇文章搞懂 DNS](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487859&idx=1&sn=55c2c40b807a6bd7036763e99954f063&chksm=e8867191dff1f88743bf88b809471926245a0ec3ef9c9d2c9f311d99533ed5656b7b3617431c&scene=27#wechat_redirect) + +10、[路由器里的广告秘密](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489132&idx=2&sn=4cf42aeef93396f7665a06b2d1dc7fba&chksm=e886768edff1ff98bbd9572d85ddd97ef91ad8d19fdd951c15520cd9756d2c40a53d7cd8111c&scene=27#wechat_redirect) + +11、[10张动图,让你搞懂计算中很重要的名词](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487331&idx=2&sn=70146e780ca63c55ca3446a5e238b487&chksm=e8866f81dff1e6974610866f756b099e0b8bed5389cfa594f4b94b6092ae31db305b1485aadd&scene=27#wechat_redirect) + +12、[数字证书、签名到底是什么?这篇文章讲得太好了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493263&idx=1&sn=8b26b39da435b619d1344b4aca79b9f7&chksm=e885866ddff20f7b936601facff760969e1ba36d981a9d26920f0acce65c96562ebffcb7d954&scene=27#wechat_redirect) + +13、[为何无法使用 ip 访问网站?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484976&idx=1&sn=503d75faea7a633729ec90d6f26e21a2&scene=21#wechat_redirect) + +## 06. 实用工具 + +### 6.1 Linux + +1、[值得收藏的 14 个 Linux 下 CPU 监控工具](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485356&idx=1&sn=9dc2f440299ad179e1874febdffa04d6&scene=21#wechat_redirect) + +2、[19 个没什么用,但是”特好玩“的命令](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485142&idx=2&sn=97b0f02f8f0153b1c7ba4899359d55e4&scene=21#wechat_redirect) + +3、[相见恨晚的15个 Linux 神器,你可能一个都没见过](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484887&idx=1&sn=3473df33893a23b4de1e176985a5265e&scene=21#wechat_redirect) + +4、[这 22 款 CLI工具,每一个都是精品](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491148&idx=1&sn=92b194ad574e8b793ebb608319c7d1df&chksm=e8867eaedff1f7b8e257ce6e80890cef6aa72b9078d20a0e1f0241a1c05f660c69891b6a7d12&scene=27#wechat_redirect) + +5、[Github 热榜项目:如何让你的终端酷炫到没朋友](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488641&idx=2&sn=c8ea38a8e216f123ca320d3ec8449bc0&chksm=e8867463dff1fd7522e9556f3ca97bb691ea92625ca664919dddabd35ead37dbd06a16b75b17&scene=27#wechat_redirect) + +6、[如何在1s内创建上百G的超大文件?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487906&idx=1&sn=a892ae3f3ab0ed0a2a20cf5d51ff1c65&chksm=e8867140dff1f85674d8c01df3c3d4a92cc75cdd34c68c3b86f8c5eb660f500f033ed35deef8&scene=27#wechat_redirect) + +7、[千万不要运行的 Linux 命令](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486845&idx=2&sn=31c1ba636db4d687b16b10981833bfbc&chksm=e8866d9fdff1e48976cab2cb62e3b4a4e2e373aeaa0e961847e49cd13ec4ea417b5162fa165b&scene=27#wechat_redirect) + +8、[不想装系统?这8个网站让你在线体验 Linux](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486303&idx=1&sn=fc6c9b73fcb12c5c0c8257e223c8f657&chksm=e8866bbddff1e2ab5edf8b3d4783bcc65975c0385c140d1c954e7d4949e334b9d6ed92ae98a2&scene=27#wechat_redirect) + +9、[比虚拟机更轻量,比 Docker 和 WSL 更简单的 Linux 环境](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498054&idx=2&sn=1aa6a118f214f3488c1188cdb2471834&chksm=e88599a4dff210b273db8dffda010bca3f1eb15219b7c268ea941bf1d036cec3fed8fac9f3db&scene=27#wechat_redirect) + +### 6.2 版本管理 + +1、[关于 Git 和 GitHub,你所不知道的十件事](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485232&idx=2&sn=c3314e79af9029bdd76ae187f7b4eb19&scene=21#wechat_redirect) + +2、[关于 Git 图谱,这一篇文章讲得很细。](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485179&idx=2&sn=a9f0a8a669d70c0dccedf71a530d1d93&scene=21#wechat_redirect) + +3、[如何巧妙处理 Git 多平台换行符问题(LF or CRLF)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485333&idx=2&sn=4e8e95e5cb12d27ea6b510aaaf15b4c0&scene=21#wechat_redirect) + +4、[这 7 个高级技巧,不会还怎么玩GitHub](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484940&idx=1&sn=9ad168943b03dff09d53e5d73e13ed46&scene=21#wechat_redirect) + +5、[ 神器!这款VSCode插件能填满Github绿色格子](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485595&idx=1&sn=65df21ab0b4042953dd8257bea8b1848&chksm=e8866879dff1e16fff97584467f2f1728f9c2da007894ae062e9201d53b82a30054036f8e81e#rd) + +6、[Git 中冷门却又非常需要的高级用法](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485890&idx=2&sn=528df28585ec16114324e391dbfebab4&chksm=e8866920dff1e036d5da7f5dfe3c944ef8b13d72f022d72b209117120c7e27ebb71036b99893&token=2013245174&lang=zh_CN#rd) + +7、[别乱提交代码了,来围观下大厂的 Git 提交规范](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486877&idx=1&sn=f14a8add6cee89f8312c7077e4e1ed22&chksm=e8866d7fdff1e4691e6b3627fdd7963942d4d568a1e1ba1d7e8ba81944e6afaadc6923429006&scene=27#wechat_redirect) + +8、[盘点提高国内访问 Github 的速度的 9 种方案](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491802&idx=2&sn=30df1c954dd8505639c6cd71c0e37d76&chksm=e8858038dff2092eed0b603635b648f2c52d720e98536aee1d50b774c9feac6b25fec6ca9a7c&scene=27#wechat_redirect) + +9、[工作流一目了然,看小姐姐用动图展示10大Git命令](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486154&idx=1&sn=9a409d493eb789c66cb5c3596195582c&chksm=e8866a28dff1e33e13148dbb98dd8a3899392481f4806805bc96de3b6dc7a954ef3eb899bace&scene=27#wechat_redirect) + +10、[如何用 GitHub Actions 写出高质量的 Python代码?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485985&idx=2&sn=ec7a102012096847b8fdf7156d9260f3&chksm=e8866ac3dff1e3d53d2ae587e0769b1c42ab0176a7982751fc9a740ae45fe1a592691f373dcc&scene=27#wechat_redirect) + +11、[连 Git 都玩不转,还写什么代码?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493645&idx=2&sn=d9408f63f9afccd28482e41efe2ba11e&chksm=e88588efdff201f9d3cc513591ad0c7b791616e8ca10a7b091038094b8b52eb1f2585c22e799#rd) + +12、[5 个 Git 工作流,改善你的开发流程](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494114&idx=2&sn=f75412a52b9c36e268decf013fffa6de&chksm=e8858900dff2001686dc51e3227a9565f7e97033539a1f2af3d9f38aad87b55a8b5d2ea6d405&scene=27#wechat_redirect) + +13、[提高国内访问 GitHub 的速度的 9 种方案](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498432&idx=2&sn=d7f398a5598e9dd6636961387a64860f&chksm=e8859a22dff21334695d1495ead5adaf9a8102d1ba85bdfe0cd8b51fc6a8db08742bec95819e&scene=27#wechat_redirect) + + +### 6.3 数据库 + +1、[写给程序员的 MySQL 面试高频 100 问](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485321&idx=1&sn=30432df24d4e7858a475192bcf233914&scene=21#wechat_redirect) + +2、[开发人员必学的几点 SQL 优化点](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485892&idx=1&sn=e0b526002a5568391e75fb0cac60ab0b&chksm=e8866926dff1e0303f2717e95ff801c2550d97812c1a775cc232d48183af802db1be54f26105&token=2013245174&lang=zh_CN#rd) + +3、[再见,Navicat!同事安利的这个 PyCharm 的兄弟,真的香!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490040&idx=1&sn=754ca33e3ddbde88ee1b7b918e89d39c&chksm=e886791adff1f00c3cd0ac513cae55ac3bbb2732c637433d15bf42c22139dd54ee47f399b492&scene=27#wechat_redirect) + +4、[太牛逼了!一款软件几乎可以操作所有的数据库!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492595&idx=1&sn=b83bab19f921bcd104caf3ed0f1cf0e3&chksm=e8858311dff20a070cd8063efbca76c74439b18f6c8863f3da14621afb082d246f204ccdba92&scene=27#wechat_redirect) + +5、[硬核!15张图解Redis为什么这么快](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494364&idx=2&sn=c1fcdfda4df97a61cf765f2b5ef1873d&chksm=e8858a3edff20328886dc88e78a2b6ea670825a3e4531d635a1bb5684671c293ac67fa079e68&scene=27#wechat_redirect) + +6、[败家玩意儿!Redis 竟然浪费了这么多内存!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494605&idx=2&sn=3e7c69fece14132d8c8df75cf282c19f&chksm=e8858b2fdff202393289d3aa73f5702ab53afb27758a64bfe4288e4489d68a896411fb33c7c2&scene=27#wechat_redirect) + +### 6.4 Chrome + +1、[推荐 8个 超实用的 Chrome 插件](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484925&idx=1&sn=110e4da78a20e8b5721f00bb36ba9d4c&scene=21#wechat_redirect) + +2、[没有这 42 款插件的Chrome是没有灵魂的](https://mp.weixin.qq.com/s?__biz=MzU4OTUwMDE1Mw==&mid=2247485016&idx=1&sn=ba38963413ee2926f6f5e69b1427491d&scene=21#wechat_redirect) + +### 6.5 Windows + +1、[没钱买 mac?一招教你如何让 Windows 秒变 macOS](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492484&idx=2&sn=d82c0faaf39c420729fba238c65d2fc8&chksm=e8858366dff20a70abbabb14693f265d7fdb1c831cd2b464c9cf1ce8431feb4f07cbb76627ec&scene=27#wechat_redirect) + +2、[微软太良心,这么强大的软件竟然完全免费!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491841&idx=1&sn=80c8c0247f9100e29f7066c874400755&chksm=e88581e3dff208f503da7dafdee037e8db578186dbef79df648c054f54c6e86b9336ff86bde3&scene=27#wechat_redirect) + +3、[双系统的日子结束了:Windows和Linux将合二为一](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491306&idx=1&sn=dfdbc7e02b54622c2eed5f36501444f0&chksm=e8867e08dff1f71e02038aa720fdd99c524adbc6687c2f32070af638b713a154f0f9767205cc&scene=27#wechat_redirect) + +4、[这个只有1.5M的软件,能让你的网速快3倍](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488856&idx=1&sn=8a96c373b04c4ce7d78c7f87f5928c1e&chksm=e88675badff1fcac0086cb2ec20845821d2441d0909b9a3395b249744217944b1fb8f3e442c9&scene=27#wechat_redirect) + +5、[原来,我一直都不会用Windows](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494412&idx=2&sn=044b47f18f70e5179c5e5c84cb6f4ec4&chksm=e8858beedff202f8728666709b385c476fe41c7d41ed1c63e40afd198db5fe27fbeed2c1639b&scene=27#wechat_redirect) + +6、[Chrome 和 Edge最大的威胁来了....](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495227&idx=1&sn=0175723a1ee9b54d6d40010aa28447b3&chksm=e8858ed9dff207cfc51eef087c07d4f6ee1bd341937c6228605fe9281d5c80ab7e02a7d617f4&scene=27#wechat_redirect) + +### 6.6 其他工具 + +1、[整理了 11 个好用的代码质量审核和管理工具](https://mp.weixin.qq.com/s/DH_TOA3Cr3y37yZ5F4bU5A) + +2、[11 个最佳的 Python 编译器和解释器](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485682&idx=1&sn=31b1b68432ca1f1db3866fb0f670270b&chksm=e8866810dff1e106a21c63c1ee198069223047afc72482c1f14ee4f473211aca85273c57d529#rd) + +3、[太酷了!用上这 5 款神器后,你写的代码逼格瞬间爆表](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486248&idx=1&sn=ff17f64475d82e99d32e35f290a49edd&chksm=e8866bcadff1e2dc2ba61ca6a0f63fd753d60e77891ee0ae93207890ee891a9097a6d27b5695&scene=27#wechat_redirect) + +4、[我最喜欢的云 IDE 有哪些?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485965&idx=1&sn=3457ed667d64773bd83bdc3432c06dc7&chksm=e8866aefdff1e3f9407dce655688e2899c177c17a6472b0d50c15e2454cd6450165aee81877d&scene=27#wechat_redirect) + +5、[整理了6个好用的程序员开发在线工具](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489186&idx=2&sn=6099d58947654ddb35b43d7067d574d6&chksm=e8867640dff1ff56fb33df9d0de1ea820f14f3690d11184b1fcf02fa9480450a0b5639a754df&scene=27#wechat_redirect) + +6、[Github上这些可视化面板也太好看了吧](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488270&idx=1&sn=459867f1761d29e4a46f2e450e3ea975&chksm=e88673ecdff1fafaa0f1f89c9739b928e0227ac54059d4ff96c15faf8b88cd3752a929e018e1&scene=27#wechat_redirect) + +7、[腾讯终于良心了!桌面混乱终于有救了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492724&idx=1&sn=58cc2c03fffcbc87995a9927b6168ba7&chksm=e8858496dff20d80365246706c32bfebceb008f28cc04cb420cddd73cd50446495d02204099b&scene=27#wechat_redirect) + +8、[奇技淫巧:在 ssh 里面把服务器的文本复制到本地电脑](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493550&idx=2&sn=2619c1402e8951ff812c69994d922eb0&chksm=e885874cdff20e5adfcd9a95bf199cfcb73fbc7962c6de11c42240e3741eb5b5de75cc711649&token=58703854&lang=zh_CN#rd) + +9、[真神器!不用手写一行代码就能做网站~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494412&idx=1&sn=6789d2aa73a05671c219db659d5ad739&chksm=e8858beedff202f880c3255210e7880bb7c56ad378a1f7b9b82f4e2f24126e4ef8ca7c435103&scene=27#wechat_redirect) + +10、[用 Python 使用 Google Colab?岂止是炫酷](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494533&idx=2&sn=58e2fd5fc63936d89853c5d550293d00&chksm=e8858b67dff2027179fd492d796d537bad57caf830e0aa3fbd51061cd91bd7c7f3f2ce56eb80&scene=27#wechat_redirect) + +11、[自从用了这个神器,我再也不想写代码了...](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498473&idx=1&sn=448af801d90b29a69cb79b4089868f97&chksm=e8859a0bdff2131dd9fa0d6923fb8ae1de9e7311691a7558499cf995c0e4be8fd036031ee1c7&scene=27#wechat_redirect) + +12、[牛逼至极!用这个神器看代码太舒服了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497577&idx=1&sn=d0061deb47fa2c6441eb7dd11272af5d&chksm=e885978bdff21e9d4bc0a8747d9b2f6b04288095c7a019cfe1214a4ae382de657076313387bf&scene=27#wechat_redirect) + +## 07. 代码优化 + +### 7.1 算法讲解 + +1、[策略模式:商场促销活动背后的代码哲学](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484937&idx=1&sn=61b81dddfb20d80060fee5f22ab28d5f&scene=21#wechat_redirect) + +2、[算法教程|八张图带你轻松理解经典排序算法(上)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485042&idx=1&sn=b9e8503905be5a960119c2893995fb6d&scene=21#wechat_redirect) + +3、[算法教程|八张图带你轻松理解经典排序算法(中)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485041&idx=1&sn=12d05e0316c8866e3876c3ed484ced15&scene=21#wechat_redirect) + +4、[一次忘记密码引发的算法思考](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484981&idx=1&sn=96f49ab2225c61b45601b68521135dd6&scene=21#wechat_redirect) + +5、[程序员走楼梯都会思考的一道题](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484993&idx=1&sn=36cc1466ed73af2fcf4a8430368a62ee&scene=21#wechat_redirect) + +6、[以为是高性能神仙算法,一看源代码才发现...](https://mp.weixin.qq.com/s/L_OvCcpIFKBLckLBvit8UQ) + +7、[ 用Python手写十大经典排序算法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485494&idx=1&sn=1580e9fc17086bc83e30b0fa4c850bd8&chksm=e88668d4dff1e1c27a26b31fda2f0e413be83519aa9dd0e3c8d3cfd08a2478eb2596ed7ba535#rd) + +### 7.2设计模式 + +1、[单例模式告诉你只能娶一个老婆](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484921&idx=1&sn=493d0a492277ecb223ddfff9de8720ff&scene=21#wechat_redirect) + +### 7.3 代码优化 + +1、[看了同事的代码,我忍不住写了这份代码指南](https://mp.weixin.qq.com/s/goqj1YVdKV171LLpjtXruQ) + +2、[怎样才能写好一个 Python 函数](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485416&idx=1&sn=85b14857e795a92985230ba3f51b73f6&chksm=e886670adff1ee1c7f4266d4e3b931d2e3d1de4dff30dc869eae2cd174da7f54080787f60a1b#rd) + +3、[三个异常处理的好习惯](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484947&idx=1&sn=2bd31279dd9714045ed4dc5481b44208&scene=21#wechat_redirect) + +4、[超实用的 30 段 Python 案例](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484866&idx=1&sn=9c85dd1b58f319bb0339e2db577a0be6&scene=21#wechat_redirect) + +5、[删除系统 Python 引发的惨案](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485386&idx=1&sn=59eed0894159f4d0599be3fb787b5201&chksm=e8866728dff1ee3ef9fb7a0464df481306be11d9aeba944804e7ea74e660501d42e159ada7fc#rd) + +6、[提速72倍,在Python里面调用Golang函数](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492536&idx=2&sn=360f13b4678fcc6a6f14ee6221474285&chksm=e885835adff20a4ca2025c926c754b9bf6accc6b5b7021f97878163491122ea97d2721c279f6&scene=27#wechat_redirect) + +7、[这一行代码,能让你的 Python运行速度提高100倍](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492321&idx=1&sn=87dc6b52a0c63b8326bcc50b2c7b3a68&chksm=e8858203dff20b15d1e4730e2851d7523c30db9b404fdfa5b1b19978a1e326e2205e3d6bf7ac&scene=27#wechat_redirect) + +8、[三行Python代码,让数据处理速度提高2到6倍](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491111&idx=2&sn=d27c6398b82749a20c46490b8fdd900f&chksm=e8867ec5dff1f7d3e9f95f221a2f33003bec76da1f0c002f97a59c7887cf460c887e1ad5a579&scene=27#wechat_redirect) + +9、[一文教你节省 90% 的内存占用](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489739&idx=1&sn=6589629f52a3a6ec18e67a3ca4952616&chksm=e8867829dff1f13fa87b08ecd09ce319dbd955c8ee15b44c60509e171f1920a6da600075dcd0&scene=27#wechat_redirect) + +10、[写出漂亮 Python 代码的 20条准则](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488402&idx=1&sn=231c2954b0ccca44a18268ab80ddbe88&chksm=e8867370dff1fa66a12d6af4adb66a1d80cbccbf9093c3a8b966a406bf5d8fc005a08166ef45&scene=27#wechat_redirect) + +11、[阅读优秀的项目代码,几点实用的经验分享](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487922&idx=2&sn=c330d9b4c7bbc1b838e8a9fe90283c27&chksm=e8867150dff1f846628f181ff9687ebf91ba5d06f7721a67bf746698c6f02ea398cdc33c5a0d&scene=27#wechat_redirect) + +12、[一行 Python 代码实现并行](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487299&idx=2&sn=5dffb95993b6fd87c6a3dd1a56b56814&chksm=e8866fa1dff1e6b78239ffa2fe8d124e4ee3af2b7b71f8e9593bdeae2221fcd2afa2b5773e26&scene=27#wechat_redirect) + +13、[代码被反编译了?这两个小技巧能帮到你](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487287&idx=1&sn=05c7ca030257db25623e98ed5d302878&chksm=e8866fd5dff1e6c3219dc6d78717411f716bfa38d89a34ed64a88c0702765258fd34b4edd428&scene=27#wechat_redirect) + +14、[一份可以令 Python 变快的工具清单](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486107&idx=1&sn=83a3f95fea5cb2c24d7f44bf11f2acdb&chksm=e8866a79dff1e36ff4e6c8c546568bca0a36ea3de0550bd87a785ed9ce5cbda4a6d84b5b5fc9&scene=27#wechat_redirect) + +15、[记一次 Python Web 接口优化](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486053&idx=1&sn=862e6fbaa8a85a4a10ee35d9eba00196&chksm=e8866a87dff1e391399c252ffcb3168ef1f4c85d5ea52d6f43ceaf81e3f7cb3e548f6de505df&scene=27#wechat_redirect) + +16、[翻车了!pyc 文件居然曝光了我的密码](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487730&idx=1&sn=eed309620be1092cf5af030be6b354ff&chksm=e8867010dff1f90611e155156075c2479bab5c9237e8a514a2c7ffe2a317ba16baa30b76d9c9&scene=27#wechat_redirect) + +17、[没有什么内存问题,是一行Python代码解决不了的](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492724&idx=2&sn=a84245d93d4191f157bac3e6a38d7066&chksm=e8858496dff20d808bb6ce5876bb0669a72722b6f90ec74f3b43de49ca1e07101c410138e113&scene=27#wechat_redirect) + + +## 08. 冷技巧集锦 + +### 8.1 冷知识 + +1、[Python那些不为人知的冷知识(一)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485031&idx=1&sn=e5ab670ce6485a50c4b7eeaee11e6f34&scene=21#wechat_redirect) + +2、[Python那些不为人知的冷知识(二)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485029&idx=1&sn=5db54b5777348e827c274cd932a15a15&scene=21#wechat_redirect) + +3、[Python那些不为人知的冷知识(三)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485009&idx=1&sn=92e268c20c6f01278e220e11397cc2f0&scene=21#wechat_redirect) + +4、[Python那些不为人知的冷知识(四)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485001&idx=1&sn=5a02f8c912518d15a0b96319cf2da7d1&scene=21#wechat_redirect) + +5、[Python那些不为人知的冷知识(五)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484997&idx=1&sn=5b8cf44b62550e1bd67284fb2b0780dc&scene=21#wechat_redirect) + +6、[Python那些不为人知的冷知识(六)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484990&idx=1&sn=bd28368eff832ba2b24a64f637a6d0c8&scene=21#wechat_redirect) + +7、[Python那些不为人知的冷知识(七)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484874&idx=1&sn=abffab70b1e265ede1fd3b994dedab90&scene=21#wechat_redirect) + +8、[Python 之父为什么嫌弃 lambda 匿名函数?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492291&idx=2&sn=3cfdde7bc5e7b55aae00a30c63abe562&chksm=e8858221dff20b3719dfed632d181b95599b843006ae0c2186b5c69593ee7c22c5a5d2c82509&scene=27#wechat_redirect) + +9、[Python 到底是强类型语言,还是弱类型语言?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491532&idx=1&sn=a86bccc1d270390331c36205d3e46497&chksm=e8867f2edff1f6388d80289bff567445c2c9c9414efec74e4a308f7e8a1cabf662c8fc0a6cc0&scene=27#wechat_redirect) + +10、[Python 为什么不支持 i++ 自增语法,不提供 ++ 操作符?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489219&idx=2&sn=0dfa7ae7d4dcdda538b35bbea80b34e4&chksm=e8867621dff1ff372969bf3901835e67a1bfa7cb72709b491987fda3daa0e5f8d15a86c2b6b5&scene=27#wechat_redirect) + +11、[Python 为什么没有 main 函数?为什么我不推荐写 main 函数?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489175&idx=2&sn=2f8f9fb4b31c91ef71d1f34841da6450&chksm=e8867675dff1ff63e6e1f28eec9f18c41c30c050118eb65cde6960e5f6ee5866d7515f2b0040&scene=27#wechat_redirect) + +12、[Python 列表的这 8 个实用技巧,你都 OK 么?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489032&idx=2&sn=38d3985803cb5b6ed10e4cc11972eff7&chksm=e88676eadff1fffc88a3a3a2e3815b2abff43318c597344857c9c5f7806b6b0e6c457b33842d&scene=27#wechat_redirect) + +13、[Python 为什么不用分号作终止符?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488816&idx=2&sn=6bc8aa32d9a266cbffabcd87664e75fd&chksm=e88675d2dff1fcc4e845a90903bda5dfe59262a2b60533045a03e574076e92aa6f30974a73f5&scene=27#wechat_redirect) + +14、[Python 为什么推荐蛇形命名法?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488517&idx=1&sn=8b449c2d7c831600be6c5d57e9cb0837&chksm=e88674e7dff1fdf1ab8092ac3cdd2f3978ee6b219daa18a5407c390f62e11448c39455437bb0&scene=27#wechat_redirect) + +15、[Python 为什么用 # 号作注释符?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489657&idx=1&sn=820a070f05096dbe2bdb979fc9492cdb&chksm=e886789bdff1f18d420ee3efa8f85468303690232c4005f775191b0a84a1921430e37aa7e74f&scene=27#wechat_redirect) + +16、[!/usr/bin/python与#!/usr/bin/env python的区别](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486008&idx=2&sn=8637cdc12799f084cd5ecdba9534fb76&chksm=e8866adadff1e3cc49b369dddfaeecc1d9b51f13de1f2733266b2f9fc8426d9e3944a15eb074&scene=27#wechat_redirect) + +17、[pip install 和conda install有什么区别吗?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487164&idx=2&sn=18de23de9c79f3c20e5a432bb675c262&chksm=e8866e5edff1e748cb6c83d2f4368cbab805fd7bfd41820b60da22061818e8d84548ac14714c&scene=27#wechat_redirect) + +18、[Python 什么情况下会生成 pyc 文件?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486071&idx=2&sn=533257b9b6156bd5588199b349266a3d&chksm=e8866a95dff1e383991b311dc73994ca103c380433a620c5bdf9f8d09b1c49daa74dce6ea941&scene=27#wechat_redirect) + + +### 8.2 冷技巧 + +1、[让Python中类的属性具有惰性求值的能力](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485759&idx=2&sn=e32b66c4666f96f84572738549a47a24&chksm=e88669dddff1e0cb0a4b6e6e965c9570b273236fa58a54351ae3b118d6a4bcbd52e452109a63&token=1148998814&lang=zh_CN#rd) + +2、[ 小技巧:如何在 Python 中实现延迟调用](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485689&idx=2&sn=43e35ff93918ef090480672d4685bce3&chksm=e886681bdff1e10dd0edee95c73a977da51fdeb514dcf8c3bb88f7f9aacc22ce155f45e84268#rd) + +3、[整理了 18个 Python 高效编程技巧](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491418&idx=1&sn=89c35f5d69a54196bd7f32a64171e05d&chksm=e8867fb8dff1f6aee18414c89c31ce118550ba11b645e2e4f6014af4d15d319decc000a70c91&scene=27#wechat_redirect) + +4、[Python 程序报错崩溃后,如何倒回到崩溃的位置?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488434&idx=1&sn=e2b4ca12b228c16e944ff36abec53e22&chksm=e8867350dff1fa46ee7799e460d2b304dbb0c227d33a39d18099761c205319d99a9b4135f9fa&scene=27#wechat_redirect) + +5、[如何用简单的位操作实现高级算法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487151&idx=1&sn=5376f42c8990027132deb52a6b66307e&chksm=e8866e4ddff1e75b3af85905177130d23118254d42e9452930cc784b209cdf294d9bb73aa247&scene=27#wechat_redirect) + +6、[如何限定Python函数只能被特定函数调用](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490777&idx=2&sn=6572f957117e0a0256da2b85c7221a37&chksm=e8867c3bdff1f52d045cadb962a2da16b015d8455147afe374cf3067dd89c580e1f9514d57d0&scene=27#wechat_redirect) + +7、[pylint 除了检查代码风格,居然还能...](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491737&idx=2&sn=f6781d158c5af171f1b916ee7b0c7a68&chksm=e885807bdff2096dc1580abbba594ed1cc1c0994c06f6bd5ede233df0593a43842f7af3e2f8b&scene=27#wechat_redirect) + +8、[让Python在退出时强制运行一段代码](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486207&idx=2&sn=5586a204b9537d37c02b9926a01719dc&chksm=e8866a1ddff1e30bb77d72ba0013d870f80e0029546729f0e8c65512c4969f5f0733f60ecddf&scene=27#wechat_redirect) + +9、[不使用 if-elif 语句,如何优雅地判断某个数字所属的等级?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486167&idx=2&sn=a31cbf6d3526bfc860b0daa69889193f&chksm=e8866a35dff1e323ed958858223dda1af6ae0d371be0037c35f9ae6894e776dec1ee53c2a00b&scene=27#wechat_redirect) + +10、[涨姿势了,raise...from... 是个什么操作?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486001&idx=1&sn=b9a89305dae7d4f57105be1d274fae43&chksm=e8866ad3dff1e3c51b5fa132147b9f9c9e4ba14b41c5409e62e2f2696d1389c6ac4ddd1e7bec&scene=27#wechat_redirect) + +11、[一些我日常使用的 Python 技巧分享](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488382&idx=2&sn=f2b090fe47bb56e0463126c69726bf10&chksm=e886739cdff1fa8a65ce4869a8bdb8fbd28396e3fbcdac2a4441b7c58ba5c77cea1765858146&scene=27#wechat_redirect) + +12、[5年 Python 功力,总结了 10 个开发技巧](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488243&idx=1&sn=66d2f534ddcbed582e7ba734b53701f5&chksm=e8867211dff1fb07f2edcebfda9e7863ee57266c15d4276bc89c2ee69baafc46fbc0e85fe703&scene=27#wechat_redirect) + +13、[我发现了个 Python 黑魔法,执行任意代码都会自动念上一段 『平安经』](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490548&idx=1&sn=c71772cc40992d8912fd8a3fa1607038&chksm=e8867b16dff1f20081431362c99fb981d0cd75f2bd5baf981b7db18e08492138aab10f98efb8&scene=27#wechat_redirect) + +14、[实用技巧分享:如何批量更新已安装的库?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487862&idx=2&sn=a1c092467b471db82060b395c341be6c&chksm=e8867194dff1f882da87863f4d4a4ccbd687daa0b4a742dc6d01fd01d0a6ca6c5fb7cc28629b&scene=27#wechat_redirect) + +15、[如何导入父文件夹中的模块并读取当前文件夹内的资源](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493878&idx=2&sn=36c70addd7c0c0640d1285ab590410e4&chksm=e8858814dff201026397c7b08034ad513c4b9e5dcf8307325951ae4077e386f87f5685ca528f#rd) + +16、[用 Python 读取资源文件?这个技巧保你涨姿势](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494041&idx=2&sn=3b3a43157290f0ff542185982aa4b537&chksm=e885897bdff2006dd634e39ccc3c753704ad0f74e2fec95249d0d7f0c30bee85426ef86028e0#rd) + +### 8.3 炫技操作 + +1、[Python 炫技操作(01):条件语句的七种写法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485991&idx=1&sn=98e17f91d2f66f62c9aa644c03d8259d&chksm=e8866ac5dff1e3d3f6810a3f9e388d46d95945af9748baea0380ecec83c768eaac09738cf959&scene=27#wechat_redirect) + + +2、[Python 炫技操作(02):合并字典的七种方法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486080&idx=1&sn=f1c5c4fc5363a1d787b9ae9baba0d07b&chksm=e8866a62dff1e374ad1e5ae2e51bc6cbeb41f631899cde980555ded61be840f0fe6c76c44b7d&scene=27#wechat_redirect) + +3、[Python 炫技操作(03):连接列表的八种方法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486132&idx=1&sn=3827cf8672d4121b77225dbf9d377d69&chksm=e8866a56dff1e34094f2f834a613800480c0cdb078ab5656640fcb8c445ffa020b175b0f55cb&scene=27#wechat_redirect) + +4、[Python 炫技操作(04):海象运算符的三种用法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486158&idx=1&sn=0bd0647702a1599082e5bf1710d89e37&chksm=e8866a2cdff1e33ab4465d88497ea3505ad8fe614365eec7592a8c75053a6c8218206807c1a3&scene=27#wechat_redirect) + +5、[Python 炫技操作(05):花式导包的八种方法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486799&idx=1&sn=1be136d4e45c1b6f8a5eda567aaf259d&chksm=e8866daddff1e4bbddc0164ae795d6c302e36226cb3ba76ffc57e5c993b0861ef4ad693c2a56&scene=27#wechat_redirect) + +6、[Python 炫技操作(06):判断是否包含子串的七种方法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491171&idx=2&sn=4596043fb72b75a04a778942c61cc2af&chksm=e8867e81dff1f7979e21f58716b3ce3e09f8515829b15acd5169dd3f160e302d617a1b94cb2d&scene=27#wechat_redirect) + +7、[Python 炫技操作(07):模块重载的五种方法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247492817&idx=1&sn=cfb52d1a3f88a61e7b22a63306dac0e9&chksm=e8858433dff20d25f06ac0b5b767121907a4679b9e6da0905694d15c432ba596ce7b202ec7bb&scene=27#wechat_redirect) + +8、[Python 炫技操作(08):五种 Python 转义表示法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247494334&idx=1&sn=9f55122545b7b5d81049af27eff7800c&chksm=e8858a5cdff2034a8158b14669aaf695656c1227428ce86f22b19230769b5b45104b0a885205&scene=27#wechat_redirect) + +9、[最全总结:把模块当做脚本来执行的 7 种案例及其原理](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490904&idx=1&sn=ca3725510c7510965cbc76bd1bf06949&chksm=e8867dbadff1f4acb08c0daf08272ca61757909b0b73a4cde1aa4d86965c149c40f44417f7d0&scene=27#wechat_redirect) + +10、[这个 Python 炫技操作千万不要用,别问我怎么知道的?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491171&idx=1&sn=27d55a4b8b717450f1ab0596263f453f&chksm=e8867e81dff1f7975674ab7fcc0019654ae0e867db045fb575623f91cb143801d03aa365be2d&scene=27#wechat_redirect) + +11、[涨见识了,在终端执行 Python 代码的 6 种方式](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488283&idx=1&sn=24a78834ec3434a90ca7c3e6e972495c&chksm=e88673f9dff1faef908c43a41133f65695a68579e0edb2d7b15f131d9be26bcf919d66a7d9e3&scene=27#wechat_redirect) + +12、[Python 炫技操作:推导式的五种写法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496358&idx=2&sn=1d4c4463663f546b5768945ce31598f7&chksm=e8859244dff21b52cd0dce2ca366782ea14bf194f66498b07b6125426cc324c21a13be5abd3a&scene=27#wechat_redirect) + +13、[Python 炫技操作:安装包的八种方法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247495287&idx=1&sn=1eb6f14782f17a552320b3d6af5e17f3&chksm=e8858e95dff207836d575464ccc2f1be06b8fa824dcd1589613fe8d6ca48ebb4578f57378858&scene=27#wechat_redirect) + + + + +## 9. 云计算 + +1、[一份面向初学者的云计算通识指南](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484908&idx=1&sn=3085e048da685440408e1bdc54feb4aa&scene=21#wechat_redirect) + +2、[ 如何探测虚拟环境是物理机、虚拟机还是容器?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485401&idx=2&sn=8bfb6e1a71e0777a4d5e55f64b55ace5&chksm=e886673bdff1ee2d8acce291e402cd2ae0c7f64e455fdeea158eb53e535ee79b2aadc449935e#rd) + +3、[超详细教你如何阅读 OpenStack 源码?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485899&idx=1&sn=7a578a553c67e794ef6ec2f463864a46&chksm=e8866929dff1e03fa8152751967106e9262980db6afdff106fe9e39854d24d25494b3e8a3a4e&token=2013245174&lang=zh_CN#rd) + +## 10. 职场相关 + +1、[一个专科生的 Python 转行之路](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485012&idx=1&sn=751796e63a30d84be01486619b5fb806&scene=21#wechat_redirect) + +2、[一个机械生的Python转行自述](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484979&idx=1&sn=6513e940bc7c0677dedf7efff80aa4c6&scene=21#wechat_redirect) + +3、[自学 Python后端开发 到什么程度可以找工作?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485010&idx=1&sn=e76d031f91633f9bfde9da36d8dcf996&scene=21#wechat_redirect) + +4、[工作不是游戏,写给编程新人的五点建议](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484984&idx=1&sn=0934415cc48004ae76345dcfb8e33b94&scene=21#wechat_redirect) + +5、[Python 从业十年是种什么体验?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485601&idx=1&sn=0dbebc02002ab01a8cd42265822ff81c&chksm=e8866843dff1e15509f2a9d2cd551b2360607cf88e73e5d3036388716ee57949d726dcea209c#rd) + +6、[40条提升编程技能的小妙招](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489765&idx=2&sn=79287da847c25ce0d72db6a33eb0c5a0&chksm=e8867807dff1f111043fe22bbfc776a52331c28abf85c2f658cbbaba3235c4e9d3c070c44d8c&scene=27#wechat_redirect) + +7、[我,一个靠GitHub打赏谋生的码农,年入十万美元](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488934&idx=1&sn=56f51b946908ed89a58b311fc77f47d6&chksm=e8867544dff1fc52e30288685c89e86cefd063d5d37f5b1aba5fb30fd3665cf18346533cad96&scene=27#wechat_redirect) + +8、[5 年 Python 的我,总结了这 90 条写 Python 程序的建议](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247487323&idx=1&sn=1b6594d63319faaadefad1f5e178235f&chksm=e8866fb9dff1e6af732304c8f3645cecca8b4bfa4158e45b0ea6c5167c2675bd748ce23e8239&scene=27#wechat_redirect) + +9、[Python这么慢,为啥大公司还在用?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489132&idx=1&sn=55dda8a6a10429b3a7016393bc058493&chksm=e886768edff1ff981d67b2ecfe52ebf5b8dbc0085289e6bd3f8a6c6631ee6fc4a633fb6bc4b8&scene=27#wechat_redirect) + +10、[一个中科大差生的 8年 程序员工作总结](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497523&idx=1&sn=725c1167873927aea519f690f3c106ec&chksm=e88597d1dff21ec709eeba54a9fb35318afcac3cb3b235e0ab831fd40f9917dc2acb40d1bbb3&scene=27#wechat_redirect) + +## 11. 通用文章 + +1、[假如有人把支付宝存储服务器炸了,你的存款会怎样?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485171&idx=1&sn=d6e2e24f68f3c6eb35e880f32e12d4ab&chksm=e8866611dff1ef0791cf5aae05697592cb804010aecd783a8592f7a90a387645fe4b47a376ea&scene=27#wechat_redirect) + +2、[手机没网了,却还能支付,这是什么原理?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493296&idx=1&sn=8dae2411c61b7e5ab4a1fcdf45b86c79&chksm=e8858652dff20f44519c328ba22b4f22c8b2320bc1464319e98d6084697dfab3ef2346a5cf49&scene=27#wechat_redirect) + +3、[Chrome 的小恐龙游戏,被我破解了...](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491911&idx=1&sn=1dcab055e480fe626d50b0324ed5b9f1&chksm=e88581a5dff208b3fbf88017c15ccfa966eec3b25e4804a72401773d7b87d5b6a2bcd8db9ac7&scene=27#wechat_redirect) + +4、[深扒微信多开的秘密后,我竟然发现了个 bug](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247489704&idx=1&sn=a33559f22517c744367a660f78dbb25a&chksm=e886784adff1f15c1524f90487d6957c86dee0f5c499528630f43b62320b28e66977afe4d884&scene=27#wechat_redirect) + +5、[让许多 Python “老玩家”心寒的 10 大槽点](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497032&idx=1&sn=0347edefbb9fc47d13c58379130aa327&chksm=e88595aadff21cbc3a6c8ad6752a75ce708244ed517fd9a3230efa2eb783927033977ba26b82&scene=27#wechat_redirect) + +6、[别瞎学了,这几门语言要被淘汰了!](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247496522&idx=1&sn=1aaa60b8295378cfcbeaaaf528b13df0&chksm=e88593a8dff21abe290ee23821ed9cebb2aa68148548852319363b4de4663d0a5ab646451e5e&scene=27#wechat_redirect) + +7、[是的,Python是慢,但我不在乎](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247497752&idx=1&sn=0d207936a803b414b04d13bb167124ba&chksm=e88598fadff211ecd77676506b5f039c0fbd8bd32eb5f68df2c41a277526304dc89f3d6f7dad&scene=27#wechat_redirect) + +8、[当今全球最厉害的14位程序员,说没听过简直离谱~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247498702&idx=1&sn=8b5a405de0649de1bf52235956840af7&chksm=e8859b2cdff2123a2f6168fb846781f2ba678ac4b70fd01513c2547b14b4d1a053ca0b67e5f6&scene=27#wechat_redirect) + +## 12. 明哥的作品 + +1、[太赞了!《Python 黑魔法指南》终于面世了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486834&idx=1&sn=f5b94c3a520624786162f78246e60246&chksm=e8866d90dff1e486a3dae97aaf347e83834c8cf204849e5f9ae23dc9fdf0c470f97e0298cf74&scene=27#wechat_redirect) + +2、[《Python黑魔法指南》全新版本 v2.0 上线发布](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247490543&idx=1&sn=449a1c7adfafdd5cf37874ed67620e89&chksm=e8867b0ddff1f21b9d622ba7a53b35143cd7fbb3da0d7c091b3d2cdd71d817a4397a99fa424e&scene=27#wechat_redirect) + +3、[明哥写了两个月,这 200页《PyCharm 中文指南》 终于可以发布了](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247491716&idx=1&sn=76a9f3ad5163a9ffdbaf0fefd0259261&chksm=e8858066dff20970fa9988675154fff6a6f84793a80b8ea1d6fe5d5bbb5bc816d2feabc50664#rd) + +4、[一个明哥真正意义上的个人网站,现在来啦~](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247493207&idx=1&sn=ced486f99dc7d9ab665849da1016ceb1&chksm=e88586b5dff20fa32db6f3074ee954cbe30b34846c5852d8772184a41e9ad58293947e22095d&scene=27#wechat_redirect) + + + +--- + + + +都看到这儿了,还不点个关注嘛? `*^_^*` + +![](http://image.iswbm.com/20201024132326.png) ## 欢迎交流 对文章有什么疑问,对项目有什么建议,可以添加微信与我交流,同时欢迎关注我的个人微信公众号。 -![](http://image.iswbm.com/20200607140327.png) +![](http://image.iswbm.com/image-20201117215520960.png) diff --git a/github-toc-maker-for-sphinx.py b/github-toc-maker-for-sphinx.py new file mode 100644 index 0000000..49f3f5c --- /dev/null +++ b/github-toc-maker-for-sphinx.py @@ -0,0 +1,71 @@ +import os +import re +from glob import glob + +pwd = os.getcwd() +all_chapters_path = glob(pwd + "/source/c0*") + + +def get_chapter_name(file): + with open(file) as f: + f.readline() + chapter_name = f.readline().strip() + + return chapter_name + +def get_title(file): + with open(file) as f: + first_line = f.readline() + + if first_line.startswith("#"): + return first_line[1:].strip() + +def generate_mapping(): + mapping = dict.fromkeys([os.path.basename(chapter_path) for chapter_path in all_chapters_path]) + for key in mapping.keys(): + chapter_file = os.path.join(pwd, "source", "chapters", key.replace("c", "p") + ".rst") + mapping[key] = get_chapter_name(chapter_file) + + return mapping + +def get_toc_info(): + toc = {} + for dir_path in all_chapters_path: + dir_name = os.path.basename(dir_path) + + chapter_toc = {} + files = glob(dir_path + "/*.md") + + for file in files: + file_name = os.path.basename(file) + section = int(re.findall(r"c\d{2}_(\d{2}).md", file_name)[0]) + + #md_path = os.path.join("./source/", dir_name, file_name) + md_path = os.path.join("http://pythontime.iswbm.com/en/latest/", dir_name, file_name.replace("md", "html")) + title = get_title(file) + if not title: + continue + + chapter_toc[section] = (title, md_path) + + toc[dir_name] = chapter_toc + + return toc + +def print_md_toc(toc_info, mapping): + for chapter in sorted(toc_info.items(), key=lambda item: item[0]): + posts = chapter[1] + chapter_name = mapping[chapter[0]] + print(f"- **{chapter_name}**") + for post in sorted(posts.items(), key=lambda item:item[0]): + # print title only + # print(f"{post[1][0]}") + print(" ", f"* [{post[1][0]}]({post[1][1]})") + +def main(): + mapping = generate_mapping() + toc_info = get_toc_info() + print_md_toc(toc_info, mapping) + +if __name__ == '__main__': + main() diff --git a/md2rst.py b/md2rst.py index 3dca78a..51a7a32 100644 --- a/md2rst.py +++ b/md2rst.py @@ -24,7 +24,7 @@ # 没有文件变更 os._exit(0) -base_link = "http://python.iswbm.com/en/latest/" +base_link = "http://pythontime.iswbm.com/en/latest/" readme_header = ''' ![](http://image.iswbm.com/20200607120940.png) @@ -36,9 +36,9 @@

-## [项目主页](http://python.iswbm.com/) +## [项目主页](http://pythontime.iswbm.com/) -在线阅读:[Python 编程时光](http://python.iswbm.com/) +在线阅读:[Python 编程时光](http://pythontime.iswbm.com/) ![](http://image.iswbm.com/20200607130051.png) diff --git a/rebuild.sh b/rebuild.sh new file mode 100755 index 0000000..319b303 --- /dev/null +++ b/rebuild.sh @@ -0,0 +1,13 @@ +cat << EOF >/usr/local/lib/python3.6/site-packages/sphinx_rtd_theme/comments.html + + + +EOF + +rm -rf build/ && sphinx-multiversion source build/html && cp -rf build/html/master/* build/html/ diff --git a/requirements.txt b/requirements.txt index f0b035b..2a34b8d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,22 +1,21 @@ alabaster==0.7.12 argh==0.26.2 -Babel==2.7.0 +Babel==2.9.1 certifi==2019.6.16 chardet==3.0.4 docutils==0.14 -idna==2.8 imagesize==1.1.0 -Jinja2==2.10.1 +Jinja2==2.11.3 livereload==2.6.1 MarkupSafe==1.1.1 packaging==19.0 pathtools==0.1.2 port-for==0.3.1 -Pygments==2.4.2 +Pygments==2.7.4 pyparsing==2.4.0 pytz==2019.1 -PyYAML==5.1.1 -requests==2.22.0 +PyYAML==6.0.1 +#requests==2.32.3 six==1.12.0 snowballstemmer==1.9.0 Sphinx==2.1.2 @@ -29,7 +28,7 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.2 sphinxcontrib-serializinghtml==1.1.3 tornado==6.0.3 -urllib3==1.25.3 +#urllib3==2.2.2 watchdog==0.9.0 sphinxcontrib-disqus==1.1.0 sphinxcontrib-applehelp==1.0.1 @@ -38,3 +37,5 @@ sphinxcontrib-htmlhelp==1.0.2 sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.2 sphinxcontrib-serializinghtml==1.1.3 +sphinx-sitemap==2.2.0 +sphinx-multiversion==0.2.4 diff --git a/source/.DS_Store b/source/.DS_Store deleted file mode 100644 index f66a82f..0000000 Binary files a/source/.DS_Store and /dev/null differ diff --git a/source/_static/js/readmore.js b/source/_static/js/readmore.js index e91d28f..f2a29c2 100644 --- a/source/_static/js/readmore.js +++ b/source/_static/js/readmore.js @@ -40,8 +40,8 @@ var setIdTimer = setInterval(function () { id: id, blogId: '15406-1578143418297-890', name: 'Python编程时光', - qrcode: 'http://image.python-online.cn/20200104210733.png', - keyword: 'vip' + qrcode: 'http://image.iswbm.com/20200104210733.png', + keyword: 'more' }); } diff --git a/source/_templates/versions.html b/source/_templates/versions.html new file mode 100644 index 0000000..31a1257 --- /dev/null +++ b/source/_templates/versions.html @@ -0,0 +1,27 @@ +{%- if current_version %} +
+ + Other Versions + v: {{ current_version.name }} + + +
+ {%- if versions.tags %} +
+
Tags
+ {%- for item in versions.tags %} +
{{ item.name }}
+ {%- endfor %} +
+ {%- endif %} + {%- if versions.branches %} +
+
Branches
+ {%- for item in versions.branches %} +
{{ item.name }}
+ {%- endfor %} +
+ {%- endif %} +
+
+{%- endif %} diff --git a/source/aboutme.rst b/source/aboutme.rst old mode 100755 new mode 100644 index 33479e6..b32bed2 --- a/source/aboutme.rst +++ b/source/aboutme.rst @@ -3,10 +3,10 @@ ============== * 姓名: 王炳明 -* 微信: mrbensonwon +* 微信: stromwbm * 公众号: 《Python编程时光》&《Go编程时光》 * Email: wongbingming@163.com -* GitHub: https://github.com/bingmingwong +* GitHub: https://github.com/iswbm -------------------------------------------- diff --git a/source/c01/c01_01.md b/source/c01/c01_01.md index ce91a40..d3b620f 100644 --- a/source/c01/c01_01.md +++ b/source/c01/c01_01.md @@ -2,19 +2,11 @@ ![](http://image.iswbm.com/20200602135014.png) ---- +## 0. 去哪里找 -从今天开始,小明将和你一起过一下,那些在面试「Python开发」岗位时面试官喜欢问的问题。内容基础,但是你不一定会噢。 +这块的内容,你随便使用搜索引擎都可以查到。 -这些问题全部来自个人经验,群友推荐以及网络上的帖子。如果你有好的问题,也可以随时向我提出(不要觉得简单),我会筛选后整理出来在这里,供大家学习取经,给大家在求职路上贡献一份力。 - -开篇讲些什么好呢? - -今天就来罗列一下,Python2.x和3.x到底有哪些区别吧。 - -## 去哪里找 - -你随便全使用搜索引擎都可以查到这些资料,但是大家说的都是一些普遍都知道的事儿。或者都是抄来抄去,内容相差无几。 +但是大家好像都在抄来抄去,内容相差无几。 授人以鱼,不如授人以渔。 @@ -24,17 +16,17 @@ 这个地址里,有所有Python历史版本(2.0+)。 点击左边,Release Version栏目 对应的版本。 -![](http://image.python-online.cn/20190511165542.png) +![](http://image.iswbm.com/20190511165542.png) 进入对应详情页后,找到如图 `what's new in Python xx` 就可以查看此版本的新特性。 -![](http://image.python-online.cn/20190511165551.png) +![](http://image.iswbm.com/20190511165551.png) 网页是全英文的,需要你有一定的英文阅读能力。快去感觉一下吧。 接下来。和大家一起过一下,Python2.x和3.x到底有哪些区别,这不仅在你开发过程中需要考虑的,也是面试过程面试官经常会问及的。 -## 1.1.1 print +## 1. print 在Python 2.6之前,只支持 ``` @@ -52,7 +44,7 @@ print("hello") print ("hello") ``` -## 1.1.2 编码方式 +## 2. 编码方式 在Python2.x中,默认使用`ASCII`编码。 @@ -76,7 +68,7 @@ print str1 所以我们可以在程序中,随意的使用中文(但并不推荐),不会报错。 -## 1.2.3 除法运算 +## 3. 除法运算 Python 2.x中除法运算,整数间运算只保留整数(向下取整)。 ```python @@ -108,7 +100,7 @@ Python 3.x中除法运算,全部保留小数(即使能被整除)。 -3.0 ``` -## 1.2.4 异常捕获 +## 4. 异常捕获 在 Python 3 中,只能使用 `as` 作为关键词。而在Python 2中经常使用 `except Exception, e` 使用语法except (exc1, exc2) as var可以同时捕获多种类别的异常。 @@ -120,7 +112,7 @@ Python 2.6已经支持这两种语法。 在2.x时代,异常在代码中除了表示程序错误,还经常做一些普通控制结构应该做的事情,在3.x中可以看出,设计者让异常变的更加专一,只有在错误发生的情况才能去用异常捕获语句来处理。 -## 1.2.5 xrange +## 5. xrange 首先,要了解的是,xrange是只有在Python2.x中才有的产物。 @@ -139,7 +131,7 @@ xrange(1, 5) range(0, 10) ``` -## 1.2.6 用户输入 +## 6. 用户输入 在2.x 中,有两个函数。raw_input()和input()。 - raw_input():将所有输入作为字符串看待,返回字符串类型。 @@ -148,7 +140,7 @@ range(0, 10) 在3.x 中,对这两个函数进行整合,只留下一个`input()`,既可输入数字,也可输入字符串,返回的是字符串类型。 -## 1.2.7 数据类型 +## 7. 数据类型 Python 3.x 一个很重要的特性是,对字符串和二进制数据流做了明确的区分。 @@ -160,7 +152,7 @@ Python 3不会以任意隐式的方式混用str和bytes,你不能拼接字符 还有一点是,3.X去除了long类型,取代它的是整型(int)。3.x的整型是没有限制大小的,可以当做long类型使用, 但实际上由于机器内存的有限,我们使用的整数是不可能无限大的。 -## 1.2.8 函数式编程 +## 8. 函数式编程 在Python中,我们常常使用到的map,filter,reduce,在2.x和3.x中也有所不同。 @@ -195,13 +187,13 @@ Python 3不会以任意隐式的方式混用str和bytes,你不能拼接字符 ``` 对于 reduce 函数,它在 Python 3.x 中已经不属于 built-in 了,被挪到 functools 模块当中。 -## 1.2.9 协程关键字 +## 9. 协程关键字 在Python3.3后,协程中,新增了yield from 和 async/await 关键字,这在2.x中是没有。 关于yield from的语法剖析,可以前往查看我的另一篇文章。 -## 1.2.10 类的类型 +## 10. 类的类型 Python2.x 默认使用经典类,只有显示继承object才是新式类。 @@ -217,7 +209,7 @@ class Cls(object): pass ``` -## 1.2.11 变量作用域 +## 11. 变量作用域 - 在2.x中无法将局部变量声明为全局变量。 - 在3.x中可以使用nonlocal语法将局部变量声明为全局变量。 @@ -236,7 +228,7 @@ foo() # 3.x输出:200 ``` -## 1.2.12 元类的使用 +## 12. 元类的使用 在2.x 中 ```python @@ -257,7 +249,7 @@ class Person(metaclass=MetaPerson): pass ``` -## 1.2.13 模块变化 +## 13. 模块变化 - 去掉了一些模块。由于不常用,这里就不列举了。 - 新增了一些模块。比如:concurrent.futures,asyncio等 @@ -265,9 +257,9 @@ class Person(metaclass=MetaPerson): ---- -大概就是这些内容,可能还有更细微的差别,这些内容要前往官网查看。但是那些对于我们普通开发者来说,并不那么重要。完全可以不去关注。 +大概就是这些内容,可能还有更细微的差别,这些内容要前往官网查看。 -实际上,当我熟悉一个版本后,基本上是可以无缝过渡到另一个版本的。这篇文章,更多的是为了科普和应对面试。 +但是那些对于我们普通开发者来说,并不那么重要,个人感觉可以不去关注。 ---- diff --git a/source/c01/c01_01.rst b/source/c01/c01_01.rst old mode 100755 new mode 100644 index 76e531c..90abe6b --- a/source/c01/c01_01.rst +++ b/source/c01/c01_01.rst @@ -3,20 +3,12 @@ |image0| --------------- - -从今天开始,小明将和你一起过一下,那些在面试「Python开发」岗位时面试官喜欢问的问题。内容基础,但是你不一定会噢。 - -这些问题全部来自个人经验,群友推荐以及网络上的帖子。如果你有好的问题,也可以随时向我提出(不要觉得简单),我会筛选后整理出来在这里,供大家学习取经,给大家在求职路上贡献一份力。 - -开篇讲些什么好呢? - -今天就来罗列一下,Python2.x和3.x到底有哪些区别吧。 +0. 去哪里找 +----------- -去哪里找 --------- +这块的内容,你随便使用搜索引擎都可以查到。 -你随便全使用搜索引擎都可以查到这些资料,但是大家说的都是一些普遍都知道的事儿。或者都是抄来抄去,内容相差无几。 +但是大家好像都在抄来抄去,内容相差无几。 授人以鱼,不如授人以渔。 @@ -34,8 +26,8 @@ 接下来。和大家一起过一下,Python2.x和3.x到底有哪些区别,这不仅在你开发过程中需要考虑的,也是面试过程面试官经常会问及的。 -1.1.1 print ------------ +1. print +-------- 在Python 2.6之前,只支持 @@ -58,8 +50,8 @@ print("hello") print ("hello") -1.1.2 编码方式 --------------- +2. 编码方式 +----------- 在Python2.x中,默认使用\ ``ASCII``\ 编码。 @@ -89,8 +81,8 @@ Python 2的正确使用方法,如下 所以我们可以在程序中,随意的使用中文(但并不推荐),不会报错。 -1.2.3 除法运算 --------------- +3. 除法运算 +----------- Python 2.x中除法运算,整数间运算只保留整数(向下取整)。 @@ -126,8 +118,8 @@ Python 3.x中除法运算,全部保留小数(即使能被整除)。 >>> -8//3.0 -3.0 -1.2.4 异常捕获 --------------- +4. 异常捕获 +----------- 在 Python 3 中,只能使用 ``as`` 作为关键词。而在Python 2中经常使用 ``except Exception, e`` 使用语法except (exc1, exc2) as @@ -141,8 +133,8 @@ Python 2.6已经支持这两种语法。 在2.x时代,异常在代码中除了表示程序错误,还经常做一些普通控制结构应该做的事情,在3.x中可以看出,设计者让异常变的更加专一,只有在错误发生的情况才能去用异常捕获语句来处理。 -1.2.5 xrange ------------- +5. xrange +--------- 首先,要了解的是,xrange是只有在Python2.x中才有的产物。 @@ -165,8 +157,8 @@ Python 2.6已经支持这两种语法。 >>> range(10) range(0, 10) -1.2.6 用户输入 --------------- +6. 用户输入 +----------- 在2.x 中,有两个函数。raw_input()和input()。 - raw_input():将所有输入作为字符串看待,返回字符串类型。 - @@ -175,8 +167,8 @@ input():只能接收“数字”的输入。 在3.x 中,对这两个函数进行整合,只留下一个\ ``input()``\ ,既可输入数字,也可输入字符串,返回的是字符串类型。 -1.2.7 数据类型 --------------- +7. 数据类型 +----------- Python 3.x 一个很重要的特性是,对字符串和二进制数据流做了明确的区分。 @@ -190,8 +182,8 @@ Python 还有一点是,3.X去除了long类型,取代它的是整型(int)。3.x的整型是没有限制大小的,可以当做long类型使用, 但实际上由于机器内存的有限,我们使用的整数是不可能无限大的。 -1.2.8 函数式编程 ----------------- +8. 函数式编程 +------------- 在Python中,我们常常使用到的map,filter,reduce,在2.x和3.x中也有所不同。 @@ -231,16 +223,16 @@ Python 对于 reduce 函数,它在 Python 3.x 中已经不属于 built-in 了,被挪到 functools 模块当中。 -1.2.9 协程关键字 ----------------- +9. 协程关键字 +------------- 在Python3.3后,协程中,新增了yield from 和 async/await 关键字,这在2.x中是没有。 关于yield from的语法剖析,可以前往查看我的另一篇文章。 -1.2.10 类的类型 ---------------- +10. 类的类型 +------------ Python2.x 默认使用经典类,只有显示继承object才是新式类。 @@ -257,8 +249,8 @@ Python3.x 没有经典类,只有新式类,而且有三种写法 class Cls(object): pass -1.2.11 变量作用域 ------------------ +11. 变量作用域 +-------------- - 在2.x中无法将局部变量声明为全局变量。 - 在3.x中可以使用nonlocal语法将局部变量声明为全局变量。 @@ -277,8 +269,8 @@ Python3.x 没有经典类,只有新式类,而且有三种写法 # 2.x输出:100 # 3.x输出:200 -1.2.12 元类的使用 ------------------ +12. 元类的使用 +-------------- 在2.x 中 @@ -301,8 +293,8 @@ Python3.x 没有经典类,只有新式类,而且有三种写法 class Person(metaclass=MetaPerson): pass -1.2.13 模块变化 ---------------- +13. 模块变化 +------------ - 去掉了一些模块。由于不常用,这里就不列举了。 - 新增了一些模块。比如:concurrent.futures,asyncio等 @@ -310,16 +302,16 @@ Python3.x 没有经典类,只有新式类,而且有三种写法 -------------- -大概就是这些内容,可能还有更细微的差别,这些内容要前往官网查看。但是那些对于我们普通开发者来说,并不那么重要。完全可以不去关注。 +大概就是这些内容,可能还有更细微的差别,这些内容要前往官网查看。 -实际上,当我熟悉一个版本后,基本上是可以无缝过渡到另一个版本的。这篇文章,更多的是为了科普和应对面试。 +但是那些对于我们普通开发者来说,并不那么重要,个人感觉可以不去关注。 -------------- |image3| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190511165542.png -.. |image2| image:: http://image.python-online.cn/20190511165551.png +.. |image1| image:: http://image.iswbm.com/20190511165542.png +.. |image2| image:: http://image.iswbm.com/20190511165551.png .. |image3| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_02.md b/source/c01/c01_02.md index cb71e7a..d2e4c2d 100644 --- a/source/c01/c01_02.md +++ b/source/c01/c01_02.md @@ -57,11 +57,7 @@ dir() 函数可能是 Python 自省机制中最著名的部分了。它返回传 ![](http://image.iswbm.com/image-20200606134519352.png) -### \__doc__ -使用 `__doc__` 这个魔法方法,可以查询该模块的文档,它输出的内容和 help() 一样。 - -![](http://image.iswbm.com/image-20200606134858285.png) ## 2. 应用到实际开发中 @@ -160,4 +156,124 @@ True +## 3. 模块(Modules) + +### \__doc__ + +使用 `__doc__` 这个魔法方法,可以查询该模块的文档,它输出的内容和 help() 一样。 + +![](http://image.iswbm.com/image-20200606134858285.png) + +### \__name__ + +始终是定义时的模块名;即使你使用import .. as 为它取了别名,或是赋值给了另一个变量名。 + +```python +>>> import json +>>> json.__name__ +'json' +>>> +>>> import json as js +>>> js.__name__ +'json' +``` + +### \__file__ + +包含了该模块的文件路径。需要注意的是内建的模块没有这个属性,访问它会抛出异常! + +```python +>>> import json +>>> json.__file__ +'/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/__init__.py' +``` + + + +### \__dict__ + +包含了模块里可用的属性名-属性的字典;也就是可以使用模块名.属性名访问的对象。 + + + +## 4. 类(Class) + +### \__doc__ + +文档字符串。如果类没有文档,这个值是None。 + +```python +>>> class People: +... ''' +... people class +... ''' +... +>>> p = People() +>>> +>>> print(p.__doc__) + + people class + +>>> +``` + + + +### \__name__ + +始终是定义时的类名。 + +```python +>>> People.__name__ +'People' +``` + + + +### \__dict__ + +包含了类里可用的属性名-属性的字典;也就是可以使用类名.属性名访问的对象。 + +```python +>>> People.__dict__ +mappingproxy({'__module__': '__main__', '__doc__': '\n people class\n ', '__dict__': , '__weakref__': }) +``` + + + +### \__module__ + +包含该类的定义的模块名;需要注意,是字符串形式的模块名而不是模块对象。 + +由于我是在 交互式命令行的环境下,所以模块是 `__main__` + +```python +>>> People.__module__ +'__main__' +``` + +如果将上面的代码放入 demo.py,并且从 people 模块导入 People 类,其值就是 people 模块 + +![](http://image.iswbm.com/image-20200905115039771.png) + + + +### \__bases__ + +直接父类对象的元组;但不包含继承树更上层的其他类,比如父类的父类。 + +```python +>>> class People: pass +... +>>> class Teenager: pass +... +>>> class Student(Teenager): pass +... +>>> Student.__bases__ +(,) +>>> +``` + + + ![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_02.rst b/source/c01/c01_02.rst old mode 100755 new mode 100644 index f59ecfb..7ea82dc --- a/source/c01/c01_02.rst +++ b/source/c01/c01_02.rst @@ -69,14 +69,6 @@ dir() 返回当前作用域中的名称。让我们将 dir() 函数应用于 key |image6| -\__doc_\_ -~~~~~~~~~ - -使用 ``__doc__`` 这个魔法方法,可以查询该模块的文档,它输出的内容和 -help() 一样。 - -|image7| - 2. 应用到实际开发中 ------------------- @@ -182,8 +174,134 @@ callable() True >>> +3. 模块(Modules) +------------------ + +\__doc_\_ +~~~~~~~~~ + +使用 ``__doc__`` 这个魔法方法,可以查询该模块的文档,它输出的内容和 +help() 一样。 + +|image7| + +\__name_\_ +~~~~~~~~~~ + +始终是定义时的模块名;即使你使用import .. as +为它取了别名,或是赋值给了另一个变量名。 + +.. code:: python + + >>> import json + >>> json.__name__ + 'json' + >>> + >>> import json as js + >>> js.__name__ + 'json' + +\__file_\_ +~~~~~~~~~~ + +包含了该模块的文件路径。需要注意的是内建的模块没有这个属性,访问它会抛出异常! + +.. code:: python + + >>> import json + >>> json.__file__ + '/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/json/__init__.py' + +\__dict_\_ +~~~~~~~~~~ + +包含了模块里可用的属性名-属性的字典;也就是可以使用模块名.属性名访问的对象。 + +4. 类(Class) +-------------- + +.. _doc__-1: + +\__doc_\_ +~~~~~~~~~ + +文档字符串。如果类没有文档,这个值是None。 + +.. code:: python + + >>> class People: + ... ''' + ... people class + ... ''' + ... + >>> p = People() + >>> + >>> print(p.__doc__) + + people class + + >>> + +.. _name__-1: + +\__name_\_ +~~~~~~~~~~ + +始终是定义时的类名。 + +.. code:: python + + >>> People.__name__ + 'People' + +.. _dict__-1: + +\__dict_\_ +~~~~~~~~~~ + +包含了类里可用的属性名-属性的字典;也就是可以使用类名.属性名访问的对象。 + +.. code:: python + + >>> People.__dict__ + mappingproxy({'__module__': '__main__', '__doc__': '\n people class\n ', '__dict__': , '__weakref__': }) + +\__module_\_ +~~~~~~~~~~~~ + +包含该类的定义的模块名;需要注意,是字符串形式的模块名而不是模块对象。 + +由于我是在 交互式命令行的环境下,所以模块是 ``__main__`` + +.. code:: python + + >>> People.__module__ + '__main__' + +如果将上面的代码放入 demo.py,并且从 people 模块导入 People 类,其值就是 +people 模块 + |image8| +\__bases_\_ +~~~~~~~~~~~ + +直接父类对象的元组;但不包含继承树更上层的其他类,比如父类的父类。 + +.. code:: python + + >>> class People: pass + ... + >>> class Teenager: pass + ... + >>> class Student(Teenager): pass + ... + >>> Student.__bases__ + (,) + >>> + +|image9| + .. |image0| image:: http://image.iswbm.com/image-20200606121047415.png .. |image1| image:: http://image.iswbm.com/image-20200606121544062.png .. |image2| image:: http://image.iswbm.com/image-20200606121942898.png @@ -192,5 +310,6 @@ callable() .. |image5| image:: http://image.iswbm.com/image-20200606123145109.png .. |image6| image:: http://image.iswbm.com/image-20200606134519352.png .. |image7| image:: http://image.iswbm.com/image-20200606134858285.png -.. |image8| image:: http://image.iswbm.com/20200607174235.png +.. |image8| image:: http://image.iswbm.com/image-20200905115039771.png +.. |image9| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_38.md b/source/c01/c01_03.md similarity index 58% rename from source/c01/c01_38.md rename to source/c01/c01_03.md index 4303123..726d0e2 100644 --- a/source/c01/c01_38.md +++ b/source/c01/c01_03.md @@ -1,46 +1,50 @@ -# 1.38 /usr/bin/env python 有什么用? +# 1.3 /usr/bin/env python 有什么用? ![](http://image.iswbm.com/20200602135014.png) 我们经常会在别人的脚本或者项目的入口文件里看到第一行是下面这样 -```json +``` #!/usr/bin/python ``` 或者这样 -```json +``` #!/usr/bin/env python ``` -这两者有什么区别呢? +那么他们有什么用呢? + +要理解它,得把这一行语句拆成两部分。 +第一部分是 `#!` +第二部分是 `/usr/bin/python` 或者 `/usr/bin/env python` -稍微接触过 linux 的人都知道 `/usr/bin/python` 就是我们执行 `python` 进入console 模式里的 `python` +关于 `#!` 这个符号,其实它是有名字的,叫做 `Shebang` 或者`Sha-bang` ,有的翻译组将它译作 `释伴`,即“解释伴随行”的简称,同时又是Shebang的音译。 -![](http://image.python-online.cn/20200331184021.png) +Shebang通常出现在类Unix系统的脚本中第一行,作为前两个字符。在Shebang之后,可以有一个或数个空白字符,后接解释器的绝对路径,用于指明执行这个脚本文件的解释器。 -而当你在可执行文件头里使用 `#!` + `/usr/bin/python` ,意思就是说你得用哪个软件 (python)来执行这个文件。 +![](http://image.iswbm.com/20200331184021.png) -那么加和不加有什么区别呢? +**那么加和不加有什么区别呢?** -不加的话,你每次执行这个脚本时,都得这样: `python xx.py` , +如果不加 `#!` 的话,你每次执行这个脚本时,都得这样 `python xx.py` , -![](http://image.python-online.cn/20200331185034.png) +![](http://image.iswbm.com/20200331185034.png) 有没有一种方式?可以省去每次都加 `python` 呢? 当然有,你可以文件头里加上`#!/usr/bin/python` ,那么当这个文件有可执行权限 时,只直接写这个脚本文件,就像下面这样。 -![](http://image.python-online.cn/20200331184755.png) +![](http://image.iswbm.com/20200331184755.png) 明白了这个后,再来看看 `!/usr/bin/env python` 这个 又是什么意思 ? 当我执行 `env python` 时,自动进入了 python console 的模式。 -![](http://image.python-online.cn/20200331185741.png) +![](http://image.iswbm.com/20200331185741.png) 这是为什么?和 直接执行 python 好像没什么区别呀 @@ -50,7 +54,7 @@ 具体演示过程,你可以看下面。 -![](http://image.python-online.cn/20200331190224.png) +![](http://image.iswbm.com/20200331190224.png) 那么对于这两者,我们应该使用哪个呢? diff --git a/source/c01/c01_03.rst b/source/c01/c01_03.rst old mode 100755 new mode 100644 index 513c244..66dfc5f --- a/source/c01/c01_03.rst +++ b/source/c01/c01_03.rst @@ -1,3 +1,82 @@ -1.3 谈谈深贝和浅拷贝的区别 -================================ +1.3 /usr/bin/env python 有什么用? +================================== + +|image0| + +我们经常会在别人的脚本或者项目的入口文件里看到第一行是下面这样 + +:: + + #!/usr/bin/python + +或者这样 + +:: + + #!/usr/bin/env python + +那么他们有什么用呢? + +要理解它,得把这一行语句拆成两部分。 + +第一部分是 ``#!`` + +第二部分是 ``/usr/bin/python`` 或者 ``/usr/bin/env python`` + +关于 ``#!`` 这个符号,其实它是有名字的,叫做 ``Shebang`` +或者\ ``Sha-bang`` ,有的翻译组将它译作 +``释伴``\ ,即“解释伴随行”的简称,同时又是Shebang的音译。 + +Shebang通常出现在类Unix系统的脚本中第一行,作为前两个字符。在Shebang之后,可以有一个或数个空白字符,后接解释器的绝对路径,用于指明执行这个脚本文件的解释器。 + +|image1| + +**那么加和不加有什么区别呢?** + +如果不加 ``#!`` 的话,你每次执行这个脚本时,都得这样 ``python xx.py`` , + +|image2| + +有没有一种方式?可以省去每次都加 ``python`` 呢? + +当然有,你可以文件头里加上\ ``#!/usr/bin/python`` +,那么当这个文件有可执行权限 时,只直接写这个脚本文件,就像下面这样。 + +|image3| + +明白了这个后,再来看看 ``!/usr/bin/env python`` 这个 又是什么意思 ? + +当我执行 ``env python`` 时,自动进入了 python console 的模式。 + +|image4| + +这是为什么?和 直接执行 python 好像没什么区别呀 + +当你执行 ``env python`` 时,它其实会去 ``env | grep PATH`` 里(也就是 +/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin +)这几个路径里去依次查找名为python的可执行文件。 + +找到一个就直接执行,上面我们的 python 路径是在 ``/usr/bin/python`` +里,在 ``PATH`` 列表里倒数第二个目录下,所以当我在 ``/usr/local/sbin`` +下创建一个名字也为 python 的可执行文件时,就会执行 ``/usr/bin/python`` +了。 + +具体演示过程,你可以看下面。 + +|image5| + +那么对于这两者,我们应该使用哪个呢? + +个人感觉应该优先使用 ``#!/usr/bin/env python``\ ,因为不是所有的机器的 +python 解释器都是 ``/usr/bin/python`` 。 + +|image6| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200331184021.png +.. |image2| image:: http://image.iswbm.com/20200331185034.png +.. |image3| image:: http://image.iswbm.com/20200331184755.png +.. |image4| image:: http://image.iswbm.com/20200331185741.png +.. |image5| image:: http://image.iswbm.com/20200331190224.png +.. |image6| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_04.md b/source/c01/c01_04.md index 8dbacd2..eba6d75 100644 --- a/source/c01/c01_04.md +++ b/source/c01/c01_04.md @@ -49,7 +49,7 @@ del pd.DataFrame.just_foo_cols # you can also remove the new method 在openstack中的例子 -![](http://image.python-online.cn/20190404215330.png) +![](http://image.iswbm.com/20190404215330.png) 还有就是gevent中也有用到。 @@ -103,4 +103,4 @@ setattr(sys.modules[module], key, --- -![](http://image.python-online.cn/20191117142849.png) +![](http://image.iswbm.com/20191117142849.png) diff --git a/source/c01/c01_04.rst b/source/c01/c01_04.rst old mode 100755 new mode 100644 index 13384fd..b0a5d5b --- a/source/c01/c01_04.rst +++ b/source/c01/c01_04.rst @@ -120,6 +120,6 @@ |image2| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190404215330.png -.. |image2| image:: http://image.python-online.cn/20191117142849.png +.. |image1| image:: http://image.iswbm.com/20190404215330.png +.. |image2| image:: http://image.iswbm.com/20191117142849.png diff --git a/source/c01/c01_05.md b/source/c01/c01_05.md index 9afedee..21f0807 100644 --- a/source/c01/c01_05.md +++ b/source/c01/c01_05.md @@ -4,7 +4,7 @@ --- -## 1.5.1 作用域 +## 1. 作用域 Python的作用域可以分为四种: - L (Local) 局部作用域 @@ -54,7 +54,7 @@ print(name) ``` -## 1.5.2 闭包 +## 2. 闭包 闭包这个概念很重要噢。你一定要掌握。 >在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。其实装饰函数,很多都是闭包。 @@ -77,7 +77,7 @@ deco()() # 输出:MING ``` -## 1.5.3 改变作用域 +## 3. 改变作用域 变量的作用域,与其定义(或赋值)的位置有关,但不是绝对相关。 因为我们可以在某种程度上去改变`向上`的作用范围。 @@ -129,7 +129,7 @@ deco()() ``` -## 1.5.4 变量集合 +## 4. 变量集合 在Python中,有两个内建函数,你可能用不到,但是需要掌握它们。 - globals() :以dict的方式存储所有全局变量 diff --git a/source/c01/c01_05.rst b/source/c01/c01_05.rst old mode 100755 new mode 100644 index a1712cc..c1f7d89 --- a/source/c01/c01_05.rst +++ b/source/c01/c01_05.rst @@ -5,8 +5,8 @@ -------------- -1.5.1 作用域 ------------- +1. 作用域 +--------- Python的作用域可以分为四种: - L (Local) 局部作用域 - E (Enclosing) 闭包函数外的函数中 - G (Global) 全局作用域 - B (Built-in) 内建作用域 @@ -54,8 +54,8 @@ Python的作用域可以分为四种: - L (Local) 局部作用域 - E (E print(name) # NameError: name 'name' is not defined -1.5.2 闭包 ----------- +2. 闭包 +------- 闭包这个概念很重要噢。你一定要掌握。 >在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。其实装饰函数,很多都是闭包。 @@ -79,8 +79,8 @@ Python的作用域可以分为四种: - L (Local) 局部作用域 - E (E deco()() # 输出:MING -1.5.3 改变作用域 ----------------- +3. 改变作用域 +------------- 变量的作用域,与其定义(或赋值)的位置有关,但不是绝对相关。 因为我们可以在某种程度上去改变\ ``向上``\ 的作用范围。 @@ -138,8 +138,8 @@ global好理解,这里只讲下nonlocal。 deco()() # 输出:10 -1.5.4 变量集合 --------------- +4. 变量集合 +----------- 在Python中,有两个内建函数,你可能用不到,但是需要掌握它们。 - globals() :以dict的方式存储所有全局变量 - locals():以dict的方式存储所有局部变量 diff --git a/source/c01/c01_06.md b/source/c01/c01_06.md index ca2f673..ab2a7a2 100644 --- a/source/c01/c01_06.md +++ b/source/c01/c01_06.md @@ -1,4 +1,4 @@ -# 1.6 深入理解元组存在的意义 +# 1.6 有了列表,为什么 Python 还有元组? ![](http://image.iswbm.com/20200602135014.png) @@ -10,7 +10,7 @@ Python中有一个基础的数据结构,叫做元组(tuple),但是一般 以下两点,第一点是大家所熟知的,而第二点可能只有老司机才会知道,只有学习了第二点,才算真正理解了元组存在的价值和意义。 -## 2.6.1 不可变列表 +## 1. 不可变列表 这是`元组`区别于`列表`最显著的特征。 @@ -54,7 +54,7 @@ s1*n ``` -## 2.6.2 具名元组 +## 2. 具名元组 这个特性,我个人认为,才是元组存在的意义所在。 @@ -97,7 +97,7 @@ print(Xiamen_dict) # OrderedDict([('name', 'Xiemen'), ('country', 'China'), ('polulation', '40,54'), ('coordinates', LatLong(lat=24.26, long=118.03))]) ``` -总结一下,元组是一种很强大的可以当作记录来用的数据类型,这才是他存在的价值和意义所在。而为人所熟知的,它的第二个角色才是充当一个不可变的列表。 +总结一下,元组是一种很强大的可以当作记录来用的数据类型,这才是他存在的价值和意义所在。而为人所熟知的,它的第二个角色才是充当一个不可变的列表。(以上都是个人看法,如有不同见解,欢迎留言讨论) -------------- diff --git a/source/c01/c01_06.rst b/source/c01/c01_06.rst old mode 100755 new mode 100644 index 9a21991..2609574 --- a/source/c01/c01_06.rst +++ b/source/c01/c01_06.rst @@ -1,5 +1,5 @@ -1.6 深入理解元组存在的意义 -========================== +1.6 有了列表,为什么 Python 还有元组? +====================================== |image0| @@ -11,8 +11,8 @@ Python中有一个基础的数据结构,叫做元组(tuple),但是一般 以下两点,第一点是大家所熟知的,而第二点可能只有老司机才会知道,只有学习了第二点,才算真正理解了元组存在的价值和意义。 -2.6.1 不可变列表 ----------------- +1. 不可变列表 +------------- 这是\ ``元组``\ 区别于\ ``列表``\ 最显著的特征。 @@ -55,8 +55,8 @@ Python中有一个基础的数据结构,叫做元组(tuple),但是一般 # 重复拼接 s1*n -2.6.2 具名元组 --------------- +2. 具名元组 +----------- 这个特性,我个人认为,才是元组存在的意义所在。 @@ -102,7 +102,7 @@ Python中有一个基础的数据结构,叫做元组(tuple),但是一般 print(Xiamen_dict) # OrderedDict([('name', 'Xiemen'), ('country', 'China'), ('polulation', '40,54'), ('coordinates', LatLong(lat=24.26, long=118.03))]) -总结一下,元组是一种很强大的可以当作记录来用的数据类型,这才是他存在的价值和意义所在。而为人所熟知的,它的第二个角色才是充当一个不可变的列表。 +总结一下,元组是一种很强大的可以当作记录来用的数据类型,这才是他存在的价值和意义所在。而为人所熟知的,它的第二个角色才是充当一个不可变的列表。(以上都是个人看法,如有不同见解,欢迎留言讨论) -------------- diff --git a/source/c01/c01_07.md b/source/c01/c01_07.md index c4e1e39..f7f78a1 100644 --- a/source/c01/c01_07.md +++ b/source/c01/c01_07.md @@ -1,62 +1,60 @@ # 1.7 15个Pythonic的代码示例 ![](http://image.iswbm.com/20200602135014.png) - ---- - Python由于语言的简洁性,让我们以人类思考的方式来写代码,新手更容易上手,老鸟更爱不释手。 -要写出 Pythonic(优雅的、地道的、整洁的)代码,还要平时多观察那些大牛代码,Github 上有很多非常优秀的源代码值得阅读,比如:requests、flask、tornado,这里小明收集了一些常见的 Pythonic 写法,帮助你养成写优秀代码的习惯。 +要写出 Pythonic(优雅的、地道的、整洁的)代码,还要平时多观察那些大牛代码,这里明哥收集了一些比较常见的 Pythonic 写法,帮助你养成写优秀代码的习惯。 ## 01. 变量交换 -Bad +交换两个变量的值,正常都会想利用一个中间临时变量来过渡。 ```python tmp = a a = b b = tmp ``` -Pythonic +能用一行代码解决的(并且不影响可读性的),决不用三行代码。 ```python a,b = b,a ``` ## 02. 列表推导 -Bad +下面是一个非常简单的 for 循环。 ```python my_list = [] for i in range(10): my_list.append(i*2) ``` -Pythonic +在一个 for 循环中,如果逻辑比较简单,不如试用一下列表的列表推导式,虽然只有一行代码,但也逻辑清晰。 + ```python my_list = [i*2 for i in range(10)] ``` ## 03. 单行表达式 -虽然列表推导式由于其简洁性及表达性,被广受推崇。 +上面两个案例,都将多行代码用另一种方式写成了一行代码。 -但是有许多可以写成单行的表达式,并不是好的做法。 +这并不意味着,代码行数越少,就越 Pythonic 。 -Bad +比如下面这样写,就不推荐。 ```python -print 'one'; print 'two' +print('hello'); print('world') -if x == 1: print 'one' +if x == 1: print('hello,world') if and : # do something ``` -Pythonic +建议还是按照如下的写法来 ```python -print 'one' -print 'two' +print('hello') +print('world') if x == 1: - print 'one' + print('hello,world') cond1 = cond2 = @@ -66,12 +64,12 @@ if cond1 and cond2: ## 04. 带索引遍历 -Bad +使用 for 循环时,如何取得对应的索引,初学者习惯使用 range + len 函数 ```python for i in range(len(my_list)): print(i, "-->", my_list[i]) ``` -Pythonic +更好的做法是利用 enumerate 这个内置函数 ```python for i,item in enumerate(my_list): print(i, "-->",item) @@ -79,7 +77,8 @@ for i,item in enumerate(my_list): ## 05. 序列解包 -Pythonic +使用 `*` 可以对一个列表解包 + ```python a, *rest = [1, 2, 3] # a = 1, rest = [2, 3] @@ -90,7 +89,7 @@ a, *middle, c = [1, 2, 3, 4] ## 06. 字符串拼接 -Bad +如果一个列表(或者可迭代对象)中的所有元素都是字符串对象,想要将他们连接起来,通常做法是 ```python letters = ['s', 'p', 'a', 'm'] s="" @@ -98,7 +97,7 @@ for let in letters: s += let ``` -Pythonic +更推荐的做法是使用 join 函数 ```python letters = ['s', 'p', 'a', 'm'] word = ''.join(letters) @@ -106,54 +105,47 @@ word = ''.join(letters) ## 07. 真假判断 -Bad +判断一个变量是否为真(假),新手习惯直接使用 `==` 与 True、False、None 进行对比 ```python if attr == True: - print 'True!' + print('True!') if attr == None: - print 'attr is None!' + print('attr is None!') ``` -Pythonic +实际上,`""`、`[]`、`{}` 这些没有任何元素的容器都是假值,可直接使用 `if not xx` 来判断。 ```python if attr: - print 'attr is truthy!' + print('attr is truthy!') if not attr: - print 'attr is falsey!' - -if attr is None: - print 'attr is None!' + print('attr is falsey!') ``` ## 08. 访问字典元素 -Bad +当直接使用 `[]` 来访问字典里的元素时,若key不存在,是会抛异常的,所以新会可能会先判断一下是否有这个 key,有再取之。 ```python d = {'hello': 'world'} if d.has_key('hello'): - print d['hello'] # prints 'world' + print(d['hello']) # prints 'world' else: - print 'default_value' + print('default_value') ``` -Pythonic +更推荐的做法是使用 `get` 来取,如果没有该 key 会默认返回 None(当然你也可以设置默认返回值) ```python d = {'hello': 'world'} -print d.get('hello', 'default_value') # prints 'world' -print d.get('thingy', 'default_value') # prints 'default_value' - -# Or: -if 'hello' in d: - print d['hello'] +print(d.get('hello', 'default_value')) # prints 'world' +print(d.get('thingy', 'default_value')) # prints 'default_value' ``` ## 09. 操作列表 -Bad +下面这段代码,会根据条件过滤过列表中的元素 ```python a = [3, 4, 5] b = [] @@ -161,8 +153,7 @@ for i in a: if i > 4: b.append(i) ``` - -Pythonic +实际上可以使用列表推导或者高阶函数 filter 来实现 ```python a = [3, 4, 5] b = [i for i in a if i > 4] @@ -170,69 +161,68 @@ b = [i for i in a if i > 4] b = filter(lambda x: x > 4, a) ``` -Bad +除了 filter 之外,还有 map、reduce 这两个函数也很好用 ```python a = [3, 4, 5] -for i in range(len(a)): - a[i] += 3 -``` - -Pythonic -```python -a = [3, 4, 5] -a = [i + 3 for i in a] -# Or: -a = map(lambda i: i + 3, a) +b = map(lambda i: i + 3, a) +# b: [6,7,8] ``` ## 10. 文件读取 -Bad + +文件读取是非常常用的操作,在使用完句柄后,是需要手动调用 close 函数来关闭句柄的 ```python -f = open('file.txt') -a = f.read() -print a -f.close() +fp = open('file.txt') +print(fp.read()) +fp.close() ``` - -Pythonic +如果代码写得太长,即使你知道需要手动关闭句柄,却也会经常会漏掉。因此推荐养成习惯使用 `with open` 来读写文件,上下文管理器会自动执行关闭句柄的操作 ```python -with open('file.txt') as f: - for line in f: - print line +with open('file.txt') as fp: + for line in fp.readlines(): + print(line) ``` ## 11. 代码续行 -Bad + +将一个长度较长的字符串放在一行中,是很影响代码可读性的(下面代码可向左滑动) + ```python -my_very_big_string = """For a long time I used to go to bed early. Sometimes, \ - when I had put out my candle, my eyes would close so quickly that I had not even \ - time to say “I’m going to sleep.”""" +long_string = 'For a long time I used to go to bed early. Sometimes, when I had put out my candle, my eyes would close so quickly that I had not even time to say “I’m going to sleep.”' +``` + +稍等注重代码可读性的人,会使用三个引号 `\`来续写 -from some.deep.module.inside.a.module import a_nice_function, another_nice_function, \ - yet_another_nice_function +```python +long_string = 'For a long time I used to go to bed early. ' \ + 'Sometimes, when I had put out my candle, ' \ + 'my eyes would close so quickly that I had not even time to say “I’m going to sleep.”' ``` -Pythonic +不过,对我来说,我更喜欢这样子写 使用括号包裹 `()` ```python -my_very_big_string = ( +long_string = ( "For a long time I used to go to bed early. Sometimes, " "when I had put out my candle, my eyes would close so quickly " "that I had not even time to say “I’m going to sleep.”" ) +``` +导包的时候亦是如此 +```python from some.deep.module.inside.a.module import ( a_nice_function, another_nice_function, yet_another_nice_function) ``` ## 12. 显式代码 -Bad + +有时候出于需要,我们会使用一些特殊的魔法来使代码适应更多的场景不确定性。 ```python def make_complex(*args): x, y = args return dict(**locals()) ``` - -Pythonic +但若非必要,请不要那么做。无端增加代码的不确定性,会让原先本就动态的语言写出更加动态的代码。 ```python def make_complex(x, y): return {'x': x, 'y': y} @@ -240,7 +230,7 @@ def make_complex(x, y): ## 13. 使用占位符 -Pythonic +对于暂不需要,却又不得不接收的的变量,请使用占位符 ```python filename = 'foobar.txt' basename, _, ext = filename.rpartition('.') @@ -248,19 +238,20 @@ basename, _, ext = filename.rpartition('.') ## 14. 链式比较 -Bad +对于下面这种写法 ```python -if age > 18 and age < 60: - print("young man") +score = 85 +if score > 80 and score < 90: + print("良好") ``` - -Pythonic +其实还有更好的写法 ```python -if 18 < age < 60: - print("young man") +score = 85 +if 80 < score < 90: + print("良好") ``` -理解了链式比较操作,那么你应该知道为什么下面这行代码输出的结果是 False +如果你理解了上面的链式比较操作,那么你应该知道为什么下面这行代码输出的结果是 False ``` >>> False == False == True False @@ -268,25 +259,22 @@ False ## 15. 三目运算 -这个保留意见。随使用习惯就好。 - -Bad +对于简单的判断并赋值 ```python -if a > 2: - b = 2 +age = 20 +if age > 18: + type = "adult" else: - b = 1 -#b = 2 - + type = "teenager" ``` -Pythonic +其实是可以使用三目运算,一行搞定。 ```python -a = 3 - -b = 2 if a > 2 else 1 -#b = 2 +age = 20 +b = "adult" if age > 18 else "teenager" ``` + + ## 参考文档 - http://docs.python-guide.org/en/latest/writing/style/ diff --git a/source/c01/c01_07.rst b/source/c01/c01_07.rst old mode 100755 new mode 100644 index 24a196f..52a6391 --- a/source/c01/c01_07.rst +++ b/source/c01/c01_07.rst @@ -2,20 +2,16 @@ ========================== |image0| - --------------- - Python由于语言的简洁性,让我们以人类思考的方式来写代码,新手更容易上手,老鸟更爱不释手。 要写出 -Pythonic(优雅的、地道的、整洁的)代码,还要平时多观察那些大牛代码,Github -上有很多非常优秀的源代码值得阅读,比如:requests、flask、tornado,这里小明收集了一些常见的 +Pythonic(优雅的、地道的、整洁的)代码,还要平时多观察那些大牛代码,这里明哥收集了一些比较常见的 Pythonic 写法,帮助你养成写优秀代码的习惯。 01. 变量交换 ------------ -Bad +交换两个变量的值,正常都会想利用一个中间临时变量来过渡。 .. code:: python @@ -23,7 +19,7 @@ Bad a = b b = tmp -Pythonic +能用一行代码解决的(并且不影响可读性的),决不用三行代码。 .. code:: python @@ -32,7 +28,7 @@ Pythonic 02. 列表推导 ------------ -Bad +下面是一个非常简单的 for 循环。 .. code:: python @@ -40,7 +36,8 @@ Bad for i in range(10): my_list.append(i*2) -Pythonic +在一个 for +循环中,如果逻辑比较简单,不如试用一下列表的列表推导式,虽然只有一行代码,但也逻辑清晰。 .. code:: python @@ -49,30 +46,30 @@ Pythonic 03. 单行表达式 -------------- -虽然列表推导式由于其简洁性及表达性,被广受推崇。 +上面两个案例,都将多行代码用另一种方式写成了一行代码。 -但是有许多可以写成单行的表达式,并不是好的做法。 +这并不意味着,代码行数越少,就越 Pythonic 。 -Bad +比如下面这样写,就不推荐。 .. code:: python - print 'one'; print 'two' + print('hello'); print('world') - if x == 1: print 'one' + if x == 1: print('hello,world') if and : # do something -Pythonic +建议还是按照如下的写法来 .. code:: python - print 'one' - print 'two' + print('hello') + print('world') if x == 1: - print 'one' + print('hello,world') cond1 = cond2 = @@ -82,14 +79,14 @@ Pythonic 04. 带索引遍历 -------------- -Bad +使用 for 循环时,如何取得对应的索引,初学者习惯使用 range + len 函数 .. code:: python for i in range(len(my_list)): print(i, "-->", my_list[i]) -Pythonic +更好的做法是利用 enumerate 这个内置函数 .. code:: python @@ -99,7 +96,7 @@ Pythonic 05. 序列解包 ------------ -Pythonic +使用 ``*`` 可以对一个列表解包 .. code:: python @@ -112,7 +109,7 @@ Pythonic 06. 字符串拼接 -------------- -Bad +如果一个列表(或者可迭代对象)中的所有元素都是字符串对象,想要将他们连接起来,通常做法是 .. code:: python @@ -121,7 +118,7 @@ Bad for let in letters: s += let -Pythonic +更推荐的做法是使用 join 函数 .. code:: python @@ -131,59 +128,57 @@ Pythonic 07. 真假判断 ------------ -Bad +判断一个变量是否为真(假),新手习惯直接使用 ``==`` 与 True、False、None +进行对比 .. code:: python if attr == True: - print 'True!' + print('True!') if attr == None: - print 'attr is None!' + print('attr is None!') -Pythonic +实际上,\ ``""``\ 、\ ``[]``\ 、\ ``{}`` +这些没有任何元素的容器都是假值,可直接使用 ``if not xx`` 来判断。 .. code:: python if attr: - print 'attr is truthy!' + print('attr is truthy!') if not attr: - print 'attr is falsey!' - - if attr is None: - print 'attr is None!' + print('attr is falsey!') 08. 访问字典元素 ---------------- -Bad +当直接使用 ``[]`` +来访问字典里的元素时,若key不存在,是会抛异常的,所以新会可能会先判断一下是否有这个 +key,有再取之。 .. code:: python d = {'hello': 'world'} if d.has_key('hello'): - print d['hello'] # prints 'world' + print(d['hello']) # prints 'world' else: - print 'default_value' + print('default_value') -Pythonic +更推荐的做法是使用 ``get`` 来取,如果没有该 key 会默认返回 +None(当然你也可以设置默认返回值) .. code:: python d = {'hello': 'world'} - print d.get('hello', 'default_value') # prints 'world' - print d.get('thingy', 'default_value') # prints 'default_value' - - # Or: - if 'hello' in d: - print d['hello'] + print(d.get('hello', 'default_value')) # prints 'world' + print(d.get('thingy', 'default_value')) # prints 'default_value' 09. 操作列表 ------------ -Bad +下面这段代码,会根据条件过滤过列表中的元素 .. code:: python @@ -193,7 +188,7 @@ Bad if i > 4: b.append(i) -Pythonic +实际上可以使用列表推导或者高阶函数 filter 来实现 .. code:: python @@ -202,74 +197,73 @@ Pythonic # Or: b = filter(lambda x: x > 4, a) -Bad +除了 filter 之外,还有 map、reduce 这两个函数也很好用 .. code:: python a = [3, 4, 5] - for i in range(len(a)): - a[i] += 3 - -Pythonic - -.. code:: python - - a = [3, 4, 5] - a = [i + 3 for i in a] - # Or: - a = map(lambda i: i + 3, a) + b = map(lambda i: i + 3, a) + # b: [6,7,8] 10. 文件读取 ------------ -Bad +文件读取是非常常用的操作,在使用完句柄后,是需要手动调用 close +函数来关闭句柄的 .. code:: python - f = open('file.txt') - a = f.read() - print a - f.close() + fp = open('file.txt') + print(fp.read()) + fp.close() -Pythonic +如果代码写得太长,即使你知道需要手动关闭句柄,却也会经常会漏掉。因此推荐养成习惯使用 +``with open`` 来读写文件,上下文管理器会自动执行关闭句柄的操作 .. code:: python - with open('file.txt') as f: - for line in f: - print line + with open('file.txt') as fp: + for line in fp.readlines(): + print(line) 11. 代码续行 ------------ -Bad +将一个长度较长的字符串放在一行中,是很影响代码可读性的(下面代码可向左滑动) .. code:: python - my_very_big_string = """For a long time I used to go to bed early. Sometimes, \ - when I had put out my candle, my eyes would close so quickly that I had not even \ - time to say “I’m going to sleep.”""" + long_string = 'For a long time I used to go to bed early. Sometimes, when I had put out my candle, my eyes would close so quickly that I had not even time to say “I’m going to sleep.”' + +稍等注重代码可读性的人,会使用三个引号 ``\``\ 来续写 + +.. code:: python - from some.deep.module.inside.a.module import a_nice_function, another_nice_function, \ - yet_another_nice_function + long_string = 'For a long time I used to go to bed early. ' \ + 'Sometimes, when I had put out my candle, ' \ + 'my eyes would close so quickly that I had not even time to say “I’m going to sleep.”' -Pythonic +不过,对我来说,我更喜欢这样子写 使用括号包裹 ``()`` .. code:: python - my_very_big_string = ( + long_string = ( "For a long time I used to go to bed early. Sometimes, " "when I had put out my candle, my eyes would close so quickly " "that I had not even time to say “I’m going to sleep.”" ) +导包的时候亦是如此 + +.. code:: python + from some.deep.module.inside.a.module import ( a_nice_function, another_nice_function, yet_another_nice_function) 12. 显式代码 ------------ -Bad +有时候出于需要,我们会使用一些特殊的魔法来使代码适应更多的场景不确定性。 .. code:: python @@ -277,7 +271,7 @@ Bad x, y = args return dict(**locals()) -Pythonic +但若非必要,请不要那么做。无端增加代码的不确定性,会让原先本就动态的语言写出更加动态的代码。 .. code:: python @@ -287,7 +281,7 @@ Pythonic 13. 使用占位符 -------------- -Pythonic +对于暂不需要,却又不得不接收的的变量,请使用占位符 .. code:: python @@ -297,21 +291,24 @@ Pythonic 14. 链式比较 ------------ -Bad +对于下面这种写法 .. code:: python - if age > 18 and age < 60: - print("young man") + score = 85 + if score > 80 and score < 90: + print("良好") -Pythonic +其实还有更好的写法 .. code:: python - if 18 < age < 60: - print("young man") + score = 85 + if 80 < score < 90: + print("良好") -理解了链式比较操作,那么你应该知道为什么下面这行代码输出的结果是 False +如果你理解了上面的链式比较操作,那么你应该知道为什么下面这行代码输出的结果是 +False :: @@ -321,26 +318,22 @@ Pythonic 15. 三目运算 ------------ -这个保留意见。随使用习惯就好。 - -Bad +对于简单的判断并赋值 .. code:: python - if a > 2: - b = 2 + age = 20 + if age > 18: + type = "adult" else: - b = 1 - #b = 2 + type = "teenager" -Pythonic +其实是可以使用三目运算,一行搞定。 .. code:: python - a = 3 - - b = 2 if a > 2 else 1 - #b = 2 + age = 20 + b = "adult" if age > 18 else "teenager" 参考文档 -------- diff --git a/source/c01/c01_08.md b/source/c01/c01_08.md index e651615..1c1730f 100644 --- a/source/c01/c01_08.md +++ b/source/c01/c01_08.md @@ -4,7 +4,7 @@ --- -## 2.8.1 版本支持 / 写法差异 +## 1. 版本支持 / 写法差异 在Python 2.x 中 @@ -38,12 +38,12 @@ class Ming(object): pass ``` -## 2.8.2 使用方法 / 独特属性 +## 2. 使用方法 / 独特属性 经典类无法使用super() -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi765tblqj20cy05cwfx.jpg) +![](http://image.iswbm.com/20201004123025.png) 经典类的类型是 classobj -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi76mgwcbj20b708cmyo.jpg) +![](http://image.iswbm.com/20201004123036.png) 新式类的类型是 type,保持class与type的统一。 @@ -80,7 +80,7 @@ AttributeError: Kls01 instance has no attribute 'name' -## 2.8.3 MRO 查找算法的演变 +## 3. MRO 查找算法的演变 **经典类中** @@ -110,7 +110,7 @@ print inspect.getmro(D) 非常好理解,但是在菱形继承时,方法的调用会出现问题。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi77urc3lj206108n74e.jpg) +![](http://image.iswbm.com/20201004123106.png) 假设 d 是 D 的一个实例,那么执行 d.show()是调用 A.show() 呢 还是调用 C.show()呢? @@ -122,7 +122,7 @@ print inspect.getmro(D) Python 2.2 的新式类 MRO 计算方式和经典类 MRO 的计算方式非常相似:它仍然采用从左至右的深度优先遍历,但是如果遍历中出现重复的类,只保留最后一个。重新考虑上面「菱形继承」的例子: -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi78drp24j20680bjaaa.jpg) +![](http://image.iswbm.com/20201004123056.png) 同样地,我们也来验证一下。另说明,在新式类中,除用inspect外,可以直接通过__mro__属性获取类的 MRO。 @@ -152,7 +152,7 @@ print inspect.getmro(D) 再来看一个复杂一点的例子。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi78odu23j20740bomxh.jpg) +![](http://image.iswbm.com/20201004123115.png) 如果只依靠上面的算法,我们来一起算下,其继承关系是怎样的。 @@ -194,7 +194,7 @@ order (MRO) for bases X, Y 例如下面这张图。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi78xuzibj20940ayq39.jpg) +![](http://image.iswbm.com/20201004123126.png) 计算过程,会采用一种 merge算法。它的基本思想如下: diff --git a/source/c01/c01_08.rst b/source/c01/c01_08.rst old mode 100755 new mode 100644 index 14ff6f1..33aaf4c --- a/source/c01/c01_08.rst +++ b/source/c01/c01_08.rst @@ -5,8 +5,8 @@ -------------- -2.8.1 版本支持 / 写法差异 -------------------------- +1. 版本支持 / 写法差异 +---------------------- 在Python 2.x 中 @@ -45,8 +45,8 @@ class Ming(object): pass -2.8.2 使用方法 / 独特属性 -------------------------- +2. 使用方法 / 独特属性 +---------------------- 经典类无法使用super() |image1| 经典类的类型是 classobj |image2| @@ -85,8 +85,8 @@ kls01.name AttributeError: Kls01 instance has no attribute 'name' -2.8.3 MRO 查找算法的演变 ------------------------- +3. MRO 查找算法的演变 +--------------------- **经典类中** @@ -274,11 +274,11 @@ C 搜索顺序中 X 和 Y 互换仍然不能解决问题,这时候它又会和 |image7| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi765tblqj20cy05cwfx.jpg -.. |image2| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi76mgwcbj20b708cmyo.jpg -.. |image3| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi77urc3lj206108n74e.jpg -.. |image4| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi78drp24j20680bjaaa.jpg -.. |image5| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi78odu23j20740bomxh.jpg -.. |image6| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi78xuzibj20940ayq39.jpg +.. |image1| image:: http://image.iswbm.com/20201004123025.png +.. |image2| image:: http://image.iswbm.com/20201004123036.png +.. |image3| image:: http://image.iswbm.com/20201004123106.png +.. |image4| image:: http://image.iswbm.com/20201004123056.png +.. |image5| image:: http://image.iswbm.com/20201004123115.png +.. |image6| image:: http://image.iswbm.com/20201004123126.png .. |image7| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_09.md b/source/c01/c01_09.md index 44f3738..697f7e0 100644 --- a/source/c01/c01_09.md +++ b/source/c01/c01_09.md @@ -8,7 +8,7 @@ -## 1.9.1 认识Mixin模式 +## 1. 认识Mixin模式 那我们今天就来讲讲这个 Mixin,对于这个Mixin,如何理解?它其实是一种设计模式,如果开发者之间没有产生这样一种设计模式的共识,那么设计模式将不复存在。 @@ -44,7 +44,7 @@ class Airplane(Vehicle, PlaneMixin): -## 1.9.2 不使用Mixin的弊端 +## 2. 不使用Mixin的弊端 你肯定会问,不使用 Mixin 行吗? diff --git a/source/c01/c01_09.rst b/source/c01/c01_09.rst old mode 100755 new mode 100644 index 7924b47..ddc0e8e --- a/source/c01/c01_09.rst +++ b/source/c01/c01_09.rst @@ -8,8 +8,8 @@ 类的单继承,是我们再熟悉不过的,写起来也毫不费力。而多继承呢,见得很多,写得很少。在很多的项目代码里,你还会见到一种很奇怪的类,他们有一个命名上的共同点,就是在类名的结尾,都喜欢用 Mixin。 -1.9.1 认识Mixin模式 -------------------- +1. 认识Mixin模式 +---------------- 那我们今天就来讲讲这个 Mixin,对于这个Mixin,如何理解?它其实是一种设计模式,如果开发者之间没有产生这样一种设计模式的共识,那么设计模式将不复存在。 @@ -48,8 +48,8 @@ Mixin 类,一般都要求开发者遵循规范,在类名末尾加上 Mixin - 功能单一:若有多个功能,那就写多个Mixin类; - 绝对独立:不能依赖于子类的实现;子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。 -1.9.2 不使用Mixin的弊端 ------------------------ +2. 不使用Mixin的弊端 +-------------------- 你肯定会问,不使用 Mixin 行吗? diff --git a/source/c01/c01_10.md b/source/c01/c01_10.md index fbe8fc3..1538e31 100644 --- a/source/c01/c01_10.md +++ b/source/c01/c01_10.md @@ -1,2193 +1,193 @@ -# 1.10 Python 黑魔法指南 50 例 +# 1.10 如何修改 CentOS 6.x 上默认Python ![](http://image.iswbm.com/20200602135014.png) ---- +最近在工作中遇到一个问题,就是有一个功能希望在各种服务器上实现,而服务器上的系统版本可能都不一样,有的是 CentOS 6.x,有的是 CentOS 7.x 。 -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 +需要说明的一点是,CentOS 6.x 上的 Python 版本是 2.6.x 的,而 CentOS 7.x 上的 Python 版本是 2.7.x 的,这意味着我要实现的功能要适配这两种版本的系统。 +你可能会说,这有什么的,自己写的时候,注意一下就好了。 -## 01. 默默无闻的省略号很好用 +事情其实没有那么容易,我要实现的功能是基于一个框架进行定制,需要修改不少的框架代码。这个框架在不同的 Linux 版本上,是有不同的版本的,而且差异巨大,曾经想过在 CentOS 6.x 和 CentOS 7.x 将这个框架安装成同一个版本,最后还是失败了,无法安装,原因就是高版本需要 Python2.7,而 CentOS 6.x 上只有 Python2.6。 -在Python中,一切皆对象,省略号也不例外。 +这个历史问题一直遗留到现在,由于这次的功能影响到的代码较多,如果要对两个版本的框架分别进行定制的话,需要花不少的时间,为了不维护两套版本,避免浪费多余的精力去做适配,我决定将 CentOS 6.x 上默认的 Python2.6 升级成 Python2.7。 -在 Python 3 中你可以直接写 `...` 来得到它 -```python ->>> ... -Ellipsis ->>> type(...) - -``` +下面是整个升级过程,别看步骤简单,这些精简步骤的背后可是有不少的坑,被我踩过后,你可以直接使用了。 -而在 Python 2 中没有`...` 这个语法,只能直接写Ellipsis来获取。 -```python ->>> Ellipsis -Ellipsis ->>> type(Ellipsis) - ->>> -``` -它转为布尔值时为真 -```python ->>> bool(...) -True -``` -最后,这东西是一个单例。 -```python ->>> id(...) -4362672336 ->>> id(...) -4362672336 -``` -那这东西有啥用呢? -1. 它是 Numpy 的一个语法糖 -2. 在 Python 3 中可以使用 ... 代替 pass +1. 首先确认下你机器上的默认的 Python 版本 ```shell -$ cat demo.py -def func01(): - ... - -def func02(): - pass - -func01() -func02() - -print("ok") - -$ python3 demo.py -ok -``` - - - -## 02. 使用 end 来结束代码块 +$ python -V +Python 2.6.6 -有不少编程语言,循环、判断代码块需要用 end 标明结束,这样一定程度上会使代码逻辑更加清晰一点。 - -但是其实在 Python 这种严格缩进的语言里并没有必要这样做。 - -如果你真的想用,也不是没有办法,具体你看下面这个例子。 - -```python -__builtins__.end = None - - -def my_abs(x): - if x > 0: - return x - else: - return -x - end -end - -print(my_abs(10)) -print(my_abs(-10)) -``` - -执行后,输出如下 - -```shell -[root@localhost ~]$ python demo.py -10 -10 +$ whereis python +python: /usr/bin/python /usr/bin/python2.6 /usr/lib/python2.6 /usr/lib64/python2.6 /usr/local/bin/python /usr/include/python2.6 /usr/share/man/man1/python.1.gz ``` -## 03. 可直接运行的 zip 包 + -我们可以经常看到有 Python 包,居然可以以 zip 包进行发布,并且可以不用解压直接使用。 +2. 由于我们将使用编译安装的方式,所以要安装下 gcc,及一些工具包。 -这与大多数人的认识的 Python 包格式不一样,正常人认为 Python 包的格式要嘛 是 egg,要嘛是whl 格式。 +注意一定要全部安装,不然后面会发现有不少 python 的工具用不了。 -那么这个zip 是如何制作的呢,请看下面的示例。 +比如不安装 zlib 会无法安装 setuptools,不装 openssl 和 openssl-devel,会无法使用 pip 工具等 -```shell -[root@localhost ~]# ls -l demo -total 8 --rw-r--r-- 1 root root 30 May 8 19:27 calc.py --rw-r--r-- 1 root root 35 May 8 19:33 __main__.py -[root@localhost ~]# -[root@localhost ~]# cat demo/__main__.py -import calc - -print(calc.add(2, 3)) -[root@localhost ~]# -[root@localhost ~]# cat demo/calc.py -def add(x, y): - return x+y -[root@localhost ~]# -[root@localhost ~]# python -m zipfile -c demo.zip demo/* -[root@localhost ~]# ``` - -制作完成后,我们可以执行用 python 去执行它 - -```shell -[root@localhost ~]# python demo.zip -5 -[root@localhost ~]# +$ yum install gcc -y +$ yum groupinstall "Development tools" +$ yum install zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel -y ``` -## 04. 反斜杠的倔强: 不写最后 - -`\` 在 Python 中的用法主要有两种 + 如果你这里未按照我的步骤来安装,你后面使用的时候出现了各种各样的问题,不要慌,只要再回来这里,把没安装的包装上,安装完成后,你需要进入第四步重新编译安装Python。 -**1、在行尾时,用做续行符** - ```python -[root@localhost ~]$ cat demo.py -print("hello "\ - "world") -[root@localhost ~]$ -[root@localhost ~]$ python demo.py -hello world - ``` +3. 下载最新的 Python2.7.x 安装包,解压并进入指定目录 - -**2、在字符串中,用做转义字符,可以将普通字符转化为有特殊含义的字符。** - -```python ->>> str1='\nhello'  #换行 ->>> print(str1) - -hello ->>> str2='\thello'  #tab ->>> print(str2) - hello -``` - -但是如果你用单`\`结尾是会报语法错误的 - -```python ->>> str3="\" - File "", line 1 - str3="\" - ^ -SyntaxError: EOL while scanning string literal ``` - -就算你指定它是个 raw 字符串,也不行。 - -```python ->>> str3=r"\" - File "", line 1 - str3=r"\" - ^ -SyntaxError: EOL while scanning string literal -``` - -## 05. 单行实现 for 死循环如何写? - -如果让你在不借助 while ,只使用 for 来写一个死循环? - -**你会写吗?** - -**如果你还说简单,你可以自己试一下。** - -... - -如果你尝试后,仍然写不出来,那我给出自己的做法。 - -```python -for i in iter(int, 1):pass +$ wget https://www.python.org/ftp/python/2.7.14/Python-2.7.14.tgz +$ tar zxvf Python-2.7.14.tgz +$ cd Python-2.7.14 ``` -**是不是傻了?iter 还有这种用法?这为啥是个死循环?** - -关于这个问题,你如果看中文网站,可能找不到相关资料。 - -还好你可以通过 IDE 看py源码里的注释内容,介绍了很详细的使用方法。 - -原来iter有两种使用方法。 - -- 通常我们的认知是第一种,将一个列表转化为一个迭代器。 - -- 而第二种方法,他接收一个 callable对象,和一个sentinel 参数。第一个对象会一直运行,直到它返回 sentinel 值才结束。 - -那`int` 呢? - -这又是一个知识点,int 是一个内建方法。通过看注释,可以看出它是有默认值0的。你可以在console 模式下输入 `int()` 看看是不是返回0。 - -由于int() 永远返回0,永远返回不了1,所以这个 for 循环会没有终点。一直运行下去。 - -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -## 06. 懒人必备技能:使用 “_” - -对于 `_` ,大家对于他的印象都是用于 **占位符**,省得为一个不需要用到的变量,绞尽脑汁的想变量名。 - -今天要介绍的是他的第二种用法,就是在 console 模式下的应用。 +4. 配置,编译,安装 -示例如下: - -```python ->>> 3 + 4 -7 ->>> _ -7 ->>> name='公众号: Python编程时光' ->>> name -'公众号: Python编程时光' ->>> _ -'公众号: Python编程时光' +```shell +# --prefix 指定 python 安装的路径 +$ ./configure --prefix=/usr/local/python/python2.7 +$ make +$ make install ``` -它可以返回上一次的运行结果。 - -但是,如果是print函数打印出来的就不行了。 +`./configure` 命令执行完毕之后创建一个文件creating Makefile,供下面的make命令使用 执行 `make install` 之后就会把程序安装到我们指定的目录中去。 -```python ->>> 3 + 4 -7 ->>> _ -7 ->>> print("公众号: Python编程时光") -ming ->>> _ -7 -``` +Configure是一个可执行脚本,它有很多选项,在待安装的源码路径下使用命令./configure –help输出详细的选项列表。其中 `--prefix` 选项是配置安装的路径,如果不配置该选项,安装后可执行文件默认放在/usr /local/bin,库文件默认放在 `/usr/local/lib` ,配置文件默认放在 `/usr/local/etc` ,其它的资源文件放在 `/usr /local/share`。如果配置 `--prefix`,如:`./configure --prefix=/usr/local/test` 可以把所有资源文件放在/usr/local/test的路径中,不会杂乱。 -我自己写了个例子,验证了下,用`__repr__`输出的内容可以被获取到的。 -首先,在我们的目录下,写一个文件 demo.py。内容如下 - -```python -# demo.py -class mytest(): - def __str__(self): - return "hello" +用了 `--prefix` 选项的另一个好处是卸载软件或移植软件。当某个安装的软件不再需要时,只须简单的删除该安装目录,就可以把软件卸载得干干净净;移植软件只需拷贝整个目录到另外一个机器即可(相同的操作系统)。当然要卸载程序,也可以在原来的make目录下用一次 `make uninstall`,但前提是make文件指定过uninstall。 - def __repr__(self): - return "world" -``` + -然后在这个目录下进入交互式环境。 +5. 查看系统的 Python 版本 -```python ->>> import demo ->>> mt=demo.mytest() ->>> mt -world ->>> print(mt) -hello ->>> _ -world +```shell +$ python -V +Python 2.6.6 ``` -知道这两个魔法方法的人,一看就明白了,这里不再解释啦。 - -## 07. 最快查看包搜索路径的方式 + 如果你查看还是 Python 2.6.6 版本,请继续看第六步。 -当你使用 import 导入一个包或模块时,Python 会去一些目录下查找,而这些目录是有优先级顺序的,正常人会使用 sys.path 查看。 -```python ->>> import sys ->>> from pprint import pprint ->>> pprint(sys.path) -['', - '/usr/local/Python3.7/lib/python37.zip', - '/usr/local/Python3.7/lib/python3.7', - '/usr/local/Python3.7/lib/python3.7/lib-dynload', - '/home/wangbm/.local/lib/python3.7/site-packages', - '/usr/local/Python3.7/lib/python3.7/site-packages'] ->>> -``` -那有没有更快的方式呢? +6. 修改系统默认的 Python 版本 -我这有一种连 console 模式都不用进入的方法,一行命令即可解决 +查看新安装的Python版本,当前系统的Python版本,并将系统指向的Python从2.6.x修改为2.7.x,再次查看当前系统的Python版本,已经变更为2.7.x ```shell -[wangbm@localhost ~]$ python3 -m site -sys.path = [ - '/home/wangbm', - '/usr/local/Python3.7/lib/python37.zip', - '/usr/local/Python3.7/lib/python3.7', - '/usr/local/Python3.7/lib/python3.7/lib-dynload', - '/home/wangbm/.local/lib/python3.7/site-packages', - '/usr/local/Python3.7/lib/python3.7/site-packages', -] -USER_BASE: '/home/wangbm/.local' (exists) -USER_SITE: '/home/wangbm/.local/lib/python3.7/site-packages' (exists) -ENABLE_USER_SITE: True -``` - -从输出你可以发现,这个列的路径会比 sys.path 更全,它包含了用户环境的目录。 +# 这是我们刚安装的 Python +$/usr/local/bin/python2.7 -V +Python 2.7.14 -## 08. and 和 or 的取值顺序 +# 这是系统默认 Python +$ /usr/bin/python -V +Python 2.6.6 -and 和 or 是我们再熟悉不过的两个逻辑运算符,在 Python 也有它有妙用。 +# 备份原来的 Python 文件 +$ mv /usr/bin/python /usr/bin/python.bak -- 当一个 **or 表达式**中所有值都为真,Python会选择第一个值 +# 建立软链接,将我们刚安装的 python2.7 做为系统默认版本 +ln -s /usr/local/bin/python2.7 /usr/bin/python -- 当一个 **and 表达式** 所有值都为真,Python 会选择第二个值。 - -示例如下: - -```python ->>>(2 or 3) * (5 and 7) -14 # 2*7 +# 再次查看 Python 版本,已经成功切换过来 +$ python -V +Python 2.7.14 ``` -## 09. 如何修改解释器提示符 +7. 重新指定 yum 的Python版本 -这个当做今天的一个小彩蛋吧。应该算是比较冷门的,估计知道的人很少了吧。 +上面我们改了系统的默认 Python 版本,由于CentOS 6.x 的 yum 是基于Python2.6 的,为了不影响 yum 的使用,需单独将yum指向python2.6版本。 -正常情况下,我们在 终端下 执行Python 命令是这样的。 -```python ->>> for i in range(2): -... print (i) -... -0 -1 -``` +编辑: vim /usr/bin/yum ,将` /usr/bin/python` 改成 `/usr/bin/python2.6` -你是否想过 `>>>` 和 `...` 这两个提示符也是可以修改的呢? ```python ->>> import sys ->>> sys.ps1 -'>>> ' ->>> sys.ps2 -'... ' ->>> ->>> sys.ps2 = '---------------- ' ->>> sys.ps1 = 'Python编程时光>>>' -Python编程时光>>>for i in range(2): ----------------- print (i) ----------------- -0 -1 +#!/usr/bin/python2.6 ``` -## 10. 逗号也有它独特的用法 -逗号,虽然是个很不起眼的符号,但在 Python 中也有他的用武之地。 -**第一个用法** +8. 安装 setuptools 及 pip -元组的转化 +pip是python的安装工具,很多python的常用工具,都可以通过pip进行安装。要安装pip,首先要安装setuptools。从这个链接,你可以得到相关信息:https://pypi.python.org/pypi/setuptools ```shell -[root@localhost ~]# cat demo.py -def func(): - return "ok", - -print(func()) -[root@localhost ~]# python3 demo.py -('ok',) +# 下载 setuptools +$ wget https://pypi.python.org/packages/ff/d4/209f4939c49e31f5524fa0027bf1c8ec3107abaf7c61fdaad704a648c281/setuptools-21.0.0.tar.gz#md5=81964fdb89534118707742e6d1a1ddb4 ``` -**第二个用法** - -print 的取消换行 +同样的,进行安装: ```shell -[root@localhost ~]# cat demo.py -for i in range(3): - print i -[root@localhost ~]# -[root@localhost ~]# python demo.py -0 -1 -2 -[root@localhost ~]# -[root@localhost ~]# vim demo.py -[root@localhost ~]# -[root@localhost ~]# cat demo.py -for i in range(3): - print i, -[root@localhost ~]# -[root@localhost ~]# python demo.py -0 1 2 -[root@localhost ~]# -``` - - - -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -## 11. 默认参数最好不为可变对象 - -函数的参数分三种 -- 可变参数 -- 默认参数 -- 关键字参数 - -当你在传递默认参数时,有新手很容易踩雷的一个坑。 - -先来看一个示例 -```python -def func(item, item_list=[]): - item_list.append(item) - print(item_list) - -func('iphone') -func('xiaomi', item_list=['oppo','vivo']) -func('huawei') -``` -在这里,你可以暂停一下,思考一下会输出什么? - -思考过后,你的答案是否和下面的一致呢 -``` -['iphone'] -['oppo', 'vivo', 'xiaomi'] -['iphone', 'huawei'] -``` - -如果是,那你可以跳过这部分内容,如果不是,请接着往下看,这里来分析一下。 - -Python 中的 def 语句在每次执行的时候都初始化一个函数对象,这个函数对象就是我们要调用的函数,可以把它当成一个一般的对象,只不过这个对象拥有一个可执行的方法和部分属性。 - -对于参数中提供了初始值的参数,由于 Python 中的函数参数传递的是对象,也可以认为是传地址,在第一次初始化 def 的时候,会先生成这个可变对象的内存地址,然后将这个默认参数 item_list 会与这个内存地址绑定。在后面的函数调用中,如果调用方指定了新的默认值,就会将原来的默认值覆盖。如果调用方没有指定新的默认值,那就会使用原来的默认值。 - -![](http://image.python-online.cn/20190511165650.png) - -## 12. 访问类中的私有方法 - -大家都知道,类中可供直接调用的方法,只有公有方法(protected类型的方法也可以,但是不建议)。也就是说,类的私有方法是无法直接调用的。 - -这里先看一下例子 -```python -class Kls(): - def public(self): - print('Hello public world!') - - def __private(self): - print('Hello private world!') - - def call_private(self): - self.__private() - -ins = Kls() - -# 调用公有方法,没问题 -ins.public() - -# 直接调用私有方法,不行 -ins.__private() - -# 但你可以通过内部公有方法,进行代理 -ins.call_private() -``` - -既然都是方法,那我们真的没有方法可以直接调用吗? - -当然有啦,只是建议你千万不要这样弄,这里只是普及,让你了解一下。 -```python -# 调用私有方法,以下两种等价 -ins._Kls__private() -ins.call_private() -``` - -## 13. 时有时无的切片异常 - -这是个简单例子,alist 只有5 个元素,当你取第 6 个元素时,会抛出索引异常。这与我们的认知一致。 -```python ->>> alist = [0, 1, 2, 3, 4] ->>> alist[5] -Traceback (most recent call last): - File "", line 1, in -IndexError: list index out of range -``` -但是当你使用 alist[5:] 取一个区间时,即使 alist 并没有 第 6个元素,也不抛出异常,而是会返回一个新的列表。 -```python ->>> alist = [0, 1, 2, 3, 4] ->>> alist[5:] -[] ->>> alist[100:] -[] -``` - -## 14. 哪些情况下不需要续行符? - -在写代码时,为了代码的可读性,代码的排版是尤为重要的。 - -为了实现高可读性的代码,我们常常使用到的就是续行符 `\`。 -``` ->>> a = 'talk is cheap,'\ -... 'show me the code.' ->>> ->>> print(a) -talk is cheap,show me the code. -``` - -那有哪些情况下,是不需要写续行符的呢? - -经过总结,在这些符号中间的代码换行可以省略掉续行符:`[]`,`()`,`{}` - +$ tar vxf setuptools-21.0.0.tar.gz +$ cd setuptools-21.0.0 +$ python setup.py install ``` ->>> my_list=[1,2,3, -... 4,5,6] ->>> my_tuple=(1,2,3, -... 4,5,6) - ->>> my_dict={"name": "MING", -... "gender": "male"} -``` -另外还有,在多行文本注释中 `'''` ,续行符也是可以不写的。 -``` ->>> text = '''talk is cheap, -... show me the code''' -``` - -## 15. Python2下 也能使用 print(“”) - -可能会有不少人,觉得只有 Python 3 才可以使用 print(),而 Python 2 只能使用`print ""`。 - -但是其实并不是这样的。 - -在Python 2.6之前,只支持 -```python -print "hello" -``` - -在Python 2.6和2.7中,可以支持如下三种 -```python -print "hello" -print("hello") -print ("hello") -``` - -在Python3.x中,可以支持如下两种 -```python -print("hello") -print ("hello") -``` - -虽然 在 Python 2.6+ 可以和 Python3.x+ 一样,像函数一样去调用 print ,但是这仅用于两个 python 版本之间的代码兼容,并不是说在 python2.6+下使用 print() 后,就成了函数。 - -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -## 16. 迷一样的字符串 - -示例一 - -```python -# Python2.7 ->>> a = "Hello_Python" ->>> id(a) -32045616 ->>> id("Hello" + "_" + "Python") -32045616 - -# Python3.7 ->>> a = "Hello_Python" ->>> id(a) -38764272 ->>> id("Hello" + "_" + "Python") -32045616 -``` - -示例二 - -```python ->>> a = "MING" ->>> b = "MING" ->>> a is b -True - -# Python2.7 ->>> a, b = "MING!", "MING!" ->>> a is b -True - -# Python3.7 ->>> a, b = "MING!", "MING!" ->>> a is b -False -``` - -示例三 - -```python -# Python2.7 ->>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa' -True ->>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa' -False - -# Python3.7 ->>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa' -True ->>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa' -True -``` - - - -## 17. return不一定都是函数的终点 - -众所周知,try…finally… 的用法是:不管try里面是正常执行还是有报异常,最终都能保证finally能够执行。 - -同时我们又知道,一个函数里只要遇到 return 函数就会立马结束。 - -那问题就来了,以上这两种规则,如果同时存在,Python 解释器会如何选择?哪个优先级更高? - -写个示例验证一下,就明白啦 - -```python ->>> def func(): -... try: -... return 'try' -... finally: -... return 'finally' -... ->>> func() -'finally' -``` - -从输出中,我们可以发现:在try…finally…语句中,try中的 return 会被直接忽视(这里的 return 不是函数的终点),因为要保证 finally 能够执行。 - -**如果 try 里的 return 真的是直接被忽视吗?** - -我们都知道如果一个函数没有 return,会隐式的返回 None,假设 try 里的 return 真的是直接被忽视,那当finally 下没有显式的 return 的时候,是不是会返回None呢? - -还是写个 示例来验证一下: - -```python ->>> def func(): -... try: -... return 'try' -... finally: -... print('finally') -... ->>> ->>> func() -finally -'try' ->>> -``` - -从结果来看,当 finally 下没有 reutrn ,其实 try 里的 return 仍然还是有效的。 - -那结论就出来了,如果 finally 里有显式的 return,那么这个 return 会直接覆盖 try 里的 return,而如果 finally 里没有 显式的 return,那么 try 里的 return 仍然有效。 - -## 18. 用户无感知的小整数池 - -为避免整数频繁申请和销毁内存空间,Python 定义了一个小整数池 [-5, 256] 这些整数对象是提前建立好的,不会被垃圾回收。 - -以上代码请在 终端Python环境下测试,如果你是在IDE中测试,由于 IDE 的影响,效果会有所不同。 - -```python ->>> a = -6 ->>> b = -6 ->>> a is b -False - ->>> a = 256 ->>> b = 256 ->>> a is b -True - ->>> a = 257 ->>> b = 257 ->>> a is b -False - ->>> a = 257; b = 257 ->>> a is b -True -``` - -**问题又来了:最后一个示例,为啥是True?** - -因为当你在同一行里,同时给两个变量赋同一值时,解释器知道这个对象已经生成,那么它就会引用到同一个对象。如果分成两成的话,解释器并不知道这个对象已经存在了,就会重新申请内存存放这个对象。 - -## 19. 神奇的 intern 机制 - -字符串类型作为Python中最常用的数据类型之一,Python解释器为了提高字符串使用的效率和使用性能,做了很多优化. - -例如:Python解释器中使用了 intern(字符串驻留)的技术来提高字符串效率,什么是intern机制?就是同样的字符串对象仅仅会保存一份,放在一个字符串储蓄池中,是共用的,当然,肯定不能改变,这也决定了字符串必须是不可变对象。 - -``` ->>> s1="hello" ->>> s2="hello" ->>> s1 is s2 -True - -# 如果有空格,默认不启用intern机制 ->>> s1="hell o" ->>> s2="hell o" ->>> s1 is s2 -False - -# 如果一个字符串长度超过20个字符,不启动intern机制 ->>> s1 = "a" * 20 ->>> s2 = "a" * 20 ->>> s1 is s2 -True - ->>> s1 = "a" * 21 ->>> s2 = "a" * 21 ->>> s1 is s2 -False - ->>> s1 = "ab" * 10 ->>> s2 = "ab" * 10 ->>> s1 is s2 -True - ->>> s1 = "ab" * 11 ->>> s2 = "ab" * 11 ->>> s1 is s2 -False -``` - - - -## 20. 反转字符串/列表最优雅的方式 - -反转序列并不难,但是如何做到最优雅呢? - -先来看看,正常是如何反转的。 - -最简单的方法是使用列表自带的reverse()方法。 - -```python ->>> ml = [1,2,3,4,5] ->>> ml.reverse() ->>> ml -[5, 4, 3, 2, 1] -``` - -但如果你要处理的是字符串,reverse就无能为力了。你可以尝试将其转化成list,再reverse,然后再转化成str。转来转去,也太麻烦了吧?需要这么多行代码(后面三行是不能合并成一行的),一点都Pythonic。 -```python -mstr1 = 'abc' -ml1 = list(mstr1) -ml1.reverse() -mstr2 = str(ml1) -``` -对于字符串还有一种稍微复杂一点的,是自定义递归函数来实现。 -```python -def my_reverse(str): - if str == "": - return str - else: - return my_reverse(str[1:]) + str[0] -``` - -在这里,介绍一种最优雅的反转方式,使用切片,不管你是字符串,还是列表,简直通杀。 -```python ->>> mstr = 'abc' ->>> ml = [1,2,3] ->>> mstr[::-1] -'cba' ->>> ml[::-1] -[3, 2, 1] -``` - -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -## 21. 改变默认递归次数限制 - -上面才提到递归,大家都知道使用递归是有风险的,递归深度过深容易导致堆栈的溢出。如果你这字符串太长啦,使用递归方式反转,就会出现问题。 - -那到底,默认递归次数限制是多少呢? -```python ->>> import sys ->>> sys.getrecursionlimit() -1000 -``` - -可以查,当然也可以自定义修改次数,退出即失效。 -```python ->>> sys.setrecursionlimit(2000) ->>> sys.getrecursionlimit() -2000 -``` - -## 22. 一行代码实现FTP服务器 - -搭建FTP,或者是搭建网络文件系统,这些方法都能够实现Linux的目录共享。但是FTP和网络文件系统的功能都过于强大,因此它们都有一些不够方便的地方。比如你想快速共享Linux系统的某个目录给整个项目团队,还想在一分钟内做到,怎么办?很简单,使用Python中的SimpleHTTPServer。 - -SimpleHTTPServer是Python 2自带的一个模块,是Python的Web服务器。它在Python 3已经合并到http.server模块中。具体例子如下,如不指定端口,则默认是8000端口。 -```python -# python2 -python -m SimpleHTTPServer 8888 - -# python3 -python3 -m http.server 8888 -``` - -![](http://image.python-online.cn/20190511165716.png) - -SimpleHTTPServer有一个特性,如果待共享的目录下有index.html,那么index.html文件会被视为默认主页;如果不存在index.html文件,那么就会显示整个目录列表。 - -## 23. 让你晕头转向的 else 用法 - -if else 用法可以说最基础的语法表达式之一,但是今天不是讲这个的,一定要讲点不一样的。 - -if else 早已烂大街,但可能有很多人都不曾见过 for else 和 try else 的用法。为什么说它曾让我晕头转向,因为它不像 if else 那么直白,非黑即白,脑子经常要想一下才能才反应过来代码怎么走。反正我是这样的。 - -先来说说,for else -```python -def check_item(source_list, target): - for item in source_list: - if item == target: - print("Exists!") - break - - else: - print("Does not exist") +安装完成后,下载pip。其信息在如下网站:https://pypi.python.org/pypi/pip +```shell +# 下载 pip +wget https://pypi.python.org/packages/41/27/9a8d24e1b55bd8c85e4d022da2922cb206f183e2d18fee4e320c9547e751/pip-8.1.1.tar.gz#md5=6b86f11841e89c8241d689956ba99ed7 ``` -在往下看之前,你可以思考一下,什么情况下才会走 else。是循环被 break,还是没有break? - -给几个例子,你体会一下。 -```python -check_item(["apple", "huawei", "oppo"], "oppo") -# Exists! -check_item(["apple", "huawei", "oppo"], "vivo") -# Does not exist -``` -可以看出,没有被 break 的程序才会正常走else流程。 +同样的,进行安装 -再来看看,try else 用法。 -```python -def test_try_else(attr1 = None): - try: - if attr1: - pass - else: - raise - except: - print("Exception occurred...") - else: - print("No Exception occurred...") +```shell +$ tar vxf pip-8.1.1.tar.gz +$ cd pip-8.1.1 +$ python setup.py install ``` -同样来几个例子。当不传参数时,就抛出异常。 -```python -test_try_else() -# Exception occurred... +安装完成后,执行 `pip list` 查看一下安装的包,若无异常,则一切顺利。或者你也可以试着安装一下第三方包 `pip install requests` 。 -test_try_else("ming") -# No Exception occurred... -``` -可以看出,没有 try 里面的代码块没有抛出异常的,会正常走else。 -总结一下,for else 和 try else 相同,只要代码正常走下去不被 break,不抛出异常,就可以走else。 +8. 转移cloudinit +上面说的项目,其实就是 cloudinit。接下来就要将 centos 7.2 上的cloudinit 的目录整体拷贝到 centos 6.5 的/usr/local/lib/python2.7/site-packages/ 目录下 -## 24. 字符串里的缝隙是什么? +![](http://image.iswbm.com/20190831160317.png) -在Python中求一个字符串里,某子字符(串)出现的次数。 +然后安装一些 cloudinit 的依赖包。 -大家都懂得使用 count() 函数,比如下面几个常规例子: +```shell +$ pip install six requests prettytable jsonpatch configobj -```python ->>> "aabb".count("a") -2 ->>> "aabb".count("b") -2 ->>> "aabb".count("ab") -1 -``` +# 默认还是安装在 python2.6 下 +$ yum install PyYAML -y -但是如果我想计算空字符串的个数呢? -```python ->>> "aabb".count("") -5 +# 将这些文件拷贝到 python2.7 目录下 +# 如果你不知道 python2.7 的目录,使用 import sys;print sys.path 就可以打印 +$ cd /usr/lib64/python2.6/site-packages +$ cp -r yaml/ /usr/local/lib/python2.7/site-packages/ +$ cp -p _yaml.so /usr/local/lib/python2.7/site-packages/ +$ cp -p PyYAML-3.10-py2.6.egg-info /usr/local/lib/python2.7/site-packages/ ``` -**奇怪了吧?** - -不是应该返回 0 吗?怎么会返回 5? - -实际上,在 Python 看来,两个字符之间都是一个空字符,通俗的说就是缝隙。 - -因此 对于 `aabb` 这个字符串在 Python 来看应该是这样的 - -![](http://image.iswbm.com/20200509172331.png) +执行一下 cloudinit 的几个命令,没有问题,任务就完成了。 -理解了这个“**缝隙**” 的概念后,以下这些就好理解了。 - -```python ->>> (" " * 10).count("") -11 ->>> ->>> "" in "" -True ->>> ->>> "" in "M" -True +```shell +$ cloud-init init -l +$ cloud-init init ``` -## 25. 正负得正,负负得正 - -从初中开始,我们就开始接触了`负数` ,并且都知道了`负负得正` 的思想。 - -Python 作为一门高级语言,它的编写符合人类的思维逻辑,包括 `负负得正` 。 - -```python ->>> 5-3 -2 ->>> 5--3 -8 ->>> 5+-3 -2 ->>> 5++3 -8 ->>> 5---3 -2 -``` - -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -## 26. 数值与字符串的比较 - -在 Python2 中,数字可以与字符串直接比较。结果是数值永远比字符串小。 -```Python ->>> 100000000 < "" -True ->>> 100000000 < "hello" -True -``` - -但在 Python3 中,却不行。 -```python ->>> 100000000 < "" -TypeError: '<' not supported between instances of 'int' and 'str' -``` - -## 27. 循环中的局部变量泄露 - -在Python 2中 x 的值在一个循环执行之后被改变了。 -```python -# Python2 ->>> x = 1 ->>> [x for x in range(5)] -[0, 1, 2, 3, 4] ->>> x -4 -``` -不过在Python3 中这个问题已经得到解决了。 -```python -# Python3 ->>> x = 1 ->>> [x for x in range(5)] -[0, 1, 2, 3, 4] ->>> x -1 -``` - -## 28. 字典居然是可以排序的? - -在 Python 3.6 之前字典不可排序的思想,似乎已经根深蒂固。 - -```python -# Python2.7.10 ->>> mydict = {str(i):i for i in range(5)} ->>> mydict -{'1': 1, '0': 0, '3': 3, '2': 2, '4': 4} -``` - -假如哪一天,有人跟你说字典也可以是有序的,不要惊讶,那确实是真的 - -在 Python3.6 + 中字典已经是有序的,并且效率相较之前的还有所提升,具体信息你可以去查询相关资料。 - -```python -# Python3.6.7 ->>> mydict = {str(i):i for i in range(5)} ->>> mydict -{'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} -``` - - - -## 29. 有趣但没啥用的 import 用法 - -import 是 Python 导包的方式。 - -你知道 Python 中内置了一些很有(wu)趣(liao)的包吗? - -**Hello World** - -``` ->>> import __hello__ -Hello World! -``` - -**Python之禅** - -``` ->>> import this - -The Zen of Python, by Tim Peters - -Beautiful is better than ugly. -Explicit is better than implicit. -Simple is better than complex. -Complex is better than complicated. -Flat is better than nested. -Sparse is better than dense. -Readability counts. -Special cases aren't special enough to break the rules. -Although practicality beats purity. -Errors should never pass silently. -Unless explicitly silenced. -In the face of ambiguity, refuse the temptation to guess. -There should be one-- and preferably only one --obvious way to do it. -Although that way may not be obvious at first unless you're Dutch. -Now is better than never. -Although never is often better than *right* now. -If the implementation is hard to explain, it's a bad idea. -If the implementation is easy to explain, it may be a good idea. -Namespaces are one honking great idea -- let's do more of those! -``` - -**反地心引力漫画** - -在 cmd 窗口中导入`antigravity` - -``` ->>> import antigravity -``` - -就会自动打开一个网页。 -![](http://image.python-online.cn/20190511165735.png) - -## 30. 局部/全局变量傻傻分不清 - -在开始讲之前,你可以试着运行一下下面这小段代码。 - -```python -# demo.py -a = 1 - -def add(): - a += 1 - -add() -``` - -看似没有毛病,但实则已经犯了一个很基础的问题,运行结果如下: - -```python -$ python demo.py -Traceback (most recent call last): - File "demo.py", line 6, in - add() - File "demo.py", line 4, in add - a += 1 -UnboundLocalError: local variable 'a' referenced before assignment -``` - -回顾一下,什么是局部变量?在非全局下定义声明的变量都是局部变量。 - -当程序运行到 `a += 1` 时,Python 解释器就认为在函数内部要给 `a` 这个变量赋值,当然就把 `a` 当做局部变量了,但是做为局部变量的 a 还没有被还没被定义。 - -因此报错是正常的。 - -理解了上面的例子,给你留个思考题。为什么下面的代码不会报错呢? - -```python -$ cat demo.py -a = 1 - -def output(): - print(a) - -output() - -$ python demo.py -1 -``` - - - -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -## 31. 字母也玩起了障眼法 - -以下我分别在 Python2.7 和 Python 3.7 的 console 模式下,运行了如下代码。 - -**在Python 2.x 中** - -``` ->>> valuе = 32 - File "", line 1 - valuе = 32 - ^ -SyntaxError: invalid syntax -``` - -**在Python 3.x 中** - -``` ->>> valuе = 32 ->>> value -11 -``` - -什么?没有截图你不信? - -![](http://image.iswbm.com/20200509122954.png) - - - -如果你在自己的电脑上尝试一下,结果可能是这样的 - -![](http://image.iswbm.com/20200509123107.png) - - - -**怎么又好了呢?** - -如果你想复现的话,请复制我这边给出的代码:`valuе = 32` - - - -**这是为什么呢?** - -原因在于,我上面使用的 value 变量名里的 `е` 又不是我们熟悉的 `e`,它是 Cyrillic(西里尔)字母。 - -``` ->>> ord('е') # cyrillic 'e' (Ye) -1077 ->>> ord('e') # latin 'e', as used in English and typed using standard keyboard -101 ->>> 'е' == 'e' -False -``` - -细思恐极,在这里可千万不要得罪同事们,万一离职的时候,对方把你项目里的 `e` 全局替换成 `e`,到时候你就哭去吧,肉眼根本看不出来嘛。 - -## 32. 字符串的分割技巧 - -当我们对字符串进行分割时,且分割符是 `\n`,有可能会出现这样一个窘境: - -```python ->>> str = "a\nb\n" ->>> print(str) -a -b - ->>> str.split('\n') -['a', 'b', ''] ->>> -``` - -会在最后一行多出一个元素,为了应对这种情况,你可以会多加一步处理。 - -但我想说的是,完成没有必要,对于这个场景,你可以使用 `splitlines` - -```python ->>> str.splitlines() -['a', 'b'] -``` - - - -## 33. 嵌套上下文管理的另类写法 - -当我们要写一个嵌套的上下文管理器时,可能会这样写 - -```python -import contextlib - -@contextlib.contextmanager -def test_context(name): - print('enter, my name is {}'.format(name)) - - yield - - print('exit, my name is {}'.format(name)) - -with test_context('aaa'): - with test_context('bbb'): - print('========== in main ============') -``` - -输出结果如下 - -```python -enter, my name is aaa -enter, my name is bbb -========== in main ============ -exit, my name is bbb -exit, my name is aaa -``` - -除此之外,你可知道,还有另一种嵌套写法 - -```python -with test_context('aaa'), test_context('bbb'): - print('========== in main ============') -``` - -## 34. += 不等同于=+ - -对列表 进行`+=` 操作相当于 extend,而使用 `=+` 操作是新增了一个列表。 - -因此会有如下两者的差异。 - -```python -# =+ ->>> a = [1, 2, 3, 4] ->>> b = a ->>> a = a + [5, 6, 7, 8] ->>> a -[1, 2, 3, 4, 5, 6, 7, 8] ->>> b -[1, 2, 3, 4] - - -# += ->>> a = [1, 2, 3, 4] ->>> b = a ->>> a += [5, 6, 7, 8] ->>> a -[1, 2, 3, 4, 5, 6, 7, 8] ->>> b -[1, 2, 3, 4, 5, 6, 7, 8] -``` - -## 35. 增量赋值的性能更好 - -诸如 `+=` 和 `*=` 这些运算符,叫做 增量赋值运算符。 - -这里使用用 += 举例,以下两种写法,在效果上是等价的。 - -``` -# 第一种 -a = 1 ; a += 1 - -# 第二种 -a = 1; a = a + 1 -``` - -`+=` 其背后使用的魔法方法是 \__iadd__,如果没有实现这个方法则会退而求其次,使用 \__add__ 。 - -这两种写法有什么区别呢? - -用列表举例 a += b,使用 \__add__ 的话就像是使用了a.extend(b),如果使用 \__add__ 的话,则是 a = a+b,前者是直接在原列表上进行扩展,而后者是先从原列表中取出值,在一个新的列表中进行扩展,然后再将新的列表对象返回给变量,显然后者的消耗要大些。 - -所以在能使用增量赋值的时候尽量使用它。 - -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -## 36. x == +x 吗? - -在大多数情况下,这个等式是成立的。 - -``` ->>> n1 = 10086 ->>> n2 = +n1 ->>> ->>> n1 == n2 -True -``` - -什么情况下,这个等式会不成立呢? - -由于Counter的机制,`+` 用于两个 Counter 实例相加,而相加的结果如果元素的个数 `<=` 0,就会被丢弃。 - -``` ->>> from collections import Counter ->>> ct = Counter('abcdbcaa') ->>> ct -Counter({'a': 3, 'b': 2, 'c': 2, 'd': 1}) ->>> ct['c'] = 0 ->>> ct['d'] = -2 ->>> ->>> ct -Counter({'a': 3, 'b': 2, 'c': 0, 'd': -2}) ->>> ->>> +ct -Counter({'a': 3, 'b': 2}) -``` - - - -## 37. 如何将 print 内容输出到文件 - -Python 3 中的 print 作为一个函数,由于可以接收更多的参数,所以功能变为更加强大。 - -比如今天要说的使用 print 将你要打印的内容,输出到日志文件中(但是我并不推荐使用它)。 - -```python ->>> with open('test.log', mode='w') as f: -... print('hello, python', file=f, flush=True) ->>> exit() - -$ cat test.log -hello, python -``` - - - -## 38. site-packages和 dist-packages - -如果你足够细心,你会在你的机器上,有些包是安装在 **site-packages** 下,而有些包安装在 **dist-packages** 下。 - -**它们有什么区别呢?** - -一般情况下,你只见过 site-packages 这个目录,而你所安装的包也将安装在 这个目录下。 - -而 dist-packages 其实是 debian 系的 Linux 系统(如 Ubuntu)才特有的目录,当你使用 apt 去安装的 Python 包会使用 dist-packages,而你使用 pip 或者 easy_install 安装的包还是照常安装在 site-packages 下。 - -Debian 这么设计的原因,是为了减少不同来源的 Python 之间产生的冲突。 - -如何查找 Python 安装目录 - -```python ->>> from distutils.sysconfig import get_python_lib ->>> print(get_python_lib()) -/usr/lib/python2.7/site-packages -``` - -## 39. argument 和 parameter 的区别 - -arguments 和 parameter 的翻译都是参数,在中文场景下,二者混用基本没有问题,毕竟都叫参数嘛。 - -但若要严格再进行区分,它们实际上还有各自的叫法 - -- parameter:形参(**formal parameter**),体现在函数内部,作用域是这个函数体。 -- argument :实参(**actual parameter**),调用函数实际传递的参数。 - -举个例子,如下这段代码,`"error"` 为 argument,而 msg 为 `parameter`。 - -```python -def output_msg(msg): - print(msg) - -output_msg("error") -``` - -## 40. 简洁而优雅的链式比较 - -先给你看一个示例: - -```python ->>> False == False == True -False -``` - -你知道这个表达式为什么会会返回 False 吗? - -它的运行原理与下面这个类似,是不是有点头绪了: - -```python -if 80 < score <= 90: - print("成绩良好") -``` - -如果你还是不明白,那我再给你整个第一个例子的等价写法。 - -```python ->>> False == False and False == True -False -``` - -这个用法叫做链式比较。 - - - -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -## 41. 连接多个列表最极客的方式 - -```python ->>> a = [1,2] ->>> b = [3,4] ->>> c = [5,6] ->>> ->>> sum((a,b,c), []) -[1, 2, 3, 4, 5, 6] -``` - -## 42. 另外 8 种连接列表的方式 - -**1. 最直观的相加** - -使用 `+` 对多个列表进行相加,你应该懂,不多说了。 - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> list03 = [7,8,9] ->>> ->>> list01 + list02 + list03 -[1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> -``` - - - -**2. 借助 itertools** - -itertools 在 Python 里有一个非常强大的内置模块,它专门用于操作可迭代对象。 - -在前面的文章中也介绍过,使用 `itertools.chain()` 函数先可迭代对象(在这里指的是列表)串联起来,组成一个更大的可迭代对象。 - -最后你再利用 list 将其转化为 列表。 - -```python ->>> from itertools import chain ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> list03 = [7,8,9] ->>> ->>> list(chain(list01, list02, list03)) -[1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> -``` - -**3. 使用 * 解包** - -使用 `*` 可以解包列表,解包后再合并。 - -示例如下: - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> ->>> [*list01, *list02] -[1, 2, 3, 4, 5, 6] ->>> -``` - - - -**4. 使用 extend** - -在字典中,使用 update 可实现原地更新,而在列表中,使用 extend 可实现列表的自我扩展。 - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> ->>> list01.extend(list02) ->>> list01 -[1, 2, 3, 4, 5, 6] -``` - -**5. 使用列表推导式** - -Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 - -那就是列表解析式,集合解析式和字典解析式,通常是 Python 发烧友的最爱,那么今天的主题:列表合并,列表推导式还能否胜任呢? - -当然可以,具体示例代码如下: - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> list03 = [7,8,9] ->>> ->>> [x for l in (list01, list02, list03) for x in l] -[1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> -``` - - - -**6. 使用 heapq** - -heapq 是 Python 的一个标准模块,它提供了堆排序算法的实现。 - -该模块里有一个 merge 方法,可以用于合并多个列表,如下所示 - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> list03 = [7,8,9] ->>> ->>> from heapq import merge ->>> ->>> list(merge(list01, list02, list03)) -[1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> -``` - -要注意的是,heapq.merge 除了合并多个列表外,它还会将合并后的最终的列表进行排序。 - -```python ->>> list01 = [2,5,3] ->>> list02 = [1,4,6] ->>> list03 = [7,9,8] ->>> ->>> from heapq import merge ->>> ->>> list(merge(list01, list02, list03)) -[1, 2, 4, 5, 3, 6, 7, 9, 8] ->>> -``` - -它的效果等价于下面这行代码: - -```python -sorted(itertools.chain(*iterables)) -``` - -如果你希望得到一个始终有序的列表,那请第一时间想到 heapq.merge,因为它采用堆排序,效率非常高。但若你不希望得到一个排过序的列表,就不要使用它了。 - -**7. 借助魔法方法** - -有一个魔法方法叫 `__add__`,当我们使用第一种方法 list01 + list02 的时候,内部实际上是作用在 `__add__` 这个魔法方法上的。 - -所以以下两种方法其实是等价的 - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> ->>> list01 + list02 -[1, 2, 3, 4, 5, 6] ->>> ->>> ->>> list01.__add__(list02) -[1, 2, 3, 4, 5, 6] ->>> -``` - -借用这个魔法特性,我们可以 reduce 这个方法来对多个列表进行合并,示例代码如下 - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> list03 = [7,8,9] ->>> ->>> from functools import reduce ->>> reduce(list.__add__, (list01, list02, list03)) -[1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> -``` - - - -**8. 使用 yield from** - -在 yield from 后可接一个可迭代对象,用于迭代并返回其中的每一个元素。 - -因此,我们可以像下面这样自定义一个合并列表的工具函数。 - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> list03 = [7,8,9] ->>> ->>> def merge(*lists): -... for l in lists: -... yield from l -... ->>> list(merge(list01, list02, list03)) -[1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> -``` - - - -## 43. 在程序退出前执行代码的技巧 - -使用 atexit 这个内置模块,可以很方便的注册退出函数。 - -不管你在哪个地方导致程序崩溃,都会执行那些你注册过的函数。 - -示例如下 - -![](http://image.iswbm.com/20200510112133.png) - -如果`clean()`函数有参数,那么你可以不用装饰器,而是直接调用`atexit.register(clean_1, 参数1, 参数2, 参数3='xxx')`。 - -可能你有其他方法可以处理这种需求,但肯定比上不使用 atexit 来得优雅,来得方便,并且它很容易扩展。 - -但是使用 atexit 仍然有一些局限性,比如: - -- 如果程序是被你没有处理过的系统信号杀死的,那么注册的函数无法正常执行。 -- 如果发生了严重的 Python 内部错误,你注册的函数无法正常执行。 -- 如果你手动调用了`os._exit()`,你注册的函数无法正常执行。 - -## 44. 合并字典的 8 种方法 - -**1. 最简单的原地更新** - -字典对象内置了一个 update 方法,用于把另一个字典更新到自己身上。 - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> profile.update(ext_info) ->>> print(profile) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - -如果想使用 update 这种最简单、最地道原生的方法,但又不想更新到自己身上,而是生成一个新的对象,那请使用深拷贝。 - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> from copy import deepcopy ->>> ->>> full_profile = deepcopy(profile) ->>> full_profile.update(ext_info) ->>> ->>> print(full_profile) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ->>> print(profile) -{"name": "xiaoming", "age": 27} -``` - - - -**2. 先解包再合并字典** - -使用 `**` 可以解包字典,解包完后再使用 dict 或者 `{}` 就可以合并。 - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> full_profile01 = {**profile, **ext_info} ->>> print(full_profile01) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ->>> ->>> full_profile02 = dict(**profile, **ext_info) ->>> print(full_profile02) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - -若你不知道 `dict(**profile, **ext_info)` 做了啥,你可以将它等价于 - -```python ->>> dict((("name", "xiaoming"), ("age", 27), ("gender", "male"))) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - - - -**3. 借助 itertools** - -在 Python 里有一个非常强大的内置模块,它专门用于操作可迭代对象。 - -正好我们字典也是可迭代对象,自然就可以想到,可以使用 `itertools.chain()` 函数先将多个字典(可迭代对象)串联起来,组成一个更大的可迭代对象,然后再使用 dict 转成字典。 - -```python ->>> import itertools ->>> ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> ->>> dict(itertools.chain(profile.items(), ext_info.items())) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - - - -**4. 借助 ChainMap** - -如果可以引入一个辅助包,那我就再提一个, `ChainMap` 也可以达到和 `itertools` 同样的效果。 - -```python ->>> from collections import ChainMap ->>> ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> dict(ChainMap(profile, ext_info)) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - -使用 ChainMap 有一点需要注意,当字典间有重复的键时,只会取第一个值,排在后面的键值并不会更新掉前面的(使用 itertools 就不会有这个问题)。 - -```python ->>> from collections import ChainMap ->>> ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info={"age": 30} ->>> dict(ChainMap(profile, ext_info)) -{'name': 'xiaoming', 'age': 27} -``` - - - -**5. 使用dict.items() 合并** - -在 Python 3.9 之前,其实就已经有 `|` 操作符了,只不过它通常用于对集合(set)取并集。 - -利用这一点,也可以将它用于字典的合并,只不过得绕个弯子,有点不好理解。 - -你得先利用 `items` 方法将 dict 转成 dict_items,再对这两个 dict_items 取并集,最后利用 dict 函数,转成字典。 - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> full_profile = dict(profile.items() | ext_info.items()) ->>> full_profile -{'gender': 'male', 'age': 27, 'name': 'xiaoming'} -``` - - - -当然了,你如果嫌这样太麻烦,也可以简单点,直接使用 list 函数再合并(示例为 Python 3.x ) - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> dict(list(profile.items()) + list(ext_info.items())) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - -若你在 Python 2.x 下,可以直接省去 list 函数。 - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> dict(profile.items() + ext_info.items()) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - - - -**6. 最酷炫的字典解析式** - -Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 - -那就是列表解析式,集合解析式和字典解析式,通常是 Python 发烧友的最爱,那么今天的主题:字典合并,字典解析式还能否胜任呢? - -当然可以,具体示例代码如下: - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> {k:v for d in [profile, ext_info] for k,v in d.items()} -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - - - -**7. Python 3.9 新特性** - -在 2 月份发布的 Python 3.9.04a 版本中,新增了一个抓眼球的新操作符操作符: `|`, PEP584 将它称之为合并操作符(Union Operator),用它可以很直观地合并多个字典。 - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> profile | ext_info -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ->>> ->>> ext_info | profile -{'gender': 'male', 'name': 'xiaoming', 'age': 27} ->>> ->>> -``` - -除了 `|` 操作符之外,还有另外一个操作符 `|=`,类似于原地更新。 - -```python ->>> ext_info |= profile ->>> ext_info -{'gender': 'male', 'name': 'xiaoming', 'age': 27} ->>> ->>> ->>> profile |= ext_info ->>> profile -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - - - -看到这里,有没有涨姿势了,学了这么久的 Python ,没想到合并字典还有这么多的方法。本篇文章的主旨,并不在于让你全部掌握这 7 种合并字典的方法,实际在工作中,你只要选用一种最顺手的方式即可,但是在协同工作中,或者在阅读他人代码时,你不可避免地会碰到各式各样的写法,这时候你能下意识的知道这是在做合并字典的操作,那这篇文章就是有意义的。 - - - -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -## 45. 条件语句的七种写法 - -**第一种:原代码** - -这是一段非常简单的通过年龄判断一个人是否成年的代码,由于代码行数过多,有些人就不太愿意这样写,因为这体现不出自己多年的 Python 功力。 - -```python -if age > 18: - return "已成年" -else: - return "未成年" -``` - -下面我列举了六种这段代码的变异写法,一个比一个还 6 ,单独拿出来比较好理解,放在工程代码里,没用过这些学法的人,一定会看得一脸懵逼,理解了之后,又不经意大呼:**卧槽,还可以这样写?**,而后就要开始骂街了:**这是给人看的代码?** (除了第一种之外) - -**第二种** - -语法: - -```python - if else -``` - -例子 - -```python ->>> age1 = 20 ->>> age2 = 17 ->>> ->>> ->>> msg1 = "已成年" if age1 > 18 else "未成年" ->>> print msg1 -已成年 ->>> ->>> msg2 = "已成年" if age2 > 18 else "未成年" ->>> print msg2 -未成年 ->>> -``` - -**第三种** - -语法 - -```python - and or -``` - -例子 - -```python ->>> msg1 = age1 > 18 and "已成年" or "未成年" ->>> msg2 = "已成年" if age2 > 18 else "未成年" ->>> ->>> print(msg1) -已成年 ->>> ->>> print(msg2) -未成年 -``` - -**第四种** - -语法 - -```python -(, )[condition] -``` - -例子 - -```python ->>> msg1 = ("未成年", "已成年")[age1 > 18] ->>> print(msg1) -已成年 ->>> ->>> ->>> msg2 = ("未成年", "已成年")[age2 > 18] ->>> print(msg2) -未成年 -``` - -**第五种** - -语法 - -```python -(lambda: , lambda:)[]() -``` - -例子 - -```python ->>> msg1 = (lambda:"未成年", lambda:"已成年")[age1 > 18]() ->>> print(msg1) -已成年 ->>> ->>> msg2 = (lambda:"未成年", lambda:"已成年")[age2 > 18]() ->>> print(msg2) -未成年 -``` - -**第六种** - -语法: - -```python -{True: , False: }[] -``` - -例子: - -```python ->>> msg1 = {True: "已成年", False: "未成年"}[age1 > 18] ->>> print(msg1) -已成年 ->>> ->>> msg2 = {True: "已成年", False: "未成年"}[age2 > 18] ->>> print(msg2) -未成年 -``` - -**第七种** - -语法 - -```python -(() and (,) or (,))[0] -``` - -例子 - -```python ->>> msg1 = ((age1 > 18) and ("已成年",) or ("未成年",))[0] ->>> print(msg1) -已成年 ->>> ->>> msg2 = ((age2 > 18) and ("已成年",) or ("未成年",))[0] ->>> print(msg2) -未成年 -``` - -以上代码,都比较简单,仔细看都能看懂,我就不做解释了。 - -看到这里,有没有涨姿势了,学了这么久的 Python ,这么多骚操作,还真是活久见。。这六种写法里,我最推荐使用的是第一种,自己也经常在用,简洁直白,代码行还少。而其他的写法虽然能写,但是不会用,也不希望在我余生里碰到会在公共代码里用这些写法的同事。 - -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -## 46. /usr/bin/env python 有什么用? - -我们经常会在别人的脚本或者项目的入口文件里看到第一行是下面这样 - -```shell -#!/usr/bin/python -``` - -或者这样 - -```sh e llsh el -#!/usr/bin/env python -``` - -这两者有什么区别呢? - -稍微接触过 linux 的人都知道 `/usr/bin/python` 就是我们执行 `python` 进入console 模式里的 `python` - -![](http://image.python-online.cn/20200331184021.png) - -而当你在可执行文件头里使用 `#!` + `/usr/bin/python` ,意思就是说你得用哪个软件 (python)来执行这个文件。 - -那么加和不加有什么区别呢? - -不加的话,你每次执行这个脚本时,都得这样: `python xx.py` , - -![](http://image.python-online.cn/20200331185034.png) - -有没有一种方式?可以省去每次都加 `python` 呢? - -当然有,你可以文件头里加上`#!/usr/bin/python` ,那么当这个文件有可执行权限 时,只直接写这个脚本文件,就像下面这样。 - -![](http://image.python-online.cn/20200331184755.png) - -明白了这个后,再来看看 `!/usr/bin/env python` 这个 又是什么意思 ? - -当我执行 `env python` 时,自动进入了 python console 的模式。 - -![](http://image.python-online.cn/20200331185741.png) - -这是为什么?和 直接执行 python 好像没什么区别呀 - -当你执行 `env python` 时,它其实会去 `env | grep PATH` 里(也就是 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin )这几个路径里去依次查找名为python的可执行文件。 - -找到一个就直接执行,上面我们的 python 路径是在 `/usr/bin/python` 里,在 `PATH` 列表里倒数第二个目录下,所以当我在 `/usr/local/sbin` 下创建一个名字也为 python 的可执行文件时,就会执行 `/usr/bin/python` 了。 - -具体演示过程,你可以看下面。 - -![](http://image.python-online.cn/20200331190224.png) - -那么对于这两者,我们应该使用哪个呢? - -个人感觉应该优先使用 `#!/usr/bin/env python`,因为不是所有的机器的 python 解释器都是 `/usr/bin/python` 。 - -## 47. 让我爱不释手的用户环境 - -当你在机器上并没有 root 权限时,如何安装 Python 的第三方包呢? - -可以使用 `pip install --user pkg` 将你的包安装在你的用户环境中,该用户环境与全局环境并不冲突,并且多用户之间相互隔离,互不影响。 - -```shell -# 在全局环境中未安装 requests -[root@localhost ~]$ pip list | grep requests -[root@localhost ~]$ su - wangbm - -# 由于用户环境继承自全局环境,这里也未安装 -[wangbm@localhost ~]$ pip list | grep requests -[wangbm@localhost ~]$ pip install --user requests -[wangbm@localhost ~]$ pip list | grep requests -requests (2.22.0) -[wangbm@localhost ~]$ - -# 从 Location 属性可发现 requests 只安装在当前用户环境中 -[wangbm@localhost ~]$ pip show requests ---- -Metadata-Version: 2.1 -Name: requests -Version: 2.22.0 -Summary: Python HTTP for Humans. -Home-page: http://python-requests.org -Author: Kenneth Reitz -Author-email: me@kennethreitz.org -Installer: pip -License: Apache 2.0 -Location: /home/wangbm/.local/lib/python2.7/site-packages -[wangbm@localhost ~]$ exit -logout - -# 退出 wangbm 用户,在 root 用户环境中发现 requests 未安装 -[root@localhost ~]$ pip list | grep requests -[root@localhost ~]$ -``` - - - -## 48. 实现类似 defer 的延迟调用 - -在 Golang 中有一种延迟调用的机制,关键字是 defer,例如下面的示例 - -```go -import "fmt" - -func myfunc() { - fmt.Println("B") -} - -func main() { - defer myfunc() - fmt.Println("A") -} -``` - -输出如下,myfunc 的调用会在函数返回前一步完成,即使你将 myfunc 的调用写在函数的第一行,这就是延迟调用。 - -``` -A -B -``` - -那么在 Python 中否有这种机制呢? - -当然也有,只不过并没有 Golang 这种简便。 - -在 Python 可以使用 **上下文管理器** 达到这种效果 - -```python -import contextlib - -def callback(): - print('B') - -with contextlib.ExitStack() as stack: - stack.callback(callback) - print('A') -``` - -输出如下 - -``` -A -B -``` - -## 49. 自带的缓存机制不用白不用 - -缓存是一种将定量数据加以保存,以备迎合后续获取需求的处理方式,旨在加快数据获取的速度。 - -数据的生成过程可能需要经过计算,规整,远程获取等操作,如果是同一份数据需要多次使用,每次都重新生成会大大浪费时间。所以,如果将计算或者远程请求等操作获得的数据缓存下来,会加快后续的数据获取需求。 - -为了实现这个需求,Python 3.2 + 中给我们提供了一个机制,可以很方便的实现,而不需要你去写这样的逻辑代码。 - -这个机制实现于 functool 模块中的 lru_cache 装饰器。 - -```python -@functools.lru_cache(maxsize=None, typed=False) -``` - -参数解读: - -- maxsize:最多可以缓存多少个此函数的调用结果,如果为None,则无限制,设置为 2 的幂时,性能最佳 -- typed:若为 True,则不同参数类型的调用将分别缓存。 - -举个例子 - -```python -from functools import lru_cache - -@lru_cache(None) -def add(x, y): - print("calculating: %s + %s" % (x, y)) - return x + y - -print(add(1, 2)) -print(add(1, 2)) -print(add(2, 3)) -``` - -输出如下,可以看到第二次调用并没有真正的执行函数体,而是直接返回缓存里的结果 - -```shell -calculating: 1 + 2 -3 -3 -calculating: 2 + 3 -5 -``` - -## 50. 重定向标准输出到日志 - -假设你有一个脚本,会执行一些任务,比如说集群健康情况的检查。 - -检查完成后,会把各服务的的健康状况以 JSON 字符串的形式打印到标准输出。 - -如果代码有问题,导致异常处理不足,最终检查失败,是很有可能将一些错误异常栈输出到标准错误或标准输出上。 - -由于最初约定的脚本返回方式是以 JSON 的格式输出,此时你的脚本却输出各种错误异常,异常调用方也无法解析。 - -如何避免这种情况的发生呢? - -我们可以这样做,把你的标准错误输出到日志文件中。 - -```python -import contextlib - -log_file="/var/log/you.log" - -def you_task(): - pass - -@contextlib.contextmanager -def close_stdout(): - raw_stdout = sys.stdout - file = open(log_file, 'a+') - sys.stdout = file - - yield - - sys.stdout = raw_stdout - file.close() - -with close_stdout(): - you_task() -``` - - +**参考文章** ---- +- https://www.cnblogs.com/stonehe/p/7944366.html ![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c01/c01_10.rst b/source/c01/c01_10.rst old mode 100755 new mode 100644 index 1c8a7a6..7b9f5a7 --- a/source/c01/c01_10.rst +++ b/source/c01/c01_10.rst @@ -1,2340 +1,207 @@ -1.10 Python 黑魔法指南 50 例 -============================ +1.10 如何修改 CentOS 6.x 上默认Python +===================================== |image0| --------------- +最近在工作中遇到一个问题,就是有一个功能希望在各种服务器上实现,而服务器上的系统版本可能都不一样,有的是 +CentOS 6.x,有的是 CentOS 7.x 。 - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 +需要说明的一点是,CentOS 6.x 上的 Python 版本是 2.6.x 的,而 CentOS 7.x +上的 Python 版本是 2.7.x +的,这意味着我要实现的功能要适配这两种版本的系统。 -01. 默默无闻的省略号很好用 --------------------------- +你可能会说,这有什么的,自己写的时候,注意一下就好了。 -在Python中,一切皆对象,省略号也不例外。 +事情其实没有那么容易,我要实现的功能是基于一个框架进行定制,需要修改不少的框架代码。这个框架在不同的 +Linux 版本上,是有不同的版本的,而且差异巨大,曾经想过在 CentOS 6.x 和 +CentOS 7.x +将这个框架安装成同一个版本,最后还是失败了,无法安装,原因就是高版本需要 +Python2.7,而 CentOS 6.x 上只有 Python2.6。 -在 Python 3 中你可以直接写 ``...`` 来得到它 +这个历史问题一直遗留到现在,由于这次的功能影响到的代码较多,如果要对两个版本的框架分别进行定制的话,需要花不少的时间,为了不维护两套版本,避免浪费多余的精力去做适配,我决定将 +CentOS 6.x 上默认的 Python2.6 升级成 Python2.7。 -.. code:: python - - >>> ... - Ellipsis - >>> type(...) - - -而在 Python 2 中没有\ ``...`` 这个语法,只能直接写Ellipsis来获取。 - -.. code:: python - - >>> Ellipsis - Ellipsis - >>> type(Ellipsis) - - >>> - -它转为布尔值时为真 - -.. code:: python - - >>> bool(...) - True - -最后,这东西是一个单例。 - -.. code:: python - - >>> id(...) - 4362672336 - >>> id(...) - 4362672336 +下面是整个升级过程,别看步骤简单,这些精简步骤的背后可是有不少的坑,被我踩过后,你可以直接使用了。 -那这东西有啥用呢? - -1. 它是 Numpy 的一个语法糖 -2. 在 Python 3 中可以使用 … 代替 pass +1. 首先确认下你机器上的默认的 Python 版本 .. code:: shell - $ cat demo.py - def func01(): - ... - - def func02(): - pass + $ python -V + Python 2.6.6 - func01() - func02() + $ whereis python + python: /usr/bin/python /usr/bin/python2.6 /usr/lib/python2.6 /usr/lib64/python2.6 /usr/local/bin/python /usr/include/python2.6 /usr/share/man/man1/python.1.gz - print("ok") +2. 由于我们将使用编译安装的方式,所以要安装下 gcc,及一些工具包。 - $ python3 demo.py - ok +注意一定要全部安装,不然后面会发现有不少 python 的工具用不了。 -02. 使用 end 来结束代码块 -------------------------- +比如不安装 zlib 会无法安装 setuptools,不装 openssl 和 +openssl-devel,会无法使用 pip 工具等 -有不少编程语言,循环、判断代码块需要用 end -标明结束,这样一定程度上会使代码逻辑更加清晰一点。 - -但是其实在 Python 这种严格缩进的语言里并没有必要这样做。 - -如果你真的想用,也不是没有办法,具体你看下面这个例子。 +:: -.. code:: python + $ yum install gcc -y + $ yum groupinstall "Development tools" + $ yum install zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel -y - __builtins__.end = None +如果你这里未按照我的步骤来安装,你后面使用的时候出现了各种各样的问题,不要慌,只要再回来这里,把没安装的包装上,安装完成后,你需要进入第四步重新编译安装Python。 +3. 下载最新的 Python2.7.x 安装包,解压并进入指定目录 - def my_abs(x): - if x > 0: - return x - else: - return -x - end - end +:: - print(my_abs(10)) - print(my_abs(-10)) + $ wget https://www.python.org/ftp/python/2.7.14/Python-2.7.14.tgz + $ tar zxvf Python-2.7.14.tgz + $ cd Python-2.7.14 -执行后,输出如下 +4. 配置,编译,安装 .. code:: shell - [root@localhost ~]$ python demo.py - 10 - 10 - -03. 可直接运行的 zip 包 ------------------------ + # --prefix 指定 python 安装的路径 + $ ./configure --prefix=/usr/local/python/python2.7 + $ make + $ make install -我们可以经常看到有 Python 包,居然可以以 zip -包进行发布,并且可以不用解压直接使用。 +``./configure`` 命令执行完毕之后创建一个文件creating +Makefile,供下面的make命令使用 执行 ``make install`` +之后就会把程序安装到我们指定的目录中去。 -这与大多数人的认识的 Python 包格式不一样,正常人认为 Python 包的格式要嘛 -是 egg,要嘛是whl 格式。 +Configure是一个可执行脚本,它有很多选项,在待安装的源码路径下使用命令./configure +–help输出详细的选项列表。其中 ``--prefix`` +选项是配置安装的路径,如果不配置该选项,安装后可执行文件默认放在/usr +/local/bin,库文件默认放在 ``/usr/local/lib`` ,配置文件默认放在 +``/usr/local/etc`` ,其它的资源文件放在 +``/usr /local/share``\ 。如果配置 +``--prefix``\ ,如:\ ``./configure --prefix=/usr/local/test`` +可以把所有资源文件放在/usr/local/test的路径中,不会杂乱。 -那么这个zip 是如何制作的呢,请看下面的示例。 - -.. code:: shell +用了 ``--prefix`` +选项的另一个好处是卸载软件或移植软件。当某个安装的软件不再需要时,只须简单的删除该安装目录,就可以把软件卸载得干干净净;移植软件只需拷贝整个目录到另外一个机器即可(相同的操作系统)。当然要卸载程序,也可以在原来的make目录下用一次 +``make uninstall``\ ,但前提是make文件指定过uninstall。 - [root@localhost ~]# ls -l demo - total 8 - -rw-r--r-- 1 root root 30 May 8 19:27 calc.py - -rw-r--r-- 1 root root 35 May 8 19:33 __main__.py - [root@localhost ~]# - [root@localhost ~]# cat demo/__main__.py - import calc - - print(calc.add(2, 3)) - [root@localhost ~]# - [root@localhost ~]# cat demo/calc.py - def add(x, y): - return x+y - [root@localhost ~]# - [root@localhost ~]# python -m zipfile -c demo.zip demo/* - [root@localhost ~]# - -制作完成后,我们可以执行用 python 去执行它 +5. 查看系统的 Python 版本 .. code:: shell - [root@localhost ~]# python demo.zip - 5 - [root@localhost ~]# - -04. 反斜杠的倔强: 不写最后 --------------------------- - -``\`` 在 Python 中的用法主要有两种 - -**1、在行尾时,用做续行符** - -.. code:: python - - [root@localhost ~]$ cat demo.py - print("hello "\ - "world") - [root@localhost ~]$ - [root@localhost ~]$ python demo.py - hello world - -**2、在字符串中,用做转义字符,可以将普通字符转化为有特殊含义的字符。** - -.. code:: python - - >>> str1='\nhello'  #换行 - >>> print(str1) - - hello - >>> str2='\thello'  #tab - >>> print(str2) - hello - -但是如果你用单\ ``\``\ 结尾是会报语法错误的 - -.. code:: python - - >>> str3="\" - File "", line 1 - str3="\" - ^ - SyntaxError: EOL while scanning string literal - -就算你指定它是个 raw 字符串,也不行。 - -.. code:: python - - >>> str3=r"\" - File "", line 1 - str3=r"\" - ^ - SyntaxError: EOL while scanning string literal - -05. 单行实现 for 死循环如何写? -------------------------------- - -如果让你在不借助 while ,只使用 for 来写一个死循环? - -**你会写吗?** - -**如果你还说简单,你可以自己试一下。** - -… - -如果你尝试后,仍然写不出来,那我给出自己的做法。 - -.. code:: python - - for i in iter(int, 1):pass - -**是不是傻了?iter 还有这种用法?这为啥是个死循环?** - -关于这个问题,你如果看中文网站,可能找不到相关资料。 - -还好你可以通过 IDE 看py源码里的注释内容,介绍了很详细的使用方法。 - -原来iter有两种使用方法。 - -- 通常我们的认知是第一种,将一个列表转化为一个迭代器。 - -- 而第二种方法,他接收一个 callable对象,和一个sentinel - 参数。第一个对象会一直运行,直到它返回 sentinel 值才结束。 - -那\ ``int`` 呢? - -这又是一个知识点,int -是一个内建方法。通过看注释,可以看出它是有默认值0的。你可以在console -模式下输入 ``int()`` 看看是不是返回0。 - -由于int() 永远返回0,永远返回不了1,所以这个 for -循环会没有终点。一直运行下去。 - - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -06. 懒人必备技能:使用 “_” --------------------------- - -对于 ``_`` ,大家对于他的印象都是用于 -**占位符**\ ,省得为一个不需要用到的变量,绞尽脑汁的想变量名。 + $ python -V + Python 2.6.6 -今天要介绍的是他的第二种用法,就是在 console 模式下的应用。 +如果你查看还是 Python 2.6.6 版本,请继续看第六步。 -示例如下: +6. 修改系统默认的 Python 版本 -.. code:: python - - >>> 3 + 4 - 7 - >>> _ - 7 - >>> name='公众号: Python编程时光' - >>> name - '公众号: Python编程时光' - >>> _ - '公众号: Python编程时光' - -它可以返回上一次的运行结果。 - -但是,如果是print函数打印出来的就不行了。 - -.. code:: python - - >>> 3 + 4 - 7 - >>> _ - 7 - >>> print("公众号: Python编程时光") - ming - >>> _ - 7 - -我自己写了个例子,验证了下,用\ ``__repr__``\ 输出的内容可以被获取到的。 -首先,在我们的目录下,写一个文件 demo.py。内容如下 - -.. code:: python - - # demo.py - class mytest(): - def __str__(self): - return "hello" - - def __repr__(self): - return "world" - -然后在这个目录下进入交互式环境。 - -.. code:: python - - >>> import demo - >>> mt=demo.mytest() - >>> mt - world - >>> print(mt) - hello - >>> _ - world - -知道这两个魔法方法的人,一看就明白了,这里不再解释啦。 - -07. 最快查看包搜索路径的方式 ----------------------------- - -当你使用 import 导入一个包或模块时,Python -会去一些目录下查找,而这些目录是有优先级顺序的,正常人会使用 sys.path -查看。 - -.. code:: python - - >>> import sys - >>> from pprint import pprint - >>> pprint(sys.path) - ['', - '/usr/local/Python3.7/lib/python37.zip', - '/usr/local/Python3.7/lib/python3.7', - '/usr/local/Python3.7/lib/python3.7/lib-dynload', - '/home/wangbm/.local/lib/python3.7/site-packages', - '/usr/local/Python3.7/lib/python3.7/site-packages'] - >>> - -那有没有更快的方式呢? - -我这有一种连 console 模式都不用进入的方法,一行命令即可解决 +查看新安装的Python版本,当前系统的Python版本,并将系统指向的Python从2.6.x修改为2.7.x,再次查看当前系统的Python版本,已经变更为2.7.x .. code:: shell - [wangbm@localhost ~]$ python3 -m site - sys.path = [ - '/home/wangbm', - '/usr/local/Python3.7/lib/python37.zip', - '/usr/local/Python3.7/lib/python3.7', - '/usr/local/Python3.7/lib/python3.7/lib-dynload', - '/home/wangbm/.local/lib/python3.7/site-packages', - '/usr/local/Python3.7/lib/python3.7/site-packages', - ] - USER_BASE: '/home/wangbm/.local' (exists) - USER_SITE: '/home/wangbm/.local/lib/python3.7/site-packages' (exists) - ENABLE_USER_SITE: True - -从输出你可以发现,这个列的路径会比 sys.path -更全,它包含了用户环境的目录。 - -08. and 和 or 的取值顺序 ------------------------- - -and 和 or 是我们再熟悉不过的两个逻辑运算符,在 Python 也有它有妙用。 - -- 当一个 **or 表达式**\ 中所有值都为真,Python会选择第一个值 - -- 当一个 **and 表达式** 所有值都为真,Python 会选择第二个值。 - -示例如下: - -.. code:: python + # 这是我们刚安装的 Python + $/usr/local/bin/python2.7 -V + Python 2.7.14 - >>>(2 or 3) * (5 and 7) - 14 # 2*7 + # 这是系统默认 Python + $ /usr/bin/python -V + Python 2.6.6 -09. 如何修改解释器提示符 ------------------------- + # 备份原来的 Python 文件 + $ mv /usr/bin/python /usr/bin/python.bak -这个当做今天的一个小彩蛋吧。应该算是比较冷门的,估计知道的人很少了吧。 + # 建立软链接,将我们刚安装的 python2.7 做为系统默认版本 + ln -s /usr/local/bin/python2.7 /usr/bin/python -正常情况下,我们在 终端下 执行Python 命令是这样的。 + # 再次查看 Python 版本,已经成功切换过来 + $ python -V + Python 2.7.14 -.. code:: python +7. 重新指定 yum 的Python版本 - >>> for i in range(2): - ... print (i) - ... - 0 - 1 +上面我们改了系统的默认 Python 版本,由于CentOS 6.x 的 yum +是基于Python2.6 的,为了不影响 yum +的使用,需单独将yum指向python2.6版本。 -你是否想过 ``>>>`` 和 ``...`` 这两个提示符也是可以修改的呢? +编辑: vim /usr/bin/yum ,将\ ``/usr/bin/python`` 改成 +``/usr/bin/python2.6`` .. code:: python - >>> import sys - >>> sys.ps1 - '>>> ' - >>> sys.ps2 - '... ' - >>> - >>> sys.ps2 = '---------------- ' - >>> sys.ps1 = 'Python编程时光>>>' - Python编程时光>>>for i in range(2): - ---------------- print (i) - ---------------- - 0 - 1 - -10. 逗号也有它独特的用法 ------------------------- + #!/usr/bin/python2.6 -逗号,虽然是个很不起眼的符号,但在 Python 中也有他的用武之地。 +8. 安装 setuptools 及 pip -**第一个用法** - -元组的转化 +pip是python的安装工具,很多python的常用工具,都可以通过pip进行安装。要安装pip,首先要安装setuptools。从这个链接,你可以得到相关信息:https://pypi.python.org/pypi/setuptools .. code:: shell - [root@localhost ~]# cat demo.py - def func(): - return "ok", - - print(func()) - [root@localhost ~]# python3 demo.py - ('ok',) + # 下载 setuptools + $ wget https://pypi.python.org/packages/ff/d4/209f4939c49e31f5524fa0027bf1c8ec3107abaf7c61fdaad704a648c281/setuptools-21.0.0.tar.gz#md5=81964fdb89534118707742e6d1a1ddb4 -**第二个用法** - -print 的取消换行 +同样的,进行安装: .. code:: shell - [root@localhost ~]# cat demo.py - for i in range(3): - print i - [root@localhost ~]# - [root@localhost ~]# python demo.py - 0 - 1 - 2 - [root@localhost ~]# - [root@localhost ~]# vim demo.py - [root@localhost ~]# - [root@localhost ~]# cat demo.py - for i in range(3): - print i, - [root@localhost ~]# - [root@localhost ~]# python demo.py - 0 1 2 - [root@localhost ~]# - -.. - - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -11. 默认参数最好不为可变对象 ----------------------------- + $ tar vxf setuptools-21.0.0.tar.gz + $ cd setuptools-21.0.0 + $ python setup.py install -函数的参数分三种 - 可变参数 - 默认参数 - 关键字参数 +安装完成后,下载pip。其信息在如下网站:https://pypi.python.org/pypi/pip -当你在传递默认参数时,有新手很容易踩雷的一个坑。 - -先来看一个示例 - -.. code:: python - - def func(item, item_list=[]): - item_list.append(item) - print(item_list) - - func('iphone') - func('xiaomi', item_list=['oppo','vivo']) - func('huawei') +.. code:: shell -在这里,你可以暂停一下,思考一下会输出什么? + # 下载 pip + wget https://pypi.python.org/packages/41/27/9a8d24e1b55bd8c85e4d022da2922cb206f183e2d18fee4e320c9547e751/pip-8.1.1.tar.gz#md5=6b86f11841e89c8241d689956ba99ed7 -思考过后,你的答案是否和下面的一致呢 +同样的,进行安装 -:: +.. code:: shell - ['iphone'] - ['oppo', 'vivo', 'xiaomi'] - ['iphone', 'huawei'] + $ tar vxf pip-8.1.1.tar.gz + $ cd pip-8.1.1 + $ python setup.py install -如果是,那你可以跳过这部分内容,如果不是,请接着往下看,这里来分析一下。 +安装完成后,执行 ``pip list`` +查看一下安装的包,若无异常,则一切顺利。或者你也可以试着安装一下第三方包 +``pip install requests`` 。 -Python 中的 def -语句在每次执行的时候都初始化一个函数对象,这个函数对象就是我们要调用的函数,可以把它当成一个一般的对象,只不过这个对象拥有一个可执行的方法和部分属性。 +8. 转移cloudinit -对于参数中提供了初始值的参数,由于 Python -中的函数参数传递的是对象,也可以认为是传地址,在第一次初始化 def -的时候,会先生成这个可变对象的内存地址,然后将这个默认参数 item_list -会与这个内存地址绑定。在后面的函数调用中,如果调用方指定了新的默认值,就会将原来的默认值覆盖。如果调用方没有指定新的默认值,那就会使用原来的默认值。 +上面说的项目,其实就是 cloudinit。接下来就要将 centos 7.2 上的cloudinit +的目录整体拷贝到 centos 6.5 的/usr/local/lib/python2.7/site-packages/ +目录下 |image1| -12. 访问类中的私有方法 ----------------------- - -大家都知道,类中可供直接调用的方法,只有公有方法(protected类型的方法也可以,但是不建议)。也就是说,类的私有方法是无法直接调用的。 - -这里先看一下例子 - -.. code:: python - - class Kls(): - def public(self): - print('Hello public world!') - - def __private(self): - print('Hello private world!') - - def call_private(self): - self.__private() - - ins = Kls() - - # 调用公有方法,没问题 - ins.public() - - # 直接调用私有方法,不行 - ins.__private() - - # 但你可以通过内部公有方法,进行代理 - ins.call_private() - -既然都是方法,那我们真的没有方法可以直接调用吗? - -当然有啦,只是建议你千万不要这样弄,这里只是普及,让你了解一下。 - -.. code:: python - - # 调用私有方法,以下两种等价 - ins._Kls__private() - ins.call_private() - -13. 时有时无的切片异常 ----------------------- - -这是个简单例子,alist 只有5 个元素,当你取第 6 -个元素时,会抛出索引异常。这与我们的认知一致。 +然后安装一些 cloudinit 的依赖包。 -.. code:: python - - >>> alist = [0, 1, 2, 3, 4] - >>> alist[5] - Traceback (most recent call last): - File "", line 1, in - IndexError: list index out of range - -但是当你使用 alist[5:] 取一个区间时,即使 alist 并没有 第 -6个元素,也不抛出异常,而是会返回一个新的列表。 - -.. code:: python - - >>> alist = [0, 1, 2, 3, 4] - >>> alist[5:] - [] - >>> alist[100:] - [] - -14. 哪些情况下不需要续行符? ----------------------------- - -在写代码时,为了代码的可读性,代码的排版是尤为重要的。 - -为了实现高可读性的代码,我们常常使用到的就是续行符 ``\``\ 。 - -:: - - >>> a = 'talk is cheap,'\ - ... 'show me the code.' - >>> - >>> print(a) - talk is cheap,show me the code. - -那有哪些情况下,是不需要写续行符的呢? - -经过总结,在这些符号中间的代码换行可以省略掉续行符:\ ``[]``,\ ``()``,\ ``{}`` - -:: - - >>> my_list=[1,2,3, - ... 4,5,6] - - >>> my_tuple=(1,2,3, - ... 4,5,6) - - >>> my_dict={"name": "MING", - ... "gender": "male"} - -另外还有,在多行文本注释中 ``'''`` ,续行符也是可以不写的。 - -:: - - >>> text = '''talk is cheap, - ... show me the code''' - -15. Python2下 也能使用 print(“”) --------------------------------- - -可能会有不少人,觉得只有 Python 3 才可以使用 print(),而 Python 2 -只能使用\ ``print ""``\ 。 - -但是其实并不是这样的。 - -在Python 2.6之前,只支持 - -.. code:: python - - print "hello" - -在Python 2.6和2.7中,可以支持如下三种 - -.. code:: python - - print "hello" - print("hello") - print ("hello") - -在Python3.x中,可以支持如下两种 - -.. code:: python - - print("hello") - print ("hello") - -虽然 在 Python 2.6+ 可以和 Python3.x+ 一样,像函数一样去调用 print -,但是这仅用于两个 python 版本之间的代码兼容,并不是说在 -python2.6+下使用 print() 后,就成了函数。 - - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -16. 迷一样的字符串 ------------------- - -示例一 - -.. code:: python - - # Python2.7 - >>> a = "Hello_Python" - >>> id(a) - 32045616 - >>> id("Hello" + "_" + "Python") - 32045616 - - # Python3.7 - >>> a = "Hello_Python" - >>> id(a) - 38764272 - >>> id("Hello" + "_" + "Python") - 32045616 - -示例二 - -.. code:: python - - >>> a = "MING" - >>> b = "MING" - >>> a is b - True - - # Python2.7 - >>> a, b = "MING!", "MING!" - >>> a is b - True - - # Python3.7 - >>> a, b = "MING!", "MING!" - >>> a is b - False - -示例三 - -.. code:: python - - # Python2.7 - >>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa' - True - >>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa' - False - - # Python3.7 - >>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa' - True - >>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa' - True - -17. return不一定都是函数的终点 ------------------------------- - -众所周知,try…finally… -的用法是:不管try里面是正常执行还是有报异常,最终都能保证finally能够执行。 - -同时我们又知道,一个函数里只要遇到 return 函数就会立马结束。 - -那问题就来了,以上这两种规则,如果同时存在,Python -解释器会如何选择?哪个优先级更高? - -写个示例验证一下,就明白啦 - -.. code:: python - - >>> def func(): - ... try: - ... return 'try' - ... finally: - ... return 'finally' - ... - >>> func() - 'finally' - -从输出中,我们可以发现:在try…finally…语句中,try中的 return -会被直接忽视(这里的 return 不是函数的终点),因为要保证 finally -能够执行。 - -**如果 try 里的 return 真的是直接被忽视吗?** - -我们都知道如果一个函数没有 return,会隐式的返回 None,假设 try 里的 -return 真的是直接被忽视,那当finally 下没有显式的 return -的时候,是不是会返回None呢? - -还是写个 示例来验证一下: - -.. code:: python - - >>> def func(): - ... try: - ... return 'try' - ... finally: - ... print('finally') - ... - >>> - >>> func() - finally - 'try' - >>> - -从结果来看,当 finally 下没有 reutrn ,其实 try 里的 return -仍然还是有效的。 - -那结论就出来了,如果 finally 里有显式的 return,那么这个 return -会直接覆盖 try 里的 return,而如果 finally 里没有 显式的 return,那么 -try 里的 return 仍然有效。 - -18. 用户无感知的小整数池 ------------------------- - -为避免整数频繁申请和销毁内存空间,Python 定义了一个小整数池 [-5, 256] -这些整数对象是提前建立好的,不会被垃圾回收。 - -以上代码请在 终端Python环境下测试,如果你是在IDE中测试,由于 IDE -的影响,效果会有所不同。 - -.. code:: python - - >>> a = -6 - >>> b = -6 - >>> a is b - False - - >>> a = 256 - >>> b = 256 - >>> a is b - True - - >>> a = 257 - >>> b = 257 - >>> a is b - False - - >>> a = 257; b = 257 - >>> a is b - True - -**问题又来了:最后一个示例,为啥是True?** - -因为当你在同一行里,同时给两个变量赋同一值时,解释器知道这个对象已经生成,那么它就会引用到同一个对象。如果分成两成的话,解释器并不知道这个对象已经存在了,就会重新申请内存存放这个对象。 - -19. 神奇的 intern 机制 ----------------------- - -字符串类型作为Python中最常用的数据类型之一,Python解释器为了提高字符串使用的效率和使用性能,做了很多优化. - -例如:Python解释器中使用了 -intern(字符串驻留)的技术来提高字符串效率,什么是intern机制?就是同样的字符串对象仅仅会保存一份,放在一个字符串储蓄池中,是共用的,当然,肯定不能改变,这也决定了字符串必须是不可变对象。 - -:: - - >>> s1="hello" - >>> s2="hello" - >>> s1 is s2 - True - - # 如果有空格,默认不启用intern机制 - >>> s1="hell o" - >>> s2="hell o" - >>> s1 is s2 - False - - # 如果一个字符串长度超过20个字符,不启动intern机制 - >>> s1 = "a" * 20 - >>> s2 = "a" * 20 - >>> s1 is s2 - True - - >>> s1 = "a" * 21 - >>> s2 = "a" * 21 - >>> s1 is s2 - False - - >>> s1 = "ab" * 10 - >>> s2 = "ab" * 10 - >>> s1 is s2 - True - - >>> s1 = "ab" * 11 - >>> s2 = "ab" * 11 - >>> s1 is s2 - False - -20. 反转字符串/列表最优雅的方式 -------------------------------- - -反转序列并不难,但是如何做到最优雅呢? - -先来看看,正常是如何反转的。 - -最简单的方法是使用列表自带的reverse()方法。 - -.. code:: python - - >>> ml = [1,2,3,4,5] - >>> ml.reverse() - >>> ml - [5, 4, 3, 2, 1] - -但如果你要处理的是字符串,reverse就无能为力了。你可以尝试将其转化成list,再reverse,然后再转化成str。转来转去,也太麻烦了吧?需要这么多行代码(后面三行是不能合并成一行的),一点都Pythonic。 - -.. code:: python - - mstr1 = 'abc' - ml1 = list(mstr1) - ml1.reverse() - mstr2 = str(ml1) - -对于字符串还有一种稍微复杂一点的,是自定义递归函数来实现。 - -.. code:: python - - def my_reverse(str): - if str == "": - return str - else: - return my_reverse(str[1:]) + str[0] - -在这里,介绍一种最优雅的反转方式,使用切片,不管你是字符串,还是列表,简直通杀。 - -.. code:: python - - >>> mstr = 'abc' - >>> ml = [1,2,3] - >>> mstr[::-1] - 'cba' - >>> ml[::-1] - [3, 2, 1] - -.. - - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -21. 改变默认递归次数限制 ------------------------- - -上面才提到递归,大家都知道使用递归是有风险的,递归深度过深容易导致堆栈的溢出。如果你这字符串太长啦,使用递归方式反转,就会出现问题。 - -那到底,默认递归次数限制是多少呢? - -.. code:: python - - >>> import sys - >>> sys.getrecursionlimit() - 1000 - -可以查,当然也可以自定义修改次数,退出即失效。 +.. code:: shell -.. code:: python + $ pip install six requests prettytable jsonpatch configobj - >>> sys.setrecursionlimit(2000) - >>> sys.getrecursionlimit() - 2000 + # 默认还是安装在 python2.6 下 + $ yum install PyYAML -y -22. 一行代码实现FTP服务器 -------------------------- + # 将这些文件拷贝到 python2.7 目录下 + # 如果你不知道 python2.7 的目录,使用 import sys;print sys.path 就可以打印 + $ cd /usr/lib64/python2.6/site-packages + $ cp -r yaml/ /usr/local/lib/python2.7/site-packages/ + $ cp -p _yaml.so /usr/local/lib/python2.7/site-packages/ + $ cp -p PyYAML-3.10-py2.6.egg-info /usr/local/lib/python2.7/site-packages/ -搭建FTP,或者是搭建网络文件系统,这些方法都能够实现Linux的目录共享。但是FTP和网络文件系统的功能都过于强大,因此它们都有一些不够方便的地方。比如你想快速共享Linux系统的某个目录给整个项目团队,还想在一分钟内做到,怎么办?很简单,使用Python中的SimpleHTTPServer。 +执行一下 cloudinit 的几个命令,没有问题,任务就完成了。 -SimpleHTTPServer是Python -2自带的一个模块,是Python的Web服务器。它在Python -3已经合并到http.server模块中。具体例子如下,如不指定端口,则默认是8000端口。 +.. code:: shell -.. code:: python + $ cloud-init init -l + $ cloud-init init - # python2 - python -m SimpleHTTPServer 8888 +**参考文章** - # python3 - python3 -m http.server 8888 +- https://www.cnblogs.com/stonehe/p/7944366.html |image2| -SimpleHTTPServer有一个特性,如果待共享的目录下有index.html,那么index.html文件会被视为默认主页;如果不存在index.html文件,那么就会显示整个目录列表。 - -23. 让你晕头转向的 else 用法 ----------------------------- - -if else -用法可以说最基础的语法表达式之一,但是今天不是讲这个的,一定要讲点不一样的。 - -if else 早已烂大街,但可能有很多人都不曾见过 for else 和 try else -的用法。为什么说它曾让我晕头转向,因为它不像 if else -那么直白,非黑即白,脑子经常要想一下才能才反应过来代码怎么走。反正我是这样的。 - -先来说说,for else - -.. code:: python - - def check_item(source_list, target): - for item in source_list: - if item == target: - print("Exists!") - break - - else: - print("Does not exist") - -在往下看之前,你可以思考一下,什么情况下才会走 else。是循环被 -break,还是没有break? - -给几个例子,你体会一下。 - -.. code:: python - - check_item(["apple", "huawei", "oppo"], "oppo") - # Exists! - - check_item(["apple", "huawei", "oppo"], "vivo") - # Does not exist - -可以看出,没有被 break 的程序才会正常走else流程。 - -再来看看,try else 用法。 - -.. code:: python - - def test_try_else(attr1 = None): - try: - if attr1: - pass - else: - raise - except: - print("Exception occurred...") - else: - print("No Exception occurred...") - -同样来几个例子。当不传参数时,就抛出异常。 - -.. code:: python - - test_try_else() - # Exception occurred... - - test_try_else("ming") - # No Exception occurred... - -可以看出,没有 try 里面的代码块没有抛出异常的,会正常走else。 - -总结一下,for else 和 try else 相同,只要代码正常走下去不被 -break,不抛出异常,就可以走else。 - -24. 字符串里的缝隙是什么? --------------------------- - -在Python中求一个字符串里,某子字符(串)出现的次数。 - -大家都懂得使用 count() 函数,比如下面几个常规例子: - -.. code:: python - - >>> "aabb".count("a") - 2 - >>> "aabb".count("b") - 2 - >>> "aabb".count("ab") - 1 - -但是如果我想计算空字符串的个数呢? - -.. code:: python - - >>> "aabb".count("") - 5 - -**奇怪了吧?** - -不是应该返回 0 吗?怎么会返回 5? - -实际上,在 Python 看来,两个字符之间都是一个空字符,通俗的说就是缝隙。 - -因此 对于 ``aabb`` 这个字符串在 Python 来看应该是这样的 - -|image3| - -理解了这个“**缝隙**” 的概念后,以下这些就好理解了。 - -.. code:: python - - >>> (" " * 10).count("") - 11 - >>> - >>> "" in "" - True - >>> - >>> "" in "M" - True - -25. 正负得正,负负得正 ----------------------- - -从初中开始,我们就开始接触了\ ``负数`` ,并且都知道了\ ``负负得正`` -的思想。 - -Python 作为一门高级语言,它的编写符合人类的思维逻辑,包括 ``负负得正`` -。 - -.. code:: python - - >>> 5-3 - 2 - >>> 5--3 - 8 - >>> 5+-3 - 2 - >>> 5++3 - 8 - >>> 5---3 - 2 - -.. - - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -26. 数值与字符串的比较 ----------------------- - -在 Python2 中,数字可以与字符串直接比较。结果是数值永远比字符串小。 - -.. code:: python - - >>> 100000000 < "" - True - >>> 100000000 < "hello" - True - -但在 Python3 中,却不行。 - -.. code:: python - - >>> 100000000 < "" - TypeError: '<' not supported between instances of 'int' and 'str' - -27. 循环中的局部变量泄露 ------------------------- - -在Python 2中 x 的值在一个循环执行之后被改变了。 - -.. code:: python - - # Python2 - >>> x = 1 - >>> [x for x in range(5)] - [0, 1, 2, 3, 4] - >>> x - 4 - -不过在Python3 中这个问题已经得到解决了。 - -.. code:: python - - # Python3 - >>> x = 1 - >>> [x for x in range(5)] - [0, 1, 2, 3, 4] - >>> x - 1 - -28. 字典居然是可以排序的? --------------------------- - -在 Python 3.6 之前字典不可排序的思想,似乎已经根深蒂固。 - -.. code:: python - - # Python2.7.10 - >>> mydict = {str(i):i for i in range(5)} - >>> mydict - {'1': 1, '0': 0, '3': 3, '2': 2, '4': 4} - -假如哪一天,有人跟你说字典也可以是有序的,不要惊讶,那确实是真的 - -在 Python3.6 + -中字典已经是有序的,并且效率相较之前的还有所提升,具体信息你可以去查询相关资料。 - -.. code:: python - - # Python3.6.7 - >>> mydict = {str(i):i for i in range(5)} - >>> mydict - {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} - -29. 有趣但没啥用的 import 用法 ------------------------------- - -import 是 Python 导包的方式。 - -你知道 Python 中内置了一些很有(wu)趣(liao)的包吗? - -**Hello World** - -:: - - >>> import __hello__ - Hello World! - -**Python之禅** - -:: - - >>> import this - - The Zen of Python, by Tim Peters - - Beautiful is better than ugly. - Explicit is better than implicit. - Simple is better than complex. - Complex is better than complicated. - Flat is better than nested. - Sparse is better than dense. - Readability counts. - Special cases aren't special enough to break the rules. - Although practicality beats purity. - Errors should never pass silently. - Unless explicitly silenced. - In the face of ambiguity, refuse the temptation to guess. - There should be one-- and preferably only one --obvious way to do it. - Although that way may not be obvious at first unless you're Dutch. - Now is better than never. - Although never is often better than *right* now. - If the implementation is hard to explain, it's a bad idea. - If the implementation is easy to explain, it may be a good idea. - Namespaces are one honking great idea -- let's do more of those! - -**反地心引力漫画** - -在 cmd 窗口中导入\ ``antigravity`` - -:: - - >>> import antigravity - -就会自动打开一个网页。 |image4| - -30. 局部/全局变量傻傻分不清 ---------------------------- - -在开始讲之前,你可以试着运行一下下面这小段代码。 - -.. code:: python - - # demo.py - a = 1 - - def add(): - a += 1 - - add() - -看似没有毛病,但实则已经犯了一个很基础的问题,运行结果如下: - -.. code:: python - - $ python demo.py - Traceback (most recent call last): - File "demo.py", line 6, in - add() - File "demo.py", line 4, in add - a += 1 - UnboundLocalError: local variable 'a' referenced before assignment - -回顾一下,什么是局部变量?在非全局下定义声明的变量都是局部变量。 - -当程序运行到 ``a += 1`` 时,Python 解释器就认为在函数内部要给 ``a`` -这个变量赋值,当然就把 ``a`` 当做局部变量了,但是做为局部变量的 a -还没有被还没被定义。 - -因此报错是正常的。 - -理解了上面的例子,给你留个思考题。为什么下面的代码不会报错呢? - -.. code:: python - - $ cat demo.py - a = 1 - - def output(): - print(a) - - output() - - $ python demo.py - 1 - -.. - - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -31. 字母也玩起了障眼法 ----------------------- - -以下我分别在 Python2.7 和 Python 3.7 的 console 模式下,运行了如下代码。 - -**在Python 2.x 中** - -:: - - >>> valuе = 32 - File "", line 1 - valuе = 32 - ^ - SyntaxError: invalid syntax - -**在Python 3.x 中** - -:: - - >>> valuе = 32 - >>> value - 11 - -什么?没有截图你不信? - -|image5| - -如果你在自己的电脑上尝试一下,结果可能是这样的 - -|image6| - -**怎么又好了呢?** - -如果你想复现的话,请复制我这边给出的代码:\ ``valuе = 32`` - -**这是为什么呢?** - -原因在于,我上面使用的 value 变量名里的 ``е`` 又不是我们熟悉的 -``e``\ ,它是 Cyrillic(西里尔)字母。 - -:: - - >>> ord('е') # cyrillic 'e' (Ye) - 1077 - >>> ord('e') # latin 'e', as used in English and typed using standard keyboard - 101 - >>> 'е' == 'e' - False - -细思恐极,在这里可千万不要得罪同事们,万一离职的时候,对方把你项目里的 -``e`` 全局替换成 ``e``\ ,到时候你就哭去吧,肉眼根本看不出来嘛。 - -32. 字符串的分割技巧 --------------------- - -当我们对字符串进行分割时,且分割符是 -``\n``\ ,有可能会出现这样一个窘境: - -.. code:: python - - >>> str = "a\nb\n" - >>> print(str) - a - b - - >>> str.split('\n') - ['a', 'b', ''] - >>> - -会在最后一行多出一个元素,为了应对这种情况,你可以会多加一步处理。 - -但我想说的是,完成没有必要,对于这个场景,你可以使用 ``splitlines`` - -.. code:: python - - >>> str.splitlines() - ['a', 'b'] - -33. 嵌套上下文管理的另类写法 ----------------------------- - -当我们要写一个嵌套的上下文管理器时,可能会这样写 - -.. code:: python - - import contextlib - - @contextlib.contextmanager - def test_context(name): - print('enter, my name is {}'.format(name)) - - yield - - print('exit, my name is {}'.format(name)) - - with test_context('aaa'): - with test_context('bbb'): - print('========== in main ============') - -输出结果如下 - -.. code:: python - - enter, my name is aaa - enter, my name is bbb - ========== in main ============ - exit, my name is bbb - exit, my name is aaa - -除此之外,你可知道,还有另一种嵌套写法 - -.. code:: python - - with test_context('aaa'), test_context('bbb'): - print('========== in main ============') - -34. += 不等同于=+ ------------------ - -对列表 进行\ ``+=`` 操作相当于 extend,而使用 ``=+`` -操作是新增了一个列表。 - -因此会有如下两者的差异。 - -.. code:: python - - # =+ - >>> a = [1, 2, 3, 4] - >>> b = a - >>> a = a + [5, 6, 7, 8] - >>> a - [1, 2, 3, 4, 5, 6, 7, 8] - >>> b - [1, 2, 3, 4] - - - # += - >>> a = [1, 2, 3, 4] - >>> b = a - >>> a += [5, 6, 7, 8] - >>> a - [1, 2, 3, 4, 5, 6, 7, 8] - >>> b - [1, 2, 3, 4, 5, 6, 7, 8] - -35. 增量赋值的性能更好 ----------------------- - -诸如 ``+=`` 和 ``*=`` 这些运算符,叫做 增量赋值运算符。 - -这里使用用 += 举例,以下两种写法,在效果上是等价的。 - -:: - - # 第一种 - a = 1 ; a += 1 - - # 第二种 - a = 1; a = a + 1 - -``+=`` 其背后使用的魔法方法是 -\__iadd__,如果没有实现这个方法则会退而求其次,使用 \__add_\_ 。 - -这两种写法有什么区别呢? - -用列表举例 a += b,使用 \__add_\_ 的话就像是使用了a.extend(b),如果使用 -\__add_\_ 的话,则是 a = -a+b,前者是直接在原列表上进行扩展,而后者是先从原列表中取出值,在一个新的列表中进行扩展,然后再将新的列表对象返回给变量,显然后者的消耗要大些。 - -所以在能使用增量赋值的时候尽量使用它。 - - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -36. x == +x 吗? ----------------- - -在大多数情况下,这个等式是成立的。 - -:: - - >>> n1 = 10086 - >>> n2 = +n1 - >>> - >>> n1 == n2 - True - -什么情况下,这个等式会不成立呢? - -由于Counter的机制,\ ``+`` 用于两个 Counter -实例相加,而相加的结果如果元素的个数 ``<=`` 0,就会被丢弃。 - -:: - - >>> from collections import Counter - >>> ct = Counter('abcdbcaa') - >>> ct - Counter({'a': 3, 'b': 2, 'c': 2, 'd': 1}) - >>> ct['c'] = 0 - >>> ct['d'] = -2 - >>> - >>> ct - Counter({'a': 3, 'b': 2, 'c': 0, 'd': -2}) - >>> - >>> +ct - Counter({'a': 3, 'b': 2}) - -37. 如何将 print 内容输出到文件 -------------------------------- - -Python 3 中的 print -作为一个函数,由于可以接收更多的参数,所以功能变为更加强大。 - -比如今天要说的使用 print -将你要打印的内容,输出到日志文件中(但是我并不推荐使用它)。 - -.. code:: python - - >>> with open('test.log', mode='w') as f: - ... print('hello, python', file=f, flush=True) - >>> exit() - - $ cat test.log - hello, python - -38. site-packages和 dist-packages ---------------------------------- - -如果你足够细心,你会在你的机器上,有些包是安装在 **site-packages** -下,而有些包安装在 **dist-packages** 下。 - -**它们有什么区别呢?** - -一般情况下,你只见过 site-packages 这个目录,而你所安装的包也将安装在 -这个目录下。 - -而 dist-packages 其实是 debian 系的 Linux 系统(如 -Ubuntu)才特有的目录,当你使用 apt 去安装的 Python 包会使用 -dist-packages,而你使用 pip 或者 easy_install 安装的包还是照常安装在 -site-packages 下。 - -Debian 这么设计的原因,是为了减少不同来源的 Python 之间产生的冲突。 - -如何查找 Python 安装目录 - -.. code:: python - - >>> from distutils.sysconfig import get_python_lib - >>> print(get_python_lib()) - /usr/lib/python2.7/site-packages - -39. argument 和 parameter 的区别 --------------------------------- - -arguments 和 parameter -的翻译都是参数,在中文场景下,二者混用基本没有问题,毕竟都叫参数嘛。 - -但若要严格再进行区分,它们实际上还有各自的叫法 - -- parameter:形参(\ **formal - parameter**\ ),体现在函数内部,作用域是这个函数体。 -- argument :实参(\ **actual parameter**\ ),调用函数实际传递的参数。 - -举个例子,如下这段代码,\ ``"error"`` 为 argument,而 msg 为 -``parameter``\ 。 - -.. code:: python - - def output_msg(msg): - print(msg) - - output_msg("error") - -40. 简洁而优雅的链式比较 ------------------------- - -先给你看一个示例: - -.. code:: python - - >>> False == False == True - False - -你知道这个表达式为什么会会返回 False 吗? - -它的运行原理与下面这个类似,是不是有点头绪了: - -.. code:: python - - if 80 < score <= 90: - print("成绩良好") - -如果你还是不明白,那我再给你整个第一个例子的等价写法。 - -.. code:: python - - >>> False == False and False == True - False - -这个用法叫做链式比较。 - - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -41. 连接多个列表最极客的方式 ----------------------------- - -.. code:: python - - >>> a = [1,2] - >>> b = [3,4] - >>> c = [5,6] - >>> - >>> sum((a,b,c), []) - [1, 2, 3, 4, 5, 6] - -42. 另外 8 种连接列表的方式 ---------------------------- - -**1. 最直观的相加** - -使用 ``+`` 对多个列表进行相加,你应该懂,不多说了。 - -.. code:: python - - >>> list01 = [1,2,3] - >>> list02 = [4,5,6] - >>> list03 = [7,8,9] - >>> - >>> list01 + list02 + list03 - [1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> - -**2. 借助 itertools** - -itertools 在 Python -里有一个非常强大的内置模块,它专门用于操作可迭代对象。 - -在前面的文章中也介绍过,使用 ``itertools.chain()`` -函数先可迭代对象(在这里指的是列表)串联起来,组成一个更大的可迭代对象。 - -最后你再利用 list 将其转化为 列表。 - -.. code:: python - - >>> from itertools import chain - >>> list01 = [1,2,3] - >>> list02 = [4,5,6] - >>> list03 = [7,8,9] - >>> - >>> list(chain(list01, list02, list03)) - [1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> - -**3. 使用 \* 解包** - -使用 ``*`` 可以解包列表,解包后再合并。 - -示例如下: - -.. code:: python - - >>> list01 = [1,2,3] - >>> list02 = [4,5,6] - >>> - >>> [*list01, *list02] - [1, 2, 3, 4, 5, 6] - >>> - -**4. 使用 extend** - -在字典中,使用 update 可实现原地更新,而在列表中,使用 extend -可实现列表的自我扩展。 - -.. code:: python - - >>> list01 = [1,2,3] - >>> list02 = [4,5,6] - >>> - >>> list01.extend(list02) - >>> list01 - [1, 2, 3, 4, 5, 6] - -**5. 使用列表推导式** - -Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 - -那就是列表解析式,集合解析式和字典解析式,通常是 Python -发烧友的最爱,那么今天的主题:列表合并,列表推导式还能否胜任呢? - -当然可以,具体示例代码如下: - -.. code:: python - - >>> list01 = [1,2,3] - >>> list02 = [4,5,6] - >>> list03 = [7,8,9] - >>> - >>> [x for l in (list01, list02, list03) for x in l] - [1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> - -**6. 使用 heapq** - -heapq 是 Python 的一个标准模块,它提供了堆排序算法的实现。 - -该模块里有一个 merge 方法,可以用于合并多个列表,如下所示 - -.. code:: python - - >>> list01 = [1,2,3] - >>> list02 = [4,5,6] - >>> list03 = [7,8,9] - >>> - >>> from heapq import merge - >>> - >>> list(merge(list01, list02, list03)) - [1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> - -要注意的是,heapq.merge -除了合并多个列表外,它还会将合并后的最终的列表进行排序。 - -.. code:: python - - >>> list01 = [2,5,3] - >>> list02 = [1,4,6] - >>> list03 = [7,9,8] - >>> - >>> from heapq import merge - >>> - >>> list(merge(list01, list02, list03)) - [1, 2, 4, 5, 3, 6, 7, 9, 8] - >>> - -它的效果等价于下面这行代码: - -.. code:: python - - sorted(itertools.chain(*iterables)) - -如果你希望得到一个始终有序的列表,那请第一时间想到 -heapq.merge,因为它采用堆排序,效率非常高。但若你不希望得到一个排过序的列表,就不要使用它了。 - -**7. 借助魔法方法** - -有一个魔法方法叫 ``__add__``\ ,当我们使用第一种方法 list01 + list02 -的时候,内部实际上是作用在 ``__add__`` 这个魔法方法上的。 - -所以以下两种方法其实是等价的 - -.. code:: python - - >>> list01 = [1,2,3] - >>> list02 = [4,5,6] - >>> - >>> list01 + list02 - [1, 2, 3, 4, 5, 6] - >>> - >>> - >>> list01.__add__(list02) - [1, 2, 3, 4, 5, 6] - >>> - -借用这个魔法特性,我们可以 reduce -这个方法来对多个列表进行合并,示例代码如下 - -.. code:: python - - >>> list01 = [1,2,3] - >>> list02 = [4,5,6] - >>> list03 = [7,8,9] - >>> - >>> from functools import reduce - >>> reduce(list.__add__, (list01, list02, list03)) - [1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> - -**8. 使用 yield from** - -在 yield from 后可接一个可迭代对象,用于迭代并返回其中的每一个元素。 - -因此,我们可以像下面这样自定义一个合并列表的工具函数。 - -.. code:: python - - >>> list01 = [1,2,3] - >>> list02 = [4,5,6] - >>> list03 = [7,8,9] - >>> - >>> def merge(*lists): - ... for l in lists: - ... yield from l - ... - >>> list(merge(list01, list02, list03)) - [1, 2, 3, 4, 5, 6, 7, 8, 9] - >>> - -43. 在程序退出前执行代码的技巧 ------------------------------- - -使用 atexit 这个内置模块,可以很方便的注册退出函数。 - -不管你在哪个地方导致程序崩溃,都会执行那些你注册过的函数。 - -示例如下 - -|image7| - -如果\ ``clean()``\ 函数有参数,那么你可以不用装饰器,而是直接调用\ ``atexit.register(clean_1, 参数1, 参数2, 参数3='xxx')``\ 。 - -可能你有其他方法可以处理这种需求,但肯定比上不使用 atexit -来得优雅,来得方便,并且它很容易扩展。 - -但是使用 atexit 仍然有一些局限性,比如: - -- 如果程序是被你没有处理过的系统信号杀死的,那么注册的函数无法正常执行。 -- 如果发生了严重的 Python 内部错误,你注册的函数无法正常执行。 -- 如果你手动调用了\ ``os._exit()``\ ,你注册的函数无法正常执行。 - -44. 合并字典的 8 种方法 ------------------------ - -**1. 最简单的原地更新** - -字典对象内置了一个 update 方法,用于把另一个字典更新到自己身上。 - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> profile.update(ext_info) - >>> print(profile) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -如果想使用 update -这种最简单、最地道原生的方法,但又不想更新到自己身上,而是生成一个新的对象,那请使用深拷贝。 - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> from copy import deepcopy - >>> - >>> full_profile = deepcopy(profile) - >>> full_profile.update(ext_info) - >>> - >>> print(full_profile) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - >>> print(profile) - {"name": "xiaoming", "age": 27} - -**2. 先解包再合并字典** - -使用 ``**`` 可以解包字典,解包完后再使用 dict 或者 ``{}`` 就可以合并。 - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> full_profile01 = {**profile, **ext_info} - >>> print(full_profile01) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - >>> - >>> full_profile02 = dict(**profile, **ext_info) - >>> print(full_profile02) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -若你不知道 ``dict(**profile, **ext_info)`` 做了啥,你可以将它等价于 - -.. code:: python - - >>> dict((("name", "xiaoming"), ("age", 27), ("gender", "male"))) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -**3. 借助 itertools** - -在 Python 里有一个非常强大的内置模块,它专门用于操作可迭代对象。 - -正好我们字典也是可迭代对象,自然就可以想到,可以使用 -``itertools.chain()`` -函数先将多个字典(可迭代对象)串联起来,组成一个更大的可迭代对象,然后再使用 -dict 转成字典。 - -.. code:: python - - >>> import itertools - >>> - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> - >>> dict(itertools.chain(profile.items(), ext_info.items())) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -**4. 借助 ChainMap** - -如果可以引入一个辅助包,那我就再提一个, ``ChainMap`` 也可以达到和 -``itertools`` 同样的效果。 - -.. code:: python - - >>> from collections import ChainMap - >>> - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> dict(ChainMap(profile, ext_info)) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -使用 ChainMap -有一点需要注意,当字典间有重复的键时,只会取第一个值,排在后面的键值并不会更新掉前面的(使用 -itertools 就不会有这个问题)。 - -.. code:: python - - >>> from collections import ChainMap - >>> - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info={"age": 30} - >>> dict(ChainMap(profile, ext_info)) - {'name': 'xiaoming', 'age': 27} - -**5. 使用dict.items() 合并** - -在 Python 3.9 之前,其实就已经有 ``|`` -操作符了,只不过它通常用于对集合(set)取并集。 - -利用这一点,也可以将它用于字典的合并,只不过得绕个弯子,有点不好理解。 - -你得先利用 ``items`` 方法将 dict 转成 dict_items,再对这两个 dict_items -取并集,最后利用 dict 函数,转成字典。 - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> full_profile = dict(profile.items() | ext_info.items()) - >>> full_profile - {'gender': 'male', 'age': 27, 'name': 'xiaoming'} - -当然了,你如果嫌这样太麻烦,也可以简单点,直接使用 list -函数再合并(示例为 Python 3.x ) - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> dict(list(profile.items()) + list(ext_info.items())) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -若你在 Python 2.x 下,可以直接省去 list 函数。 - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> dict(profile.items() + ext_info.items()) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -**6. 最酷炫的字典解析式** - -Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 - -那就是列表解析式,集合解析式和字典解析式,通常是 Python -发烧友的最爱,那么今天的主题:字典合并,字典解析式还能否胜任呢? - -当然可以,具体示例代码如下: - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> {k:v for d in [profile, ext_info] for k,v in d.items()} - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -**7. Python 3.9 新特性** - -在 2 月份发布的 Python 3.9.04a -版本中,新增了一个抓眼球的新操作符操作符: ``|``\ , PEP584 -将它称之为合并操作符(Union Operator),用它可以很直观地合并多个字典。 - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> profile | ext_info - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - >>> - >>> ext_info | profile - {'gender': 'male', 'name': 'xiaoming', 'age': 27} - >>> - >>> - -除了 ``|`` 操作符之外,还有另外一个操作符 ``|=``\ ,类似于原地更新。 - -.. code:: python - - >>> ext_info |= profile - >>> ext_info - {'gender': 'male', 'name': 'xiaoming', 'age': 27} - >>> - >>> - >>> profile |= ext_info - >>> profile - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -看到这里,有没有涨姿势了,学了这么久的 Python -,没想到合并字典还有这么多的方法。本篇文章的主旨,并不在于让你全部掌握这 -7 -种合并字典的方法,实际在工作中,你只要选用一种最顺手的方式即可,但是在协同工作中,或者在阅读他人代码时,你不可避免地会碰到各式各样的写法,这时候你能下意识的知道这是在做合并字典的操作,那这篇文章就是有意义的。 - - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -45. 条件语句的七种写法 ----------------------- - -**第一种:原代码** - -这是一段非常简单的通过年龄判断一个人是否成年的代码,由于代码行数过多,有些人就不太愿意这样写,因为这体现不出自己多年的 -Python 功力。 - -.. code:: python - - if age > 18: - return "已成年" - else: - return "未成年" - -下面我列举了六种这段代码的变异写法,一个比一个还 6 -,单独拿出来比较好理解,放在工程代码里,没用过这些学法的人,一定会看得一脸懵逼,理解了之后,又不经意大呼:\ **卧槽,还可以这样写?**\ ,而后就要开始骂街了:\ **这是给人看的代码?** -(除了第一种之外) - -**第二种** - -语法: - -.. code:: python - - if else - -例子 - -.. code:: python - - >>> age1 = 20 - >>> age2 = 17 - >>> - >>> - >>> msg1 = "已成年" if age1 > 18 else "未成年" - >>> print msg1 - 已成年 - >>> - >>> msg2 = "已成年" if age2 > 18 else "未成年" - >>> print msg2 - 未成年 - >>> - -**第三种** - -语法 - -.. code:: python - - and or - -例子 - -.. code:: python - - >>> msg1 = age1 > 18 and "已成年" or "未成年" - >>> msg2 = "已成年" if age2 > 18 else "未成年" - >>> - >>> print(msg1) - 已成年 - >>> - >>> print(msg2) - 未成年 - -**第四种** - -语法 - -.. code:: python - - (, )[condition] - -例子 - -.. code:: python - - >>> msg1 = ("未成年", "已成年")[age1 > 18] - >>> print(msg1) - 已成年 - >>> - >>> - >>> msg2 = ("未成年", "已成年")[age2 > 18] - >>> print(msg2) - 未成年 - -**第五种** - -语法 - -.. code:: python - - (lambda: , lambda:)[]() - -例子 - -.. code:: python - - >>> msg1 = (lambda:"未成年", lambda:"已成年")[age1 > 18]() - >>> print(msg1) - 已成年 - >>> - >>> msg2 = (lambda:"未成年", lambda:"已成年")[age2 > 18]() - >>> print(msg2) - 未成年 - -**第六种** - -语法: - -.. code:: python - - {True: , False: }[] - -例子: - -.. code:: python - - >>> msg1 = {True: "已成年", False: "未成年"}[age1 > 18] - >>> print(msg1) - 已成年 - >>> - >>> msg2 = {True: "已成年", False: "未成年"}[age2 > 18] - >>> print(msg2) - 未成年 - -**第七种** - -语法 - -.. code:: python - - (() and (,) or (,))[0] - -例子 - -.. code:: python - - >>> msg1 = ((age1 > 18) and ("已成年",) or ("未成年",))[0] - >>> print(msg1) - 已成年 - >>> - >>> msg2 = ((age2 > 18) and ("已成年",) or ("未成年",))[0] - >>> print(msg2) - 未成年 - -以上代码,都比较简单,仔细看都能看懂,我就不做解释了。 - -看到这里,有没有涨姿势了,学了这么久的 Python -,这么多骚操作,还真是活久见。。这六种写法里,我最推荐使用的是第一种,自己也经常在用,简洁直白,代码行还少。而其他的写法虽然能写,但是不会用,也不希望在我余生里碰到会在公共代码里用这些写法的同事。 - - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -46. /usr/bin/env python 有什么用? ----------------------------------- - -我们经常会在别人的脚本或者项目的入口文件里看到第一行是下面这样 - -.. code:: shell - - #!/usr/bin/python - -或者这样 - -``sh e llsh el #!/usr/bin/env python`` - -这两者有什么区别呢? - -稍微接触过 linux 的人都知道 ``/usr/bin/python`` 就是我们执行 ``python`` -进入console 模式里的 ``python`` - -|image8| - -而当你在可执行文件头里使用 ``#!`` + ``/usr/bin/python`` -,意思就是说你得用哪个软件 (python)来执行这个文件。 - -那么加和不加有什么区别呢? - -不加的话,你每次执行这个脚本时,都得这样: ``python xx.py`` , - -|image9| - -有没有一种方式?可以省去每次都加 ``python`` 呢? - -当然有,你可以文件头里加上\ ``#!/usr/bin/python`` -,那么当这个文件有可执行权限 时,只直接写这个脚本文件,就像下面这样。 - -|image10| - -明白了这个后,再来看看 ``!/usr/bin/env python`` 这个 又是什么意思 ? - -当我执行 ``env python`` 时,自动进入了 python console 的模式。 - -|image11| - -这是为什么?和 直接执行 python 好像没什么区别呀 - -当你执行 ``env python`` 时,它其实会去 ``env | grep PATH`` 里(也就是 -/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin -)这几个路径里去依次查找名为python的可执行文件。 - -找到一个就直接执行,上面我们的 python 路径是在 ``/usr/bin/python`` -里,在 ``PATH`` 列表里倒数第二个目录下,所以当我在 ``/usr/local/sbin`` -下创建一个名字也为 python 的可执行文件时,就会执行 ``/usr/bin/python`` -了。 - -具体演示过程,你可以看下面。 - -|image12| - -那么对于这两者,我们应该使用哪个呢? - -个人感觉应该优先使用 ``#!/usr/bin/env python``\ ,因为不是所有的机器的 -python 解释器都是 ``/usr/bin/python`` 。 - -47. 让我爱不释手的用户环境 --------------------------- - -当你在机器上并没有 root 权限时,如何安装 Python 的第三方包呢? - -可以使用 ``pip install --user pkg`` -将你的包安装在你的用户环境中,该用户环境与全局环境并不冲突,并且多用户之间相互隔离,互不影响。 - -.. code:: shell - - # 在全局环境中未安装 requests - [root@localhost ~]$ pip list | grep requests - [root@localhost ~]$ su - wangbm - - # 由于用户环境继承自全局环境,这里也未安装 - [wangbm@localhost ~]$ pip list | grep requests - [wangbm@localhost ~]$ pip install --user requests - [wangbm@localhost ~]$ pip list | grep requests - requests (2.22.0) - [wangbm@localhost ~]$ - - # 从 Location 属性可发现 requests 只安装在当前用户环境中 - [wangbm@localhost ~]$ pip show requests - --- - Metadata-Version: 2.1 - Name: requests - Version: 2.22.0 - Summary: Python HTTP for Humans. - Home-page: http://python-requests.org - Author: Kenneth Reitz - Author-email: me@kennethreitz.org - Installer: pip - License: Apache 2.0 - Location: /home/wangbm/.local/lib/python2.7/site-packages - [wangbm@localhost ~]$ exit - logout - - # 退出 wangbm 用户,在 root 用户环境中发现 requests 未安装 - [root@localhost ~]$ pip list | grep requests - [root@localhost ~]$ - -48. 实现类似 defer 的延迟调用 ------------------------------ - -在 Golang 中有一种延迟调用的机制,关键字是 defer,例如下面的示例 - -.. code:: go - - import "fmt" - - func myfunc() { - fmt.Println("B") - } - - func main() { - defer myfunc() - fmt.Println("A") - } - -输出如下,myfunc 的调用会在函数返回前一步完成,即使你将 myfunc -的调用写在函数的第一行,这就是延迟调用。 - -:: - - A - B - -那么在 Python 中否有这种机制呢? - -当然也有,只不过并没有 Golang 这种简便。 - -在 Python 可以使用 **上下文管理器** 达到这种效果 - -.. code:: python - - import contextlib - - def callback(): - print('B') - - with contextlib.ExitStack() as stack: - stack.callback(callback) - print('A') - -输出如下 - -:: - - A - B - -49. 自带的缓存机制不用白不用 ----------------------------- - -缓存是一种将定量数据加以保存,以备迎合后续获取需求的处理方式,旨在加快数据获取的速度。 - -数据的生成过程可能需要经过计算,规整,远程获取等操作,如果是同一份数据需要多次使用,每次都重新生成会大大浪费时间。所以,如果将计算或者远程请求等操作获得的数据缓存下来,会加快后续的数据获取需求。 - -为了实现这个需求,Python 3.2 + -中给我们提供了一个机制,可以很方便的实现,而不需要你去写这样的逻辑代码。 - -这个机制实现于 functool 模块中的 lru_cache 装饰器。 - -.. code:: python - - @functools.lru_cache(maxsize=None, typed=False) - -参数解读: - -- maxsize:最多可以缓存多少个此函数的调用结果,如果为None,则无限制,设置为 - 2 的幂时,性能最佳 -- typed:若为 True,则不同参数类型的调用将分别缓存。 - -举个例子 - -.. code:: python - - from functools import lru_cache - - @lru_cache(None) - def add(x, y): - print("calculating: %s + %s" % (x, y)) - return x + y - - print(add(1, 2)) - print(add(1, 2)) - print(add(2, 3)) - -输出如下,可以看到第二次调用并没有真正的执行函数体,而是直接返回缓存里的结果 - -.. code:: shell - - calculating: 1 + 2 - 3 - 3 - calculating: 2 + 3 - 5 - -50. 重定向标准输出到日志 ------------------------- - -假设你有一个脚本,会执行一些任务,比如说集群健康情况的检查。 - -检查完成后,会把各服务的的健康状况以 JSON 字符串的形式打印到标准输出。 - -如果代码有问题,导致异常处理不足,最终检查失败,是很有可能将一些错误异常栈输出到标准错误或标准输出上。 - -由于最初约定的脚本返回方式是以 JSON -的格式输出,此时你的脚本却输出各种错误异常,异常调用方也无法解析。 - -如何避免这种情况的发生呢? - -我们可以这样做,把你的标准错误输出到日志文件中。 - -.. code:: python - - import contextlib - - log_file="/var/log/you.log" - - def you_task(): - pass - - @contextlib.contextmanager - def close_stdout(): - raw_stdout = sys.stdout - file = open(log_file, 'a+') - sys.stdout = file - - yield - - sys.stdout = raw_stdout - file.close() - - with close_stdout(): - you_task() - --------------- - -|image13| - .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190511165650.png -.. |image2| image:: http://image.python-online.cn/20190511165716.png -.. |image3| image:: http://image.iswbm.com/20200509172331.png -.. |image4| image:: http://image.python-online.cn/20190511165735.png -.. |image5| image:: http://image.iswbm.com/20200509122954.png -.. |image6| image:: http://image.iswbm.com/20200509123107.png -.. |image7| image:: http://image.iswbm.com/20200510112133.png -.. |image8| image:: http://image.python-online.cn/20200331184021.png -.. |image9| image:: http://image.python-online.cn/20200331185034.png -.. |image10| image:: http://image.python-online.cn/20200331184755.png -.. |image11| image:: http://image.python-online.cn/20200331185741.png -.. |image12| image:: http://image.python-online.cn/20200331190224.png -.. |image13| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20190831160317.png +.. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_11.md b/source/c01/c01_11.md index f2c9867..9d06f86 100644 --- a/source/c01/c01_11.md +++ b/source/c01/c01_11.md @@ -4,7 +4,7 @@ --- -## 一、正则表达式先导 +## 1. 正则表达式先导 ### 1.1 正则基础知识 @@ -72,7 +72,7 @@ re_match.group(2) `{n,m}?` :重复n到m次,但尽可能少重复 `{n,}?`: 重复n次以上,但尽可能少重复 -## 二、Python中的正则 +## 2. Python中的正则 在Python中,自带了re模块,这是专门用来做正则表达式的匹配的。 @@ -176,7 +176,7 @@ match.span() (0, 3) ``` -## 三、检验表达式 +## 3. 检验表达式 ### 3.1 校验数字 ``` 1. 数字:^[0-9]*$ diff --git a/source/c01/c01_11.rst b/source/c01/c01_11.rst old mode 100755 new mode 100644 index 0334634..7113dfd --- a/source/c01/c01_11.rst +++ b/source/c01/c01_11.rst @@ -5,8 +5,8 @@ -------------- -一、正则表达式先导 ------------------- +1. 正则表达式先导 +----------------- 1.1 正则基础知识 ~~~~~~~~~~~~~~~~ @@ -77,8 +77,8 @@ | ``{n,m}?`` :重复n到m次,但尽可能少重复 | ``{n,}?``\ : 重复n次以上,但尽可能少重复 -二、Python中的正则 ------------------- +2. Python中的正则 +----------------- 在Python中,自带了re模块,这是专门用来做正则表达式的匹配的。 @@ -190,8 +190,8 @@ start(),end(),end() match.span() (0, 3) -三、检验表达式 --------------- +3. 检验表达式 +------------- 3.1 校验数字 ~~~~~~~~~~~~ diff --git a/source/c01/c01_12.md b/source/c01/c01_12.md index 58e6dd7..9d0c0af 100644 --- a/source/c01/c01_12.md +++ b/source/c01/c01_12.md @@ -6,7 +6,7 @@ 初学计算机的人,肯定对众多字符编码感到头疼。为什么会那么多字符串编码? 这些内容是在去年整理的,现在重新整理下,发布在博客,搞懂字符串编码,这一篇文章足矣 -## 1.12.1 前言必知 +## 1. 前言必知 初学计算机的人,肯定对众多字符编码感到头疼。为什么会那么多字符串编码? @@ -15,7 +15,7 @@ bit,位,一个bit可以表示两个数字,0和1 byte,字节,一个byte由8个bit表示,一个byte表示的数字区间[0,255] -## 1.12.2 ASCII编码 +## 2. ASCII编码 ### 原始ASCII编码 @@ -31,7 +31,7 @@ byte,字节,一个byte由8个bit表示,一个byte表示的数字区间[0,2 ![原始ASCII编码](https://ooo.0o0.ooo/2017/08/02/59815bd96dd8a.gif) -## 1.12.3 ANSI标准 +## 3. ANSI标准 >ANSI:美国国家标准学会(AMERICAN NATIONAL STANDARDS INSTITUTE) 随着计算机的全世界普及,世界各国都用的上计算机了。但是每个国家都有各自的语言,而传统的计算机只支持ASCII编码表的字符。这对于不以英语为母语的人来说,使用计算机是非常吃力的。为了解决这个问题,各个国家的人都出了一套收录自己文字的字符编码(当然包含了ASCII里所有字符)。 @@ -48,7 +48,7 @@ byte,字节,一个byte由8个bit表示,一个byte表示的数字区间[0,2 * 在日文Windows中,ANSI编码是`Shift_JIS` * ... -## 1.12.4 Unicode编码 +## 4. Unicode编码 上节讲到各国都有了自己的编码,已经可以正常使用电脑了。 但是随着国际交流的日益频繁和迫切需要,我们中国人也要和美国人进行信息交流,美国人还要和日本人进行信息交流。假设,我们把一篇中文论文发到网上,美国人在用自己的计算机查看这个网页的时候,由于本地计算机不支持GB2312,结果无法显示正确信息,或者乱码。你会说,那很简单啊,让美国人在电脑上装上GB2312编码不就OK。真的OK吗?世界上那么多国家,那么多语言,那么多ANSI编码,都装上是不是要疯了。好吧,假如你真的不厌其烦的装上了,再假设,有一个中国人,他不仅会说汉语,还会说日语。他发表了一篇既有汉语也有日文的文章,而美国人在看这篇文章的时候,是用GB2312来解码呢,还是用Shift_JIS来解码呢?无论用哪个解码都会出现乱码的情况。 @@ -61,7 +61,7 @@ Unicode是由统一码联盟(英语:The Unicode Consortium),一个统筹 Unicode至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为2017年6月20日公布的10.0.0,已经收入超过十万个字符(第十万个字符在2005年获采纳)。[Unicode-维基百科](https://zh.wikipedia.org/wiki/Unicode) -## 1.12.5 UTF-8编码 +## 5. UTF-8编码 到目前为止,世界各国人民,都能愉快的无语言障碍的使用计算机了。 但是随着信息化时代的来临,人们越来越追求资源的传输速度和硬盘的存储效率。 @@ -85,7 +85,7 @@ UTF-8(UTF:Unicode TransferFormat,即把Unicode转做某种格式的意思) 当在txt输入输入'联通',保存再次打开就乱码,输入'你好联通'就不会出现这个情况。 ![](https://i.loli.net/2017/08/02/59816d652aeb9.png) -## 1.12.6 编码之于Python +## 6. 编码之于Python **Python2** Python2默认是使用ASCII编码,这也是出现编码问题的罪魁祸首。但这也不怪Python,在Python诞生的时候,Unicode还没出现。 @@ -113,7 +113,7 @@ print '\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8') 在Python3中,已经默认使用Unicode编码了。解决了很多编码的问题。 如果py文件中,含有中文还是得在文件头出加入 `# coding=utf-8` -## 1.12.7 扩展阅读 +## 7. 扩展阅读 中文编码的发展 GB2312-> GBK -> GB18030 diff --git a/source/c01/c01_12.rst b/source/c01/c01_12.rst old mode 100755 new mode 100644 index 540dd6e..695d245 --- a/source/c01/c01_12.rst +++ b/source/c01/c01_12.rst @@ -8,8 +8,8 @@ 初学计算机的人,肯定对众多字符编码感到头疼。为什么会那么多字符串编码? 这些内容是在去年整理的,现在重新整理下,发布在博客,搞懂字符串编码,这一篇文章足矣 -1.12.1 前言必知 ---------------- +1. 前言必知 +----------- 初学计算机的人,肯定对众多字符编码感到头疼。为什么会那么多字符串编码? @@ -18,8 +18,8 @@ bit,位,一个bit可以表示两个数字,0和1 byte,字节,一个byte由8个bit表示,一个byte表示的数字区间[0,255] -1.12.2 ASCII编码 ----------------- +2. ASCII编码 +------------ 原始ASCII编码 ~~~~~~~~~~~~~ @@ -44,8 +44,8 @@ byte,字节,一个byte由8个bit表示,一个byte表示的数字区间[0,2 原始ASCII编码 -1.12.3 ANSI标准 ---------------- +3. ANSI标准 +----------- ANSI:美国国家标准学会(AMERICAN NATIONAL STANDARDS INSTITUTE) @@ -63,8 +63,8 @@ byte,字节,一个byte由8个bit表示,一个byte表示的数字区间[0,2 - 在日文Windows中,ANSI编码是\ ``Shift_JIS`` - … -1.12.4 Unicode编码 ------------------- +4. Unicode编码 +-------------- 上节讲到各国都有了自己的编码,已经可以正常使用电脑了。 但是随着国际交流的日益频繁和迫切需要,我们中国人也要和美国人进行信息交流,美国人还要和日本人进行信息交流。假设,我们把一篇中文论文发到网上,美国人在用自己的计算机查看这个网页的时候,由于本地计算机不支持GB2312,结果无法显示正确信息,或者乱码。你会说,那很简单啊,让美国人在电脑上装上GB2312编码不就OK。真的OK吗?世界上那么多国家,那么多语言,那么多ANSI编码,都装上是不是要疯了。好吧,假如你真的不厌其烦的装上了,再假设,有一个中国人,他不仅会说汉语,还会说日语。他发表了一篇既有汉语也有日文的文章,而美国人在看这篇文章的时候,是用GB2312来解码呢,还是用Shift_JIS来解码呢?无论用哪个解码都会出现乱码的情况。 @@ -78,8 +78,8 @@ Consortium),一个统筹统一码(Unicode)发展的非营利机构,其 Unicode至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为2017年6月20日公布的10.0.0,已经收入超过十万个字符(第十万个字符在2005年获采纳)。\ `Unicode-维基百科 `__ -1.12.5 UTF-8编码 ----------------- +5. UTF-8编码 +------------ | 到目前为止,世界各国人民,都能愉快的无语言障碍的使用计算机了。 | 但是随着信息化时代的来临,人们越来越追求资源的传输速度和硬盘的存储效率。 @@ -103,8 +103,8 @@ TransferFormat,即把Unicode转做某种格式的意思)应运而生 当在txt输入输入’联通’,保存再次打开就乱码,输入’你好联通’就不会出现这个情况。 |image2| -1.12.6 编码之于Python ---------------------- +6. 编码之于Python +----------------- **Python2** Python2默认是使用ASCII编码,这也是出现编码问题的罪魁祸首。但这也不怪Python,在Python诞生的时候,Unicode还没出现。 @@ -132,8 +132,8 @@ Python2默认是使用ASCII编码,这也是出现编码问题的罪魁祸首 在Python3中,已经默认使用Unicode编码了。解决了很多编码的问题。 如果py文件中,含有中文还是得在文件头出加入 ``# coding=utf-8`` -1.12.7 扩展阅读 ---------------- +7. 扩展阅读 +----------- 中文编码的发展 GB2312-> GBK -> GB18030 diff --git a/source/c01/c01_13.md b/source/c01/c01_13.md index ec0ee13..8cb56c2 100644 --- a/source/c01/c01_13.md +++ b/source/c01/c01_13.md @@ -1,129 +1,353 @@ -# 1.13 Python几个高阶函数 +# 1.13 别再使用 pprint 打印字典了 ![](http://image.iswbm.com/20200602135014.png) ---- +## 1. 吐槽问题 -## 1.13.1 lambda 表达式 +pprint 你应该很熟悉了吧? -匿名函数(英语:anonymous function)是指一类无需定义标识符(函数名)的函数。通俗来说呢,就是它可以让我们的函数,可以不需要函数名。 +随便在搜索引擎上搜索如何打印漂亮的字典或者格式化字符串时,大部分人都会推荐你使用这货 。 -正常情况下,我们定义一个函数,使用的是 `def` 关键字,而当你学会使用匿名函数后,替代 `def` 的是 `lambda`。 +比如这下面这个 json 字符串或者说字典(我随便在网上找的),如果不格式化美化一下,根本无法阅读。 -这边使用`def` 和 `lambda` 分别举个例子,你很快就能理解。 +```json +[{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017 你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"斗鱼271934 走过路过不要错过,这里有最好的鸡儿"}] ``` -def mySum(x, y): - return x+y -mySum(2, 3) -# 5 -(lambda x, y: x+y)(2, 4) -# 6 +如果你不想看到一堆密密麻麻的字,那就使用大伙都极力推荐的 pprint 看下什么效果(以下在 Python 2 中演示,Python 3 中是不一样的效果)。 + +```python +>>> info=[{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017 你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"斗鱼271934 走过路过不要错过,这里有最好的鸡儿"}] +>>> +>>> from pprint import pprint +>>> pprint(info) +[{'des': '2011-2017 \xe4\xbd\xa0\xe7\x9a\x84\xe9\x93\x81\xe5\xa4\xb4\xe5\xa8\x83\xe4\xb8\x80\xe7\x9b\xb4\xe5\x9c\xa8\xe8\xbf\x99\xe5\x84\xbf\xe3\x80\x82\xe4\xb8\xad\xe5\x9b\xbd\xe6\x9c\x80\xe5\xa4\xa7\xe7\x9a\x84\xe5\xae\x9e\xe5\x90\x8d\xe5\x88\xb6SNS\xe7\xbd\x91\xe7\xbb\x9c\xe5\xb9\xb3\xe5\x8f\xb0\xef\xbc\x8c\xe5\xab\xa9\xe5\xa4\xb4\xe9\x9d\x92', + 'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk', + 'iconUrl': 'app/com.renren.mobile.android/icon.jpg', + 'id': 1580615, + 'name': '\xe7\x9a\xae\xe7\x9a\x84\xe5\x98\x9b', + 'packageName': 'com.renren.mobile.android', + 'size': 21803987, + 'stars': 2}, + {'des': '\xe6\x96\x97\xe9\xb1\xbc271934 \xe8\xb5\xb0\xe8\xbf\x87\xe8\xb7\xaf\xe8\xbf\x87\xe4\xb8\x8d\xe8\xa6\x81\xe9\x94\x99\xe8\xbf\x87\xef\xbc\x8c\xe8\xbf\x99\xe9\x87\x8c\xe6\x9c\x89\xe6\x9c\x80\xe5\xa5\xbd\xe7\x9a\x84\xe9\xb8\xa1\xe5\x84\xbf', + 'downloadUrl': 'app/com.ct.client/com.ct.client.apk', + 'iconUrl': 'app/com.ct.client/icon.jpg', + 'id': 1540629, + 'name': '\xe4\xb8\x8d\xe5\xad\x98\xe5\x9c\xa8\xe7\x9a\x84', + 'packageName': 'com.ct.client', + 'size': 4794202, + 'stars': 2}] ``` -从上面的示例,我们可以看到匿名函数直接运行,省下了很多行的代码,有没有? -接下来,我们的仔细看一下它的用法 +好像有点效果,真的是 “神器”呀。 -带 if/else -``` ->>>( lambda x, y: x if x < y else y )( 1, 2 ) -1 -``` -嵌套函数 -``` ->>>( lambda x: ( lambda y: ( lambda z: x + y + z )( 1 ) )( 2 ) )( 3 ) -6 -``` -递归函数 -``` ->>> func = lambda n:1 if n == 0 else n * func(n-1) ->>> func(5) -120 +但是你告诉我, **\xe4\xbd\xa0\xe7\x9a** 这些是什么玩意?本来想提高可读性的,现在变成完全不可读了。 + +好在我懂点 Python 2 的编码,知道 Python 2 中默认(不带u)的字符串格式都是 str 类型,也是 bytes 类型,它是以 byte 存储的。 + +行吧,好像是我错了,我改了下,使用 unicode 类型来定义中文字符串吧。 + +```python +>>> info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] +>>> +>>> from pprint import pprint +>>> pprint(info) +[{'des': u'2011-2017\u4f60\u7684\u94c1\u5934\u5a03\u4e00\u76f4\u5728\u8fd9\u513f\u3002\u4e2d\u56fd\u6700\u5927\u7684\u5b9e\u540d\u5236SNS\u7f51\u7edc\u5e73\u53f0\uff0c\u5ae9\u5934\u9752', + 'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk', + 'iconUrl': 'app/com.renren.mobile.android/icon.jpg', + 'id': 1580615, + 'name': u'\u76ae\u7684\u561b', + 'packageName': 'com.renren.mobile.android', + 'size': 21803987, + 'stars': 2}, + {'des': u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u6700\u597d\u7684\u9e21\u513f', + 'downloadUrl': 'app/com.ct.client/com.ct.client.apk', + 'iconUrl': 'app/com.ct.client/icon.jpg', + 'id': 1540629, + 'name': u'\u4e0d\u5b58\u5728\u7684', + 'packageName': 'com.ct.client', + 'size': 4794202, + 'stars': 2}] ``` -或者 + +确实是有好点了,但是看到下面这些,我崩溃了,我哪里知道这是什么鬼,难道是我太菜了吗?当我是计算机呀? + ``` ->>> f = lambda func, n: 1 if n == 0 else n * func( func, n - 1 ) ->>> f(f,4) -24 +u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u6700\u597d\u7684\u9e21\u513f' ``` -从以上示例来看,lambda 表达式和常规的函数相比,写法比较怪异,可读性相对较差。除了可以直接运行之外,好像并没有其他较为突出的功能,为什么在今天我们要介绍它呢? +除此之外,我们知道 json 的严格要求必须使用 **双引号**,而我定义字典时,也使用了双引号了,为什么打印出来的为什么是 **单引号**?我也太难了吧,我连自己的代码都无法控制了吗? -首先我们要知道 lambda 是一个表达式,而不是一个语句。正因为这个特点,我们可以在一些特殊的场景中去使用它。具体是什么场景呢?接下来我们会介绍到几个非常好用的内置函数。 +到这里,我们知道了 pprint 带来的两个问题: -## 1.13.2 map 函数 +1. 没法在 Python 2 下正常打印中文 +2. 没法输出 JSON 标准格式的格式化内容(双引号) -map 函数,它接收两个参数,第一个参数是一个函数对象(当然也可以是一个lambda表达式),第二个参数是一个序列。 +## 2. 解决问题 -它可以实现怎样的功能呢,我举个例子你就明白了。 -``` ->>> map(lambda x: x*2, [1,2,3,4,5]) -[2, 4, 6, 8, 10] -``` -可以很清楚地看到,它可以将后面序列中的每一个元素做为参数传入lambda中。 +### 打印中文 + +如果你是在 Python 3 下使用,你会发现中文是可以正常显示的。 + +```python +# Python3.7 +>>> info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] +>>> +>>> from pprint import pprint +>>> pprint(info) +[{'des': '2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青', + 'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk', + 'iconUrl': 'app/com.renren.mobile.android/icon.jpg', + 'id': 1580615, + 'name': '皮的嘛', + 'packageName': 'com.renren.mobile.android', + 'size': 21803987, + 'stars': 2}, + {'des': '斗鱼271934走过路过不要错过,这里有最好的鸡儿', + 'downloadUrl': 'app/com.ct.client/com.ct.client.apk', + 'iconUrl': 'app/com.ct.client/icon.jpg', + 'id': 1540629, + 'name': '不存在的', + 'packageName': 'com.ct.client', + 'size': 4794202, + 'stars': 2}] +>>> -当我们不使用 map 函数时,你也许会这样子写。 -``` -mylist=[] -for i in [1,2,3,4,5]: - mylist.append(i*2) ``` -## 1.13.3 filter 函数 +但是很多时候(在公司的一些服务器)你无法选择自己使用哪个版本的 Python,本来我可以选择不用的,因为有更好的替代方案(**这个后面会讲**)。 -filter 函数,和 map 函数相似。同样也是接收两个参数,一个lambda 表达式,一个序列。它会遍历后面序列中每一个元素,并将其做为参数传入lambda表达式中,当表达式返回 True,则元素会被保留下来,当表达式返回 False ,则元素会被丢弃。 +但是我出于猎奇,正好前两天不是写过一篇关于 编码 的文章吗,我自认为自己对于 编码还是掌握比较熟练的,就想着来解决一下这个问题。 -下面这个例子,将过滤出一个列表中小于0的元素。 -``` ->>>filter(lambda x: x < 0, range(-5, 5)) -[-5, -4, -3, -2, -1] -``` +索性就来看下 pprint 的源代码,还真被我找到了解决方法,如果你也想挑战一下,不防在这里停住,自己研究一下如何实现,我相信对你阅读源码会有帮助。 -## 1.13.4 reduce 函数 +**以下是我的解决方案,供你参考**: -reduce 函数,也是类似的。它的作用是先对序列中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 lambda 函数运算,将其得到的结果再与第四个元素进行运算,以此类推下去直到后面没有元素了。 +写一个自己的 printer 对象,继承自 PrettyPrinter (pprint 使用的printer) -![reduce 逻辑演示](https://ws1.sinaimg.cn/large/8f640247gy1fyx6i8q3anj208c04u3yu.jpg) -这边举个例子你也就明白了。 -``` ->>>reduce(lambda x,y: x+y, [1,2,3,4,5]) -15 -``` -它的运算过程分解一下是这样的。 -``` -1+2=3 -3+3=6 -6+4+10 -10+5=15 +并且复写 format 方法,判断传进来的字符串对象是否 str 类型,如果不是 str 类型,而是 unicode 类型,就用 uft8 编码成 str 类型。 + +```python +# coding: utf-8 +from pprint import PrettyPrinter + +# 继承 PrettyPrinter,复写 format 方法 +class MyPrettyPrinter(PrettyPrinter): + def format(self, object, context, maxlevels, level): + if isinstance(object, unicode): + return (object.encode('utf8'), True, False) + return PrettyPrinter.format(self, object, context, maxlevels, level) + +info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] + +MyPrettyPrinter().pprint(info) ``` -## 1.13.5 注意点 +输出如下,已经解决了中文的显示问题: -以上几个函数,熟练的掌握它们的写法,可以让我们的代码看起来更加的 Pythonic ,在某一程度上代码看起来更加的简洁。 +![](http://image.iswbm.com/20200507171451.png) -如果你是新手呢,你需要注意的是,以上示例是在 Python2.x 环境下演示的。而在 Python3.x 中,却有所不同,你可以自己尝试一下。 +### 打印双引号 -这里总结一下: +解决了中文问题后,再来看看如何让 pprint 打印双引号。 -第一点,map 和 filter 函数返回的都不再是一个列表,而是一个迭代器对象。这里以map为例 +在实例化 PrettyPrinter 对象的时候,可以接收一个 stream 对象,它表示你要将内容输出到哪里,默认是使用 sys.stdout 这个 stream,也就是标准输出。 + +现在我们要修改输出的内容,也就是将输出的单引号替换成双引号。 + +那我们完全可以自己定义一个 stream 类型的对象,该对象不需要继承任何父类,只要你实现 write 方法就可以。 + +有了思路,就可以开始写代码了,如下: + +```python +# coding: utf-8 +from pprint import PrettyPrinter + +class MyPrettyPrinter(PrettyPrinter): + def format(self, object, context, maxlevels, level): + if isinstance(object, unicode): + return (object.encode('utf8'), True, False) + return PrettyPrinter.format(self, object, context, maxlevels, level) + +class MyStream(): + def write(self, text): + print text.replace('\'', '"') + +info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] +MyPrettyPrinter(stream=MyStream()).pprint(info) ``` ->>> map_obj = map(lambda x: x*2, [1,2,3,4,5]) ->>> from collections.abc import Iterator ->>> isinstance(map_obj, Iterator) -True ->>> next(map_obj) + +尝试执行了下,我的天,怎么是这样子的。 + +```json +[ +{ +"des" +: +2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青 +, + "downloadUrl": +"app/com.renren.mobile.android/com.renren.mobile.android.apk" +, + "iconUrl": +"app/com.renren.mobile.android/icon.jpg" +, + "id": +1580615 +, + "name": +皮的嘛 +, + "packageName": +"com.renren.mobile.android" +, + "size": +21803987 +, + "stars": +2 +} +, + +{ +"des" +: +斗鱼271934走过路过不要错过,这里有最好的鸡儿 +, + "downloadUrl": +"app/com.ct.client/com.ct.client.apk" +, + "iconUrl": +"app/com.ct.client/icon.jpg" +, + "id": +1540629 +, + "name": +不存在的 +, + "packageName": +"com.ct.client" +, + "size": +4794202 +, + "stars": 2 ->>> list(map_obj) -[4, 6, 8, 10] +} +] ``` -第二点,reduce 不可以直接调用,而是要先导入才能使用, +经过一番研究,才知道是因为 print 函数默认会将打印的内容后面加个 **换行符**。 + +那如何将使 print 函数打印的内容,不进行换行呢? + +方法很简单,但是我相信很多人都不知道,只要在 print 的内容后加一个 **逗号** 就行。 + +就像下面这样。 + +![](http://image.iswbm.com/20200507174459.png) + +知道了问题所在,再修改下代码 + +```python +# coding: utf-8 +from pprint import PrettyPrinter + +class MyPrettyPrinter(PrettyPrinter): + def format(self, object, context, maxlevels, level): + if isinstance(object, unicode): + return (object.encode('utf8'), True, False) + return PrettyPrinter.format(self, object, context, maxlevels, level) + +class MyStream(): + def write(self, text): + print text.replace('\'', '"'), + +info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] + +MyPrettyPrinter(stream=MyStream()).pprint(info) ``` -from functools import reduce + +终于成功了,太不容易了吧。 + +![](http://image.iswbm.com/20200507174802.png) + +## 3. 何必折腾 + +通过上面的一番折腾,我终于实现了我 **梦寐以求** 的需求。 + +代价就是我整整花费了两个小时,才得以实现,而对于小白来说,可能没有信心,也没有耐心去做这样的事情。 + +**所以我想说的是,Python 2 下的 pprint ,真的不要再用了**。 + +为什么我要用这么 说,因为明明有更好的替代品,人生苦短,既然用了 Python ,当然是怎么简单怎么来咯,何必为难自己呢,一行代码可以解决的事情,偏偏要去写两个类,那不是自讨苦吃吗?(我这是在骂自己吗? + +如果你愿意抛弃 pprint ,那我推荐你用 json.dumps ,我保证你再也不想用 pprint 了。 + +### 打印中文 + +其实无法打印中文,是 Python 2 引来的大坑,并不能全怪 pprint 。 + +但是同样的问题,在 json.dumps 这里,却只要加个参数就好了,可比 pprint 简单得不要太多。 + +具体的代码示例如下: + +```python +>>> info = [{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] +>>> +>>> import json +>>> +>>> +>>> print json.dumps(info, indent=4, ensure_ascii=False) +[ + { + "downloadUrl": "app/com.renren.mobile.android/com.renren.mobile.android.apk", + "iconUrl": "app/com.renren.mobile.android/icon.jpg", + "name": "皮的嘛", + "stars": 2, + "packageName": "com.renren.mobile.android", + "des": "2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青", + "id": 1580615, + "size": 21803987 + }, + { + "downloadUrl": "app/com.ct.client/com.ct.client.apk", + "iconUrl": "app/com.ct.client/icon.jpg", + "name": "不存在的", + "stars": 2, + "packageName": "com.ct.client", + "des": "斗鱼271934走过路过不要错过,这里有最好的鸡儿", + "id": 1540629, + "size": 4794202 + } +] +>>> ``` +json.dumps 的关键参数有两个: + +- **indent=4**:以 4 个空格缩进单位 +- **ensure_ascii=False**:接收非 ASCII 编码的字符,这样才能使用中文 + +与 pprint 相比 json.dumps 可以说完胜: + +1. 两个参数就能实现所有我的需求(打印中文与双引号) +2. 就算在 Python 2 下,使用中文也不需要用 `u'中文'` 这种写法 +3. Python2 和 Python3 的写法完全一致,对于这一点不需要考虑兼容问题 + +## 4. 总结一下 + +本来很简单的一个观点,我为了证明 pprint 实现那两个需求有多么困难,花了很多的时间去研究了 pprint 的源码(各种处理其实还是挺复杂的),不过好在最后也能有所收获。 + +本文的分享就到这里,阅读本文,我认为你可以获取到三个知识点 + +1. 核心观点:Python2 下不要再使用 pprint +2. 若真要使用,且有和一样的改造需求,可以参考我的实现 +3. Python 2 中的 print 语句后居然可以加 逗号 + +以上。希望此文能对你有帮助。 ---- -![](http://image.iswbm.com/20200607174235.png) +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_13.rst b/source/c01/c01_13.rst old mode 100755 new mode 100644 index e5f826c..99db502 --- a/source/c01/c01_13.rst +++ b/source/c01/c01_13.rst @@ -1,167 +1,391 @@ -1.13 Python几个高阶函数 -======================= +1.13 别再使用 pprint 打印字典了 +=============================== |image0| --------------- +1. 吐槽问题 +----------- + +pprint 你应该很熟悉了吧? + +随便在搜索引擎上搜索如何打印漂亮的字典或者格式化字符串时,大部分人都会推荐你使用这货 +。 + +比如这下面这个 json +字符串或者说字典(我随便在网上找的),如果不格式化美化一下,根本无法阅读。 + +.. code:: json + + [{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017 你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"斗鱼271934 走过路过不要错过,这里有最好的鸡儿"}] + +如果你不想看到一堆密密麻麻的字,那就使用大伙都极力推荐的 pprint +看下什么效果(以下在 Python 2 中演示,Python 3 中是不一样的效果)。 + +.. code:: python + + >>> info=[{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017 你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"斗鱼271934 走过路过不要错过,这里有最好的鸡儿"}] + >>> + >>> from pprint import pprint + >>> pprint(info) + [{'des': '2011-2017 \xe4\xbd\xa0\xe7\x9a\x84\xe9\x93\x81\xe5\xa4\xb4\xe5\xa8\x83\xe4\xb8\x80\xe7\x9b\xb4\xe5\x9c\xa8\xe8\xbf\x99\xe5\x84\xbf\xe3\x80\x82\xe4\xb8\xad\xe5\x9b\xbd\xe6\x9c\x80\xe5\xa4\xa7\xe7\x9a\x84\xe5\xae\x9e\xe5\x90\x8d\xe5\x88\xb6SNS\xe7\xbd\x91\xe7\xbb\x9c\xe5\xb9\xb3\xe5\x8f\xb0\xef\xbc\x8c\xe5\xab\xa9\xe5\xa4\xb4\xe9\x9d\x92', + 'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk', + 'iconUrl': 'app/com.renren.mobile.android/icon.jpg', + 'id': 1580615, + 'name': '\xe7\x9a\xae\xe7\x9a\x84\xe5\x98\x9b', + 'packageName': 'com.renren.mobile.android', + 'size': 21803987, + 'stars': 2}, + {'des': '\xe6\x96\x97\xe9\xb1\xbc271934 \xe8\xb5\xb0\xe8\xbf\x87\xe8\xb7\xaf\xe8\xbf\x87\xe4\xb8\x8d\xe8\xa6\x81\xe9\x94\x99\xe8\xbf\x87\xef\xbc\x8c\xe8\xbf\x99\xe9\x87\x8c\xe6\x9c\x89\xe6\x9c\x80\xe5\xa5\xbd\xe7\x9a\x84\xe9\xb8\xa1\xe5\x84\xbf', + 'downloadUrl': 'app/com.ct.client/com.ct.client.apk', + 'iconUrl': 'app/com.ct.client/icon.jpg', + 'id': 1540629, + 'name': '\xe4\xb8\x8d\xe5\xad\x98\xe5\x9c\xa8\xe7\x9a\x84', + 'packageName': 'com.ct.client', + 'size': 4794202, + 'stars': 2}] + +好像有点效果,真的是 “神器”呀。 + +但是你告诉我, +**:raw-latex:`\xe`4:raw-latex:`\xbd`:raw-latex:`\xa`0:raw-latex:`\xe`7:raw-latex:`\x`9a** +这些是什么玩意?本来想提高可读性的,现在变成完全不可读了。 + +好在我懂点 Python 2 的编码,知道 Python 2 +中默认(不带u)的字符串格式都是 str 类型,也是 bytes 类型,它是以 byte +存储的。 + +行吧,好像是我错了,我改了下,使用 unicode 类型来定义中文字符串吧。 + +.. code:: python + + >>> info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] + >>> + >>> from pprint import pprint + >>> pprint(info) + [{'des': u'2011-2017\u4f60\u7684\u94c1\u5934\u5a03\u4e00\u76f4\u5728\u8fd9\u513f\u3002\u4e2d\u56fd\u6700\u5927\u7684\u5b9e\u540d\u5236SNS\u7f51\u7edc\u5e73\u53f0\uff0c\u5ae9\u5934\u9752', + 'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk', + 'iconUrl': 'app/com.renren.mobile.android/icon.jpg', + 'id': 1580615, + 'name': u'\u76ae\u7684\u561b', + 'packageName': 'com.renren.mobile.android', + 'size': 21803987, + 'stars': 2}, + {'des': u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u6700\u597d\u7684\u9e21\u513f', + 'downloadUrl': 'app/com.ct.client/com.ct.client.apk', + 'iconUrl': 'app/com.ct.client/icon.jpg', + 'id': 1540629, + 'name': u'\u4e0d\u5b58\u5728\u7684', + 'packageName': 'com.ct.client', + 'size': 4794202, + 'stars': 2}] + +确实是有好点了,但是看到下面这些,我崩溃了,我哪里知道这是什么鬼,难道是我太菜了吗?当我是计算机呀? -1.13.1 lambda 表达式 --------------------- +:: -匿名函数(英语:anonymous -function)是指一类无需定义标识符(函数名)的函数。通俗来说呢,就是它可以让我们的函数,可以不需要函数名。 + u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u6700\u597d\u7684\u9e21\u513f' -正常情况下,我们定义一个函数,使用的是 ``def`` -关键字,而当你学会使用匿名函数后,替代 ``def`` 的是 ``lambda``\ 。 +除此之外,我们知道 json 的严格要求必须使用 +**双引号**\ ,而我定义字典时,也使用了双引号了,为什么打印出来的为什么是 +**单引号**\ ?我也太难了吧,我连自己的代码都无法控制了吗? -这边使用\ ``def`` 和 ``lambda`` 分别举个例子,你很快就能理解。 +到这里,我们知道了 pprint 带来的两个问题: -:: +1. 没法在 Python 2 下正常打印中文 +2. 没法输出 JSON 标准格式的格式化内容(双引号) - def mySum(x, y): - return x+y - mySum(2, 3) - # 5 +2. 解决问题 +----------- - (lambda x, y: x+y)(2, 4) - # 6 +打印中文 +~~~~~~~~ -从上面的示例,我们可以看到匿名函数直接运行,省下了很多行的代码,有没有? +如果你是在 Python 3 下使用,你会发现中文是可以正常显示的。 -接下来,我们的仔细看一下它的用法 +.. code:: python -带 if/else + # Python3.7 + >>> info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] + >>> + >>> from pprint import pprint + >>> pprint(info) + [{'des': '2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青', + 'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk', + 'iconUrl': 'app/com.renren.mobile.android/icon.jpg', + 'id': 1580615, + 'name': '皮的嘛', + 'packageName': 'com.renren.mobile.android', + 'size': 21803987, + 'stars': 2}, + {'des': '斗鱼271934走过路过不要错过,这里有最好的鸡儿', + 'downloadUrl': 'app/com.ct.client/com.ct.client.apk', + 'iconUrl': 'app/com.ct.client/icon.jpg', + 'id': 1540629, + 'name': '不存在的', + 'packageName': 'com.ct.client', + 'size': 4794202, + 'stars': 2}] + >>> -:: +但是很多时候(在公司的一些服务器)你无法选择自己使用哪个版本的 +Python,本来我可以选择不用的,因为有更好的替代方案(\ **这个后面会讲**\ )。 - >>>( lambda x, y: x if x < y else y )( 1, 2 ) - 1 +但是我出于猎奇,正好前两天不是写过一篇关于 编码 +的文章吗,我自认为自己对于 +编码还是掌握比较熟练的,就想着来解决一下这个问题。 -嵌套函数 +索性就来看下 pprint +的源代码,还真被我找到了解决方法,如果你也想挑战一下,不防在这里停住,自己研究一下如何实现,我相信对你阅读源码会有帮助。 -:: +**以下是我的解决方案,供你参考**\ : - >>>( lambda x: ( lambda y: ( lambda z: x + y + z )( 1 ) )( 2 ) )( 3 ) - 6 +写一个自己的 printer 对象,继承自 PrettyPrinter (pprint 使用的printer) -递归函数 +并且复写 format 方法,判断传进来的字符串对象是否 str 类型,如果不是 str +类型,而是 unicode 类型,就用 uft8 编码成 str 类型。 -:: +.. code:: python - >>> func = lambda n:1 if n == 0 else n * func(n-1) - >>> func(5) - 120 + # coding: utf-8 + from pprint import PrettyPrinter -或者 + # 继承 PrettyPrinter,复写 format 方法 + class MyPrettyPrinter(PrettyPrinter): + def format(self, object, context, maxlevels, level): + if isinstance(object, unicode): + return (object.encode('utf8'), True, False) + return PrettyPrinter.format(self, object, context, maxlevels, level) -:: + info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] - >>> f = lambda func, n: 1 if n == 0 else n * func( func, n - 1 ) - >>> f(f,4) - 24 + MyPrettyPrinter().pprint(info) -从以上示例来看,lambda -表达式和常规的函数相比,写法比较怪异,可读性相对较差。除了可以直接运行之外,好像并没有其他较为突出的功能,为什么在今天我们要介绍它呢? +输出如下,已经解决了中文的显示问题: -首先我们要知道 lambda -是一个表达式,而不是一个语句。正因为这个特点,我们可以在一些特殊的场景中去使用它。具体是什么场景呢?接下来我们会介绍到几个非常好用的内置函数。 +|image1| -1.13.2 map 函数 ---------------- +打印双引号 +~~~~~~~~~~ -map -函数,它接收两个参数,第一个参数是一个函数对象(当然也可以是一个lambda表达式),第二个参数是一个序列。 +解决了中文问题后,再来看看如何让 pprint 打印双引号。 -它可以实现怎样的功能呢,我举个例子你就明白了。 +在实例化 PrettyPrinter 对象的时候,可以接收一个 stream +对象,它表示你要将内容输出到哪里,默认是使用 sys.stdout 这个 +stream,也就是标准输出。 -:: +现在我们要修改输出的内容,也就是将输出的单引号替换成双引号。 - >>> map(lambda x: x*2, [1,2,3,4,5]) - [2, 4, 6, 8, 10] +那我们完全可以自己定义一个 stream +类型的对象,该对象不需要继承任何父类,只要你实现 write 方法就可以。 -可以很清楚地看到,它可以将后面序列中的每一个元素做为参数传入lambda中。 +有了思路,就可以开始写代码了,如下: -当我们不使用 map 函数时,你也许会这样子写。 +.. code:: python -:: + # coding: utf-8 + from pprint import PrettyPrinter - mylist=[] - for i in [1,2,3,4,5]: - mylist.append(i*2) + class MyPrettyPrinter(PrettyPrinter): + def format(self, object, context, maxlevels, level): + if isinstance(object, unicode): + return (object.encode('utf8'), True, False) + return PrettyPrinter.format(self, object, context, maxlevels, level) + + class MyStream(): + def write(self, text): + print text.replace('\'', '"') + + info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] + MyPrettyPrinter(stream=MyStream()).pprint(info) + +尝试执行了下,我的天,怎么是这样子的。 + +.. code:: json + + [ + { + "des" + : + 2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青 + , + "downloadUrl": + "app/com.renren.mobile.android/com.renren.mobile.android.apk" + , + "iconUrl": + "app/com.renren.mobile.android/icon.jpg" + , + "id": + 1580615 + , + "name": + 皮的嘛 + , + "packageName": + "com.renren.mobile.android" + , + "size": + 21803987 + , + "stars": + 2 + } + , + + { + "des" + : + 斗鱼271934走过路过不要错过,这里有最好的鸡儿 + , + "downloadUrl": + "app/com.ct.client/com.ct.client.apk" + , + "iconUrl": + "app/com.ct.client/icon.jpg" + , + "id": + 1540629 + , + "name": + 不存在的 + , + "packageName": + "com.ct.client" + , + "size": + 4794202 + , + "stars": + 2 + } + ] -1.13.3 filter 函数 ------------------- +经过一番研究,才知道是因为 print 函数默认会将打印的内容后面加个 +**换行符**\ 。 -filter 函数,和 map 函数相似。同样也是接收两个参数,一个lambda -表达式,一个序列。它会遍历后面序列中每一个元素,并将其做为参数传入lambda表达式中,当表达式返回 -True,则元素会被保留下来,当表达式返回 False ,则元素会被丢弃。 +那如何将使 print 函数打印的内容,不进行换行呢? -下面这个例子,将过滤出一个列表中小于0的元素。 +方法很简单,但是我相信很多人都不知道,只要在 print 的内容后加一个 +**逗号** 就行。 -:: +就像下面这样。 - >>>filter(lambda x: x < 0, range(-5, 5)) - [-5, -4, -3, -2, -1] +|image2| -1.13.4 reduce 函数 ------------------- +知道了问题所在,再修改下代码 -reduce 函数,也是类似的。它的作用是先对序列中的第 1、2 -个元素进行操作,得到的结果再与第三个数据用 lambda -函数运算,将其得到的结果再与第四个元素进行运算,以此类推下去直到后面没有元素了。 +.. code:: python -|reduce 逻辑演示| 这边举个例子你也就明白了。 + # coding: utf-8 + from pprint import PrettyPrinter -:: + class MyPrettyPrinter(PrettyPrinter): + def format(self, object, context, maxlevels, level): + if isinstance(object, unicode): + return (object.encode('utf8'), True, False) + return PrettyPrinter.format(self, object, context, maxlevels, level) - >>>reduce(lambda x,y: x+y, [1,2,3,4,5]) - 15 + class MyStream(): + def write(self, text): + print text.replace('\'', '"'), -它的运算过程分解一下是这样的。 + info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] -:: + MyPrettyPrinter(stream=MyStream()).pprint(info) - 1+2=3 - 3+3=6 - 6+4+10 - 10+5=15 +终于成功了,太不容易了吧。 -1.13.5 注意点 -------------- +|image3| -以上几个函数,熟练的掌握它们的写法,可以让我们的代码看起来更加的 -Pythonic ,在某一程度上代码看起来更加的简洁。 +3. 何必折腾 +----------- -如果你是新手呢,你需要注意的是,以上示例是在 Python2.x -环境下演示的。而在 Python3.x 中,却有所不同,你可以自己尝试一下。 +通过上面的一番折腾,我终于实现了我 **梦寐以求** 的需求。 -这里总结一下: +代价就是我整整花费了两个小时,才得以实现,而对于小白来说,可能没有信心,也没有耐心去做这样的事情。 -第一点,map 和 filter -函数返回的都不再是一个列表,而是一个迭代器对象。这里以map为例 +**所以我想说的是,Python 2 下的 pprint ,真的不要再用了**\ 。 -:: +为什么我要用这么 说,因为明明有更好的替代品,人生苦短,既然用了 Python +,当然是怎么简单怎么来咯,何必为难自己呢,一行代码可以解决的事情,偏偏要去写两个类,那不是自讨苦吃吗?(我这是在骂自己吗? - >>> map_obj = map(lambda x: x*2, [1,2,3,4,5]) - >>> from collections.abc import Iterator - >>> isinstance(map_obj, Iterator) - True - >>> next(map_obj) - 2 - >>> list(map_obj) - [4, 6, 8, 10] +如果你愿意抛弃 pprint ,那我推荐你用 json.dumps ,我保证你再也不想用 +pprint 了。 -第二点,reduce 不可以直接调用,而是要先导入才能使用, +.. _打印中文-1: -:: +打印中文 +~~~~~~~~ - from functools import reduce +其实无法打印中文,是 Python 2 引来的大坑,并不能全怪 pprint 。 --------------- +但是同样的问题,在 json.dumps 这里,却只要加个参数就好了,可比 pprint +简单得不要太多。 -|image2| +具体的代码示例如下: + +.. code:: python + + >>> info = [{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] + >>> + >>> import json + >>> + >>> + >>> print json.dumps(info, indent=4, ensure_ascii=False) + [ + { + "downloadUrl": "app/com.renren.mobile.android/com.renren.mobile.android.apk", + "iconUrl": "app/com.renren.mobile.android/icon.jpg", + "name": "皮的嘛", + "stars": 2, + "packageName": "com.renren.mobile.android", + "des": "2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青", + "id": 1580615, + "size": 21803987 + }, + { + "downloadUrl": "app/com.ct.client/com.ct.client.apk", + "iconUrl": "app/com.ct.client/icon.jpg", + "name": "不存在的", + "stars": 2, + "packageName": "com.ct.client", + "des": "斗鱼271934走过路过不要错过,这里有最好的鸡儿", + "id": 1540629, + "size": 4794202 + } + ] + >>> + +json.dumps 的关键参数有两个: + +- **indent=4**\ :以 4 个空格缩进单位 +- **ensure_ascii=False**\ :接收非 ASCII 编码的字符,这样才能使用中文 + +与 pprint 相比 json.dumps 可以说完胜: + +1. 两个参数就能实现所有我的需求(打印中文与双引号) +2. 就算在 Python 2 下,使用中文也不需要用 ``u'中文'`` 这种写法 +3. Python2 和 Python3 的写法完全一致,对于这一点不需要考虑兼容问题 + +4. 总结一下 +----------- + +本来很简单的一个观点,我为了证明 pprint +实现那两个需求有多么困难,花了很多的时间去研究了 pprint +的源码(各种处理其实还是挺复杂的),不过好在最后也能有所收获。 + +本文的分享就到这里,阅读本文,我认为你可以获取到三个知识点 + +1. 核心观点:Python2 下不要再使用 pprint +2. 若真要使用,且有和一样的改造需求,可以参考我的实现 +3. Python 2 中的 print 语句后居然可以加 逗号 + +以上。希望此文能对你有帮助。 + +|image4| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |reduce 逻辑演示| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyx6i8q3anj208c04u3yu.jpg -.. |image2| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20200507171451.png +.. |image2| image:: http://image.iswbm.com/20200507174459.png +.. |image3| image:: http://image.iswbm.com/20200507174802.png +.. |image4| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_14.md b/source/c01/c01_14.md index efae821..d47444a 100644 --- a/source/c01/c01_14.md +++ b/source/c01/c01_14.md @@ -2,8 +2,6 @@ ![](http://image.iswbm.com/20200602135014.png) -> 提示:前面的内容较为基础,重点知识在后半段。 - `with` 这个关键字,对于每一学习Python的人,都不会陌生。 操作文本对象的时候,几乎所有的人都会让我们要用 `with open` ,这就是一个上下文管理的例子。你一定已经相当熟悉了,我就不再废话了。 @@ -13,7 +11,7 @@ with open('test.txt') as f: print f.readlines() ``` -## 1.14.1 what context manager? +## 1. what context manager? **基本语法** @@ -30,7 +28,7 @@ with EXPR as VAR: 3. f 不是上下文管理器,应该是资源对象。 ``` -## 1.14.2 how context manager? +## 2. how context manager? 要自己实现这样一个上下文管理,要先知道上下文管理协议。 @@ -63,7 +61,7 @@ with Resource() as res: 从这个示例可以很明显的看出,在编写代码时,可以将资源的连接或者获取放在`__enter__`中,而将资源的关闭写在`__exit__` 中。 -## 1.14.3 why context manager? +## 3. why context manager? 学习时多问自己几个为什么,养成对一些细节的思考,有助于加深对知识点的理解。 @@ -113,7 +111,7 @@ with Resource() as res: 当主逻辑代码没有报异常时,这三个参数将都为None。 -## 1.14.4 how contextlib? +## 4. how contextlib? 在上面的例子中,我们只是为了构建一个上下文管理器,却写了一个类。如果只是要实现一个简单的功能,写一个类未免有点过于繁杂。这时候,我们就想,如果只写一个函数就可以实现上下文管理器就好了。 @@ -187,7 +185,7 @@ with open_func('/Users/MING/mytest.txt') as file_in: 代码是这样的 -![](http://image.python-online.cn/20190310172800.png) +![](http://image.iswbm.com/20190310172800.png) 总结起来,使用上下文管理器有三个好处: diff --git a/source/c01/c01_14.rst b/source/c01/c01_14.rst index 62ccd87..bb99084 100644 --- a/source/c01/c01_14.rst +++ b/source/c01/c01_14.rst @@ -3,8 +3,6 @@ |image0| - 提示:前面的内容较为基础,重点知识在后半段。 - ``with`` 这个关键字,对于每一学习Python的人,都不会陌生。 操作文本对象的时候,几乎所有的人都会让我们要用 ``with open`` @@ -15,8 +13,8 @@ with open('test.txt') as f: print f.readlines() -1.14.1 what context manager? ------------------------------ +1. what context manager? +------------------------- **基本语法** @@ -33,8 +31,8 @@ 2. 上下文管理器:open('test.txt') 3. f 不是上下文管理器,应该是资源对象。 -1.14.2 how context manager? ----------------------------- +2. how context manager? +------------------------ 要自己实现这样一个上下文管理,要先知道上下文管理协议。 @@ -68,8 +66,8 @@ 从这个示例可以很明显的看出,在编写代码时,可以将资源的连接或者获取放在\ ``__enter__``\ 中,而将资源的关闭写在\ ``__exit__`` 中。 -1.14.3 why context manager? ----------------------------- +3. why context manager? +------------------------ 学习时多问自己几个为什么,养成对一些细节的思考,有助于加深对知识点的理解。 @@ -124,8 +122,8 @@ Python解释器,这个异常我们已经捕获了,不需要再往外抛了 当主逻辑代码没有报异常时,这三个参数将都为None。 -1.14.4 how contextlib? ----------------------- +4. how contextlib? +------------------ 在上面的例子中,我们只是为了构建一个上下文管理器,却写了一个类。如果只是要实现一个简单的功能,写一个类未免有点过于繁杂。这时候,我们就想,如果只写一个函数就可以实现上下文管理器就好了。 @@ -214,6 +212,6 @@ open)的上下文管理器。 |image2| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190310172800.png +.. |image1| image:: http://image.iswbm.com/20190310172800.png .. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_15.md b/source/c01/c01_15.md index 03ca8f1..d6561cd 100644 --- a/source/c01/c01_15.md +++ b/source/c01/c01_15.md @@ -1,73 +1,49 @@ -# 1.15 提升Python性能的7个习惯 +# 1.21 Python 开发的几个小 Tips -![](http://image.iswbm.com/20200602135014.png) +## 1. 重定向标准输出到文件 ---- - -> 转载自:https://zhuanlan.zhihu.com/p/38160586 - -## 1.15.1 使用局部变量 - -尽量使用局部变量代替全局变量:便于维护,提高性能并节省内存。 - -使用局部变量替换模块名字空间中的变量,例如 ls = os.linesep。一方面可以提高程序性能,局部变量查找速度更快;另一方面可用简短标识符替代冗长的模块变量,提高可读性。 - -## 1.15.2 减少函数调用次数 - -对象类型判断时,采用isinstance()最优,采用对象类型身份(id())次之,采用对象值(type())比较最次。 - -``` -#判断变量num是否为整数类型type(num) == type(0) #调用三次函数type(num) is type(0) #身份比较isinstance(num,(int)) #调用一次函数 -``` - -不要在重复操作的内容作为参数放到循环条件中,避免重复运算。 - -``` -#每次循环都需要重新执行len(a)while i < len(a): statement#len(a)仅执行一次m = len(a)while i < m: statement ``` +import contextlib -如需使用模块X中的某个函数或对象Y,应直接使用from X import Y,而不是import X; X.Y。这样在使用Y时,可以减少一次查询(解释器不必首先查找到X模块,然后在X模块的字典中查找Y)。 +def unshelve_task(): + pass -## 1.15.3 采用映射替代条件查找 +@contextlib.contextmanager +def close_stdout(): + raw_stdout = sys.stdout + file = open(log_file, 'a+') + sys.stdout = file -映射(比如dict等)的搜索速度远快于条件语句(如if等)。Python中也没有select-case语句。 + yield + sys.stdout = raw_stdout + file.close() + +with close_stdout(): + unshelve_task() ``` -#if查找if a == 1: b = 10elif a == 2: b = 20...#dict查找,性能更优d = {1:10,2:20,...}b = d[a] -``` -## 1.15.4 直接迭代序列元素 +## 2. 将子网掩码转换为cidr + +如何使用netaddr库将ipv4子网掩码转换为cidr表示法? +示例:255.255.255.0到/ 24 -对序列(str、list、tuple等),直接迭代序列元素,比迭代元素的索引速度要更快。 +使用netaddr: ``` -a = [1,2,3]#迭代元素for item in a: print(item)#迭代索引for i in range(len(a)): print(a[i]) +>>> from netaddr import IPAddress +>>> IPAddress("255.255.255.0").netmask_bits() +24 ``` -## 1.15.5 采用生成器表达式替代列表解析 - -列表解析(list comprehension),会产生整个列表,对大量数据的迭代会产生负面效应。 - -而生成器表达式则不会,其不会真正创建列表,而是返回一个生成器,在需要时产生一个值(延迟计算),对内存更加友好。 +您也可以在不使用任何库的情况下执行此操作,只需在网络掩码的二进制表示中计算1位: ``` -#计算文件f的非空字符个数#生成器表达式l = sum([len(word) for line in f for word in line.split()])#列表解析l = sum(len(word) for line in f for word in line.split()) +>>> netmask = "255.255.255.0" +>>> sum([bin(int(x)).count("1") for x in netmask.split(".")]) +24 ``` -## 1.15.6 先编译后调用 - -使用eval()、exec()函数执行代码时,最好调用代码对象(提前通过compile()函数编译成字节码),而不是直接调用str,可以避免多次执行重复编译过程,提高程序性能。 - -正则表达式模式匹配也类似,也最好先将正则表达式模式编译成regex对象(通过re.complie()函数),然后再执行比较和匹配。 - -## 1.15.7 模块编程习惯 - -模块中的最高级别Python语句(没有缩进的代码)会在模块导入(import)时执行(不论其是否真的必要执行)。因此,应尽量将模块所有的功能代码放到函数中,包括主程序相关的功能代码也可放到main()函数中,主程序本身调用main()函数。 - -可以在模块的main()函数中书写测试代码。在主程序中,检测name的值,如果为'main'(表示模块是被直接执行),则调用main()函数,进行测试;如果为模块名字(表示模块是被调用),则不进行测试。 - - ------- -![](http://image.iswbm.com/20200607174235.png) +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_15.rst b/source/c01/c01_15.rst index 1ed39f1..97bab2d 100644 --- a/source/c01/c01_15.rst +++ b/source/c01/c01_15.rst @@ -1,87 +1,53 @@ -1.15 提升Python性能的7个习惯 -============================ +1.21 Python 开发的几个小 Tips +============================= -|image0| - --------------- - - 转载自:https://zhuanlan.zhihu.com/p/38160586 - -1.15.1 使用局部变量 -------------------- - -尽量使用局部变量代替全局变量:便于维护,提高性能并节省内存。 - -使用局部变量替换模块名字空间中的变量,例如 ls = -os.linesep。一方面可以提高程序性能,局部变量查找速度更快;另一方面可用简短标识符替代冗长的模块变量,提高可读性。 - -1.15.2 减少函数调用次数 +1. 重定向标准输出到文件 ----------------------- -对象类型判断时,采用isinstance()最优,采用对象类型身份(id())次之,采用对象值(type())比较最次。 - -:: - - #判断变量num是否为整数类型type(num) == type(0) #调用三次函数type(num) is type(0) #身份比较isinstance(num,(int)) #调用一次函数 - -不要在重复操作的内容作为参数放到循环条件中,避免重复运算。 - :: - #每次循环都需要重新执行len(a)while i < len(a): statement#len(a)仅执行一次m = len(a)while i < m: statement + import contextlib -如需使用模块X中的某个函数或对象Y,应直接使用from X import -Y,而不是import X; -X.Y。这样在使用Y时,可以减少一次查询(解释器不必首先查找到X模块,然后在X模块的字典中查找Y)。 + def unshelve_task(): + pass -1.15.3 采用映射替代条件查找 ---------------------------- + @contextlib.contextmanager + def close_stdout(): + raw_stdout = sys.stdout + file = open(log_file, 'a+') + sys.stdout = file -映射(比如dict等)的搜索速度远快于条件语句(如if等)。Python中也没有select-case语句。 + yield -:: + sys.stdout = raw_stdout + file.close() + + with close_stdout(): + unshelve_task() - #if查找if a == 1: b = 10elif a == 2: b = 20...#dict查找,性能更优d = {1:10,2:20,...}b = d[a] - -1.15.4 直接迭代序列元素 +2. 将子网掩码转换为cidr ----------------------- -对序列(str、list、tuple等),直接迭代序列元素,比迭代元素的索引速度要更快。 - -:: - - a = [1,2,3]#迭代元素for item in a: print(item)#迭代索引for i in range(len(a)): print(a[i]) - -1.15.5 采用生成器表达式替代列表解析 ------------------------------------ - -列表解析(list -comprehension),会产生整个列表,对大量数据的迭代会产生负面效应。 +如何使用netaddr库将ipv4子网掩码转换为cidr表示法? 示例:255.255.255.0到/ +24 -而生成器表达式则不会,其不会真正创建列表,而是返回一个生成器,在需要时产生一个值(延迟计算),对内存更加友好。 +使用netaddr: :: - #计算文件f的非空字符个数#生成器表达式l = sum([len(word) for line in f for word in line.split()])#列表解析l = sum(len(word) for line in f for word in line.split()) + >>> from netaddr import IPAddress + >>> IPAddress("255.255.255.0").netmask_bits() + 24 -1.15.6 先编译后调用 -------------------- +您也可以在不使用任何库的情况下执行此操作,只需在网络掩码的二进制表示中计算1位: -使用eval()、exec()函数执行代码时,最好调用代码对象(提前通过compile()函数编译成字节码),而不是直接调用str,可以避免多次执行重复编译过程,提高程序性能。 - -正则表达式模式匹配也类似,也最好先将正则表达式模式编译成regex对象(通过re.complie()函数),然后再执行比较和匹配。 - -1.15.7 模块编程习惯 -------------------- - -模块中的最高级别Python语句(没有缩进的代码)会在模块导入(import)时执行(不论其是否真的必要执行)。因此,应尽量将模块所有的功能代码放到函数中,包括主程序相关的功能代码也可放到main()函数中,主程序本身调用main()函数。 - -可以在模块的main()函数中书写测试代码。在主程序中,检测name的值,如果为’main’(表示模块是被直接执行),则调用main()函数,进行测试;如果为模块名字(表示模块是被调用),则不进行测试。 +:: --------------- + >>> netmask = "255.255.255.0" + >>> sum([bin(int(x)).count("1") for x in netmask.split(".")]) + 24 -|image1| +|image0| -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200607174235.png +.. |image0| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_17.md b/source/c01/c01_17.md index fb3f247..9e13038 100644 --- a/source/c01/c01_17.md +++ b/source/c01/c01_17.md @@ -1,530 +1,274 @@ -# 1.17 深入理解「描述符」 +# 1.17 详解 Python 中的编码问题 ![](http://image.iswbm.com/20200602135014.png) -学习 Python 这么久了,说起 Python 的优雅之处,能让我脱口而出的, Descriptor(描述符)特性可以排得上号。 +Python 中编码问题,一直是很多 Python 开发者的噩梦,尽管你是工作多年的 Python 开发者,也肯定会经常遇到令人神烦的编码问题,好不容易花了半天搞明白了。 -描述符 是Python 语言独有的特性,它不仅在应用层使用,在语言的基础设施中也有涉及。 +一段时间后,又全都忘光光了,一脸懵逼的你又开始你找各种博客、帖子,从头搞清楚什么是编码?什么是 unicode?它和 ASCII 有什么区别?为什么 decode encode 老是报错?python2 里和 python3 的字符串类型怎么都不一样,怎么对应起来?如何检测编码格式? -我可以大胆地猜测,你对于描述符的了解是始于诸如 Django ORM 和 SQLAlchemy 中的字段对象,是的,它们都是描述符。你的它的认识,可能也止步于此,如果你没有去深究,它为何要如此设计?也就加体会不到 Python 给我们带来的便利与优雅。 +反反复复,这个过程真是太痛苦了。 -由于 描述符的内容较多,长篇大论,容易让你倦怠,所以我打算分几篇来讲。 +今天我把大家在 Python 上会遇到的一些编码问题都讲清楚了,以后你可以不用再 Google,收藏这篇文章就行。 -## 1.17.1 为什么要使用描述符? -假想你正在给学校写一个成绩管理系统,并没有太多编码经验的你,可能会这样子写。 -```python -class Student: - def __init__(self, name, math, chinese, english): - self.name = name - self.math = math - self.chinese = chinese - self.english = english - - def __repr__(self): - return "".format( - self.name, self.math, self.chinese, self.english - ) -``` +## 1. Python 3 中 str 与 bytes -看起来一切都很合理 +在 Python3中,字符串有两种类型 ,str 和 bytes。 -```python ->>> std1 = Student('小明', 76, 87, 68) ->>> std1 - -``` +今天就来说一说这二者的区别: -但是程序并不像人那么智能,不会自动根据使用场景判断数据的合法性,如果老师在录入成绩的时候,不小心录入了将成绩录成了负数,或者超过100,程序是无法感知的。 +- `unicode string(str 类型)`:以 Unicode code points 形式存储,**人类认识的形式** +- `byte string(bytes 类型)`:以 byte 形式存储,**机器认识的形式** -聪明的你,马上在代码中加入了判断逻辑。 +在 Python 3 中你定义的所有字符串,都是 unicode string类型,使用 `type` 和 `isinstance` 可以判别 ```python -class Student: - def __init__(self, name, math, chinese, english): - self.name = name - if 0 <= math <= 100: - self.math = math - else: - raise ValueError("Valid value must be in [0, 100]") - - if 0 <= chinese <= 100: - self.chinese = chinese - else: - raise ValueError("Valid value must be in [0, 100]") - - if 0 <= chinese <= 100: - self.english = english - else: - raise ValueError("Valid value must be in [0, 100]") - - - def __repr__(self): - return "".format( - self.name, self.math, self.chinese, self.english - ) +# python3 + +>>> str_obj = "你好" +>>> +>>> type(str_obj) + +>>> +>>> isinstance("你好", str) +True +>>> +>>> isinstance("你好", bytes) +False +>>> ``` -这下程序稍微有点人工智能了,能够自己明辨是非了。 +而 bytes 是一个二进制序列对象,你只要你在定义字符串时前面加一个 `b`,就表示你要定义一个 bytes 类型的字符串对象。 -![](http://image.python-online.cn/20190425221322.png) +```python +# python3 +>>> byte_obj = b"Hello World!" +>>> type(byte_obj) + +>>> +>>> isinstance(byte_obj, str) +False +>>> +>>> isinstance(byte_obj, bytes) +True +>>> +``` -程序是智能了,但在`__init__`里有太多的判断逻辑,很影响代码的可读性。巧的是,你刚好学过 Property 特性,可以很好的应用在这里。于是你将代码修改成如下,代码的可读性瞬间提升了不少 +但是在定义中文字符串时,你就不能直接在前面加 `b` 了,而应该使用 `encode` 转一下。 ```python -class Student: - def __init__(self, name, math, chinese, english): - self.name = name - self.math = math - self.chinese = chinese - self.english = english - - @property - def math(self): - return self._math - - @math.setter - def math(self, value): - if 0 <= value <= 100: - self._math = value - else: - raise ValueError("Valid value must be in [0, 100]") - - @property - def chinese(self): - return self._chinese - - @chinese.setter - def chinese(self, value): - if 0 <= value <= 100: - self._chinese = value - else: - raise ValueError("Valid value must be in [0, 100]") - - @property - def english(self): - return self._english - - @english.setter - def english(self, value): - if 0 <= value <= 100: - self._english = value - else: - raise ValueError("Valid value must be in [0, 100]") - - def __repr__(self): - return "".format( - self.name, self.math, self.chinese, self.english - ) +>>> byte_obj=b"你好" + File "", line 1 +SyntaxError: bytes can only contain ASCII literal characters. +>>> +>>> str_obj="你好" +>>> +>>> str_obj.encode("utf-8") +b'\xe4\xbd\xa0\xe5\xa5\xbd' +>>> ``` -程序还是一样的人工智能,非常好。 +## 2. Python 2 中 str 与 unicode -![](http://image.python-online.cn/20190425221322.png) +而在 Python2 中,字符串的类型又与 Python3 不一样,需要仔细区分。 -你以为你写的代码,已经非常优秀,无懈可击了。 +在 Python2 里,字符串也只有两种类型,unicode 和 str 。 -没想到,人外有天,你的主管看了你的代码后,深深地叹了口气:类里的三个属性,math、chinese、english,都使用了 Property 对属性的合法性进行了有效控制。功能上,没有问题,但就是太啰嗦了,三个变量的合法性逻辑都是一样的,只要大于0,小于100 就可以,代码重复率太高了,这里三个成绩还好,但假设还有地理、生物、历史、化学等十几门的成绩呢,这代码简直没法忍。去了解一下 Python 的描述符吧。 +只有 unicode object 和 非unicode object(其实应该叫 str object) 的区别: -经过主管的指点,你知道了「描述符」这个东西。怀着一颗敬畏之心,你去搜索了下关于 描述符的用法。 +- `unicode string(unicode类型)`:以 Unicode code points 形式存储,**人类认识的形式** +- `byte string(str 类型)`:以 byte 形式存储,**机器认识的形式** -其实也很简单,一个实现了 `描述符协议` 的类就是一个描述符。 +当我们直接使用双引号或单引号包含字符的方式来定义字符串时,就是 str 字符串对象,比如这样 -什么描述符协议:实现了 `__get__()`、`__set__()`、`__delete__()` 其中至少一个方法的类,就是一个描述符。 - -- `__get__`: 用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。 -- `__set__ `:将在属性分配操作中调用。不会返回任何内容。 -- `__delete__ `:控制删除操作。不会返回内容。 +```python +# python2 -对描述符有了大概的了解后,你开始重写上面的方法。 +>>> str_obj="你好" +>>> +>>> type(str_obj) + +>>> +>>> str_obj +'\xe4\xbd\xa0\xe5\xa5\xbd' +>>> +>>> isinstance(str_obj, bytes) +True +>>> isinstance(str_obj, str) +True +>>> isinstance(str_obj, unicode) +False +>>> +>>> str is bytes +True +``` -如前所述,Score 类是一个描述器,当从 Student 的实例访问 math、chinese、english这三个属性的时候,都会经过 Score 类里的三个特殊的方法。这里的 Score 避免了 使用Property 出现大量的代码无法复用的尴尬。 +而当我们在双引号或单引号前面加个 `u`,就表明我们定义的是 unicode 字符串对象,比如这样 ```python -class Score: - def __init__(self, default=0): - self._score = default - - def __set__(self, instance, value): - if not isinstance(value, int): - raise TypeError('Score must be integer') - if not 0 <= value <= 100: - raise ValueError('Valid value must be in [0, 100]') - - self._score = value - - def __get__(self, instance, owner): - return self._score - - def __delete__(self): - del self._score - -class Student: - math = Score(0) - chinese = Score(0) - english = Score(0) - - def __init__(self, name, math, chinese, english): - self.name = name - self.math = math - self.chinese = chinese - self.english = english - - - def __repr__(self): - return "".format( - self.name, self.math, self.chinese, self.english - ) -``` - -实现的效果和前面的一样,可以对数据的合法性进行有效控制(字段类型、数值区间等) +# python2 -![](http://image.python-online.cn/20190425221233.png) +>>> unicode_obj = u"你好" +>>> +>>> unicode_obj +u'\u4f60\u597d' +>>> +>>> type(unicode_obj) + +>>> +>>> isinstance(unicode_obj, bytes) +False +>>> isinstance(unicode_obj, str) +False +>>> +>>> isinstance(unicode_obj, unicode) +True +``` -以上,我举了下具体的实例,从最原始的编码风格到 Property ,最后引出描述符。由浅入深,一步一步带你感受到描述符的优雅之处。 -通过此文,你需要记住的只有一点,就是描述符给我们带来的编码上的便利,它在实现 `保护属性不受修改`、`属性类型检查` 的基本功能,同时有大大提高代码的复用率。 ---- +## 3. 如何检测对象的编码 -## 1.17.2 描述符的访问规则 +所有的字符,在 unicode 字符集中都有对应的编码值(英文叫做:`code point`) -描述符分两种: +而把这些编码值按照一定的规则保存成二进制字节码,就是我们说的编码方式,常见的有:UTF-8,GB2312 等。 -- 数据描述符:实现了`__get__` 和 `__set__` 两种方法的描述符 -- 非数据描述符:只实现了`__get__` 一种方法的描述符 +也就是说,当我们要将内存中的字符串持久化到硬盘中的时候,都要指定编码方法,而反过来,读取的时候,也要指定正确的编码方法(这个过程叫解码),不然会出现乱码。 -你一定会问,他们有什么区别呢?网上的讲解,我看过几个,很多都把一个简单的东西讲得复杂了。 +那问题就来了,当我们知道了其对应的编码方法,我们就可以正常解码,但并不是所有时候我们都能知道应该用什么编码方式去解码? -其实就一句话,**数据描述器和非数据描述器的区别在于:它们相对于实例的字典的优先级不同**。 +这时候就要介绍到一个 python 的库 -- `chardet` ,使用它之前 需要先安装 -如果实例字典中有与描述器同名的属性,如果描述器是数据描述器,优先使用数据描述器,如果是非数据描述器,优先使用字典中的属性。 +``` +python3 -m pip install chardet +``` -这边还是以上节的成绩管理的例子来说明,方便你理解。 +chardet 有一个 detect 方法,可以 `预测`其其编码格式 ```python -# 数据描述符 -class DataDes: - def __init__(self, default=0): - self._score = default - - def __set__(self, instance, value): - self._score = value - - def __get__(self, instance, owner): - print("访问数据描述符里的 __get__") - return self._score - -# 非数据描述符 -class NoDataDes: - def __init__(self, default=0): - self._score = default - - def __get__(self, instance, owner): - print("访问非数据描述符里的 __get__") - return self._score - - -class Student: - math = DataDes(0) - chinese = NoDataDes(0) - - def __init__(self, name, math, chinese): - self.name = name - self.math = math - self.chinese = chinese - - def __getattribute__(self, item): - print("调用 __getattribute__") - return super(Student, self).__getattribute__(item) - - def __repr__(self): - return "".format( - self.name, self.math, self.chinese) +>>> import chardet +>>> chardet.detect('微信公众号:Python编程时光'.encode('gbk')) +{'encoding': 'GB2312', 'confidence': 0.99, 'language': 'Chinese'} ``` -需要注意的是,math 是数据描述符,而 chinese 是非数据描述符。从下面的验证中,可以看出,当实例属性和数据描述符同名时,会优先访问数据描述符(如下面的math),而当实例属性和非数据描述符同名时,会优先访问实例属性(`__getattribute__`) +为什么说是预测呢,通过上面的输出来看,你会看到有一个 confidence 字段,其表示预测的可信度,或者说成功率。 + +但是使用它时,若你的字符数较少,就有可能 “`误诊`”),比如只有 `中文` 两个字,就像下面这样,我们是 使用 gbk 编码的,使用 chardet 却识别成 KOI8-R 编码。 ```python ->>> std = Student('xm', 88, 99) +>>> str_obj = "中文" +>>> byte_obj = bytes(a, encoding='gbk') # 先得到一个 gbk 编码的 bytes +>>> +>>> chardet.detect(byte_obj) +{'encoding': 'KOI8-R', 'confidence': 0.682639754276994, 'language': 'Russian'} >>> ->>> std.math -调用 __getattribute__ -访问数据描述符里的 __get__ -88 ->>> std.chinese -调用 __getattribute__ -99 +>>> str_obj2 = str(byte_obj, encoding='KOI8-R') +>>> str_obj2 +'жпнд' ``` -讲完了数据描述符和非数据描述符,我们还需要了解的对象属性的查找规律。 +所以为了编码诊断的准确,要尽量使用足够多的字符。 -当我们对一个实例属性进行访问时,Python 会按 `obj.__dict__` → `type(obj).__dict__` → `type(obj)的父类.__dict__` 顺序进行查找,如果查找到目标属性并发现是一个描述符,Python 会调用描述符协议来改变默认的控制行为。 +chardet 支持多国的语言,从官方文档中可以看到支持如下这些语言(https://chardet.readthedocs.io/en/latest/supported-encodings.html) -## 1.17.3 基于描述符如何实现property +![](http://image.iswbm.com/20200423185819.png) -经过上面的讲解,我们已经知道如何定义描述符,且明白了描述符是如何工作的。 -正常人所见过的描述符的用法就是上篇文章提到的那些,我想说的是那只是描述符协议最常见的应用之一,或许你还不知道,其实有很多 Python 的特性的底层实现机制都是基于 `描述符协议` 的,比如我们熟悉的`@property` 、`@classmethod` 、`@staticmethod` 和 `super` 等。 -先来说说 `property` 吧。 +## 4. 编码与解码的区别 -有了第一篇的基础,我们知道了 property 的基本用法。这里我直接切入主题,从第一篇的例子里精简了一下。 +编码和解码,其实就是 str 与 bytes 的相互转化的过程(Python 2 已经远去,这里以及后面都只用 Python 3 举例) -```python -class Student: - def __init__(self, name): - self.name = name - - @property - def math(self): - return self._math - - @math.setter - def math(self, value): - if 0 <= value <= 100: - self._math = value - else: - raise ValueError("Valid value must be in [0, 100]") -``` +- **编码**:encode 方法,把字符串对象转化为二进制字节序列 -不防再简单回顾一下它的用法,通过property装饰的函数,如例子中的 math 会变成 Student 实例的属性。而对 math 属性赋值会进入 使用 `math.setter` 装饰函数的逻辑代码块。 +- **解码**:decode 方法,把二进制字节序列转化为字符串对象 -为什么说 property 底层是基于描述符协议的呢?通过 PyCharm 点击进入 property 的源码,很可惜,只是一份类似文档一样的伪源码,并没有其具体的实现逻辑。 +![](http://image.iswbm.com/20200423190331.png) -不过,从这份伪源码的魔法函数结构组成,可以大体知道其实现逻辑。 -这里我自己通过模仿其函数结构,结合「描述符协议」来自己实现类 `property` 特性。 -代码如下: +那么假如我们真知道了其编码格式,如何来转成 unicode 呢? -```python -class TestProperty(object): - - def __init__(self, fget=None, fset=None, fdel=None, doc=None): - self.fget = fget - self.fset = fset - self.fdel = fdel - self.__doc__ = doc - - def __get__(self, obj, objtype=None): - print("in __get__") - if obj is None: - return self - if self.fget is None: - raise AttributeError - return self.fget(obj) - - def __set__(self, obj, value): - print("in __set__") - if self.fset is None: - raise AttributeError - self.fset(obj, value) - - def __delete__(self, obj): - print("in __delete__") - if self.fdel is None: - raise AttributeError - self.fdel(obj) - - - def getter(self, fget): - print("in getter") - return type(self)(fget, self.fset, self.fdel, self.__doc__) - - def setter(self, fset): - print("in setter") - return type(self)(self.fget, fset, self.fdel, self.__doc__) - - def deleter(self, fdel): - print("in deleter") - return type(self)(self.fget, self.fset, fdel, self.__doc__) -``` +**有两种方法** -然后 Student 类,我们也相应改成如下 +**第一种**是,直接使用 decode 方法 ```python -class Student: - def __init__(self, name): - self.name = name - - # 其实只有这里改变 - @TestProperty - def math(self): - return self._math - - @math.setter - def math(self, value): - if 0 <= value <= 100: - self._math = value - else: - raise ValueError("Valid value must be in [0, 100]") +>>> byte_obj.decode('gbk') +'中文' +>>> ``` -为了尽量让你少产生一点疑惑,我这里做两点说明: - -1. 使用`TestProperty`装饰后,`math` 不再是一个函数,而是`TestProperty` 类的一个实例。所以第二个math函数可以使用 `math.setter` 来装饰,本质是调用`TestProperty.setter` 来产生一个新的 `TestProperty` 实例赋值给第二个`math`。 - -2. 第一个 `math` 和第二个 `math` 是两个不同 `TestProperty` 实例。但他们都属于同一个描述符类(TestProperty),当对 math 对于赋值时,就会进入 `TestProperty.__set__`,当对math 进行取值里,就会进入 `TestProperty.__get__`。仔细一看,其实最终访问的还是Student实例的 `_math` 属性。 - -说了这么多,还是运行一下,更加直观一点。 +**第二种**是,使用 str 类来转 ```python -# 运行后,会直接打印这一行,这是在实例化 TestProperty 并赋值给第二个math -in setter ->>> ->>> s1.math = 90 -in __set__ ->>> s1.math -in __get__ -90 +>>> str_obj = str(byte_obj, encoding='gbk') +>>> str_obj +'中文' +>>> ``` -对于以上理解 `property` 的运行原理有困难的同学,请务必参照我上面写的两点说明。如有其他疑问,可以加微信与我进行探讨。 -## 1.17.4 基于描述符如何实现staticmethod -说完了 `property` ,这里再来讲讲 `@classmethod` 和 `@staticmethod` 的实现原理。 +## 5. 如何设置文件编码 -我这里定义了一个类,用了两种方式来实现静态方法。 +在 Python 2 中,默认使用的是 ASCII 编码来读取的,因此,我们在使用 Python 2 的时候,如果你的 python 文件里有中文,运行是会报错的。 -```python -class Test: - @staticmethod - def myfunc(): - print("hello") - -# 上下两种写法等价 - -class Test: - def myfunc(): - print("hello") - # 重点:这就是描述符的体现 - myfunc = staticmethod(myfunc) ``` - -这两种写法是等价的,就好像在 `property` 一样,其实以下两种写法也是等价的。 - -```python -@TestProperty -def math(self): - return self._math - -math = TestProperty(fget=math) +SyntaxError: Non-ASCII character '\xe4' in file demo.py ``` -话题还是转回到 `staticmethod` 这边来吧。 +原因就是 ASCII 编码表太小,无法解释中文。 -由上面的注释,可以看出 `staticmethod` 其实就相当于一个描述符类,而`myfunc` 在此刻变成了一个描述符。关于 `staticmethod` 的实现,你可以参照下面这段我自己写的代码,加以理解。 +而在 Python 3 中,默认使用的是 uft-8 来读取,所以省了不少的事。 -![](http://image.python-online.cn/20190519001930.png) +对于这个问题,通常解决方法有两种: -调用这个方法可以知道,每调用一次,它都会经过描述符类的 `__get__` 。 +**第一种方法** -```python ->>> Test.myfunc() -in staticmethod __get__ -hello ->>> Test().myfunc() -in staticmethod __get__ -hello -``` - -## 1.17.4 基于描述符如何实现classmethod +在 python2 中,可以使用在头部指定 -同样的 ` classmethod` 也是一样。 +可以这样写,虽然很好看 -```python -class classmethod(object): - def __init__(self, f): - self.f = f - - def __get__(self, instance, owner=None): - print("in classmethod __get__") - - def newfunc(*args): - return self.f(owner, *args) - return newfunc - -class Test: - def myfunc(cls): - print("hello") - - # 重点:这就是描述符的体现 - myfunc = classmethod(myfunc) ``` - -验证结果如下 - -```python ->>> Test.myfunc() -in classmethod __get__ -hello ->>> Test().myfunc() -in classmethod __get__ -hello +# -*- coding: utf-8 -*- ``` -讲完了 `property`、`staticmethod`和`classmethod` 与 描述符的关系。我想你应该对描述符在 Python 中的应用有了更深的理解。对于 super 的实现原理,就交由你来自己完成。 - -## 1.17.5 所有实例共享描述符 - -若要合理使用描述符,利用描述符给我们带来的编码上的便利。有一个坑,需要注意,比如下面这个Student我们没有定义构造函数 +但这样写太麻烦了,我通常使用下面两种写法 -```python -class Score: - def __init__(self, default=0): - self._value = default - - def __get__(self, instance, owner): - return self._value +``` +# coding:utf-8 +# coding=utf-8 +``` - def __set__(self, instance, value): - if 0 <= value <= 100: - self._value = value - else: - raise ValueError -class Student: - math = Score(0) - chinese = Score(0) - english = Score(0) +**第二种方法** - def __repr__(self): - return "".format(self.math, self.chinese, self.english) ``` +import sys -看一下会出现什么样的问题,std2 居然共享了std1 的属性值,因为它被当成了一个类变量了,而每个实例都没有自己的实例变量,自然访问的是同一个变量。这样是很难达到我们使用描述符的初衷。 - -```python ->>> std1 = Student() ->>> std1 - ->>> std1.math = 85 ->>> std1 - ->>> std2 = Student() ->>> std2 # std2 居然共享了std1 的属性值 - ->>> std2.math = 100 ->>> std1 # std2 也会改变std1 的属性值 - +reload(sys) +sys.setdefaultencoding('utf-8') ``` -而正确的做法应该是,所有的实例数据只属于该实例本身(通过实例初始化传入),具体写法可参考上一节。 +这里在调用sys.setdefaultencoding(‘utf-8’) 设置默认的解码方式之前,执行了reload(sys),这是必须的,因为python在加载完sys之后,会删除 sys.setdefaultencoding 这个方法,我们需要重新载入sys,才能调用 sys.setdefaultencoding 这个方法。 -## 参考文档 +## 6. 参考文章 -- [Python描述器引导(翻译)](https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html#python) +- [阮一峰老师文章的常识性错误之 Unicode 与 UTF-8](https://foofish.net/unicode_utf-8.html) +- [Strings, Bytes, and Unicode in Python 2 and 3](https://timothybramlett.com/Strings_Bytes_and_Unicode_in_Python_2_and_3.html) +- [字符编码笔记:ASCII,Unicode 和 UTF-8](http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html) --- -![](http://image.iswbm.com/20200607174235.png) + + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_17.rst b/source/c01/c01_17.rst index 2594d3b..b8f6202 100644 --- a/source/c01/c01_17.rst +++ b/source/c01/c01_17.rst @@ -1,590 +1,295 @@ -1.17 深入理解「描述符」 -======================= +1.17 详解 Python 中的编码问题 +============================= |image0| -学习 Python 这么久了,说起 Python 的优雅之处,能让我脱口而出的, -Descriptor(描述符)特性可以排得上号。 +Python 中编码问题,一直是很多 Python 开发者的噩梦,尽管你是工作多年的 +Python +开发者,也肯定会经常遇到令人神烦的编码问题,好不容易花了半天搞明白了。 -描述符 是Python -语言独有的特性,它不仅在应用层使用,在语言的基础设施中也有涉及。 +一段时间后,又全都忘光光了,一脸懵逼的你又开始你找各种博客、帖子,从头搞清楚什么是编码?什么是 +unicode?它和 ASCII 有什么区别?为什么 decode encode 老是报错?python2 +里和 python3 的字符串类型怎么都不一样,怎么对应起来?如何检测编码格式? -我可以大胆地猜测,你对于描述符的了解是始于诸如 Django ORM 和 SQLAlchemy -中的字段对象,是的,它们都是描述符。你的它的认识,可能也止步于此,如果你没有去深究,它为何要如此设计?也就加体会不到 -Python 给我们带来的便利与优雅。 +反反复复,这个过程真是太痛苦了。 -由于 描述符的内容较多,长篇大论,容易让你倦怠,所以我打算分几篇来讲。 +今天我把大家在 Python 上会遇到的一些编码问题都讲清楚了,以后你可以不用再 +Google,收藏这篇文章就行。 -1.17.1 为什么要使用描述符? +1. Python 3 中 str 与 bytes --------------------------- -假想你正在给学校写一个成绩管理系统,并没有太多编码经验的你,可能会这样子写。 +在 Python3中,字符串有两种类型 ,str 和 bytes。 -.. code:: python - - class Student: - def __init__(self, name, math, chinese, english): - self.name = name - self.math = math - self.chinese = chinese - self.english = english - - def __repr__(self): - return "".format( - self.name, self.math, self.chinese, self.english - ) - -看起来一切都很合理 - -.. code:: python - - >>> std1 = Student('小明', 76, 87, 68) - >>> std1 - +今天就来说一说这二者的区别: -但是程序并不像人那么智能,不会自动根据使用场景判断数据的合法性,如果老师在录入成绩的时候,不小心录入了将成绩录成了负数,或者超过100,程序是无法感知的。 - -聪明的你,马上在代码中加入了判断逻辑。 - -.. code:: python - - class Student: - def __init__(self, name, math, chinese, english): - self.name = name - if 0 <= math <= 100: - self.math = math - else: - raise ValueError("Valid value must be in [0, 100]") - - if 0 <= chinese <= 100: - self.chinese = chinese - else: - raise ValueError("Valid value must be in [0, 100]") - - if 0 <= chinese <= 100: - self.english = english - else: - raise ValueError("Valid value must be in [0, 100]") - - - def __repr__(self): - return "".format( - self.name, self.math, self.chinese, self.english - ) - -这下程序稍微有点人工智能了,能够自己明辨是非了。 - -|image1| +- ``unicode string(str 类型)``\ :以 Unicode code points + 形式存储,\ **人类认识的形式** +- ``byte string(bytes 类型)``\ :以 byte + 形式存储,\ **机器认识的形式** -程序是智能了,但在\ ``__init__``\ 里有太多的判断逻辑,很影响代码的可读性。巧的是,你刚好学过 -Property -特性,可以很好的应用在这里。于是你将代码修改成如下,代码的可读性瞬间提升了不少 +在 Python 3 中你定义的所有字符串,都是 unicode string类型,使用 ``type`` +和 ``isinstance`` 可以判别 .. code:: python - class Student: - def __init__(self, name, math, chinese, english): - self.name = name - self.math = math - self.chinese = chinese - self.english = english - - @property - def math(self): - return self._math - - @math.setter - def math(self, value): - if 0 <= value <= 100: - self._math = value - else: - raise ValueError("Valid value must be in [0, 100]") - - @property - def chinese(self): - return self._chinese - - @chinese.setter - def chinese(self, value): - if 0 <= value <= 100: - self._chinese = value - else: - raise ValueError("Valid value must be in [0, 100]") - - @property - def english(self): - return self._english - - @english.setter - def english(self, value): - if 0 <= value <= 100: - self._english = value - else: - raise ValueError("Valid value must be in [0, 100]") - - def __repr__(self): - return "".format( - self.name, self.math, self.chinese, self.english - ) - -程序还是一样的人工智能,非常好。 - -|image2| - -你以为你写的代码,已经非常优秀,无懈可击了。 - -没想到,人外有天,你的主管看了你的代码后,深深地叹了口气:类里的三个属性,math、chinese、english,都使用了 -Property -对属性的合法性进行了有效控制。功能上,没有问题,但就是太啰嗦了,三个变量的合法性逻辑都是一样的,只要大于0,小于100 -就可以,代码重复率太高了,这里三个成绩还好,但假设还有地理、生物、历史、化学等十几门的成绩呢,这代码简直没法忍。去了解一下 -Python 的描述符吧。 - -经过主管的指点,你知道了「描述符」这个东西。怀着一颗敬畏之心,你去搜索了下关于 -描述符的用法。 - -其实也很简单,一个实现了 ``描述符协议`` 的类就是一个描述符。 - -什么描述符协议:实现了 -``__get__()``\ 、\ ``__set__()``\ 、\ ``__delete__()`` -其中至少一个方法的类,就是一个描述符。 - -- ``__get__``\ : - 用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。 -- ``__set__``\ :将在属性分配操作中调用。不会返回任何内容。 -- ``__delete__``\ :控制删除操作。不会返回内容。 - -对描述符有了大概的了解后,你开始重写上面的方法。 - -如前所述,Score 类是一个描述器,当从 Student 的实例访问 -math、chinese、english这三个属性的时候,都会经过 Score -类里的三个特殊的方法。这里的 Score 避免了 使用Property -出现大量的代码无法复用的尴尬。 - -.. code:: python + # python3 - class Score: - def __init__(self, default=0): - self._score = default - - def __set__(self, instance, value): - if not isinstance(value, int): - raise TypeError('Score must be integer') - if not 0 <= value <= 100: - raise ValueError('Valid value must be in [0, 100]') - - self._score = value - - def __get__(self, instance, owner): - return self._score - - def __delete__(self): - del self._score - - class Student: - math = Score(0) - chinese = Score(0) - english = Score(0) - - def __init__(self, name, math, chinese, english): - self.name = name - self.math = math - self.chinese = chinese - self.english = english - - - def __repr__(self): - return "".format( - self.name, self.math, self.chinese, self.english - ) - -实现的效果和前面的一样,可以对数据的合法性进行有效控制(字段类型、数值区间等) - -|image3| - -以上,我举了下具体的实例,从最原始的编码风格到 Property -,最后引出描述符。由浅入深,一步一步带你感受到描述符的优雅之处。 - -通过此文,你需要记住的只有一点,就是描述符给我们带来的编码上的便利,它在实现 -``保护属性不受修改``\ 、\ ``属性类型检查`` -的基本功能,同时有大大提高代码的复用率。 - --------------- - -1.17.2 描述符的访问规则 ------------------------ - -描述符分两种: - -- 数据描述符:实现了\ ``__get__`` 和 ``__set__`` 两种方法的描述符 -- 非数据描述符:只实现了\ ``__get__`` 一种方法的描述符 - -你一定会问,他们有什么区别呢?网上的讲解,我看过几个,很多都把一个简单的东西讲得复杂了。 - -其实就一句话,\ **数据描述器和非数据描述器的区别在于:它们相对于实例的字典的优先级不同**\ 。 - -如果实例字典中有与描述器同名的属性,如果描述器是数据描述器,优先使用数据描述器,如果是非数据描述器,优先使用字典中的属性。 + >>> str_obj = "你好" + >>> + >>> type(str_obj) + + >>> + >>> isinstance("你好", str) + True + >>> + >>> isinstance("你好", bytes) + False + >>> -这边还是以上节的成绩管理的例子来说明,方便你理解。 +而 bytes 是一个二进制序列对象,你只要你在定义字符串时前面加一个 +``b``\ ,就表示你要定义一个 bytes 类型的字符串对象。 .. code:: python - # 数据描述符 - class DataDes: - def __init__(self, default=0): - self._score = default - - def __set__(self, instance, value): - self._score = value - - def __get__(self, instance, owner): - print("访问数据描述符里的 __get__") - return self._score - - # 非数据描述符 - class NoDataDes: - def __init__(self, default=0): - self._score = default - - def __get__(self, instance, owner): - print("访问非数据描述符里的 __get__") - return self._score - - - class Student: - math = DataDes(0) - chinese = NoDataDes(0) - - def __init__(self, name, math, chinese): - self.name = name - self.math = math - self.chinese = chinese - - def __getattribute__(self, item): - print("调用 __getattribute__") - return super(Student, self).__getattribute__(item) - - def __repr__(self): - return "".format( - self.name, self.math, self.chinese) + # python3 + >>> byte_obj = b"Hello World!" + >>> type(byte_obj) + + >>> + >>> isinstance(byte_obj, str) + False + >>> + >>> isinstance(byte_obj, bytes) + True + >>> -需要注意的是,math 是数据描述符,而 chinese -是非数据描述符。从下面的验证中,可以看出,当实例属性和数据描述符同名时,会优先访问数据描述符(如下面的math),而当实例属性和非数据描述符同名时,会优先访问实例属性(\ ``__getattribute__``\ ) +但是在定义中文字符串时,你就不能直接在前面加 ``b`` 了,而应该使用 +``encode`` 转一下。 .. code:: python - >>> std = Student('xm', 88, 99) + >>> byte_obj=b"你好" + File "", line 1 + SyntaxError: bytes can only contain ASCII literal characters. + >>> + >>> str_obj="你好" + >>> + >>> str_obj.encode("utf-8") + b'\xe4\xbd\xa0\xe5\xa5\xbd' >>> - >>> std.math - 调用 __getattribute__ - 访问数据描述符里的 __get__ - 88 - >>> std.chinese - 调用 __getattribute__ - 99 - -讲完了数据描述符和非数据描述符,我们还需要了解的对象属性的查找规律。 -当我们对一个实例属性进行访问时,Python 会按 ``obj.__dict__`` → -``type(obj).__dict__`` → ``type(obj)的父类.__dict__`` -顺序进行查找,如果查找到目标属性并发现是一个描述符,Python -会调用描述符协议来改变默认的控制行为。 +2. Python 2 中 str 与 unicode +----------------------------- -1.17.3 基于描述符如何实现property ---------------------------------- +而在 Python2 中,字符串的类型又与 Python3 不一样,需要仔细区分。 -经过上面的讲解,我们已经知道如何定义描述符,且明白了描述符是如何工作的。 +在 Python2 里,字符串也只有两种类型,unicode 和 str 。 -正常人所见过的描述符的用法就是上篇文章提到的那些,我想说的是那只是描述符协议最常见的应用之一,或许你还不知道,其实有很多 -Python 的特性的底层实现机制都是基于 ``描述符协议`` -的,比如我们熟悉的\ ``@property`` 、\ ``@classmethod`` -、\ ``@staticmethod`` 和 ``super`` 等。 +只有 unicode object 和 非unicode object(其实应该叫 str object) +的区别: -先来说说 ``property`` 吧。 +- ``unicode string(unicode类型)``\ :以 Unicode code points + 形式存储,\ **人类认识的形式** +- ``byte string(str 类型)``\ :以 byte 形式存储,\ **机器认识的形式** -有了第一篇的基础,我们知道了 property -的基本用法。这里我直接切入主题,从第一篇的例子里精简了一下。 +当我们直接使用双引号或单引号包含字符的方式来定义字符串时,就是 str +字符串对象,比如这样 .. code:: python - class Student: - def __init__(self, name): - self.name = name - - @property - def math(self): - return self._math - - @math.setter - def math(self, value): - if 0 <= value <= 100: - self._math = value - else: - raise ValueError("Valid value must be in [0, 100]") - -不防再简单回顾一下它的用法,通过property装饰的函数,如例子中的 math -会变成 Student 实例的属性。而对 math 属性赋值会进入 使用 ``math.setter`` -装饰函数的逻辑代码块。 - -为什么说 property 底层是基于描述符协议的呢?通过 PyCharm 点击进入 -property -的源码,很可惜,只是一份类似文档一样的伪源码,并没有其具体的实现逻辑。 - -不过,从这份伪源码的魔法函数结构组成,可以大体知道其实现逻辑。 + # python2 -这里我自己通过模仿其函数结构,结合「描述符协议」来自己实现类 -``property`` 特性。 + >>> str_obj="你好" + >>> + >>> type(str_obj) + + >>> + >>> str_obj + '\xe4\xbd\xa0\xe5\xa5\xbd' + >>> + >>> isinstance(str_obj, bytes) + True + >>> isinstance(str_obj, str) + True + >>> isinstance(str_obj, unicode) + False + >>> + >>> str is bytes + True -代码如下: +而当我们在双引号或单引号前面加个 ``u``\ ,就表明我们定义的是 unicode +字符串对象,比如这样 .. code:: python - class TestProperty(object): - - def __init__(self, fget=None, fset=None, fdel=None, doc=None): - self.fget = fget - self.fset = fset - self.fdel = fdel - self.__doc__ = doc - - def __get__(self, obj, objtype=None): - print("in __get__") - if obj is None: - return self - if self.fget is None: - raise AttributeError - return self.fget(obj) + # python2 - def __set__(self, obj, value): - print("in __set__") - if self.fset is None: - raise AttributeError - self.fset(obj, value) - - def __delete__(self, obj): - print("in __delete__") - if self.fdel is None: - raise AttributeError - self.fdel(obj) + >>> unicode_obj = u"你好" + >>> + >>> unicode_obj + u'\u4f60\u597d' + >>> + >>> type(unicode_obj) + + >>> + >>> isinstance(unicode_obj, bytes) + False + >>> isinstance(unicode_obj, str) + False + >>> + >>> isinstance(unicode_obj, unicode) + True +3. 如何检测对象的编码 +--------------------- - def getter(self, fget): - print("in getter") - return type(self)(fget, self.fset, self.fdel, self.__doc__) +所有的字符,在 unicode +字符集中都有对应的编码值(英文叫做:\ ``code point``\ ) - def setter(self, fset): - print("in setter") - return type(self)(self.fget, fset, self.fdel, self.__doc__) +而把这些编码值按照一定的规则保存成二进制字节码,就是我们说的编码方式,常见的有:UTF-8,GB2312 +等。 - def deleter(self, fdel): - print("in deleter") - return type(self)(self.fget, self.fset, fdel, self.__doc__) +也就是说,当我们要将内存中的字符串持久化到硬盘中的时候,都要指定编码方法,而反过来,读取的时候,也要指定正确的编码方法(这个过程叫解码),不然会出现乱码。 -然后 Student 类,我们也相应改成如下 +那问题就来了,当我们知道了其对应的编码方法,我们就可以正常解码,但并不是所有时候我们都能知道应该用什么编码方式去解码? -.. code:: python +这时候就要介绍到一个 python 的库 – ``chardet`` ,使用它之前 需要先安装 - class Student: - def __init__(self, name): - self.name = name +:: - # 其实只有这里改变 - @TestProperty - def math(self): - return self._math + python3 -m pip install chardet - @math.setter - def math(self, value): - if 0 <= value <= 100: - self._math = value - else: - raise ValueError("Valid value must be in [0, 100]") +chardet 有一个 detect 方法,可以 ``预测``\ 其其编码格式 -为了尽量让你少产生一点疑惑,我这里做两点说明: +.. code:: python -1. 使用\ ``TestProperty``\ 装饰后,\ ``math`` - 不再是一个函数,而是\ ``TestProperty`` - 类的一个实例。所以第二个math函数可以使用 ``math.setter`` - 来装饰,本质是调用\ ``TestProperty.setter`` 来产生一个新的 - ``TestProperty`` 实例赋值给第二个\ ``math``\ 。 + >>> import chardet + >>> chardet.detect('微信公众号:Python编程时光'.encode('gbk')) + {'encoding': 'GB2312', 'confidence': 0.99, 'language': 'Chinese'} -2. 第一个 ``math`` 和第二个 ``math`` 是两个不同 ``TestProperty`` - 实例。但他们都属于同一个描述符类(TestProperty),当对 math - 对于赋值时,就会进入 ``TestProperty.__set__``\ ,当对math - 进行取值里,就会进入 - ``TestProperty.__get__``\ 。仔细一看,其实最终访问的还是Student实例的 - ``_math`` 属性。 +为什么说是预测呢,通过上面的输出来看,你会看到有一个 confidence +字段,其表示预测的可信度,或者说成功率。 -说了这么多,还是运行一下,更加直观一点。 +但是使用它时,若你的字符数较少,就有可能 “``误诊``”),比如只有 ``中文`` +两个字,就像下面这样,我们是 使用 gbk 编码的,使用 chardet 却识别成 +KOI8-R 编码。 .. code:: python - # 运行后,会直接打印这一行,这是在实例化 TestProperty 并赋值给第二个math - in setter + >>> str_obj = "中文" + >>> byte_obj = bytes(a, encoding='gbk') # 先得到一个 gbk 编码的 bytes >>> - >>> s1.math = 90 - in __set__ - >>> s1.math - in __get__ - 90 - -对于以上理解 ``property`` -的运行原理有困难的同学,请务必参照我上面写的两点说明。如有其他疑问,可以加微信与我进行探讨。 - -1.17.4 基于描述符如何实现staticmethod -------------------------------------- - -说完了 ``property`` ,这里再来讲讲 ``@classmethod`` 和 ``@staticmethod`` -的实现原理。 - -我这里定义了一个类,用了两种方式来实现静态方法。 + >>> chardet.detect(byte_obj) + {'encoding': 'KOI8-R', 'confidence': 0.682639754276994, 'language': 'Russian'} + >>> + >>> str_obj2 = str(byte_obj, encoding='KOI8-R') + >>> str_obj2 + 'жпнд' -.. code:: python +所以为了编码诊断的准确,要尽量使用足够多的字符。 - class Test: - @staticmethod - def myfunc(): - print("hello") +chardet +支持多国的语言,从官方文档中可以看到支持如下这些语言(https://chardet.readthedocs.io/en/latest/supported-encodings.html) - # 上下两种写法等价 +|image1| - class Test: - def myfunc(): - print("hello") - # 重点:这就是描述符的体现 - myfunc = staticmethod(myfunc) +4. 编码与解码的区别 +------------------- -这两种写法是等价的,就好像在 ``property`` -一样,其实以下两种写法也是等价的。 +编码和解码,其实就是 str 与 bytes 的相互转化的过程(Python 2 +已经远去,这里以及后面都只用 Python 3 举例) -.. code:: python +- **编码**\ :encode 方法,把字符串对象转化为二进制字节序列 - @TestProperty - def math(self): - return self._math - - math = TestProperty(fget=math) +- **解码**\ :decode 方法,把二进制字节序列转化为字符串对象 -话题还是转回到 ``staticmethod`` 这边来吧。 +|image2| -由上面的注释,可以看出 ``staticmethod`` -其实就相当于一个描述符类,而\ ``myfunc`` 在此刻变成了一个描述符。关于 -``staticmethod`` 的实现,你可以参照下面这段我自己写的代码,加以理解。 +那么假如我们真知道了其编码格式,如何来转成 unicode 呢? -|image4| +**有两种方法** -调用这个方法可以知道,每调用一次,它都会经过描述符类的 ``__get__`` 。 +**第一种**\ 是,直接使用 decode 方法 .. code:: python - >>> Test.myfunc() - in staticmethod __get__ - hello - >>> Test().myfunc() - in staticmethod __get__ - hello - -1.17.4 基于描述符如何实现classmethod ------------------------------------- + >>> byte_obj.decode('gbk') + '中文' + >>> -同样的 ``classmethod`` 也是一样。 +**第二种**\ 是,使用 str 类来转 .. code:: python - class classmethod(object): - def __init__(self, f): - self.f = f + >>> str_obj = str(byte_obj, encoding='gbk') + >>> str_obj + '中文' + >>> - def __get__(self, instance, owner=None): - print("in classmethod __get__") - - def newfunc(*args): - return self.f(owner, *args) - return newfunc +5. 如何设置文件编码 +------------------- - class Test: - def myfunc(cls): - print("hello") - - # 重点:这就是描述符的体现 - myfunc = classmethod(myfunc) +在 Python 2 中,默认使用的是 ASCII 编码来读取的,因此,我们在使用 Python +2 的时候,如果你的 python 文件里有中文,运行是会报错的。 -验证结果如下 +:: -.. code:: python + SyntaxError: Non-ASCII character '\xe4' in file demo.py - >>> Test.myfunc() - in classmethod __get__ - hello - >>> Test().myfunc() - in classmethod __get__ - hello +原因就是 ASCII 编码表太小,无法解释中文。 -讲完了 ``property``\ 、\ ``staticmethod``\ 和\ ``classmethod`` 与 -描述符的关系。我想你应该对描述符在 Python 中的应用有了更深的理解。对于 -super 的实现原理,就交由你来自己完成。 +而在 Python 3 中,默认使用的是 uft-8 来读取,所以省了不少的事。 -1.17.5 所有实例共享描述符 -------------------------- +对于这个问题,通常解决方法有两种: -若要合理使用描述符,利用描述符给我们带来的编码上的便利。有一个坑,需要注意,比如下面这个Student我们没有定义构造函数 +**第一种方法** -.. code:: python +在 python2 中,可以使用在头部指定 - class Score: - def __init__(self, default=0): - self._value = default +可以这样写,虽然很好看 - def __get__(self, instance, owner): - return self._value +:: - def __set__(self, instance, value): - if 0 <= value <= 100: - self._value = value - else: - raise ValueError + # -*- coding: utf-8 -*- +但这样写太麻烦了,我通常使用下面两种写法 - class Student: - math = Score(0) - chinese = Score(0) - english = Score(0) +:: - def __repr__(self): - return "".format(self.math, self.chinese, self.english) + # coding:utf-8 + # coding=utf-8 -看一下会出现什么样的问题,std2 居然共享了std1 -的属性值,因为它被当成了一个类变量了,而每个实例都没有自己的实例变量,自然访问的是同一个变量。这样是很难达到我们使用描述符的初衷。 +**第二种方法** -.. code:: python +:: + + import sys - >>> std1 = Student() - >>> std1 - - >>> std1.math = 85 - >>> std1 - - >>> std2 = Student() - >>> std2 # std2 居然共享了std1 的属性值 - - >>> std2.math = 100 - >>> std1 # std2 也会改变std1 的属性值 - + reload(sys) + sys.setdefaultencoding('utf-8') -而正确的做法应该是,所有的实例数据只属于该实例本身(通过实例初始化传入),具体写法可参考上一节。 +这里在调用sys.setdefaultencoding(‘utf-8’) +设置默认的解码方式之前,执行了reload(sys),这是必须的,因为python在加载完sys之后,会删除 +sys.setdefaultencoding 这个方法,我们需要重新载入sys,才能调用 +sys.setdefaultencoding 这个方法。 -参考文档 --------- +6. 参考文章 +----------- -- `Python描述器引导(翻译) `__ +- `阮一峰老师文章的常识性错误之 Unicode 与 + UTF-8 `__ +- `Strings, Bytes, and Unicode in Python 2 and + 3 `__ +- `字符编码笔记:ASCII,Unicode 和 + UTF-8 `__ -------------- -|image5| +|image3| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190425221322.png -.. |image2| image:: http://image.python-online.cn/20190425221322.png -.. |image3| image:: http://image.python-online.cn/20190425221233.png -.. |image4| image:: http://image.python-online.cn/20190519001930.png -.. |image5| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20200423185819.png +.. |image2| image:: http://image.iswbm.com/20200423190331.png +.. |image3| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_18.md b/source/c01/c01_18.md index d56d934..0a20d4b 100644 --- a/source/c01/c01_18.md +++ b/source/c01/c01_18.md @@ -2,7 +2,7 @@ ![](http://image.iswbm.com/20200602135014.png) -## 1.18.1 安装MySQL-python +## 1. 安装MySQL-python MySQL-python 这玩意实在是太难装了,为了以防后面再踩坑,这里还是记录一下吧。 @@ -50,29 +50,29 @@ brew install mysql@5.7 经过漫长的等待后,mysql 终于安装成功 -![](http://image.python-online.cn/20190615001340.png) +![](http://image.iswbm.com/20190615001340.png) 这时候,再 执行 pip install MySQL-python,发现还是报错。 -![](http://image.python-online.cn/20190615001414.png) +![](http://image.iswbm.com/20190615001414.png) 有经验的我,立马知道了 `mysql_config` 这个文件的路径可能没有在环境变量中。 -![](http://image.python-online.cn/20190615001633.png) +![](http://image.iswbm.com/20190615001633.png) 然后,我又重新执行 `pip install MySQL-python` ,发现还是报错。 -![](http://image.python-online.cn/20190615001706.png) +![](http://image.iswbm.com/20190615001706.png) 但是这个错误相对比较明显,明眼人一看就知道是权限不足。 那我就以 root 权限去安装好了。 -![](http://image.python-online.cn/20190615001908.png) +![](http://image.iswbm.com/20190615001908.png) 终于安装成功,折腾了两个晚上(主要是网速慢)。 -## 1.18.2 Mac 启动MySQL服务 +## 2. Mac 启动MySQL服务 使用 brew 安装 mysql 成功后,又陷入了一个坑。。 @@ -100,7 +100,7 @@ cd /usr/local/Cellar/mysql@5.7/5.7.25/bin 选择密码强度,视情况而写,我这边选最强的,长度大于8,有数字,有大小写,有特殊字符。 -![](http://image.python-online.cn/20190615112422.png) +![](http://image.iswbm.com/20190615112422.png) 接下来还会问你,是否删除其他匿名用户,是否删除 test 数据库,是否允许远程使用root登陆(安全起见我选不允许)。 @@ -112,7 +112,7 @@ mysql -uroot -p -## 1.18.3 Win上忘记密码 +## 3. Win上忘记密码 ```shell @@ -140,7 +140,7 @@ mysql -uroot -p -## 1.18.4 命令行使用技巧 +## 4. 命令行使用技巧 -![](http://image.python-online.cn/20190705225651.png) +![](http://image.iswbm.com/20190705225651.png) diff --git a/source/c01/c01_18.rst b/source/c01/c01_18.rst index 0b18f82..add8a60 100644 --- a/source/c01/c01_18.rst +++ b/source/c01/c01_18.rst @@ -3,8 +3,8 @@ |image0| -1.18.1 安装MySQL-python ------------------------ +1. 安装MySQL-python +------------------- MySQL-python 这玩意实在是太难装了,为了以防后面再踩坑,这里还是记录一下吧。 @@ -83,8 +83,8 @@ mysql,这时也请将其卸载再重新安装吧。 终于安装成功,折腾了两个晚上(主要是网速慢)。 -1.18.2 Mac 启动MySQL服务 ------------------------- +2. Mac 启动MySQL服务 +-------------------- 使用 brew 安装 mysql 成功后,又陷入了一个坑。。 @@ -124,8 +124,8 @@ mysql,这时也请将其卸载再重新安装吧。 mysql -uroot -p -1.18.3 Win上忘记密码 --------------------- +3. Win上忘记密码 +---------------- .. code:: shell @@ -151,17 +151,17 @@ mysql,这时也请将其卸载再重新安装吧。 # 再重新登陆,用新的密码登陆,发现可以生效 mysql -uroot -p -1.18.4 命令行使用技巧 ---------------------- +4. 命令行使用技巧 +----------------- |image7| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190615001340.png -.. |image2| image:: http://image.python-online.cn/20190615001414.png -.. |image3| image:: http://image.python-online.cn/20190615001633.png -.. |image4| image:: http://image.python-online.cn/20190615001706.png -.. |image5| image:: http://image.python-online.cn/20190615001908.png -.. |image6| image:: http://image.python-online.cn/20190615112422.png -.. |image7| image:: http://image.python-online.cn/20190705225651.png +.. |image1| image:: http://image.iswbm.com/20190615001340.png +.. |image2| image:: http://image.iswbm.com/20190615001414.png +.. |image3| image:: http://image.iswbm.com/20190615001633.png +.. |image4| image:: http://image.iswbm.com/20190615001706.png +.. |image5| image:: http://image.iswbm.com/20190615001908.png +.. |image6| image:: http://image.iswbm.com/20190615112422.png +.. |image7| image:: http://image.iswbm.com/20190705225651.png diff --git a/source/c01/c01_33.md b/source/c01/c01_19.md similarity index 96% rename from source/c01/c01_33.md rename to source/c01/c01_19.md index d5acadd..8d3745b 100644 --- a/source/c01/c01_33.md +++ b/source/c01/c01_19.md @@ -1,4 +1,4 @@ -# 1.33 如何调试已经运行中的程序 +# 1.19 如何调试已经运行中的程序 ![](http://image.iswbm.com/20200602135014.png) diff --git a/source/c01/c01_33.rst b/source/c01/c01_19.rst similarity index 96% rename from source/c01/c01_33.rst rename to source/c01/c01_19.rst index b0eded2..65fc111 100644 --- a/source/c01/c01_33.rst +++ b/source/c01/c01_19.rst @@ -1,4 +1,4 @@ -1.33 如何调试已经运行中的程序 +1.19 如何调试已经运行中的程序 ============================= |image0| diff --git a/source/c01/c01_20.md b/source/c01/c01_20.md index bc98f95..1e9fde1 100644 --- a/source/c01/c01_20.md +++ b/source/c01/c01_20.md @@ -1,102 +1,39 @@ -# 1.20 静态方法其实暗藏玄机 +# 1.20 在 CentOS 7.2 上安装 Python3.7 ![](http://image.iswbm.com/20200602135014.png) -这个标题「**静态方法其实暗藏玄机**」其实只是该文章的一个知识点。或许有些标题党,但没有关系,我相信有不少人对此并没有深入研究他们,不信我问你三个问题,你看能否答上来。 +首先下载 python3.7的源码包,然后解压 -1、Python2.x和3.x中,函数和方法的区分有什么不同? +```shell +$ cd ~ +$ wget -c https://www.python.org/ftp/python/3.7.1/Python-3.7.1.tgz +$ tar xf Python-3.7.1.tgz && cd Python-3.7.1 +``` -2、有了类/实例方法和普通函数,为什么还会有静态方法? +安装 一些依赖包 -3、Python3.x 中,静态方法有几种写法? +```shell +$ yum install gcc zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel libffi-devel python3-devel -y +``` -带着这三个问题,你可以尝试在下文中寻找答案。 +编译安装 ---- +```shell +$ ./configure +$ make +$ sudo make install +``` -在 Python 2 中的函数和方法的区别,十分清晰,很好分辨。但在 Python3中,我却发现完全又是另一套准则。 +至此,你已经成功安装 了 Python3, pip3,setuptools -首先先来 Python2 的(以下在 Python2.7中测试通过) +requests.get("https://www.baidu.com") -![](http://image.python-online.cn/20190630111243.png) +``` +python3 -m pip install --user requests aiohttp cryptography pymysql prettytable sh Fabric paramiko apscheduler bashplotlib httpie PathPicker -i https://pypi.douban.com/simple +``` -可以得出结论: -1、普通函数(未定位在类里),都是函数。 -2、静态方法(@staticmethod),都是函数。 -3、类方法(@classmethod),都是方法。 - -4、实例方法(首参数为self且非静态、非类方法的),都是方法。 - -你一定想说,类方法和实例方法,是方法没错呀,毕竟名字本身就有方法,普通函数是函数,我也理解呀。那静态方法,为什么不是方法而是函数呢? - -名字只是一个外在的表面称呼,你能说「赵铁男」就一定是汉子吗? - -我的理解是:方法是一种和对象(实例或者类)绑定后的函数。 - -类方法的首参是`cls`,调用时,无需显示传入。实例方法首参是self,调用时,也无需显示传入。 - -而静态方法,其实和普通函数没啥区别,唯一的区别就是,他定义的位置被放在了类里,做为类或者实例的一个函数。 - -那你肯定又要问了,既然静态方法和普通函数都是一样的,为什么要刻意将它放在类里呢? - -我上面说了,放在类里定义,就可以让它成为类的一个工具函数,这就像你身上随身携带了一把刀,这把刀与你本人并没有什么直接关系,唯一的联系就是这把刀是你的,而你把它带在身上,无论你去到哪里,只要需要,你就可以直接拿出来用上。对比将函数放在类外面,缺点是什么呢?就是当你出门在外(在别的模块里)发现你要用刀的时候,还要特地跑一趟去商店买一把刀(import 这个函数)。 - -另外,我觉得静态方法在业务和设计上的意义,会更多一些。 - -一般静态方法是做为类或者实例的一个工具函数,比如对变量的做一个合法性的检验,对数据进行序列化反序列化操作等等。 - -说完了 Python2 ,再来说说Python3. - -以前我觉得 Python2 对于方法和函数的界线更加清晰,但接触了 Python3,我反而觉得Python3里方法和函数的区分似乎更加合理。 - -还是刚刚那段代码,我更改了解释器为Python3.6(以下在 Python3.6中测试通过) - -![](http://image.python-online.cn/20190630104956.png) - -和Python2的唯一区别是,`People.jump` 在Python3 中变成了函数。 - -这一下颠覆了你刚刚才建立起来的知识体系有木有? - -先别急,我再做个试验,也许你就知道了。 - -**在 Python2中** - -执行People.jump('hello'),会报错说,jump的首参必须为People的实例对象,这可以理解,毕竟jump定义时,第一个参数为self。 - -![](http://image.python-online.cn/20190630105735.png) - -**在 Python3中** - -你可以发现,这里的jump的首参不再要求是 People 的一个实例,而可以是任意的对象,比如我使用字符串对象,也没有报错。 - -![](http://image.python-online.cn/20190630105600.png) - -也就是说,当你往jump中传入的首参为People的实例时,jump 就是方法,而当你传入的首参不是People的实例对象时,jump就是函数。 - -你看,多么灵活呀。 - -再总结一下,在Python3中: - -1、普通函数(未定位在类里),都是函数。 - -2、静态方法(@staticmethod),都是函数。 - -3、类方法(@classmethod),都是方法。 - -4、方法和函数区分没有那么明确,而是更加灵活了,一个函数有可能是方法也有可能是函数。 - -你肯定又要问了,那这是不是就意味着,Python3 中静态方法,可以不用再使用@staticmethod 装饰了呢,反正Python3都可以识别。 - -这是个好问题,是的,可以不用指定,但是最好指定,如果你不指定,你调用这个方法只能通过People.jump,而不能通过 self.jump了,因为首参不是 self,而如果使用@staticmethod 就可以使用self.jump。 - -所以说这是一个规范,就像类的私有方法,规范要求外部最好不要调用,但这不是强制要求,不是说外部就不能调用。 - -写这篇文章的起源,是前两天有位读者在交流里问到了相关的问题,正好没什么主题可以写,就拿过来做为素材整理一下,也正好没有写过静态方法、类方法的内容,没想到简单的东西,也能写出这么多的内容出来。 - - - -![](http://image.iswbm.com/20200607174235.png) +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_20.rst b/source/c01/c01_20.rst index de5e995..843d931 100644 --- a/source/c01/c01_20.rst +++ b/source/c01/c01_20.rst @@ -1,118 +1,40 @@ -1.20 静态方法其实暗藏玄机 -========================= +1.20 在 CentOS 7.2 上安装 Python3.7 +=================================== |image0| -这个标题「\ **静态方法其实暗藏玄机**\ 」其实只是该文章的一个知识点。或许有些标题党,但没有关系,我相信有不少人对此并没有深入研究他们,不信我问你三个问题,你看能否答上来。 +首先下载 python3.7的源码包,然后解压 -1、Python2.x和3.x中,函数和方法的区分有什么不同? +.. code:: shell -2、有了类/实例方法和普通函数,为什么还会有静态方法? + $ cd ~ + $ wget -c https://www.python.org/ftp/python/3.7.1/Python-3.7.1.tgz + $ tar xf Python-3.7.1.tgz && cd Python-3.7.1 -3、Python3.x 中,静态方法有几种写法? +安装 一些依赖包 -带着这三个问题,你可以尝试在下文中寻找答案。 +.. code:: shell --------------- + $ yum install gcc zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel libffi-devel python3-devel -y -在 Python 2 中的函数和方法的区别,十分清晰,很好分辨。但在 -Python3中,我却发现完全又是另一套准则。 +编译安装 -首先先来 Python2 的(以下在 Python2.7中测试通过) +.. code:: shell -|image1| - -可以得出结论: - -1、普通函数(未定位在类里),都是函数。 - -2、静态方法(@staticmethod),都是函数。 - -3、类方法(@classmethod),都是方法。 - -4、实例方法(首参数为self且非静态、非类方法的),都是方法。 - -你一定想说,类方法和实例方法,是方法没错呀,毕竟名字本身就有方法,普通函数是函数,我也理解呀。那静态方法,为什么不是方法而是函数呢? - -名字只是一个外在的表面称呼,你能说「赵铁男」就一定是汉子吗? - -我的理解是:方法是一种和对象(实例或者类)绑定后的函数。 - -类方法的首参是\ ``cls``\ ,调用时,无需显示传入。实例方法首参是self,调用时,也无需显示传入。 - -而静态方法,其实和普通函数没啥区别,唯一的区别就是,他定义的位置被放在了类里,做为类或者实例的一个函数。 - -那你肯定又要问了,既然静态方法和普通函数都是一样的,为什么要刻意将它放在类里呢? - -我上面说了,放在类里定义,就可以让它成为类的一个工具函数,这就像你身上随身携带了一把刀,这把刀与你本人并没有什么直接关系,唯一的联系就是这把刀是你的,而你把它带在身上,无论你去到哪里,只要需要,你就可以直接拿出来用上。对比将函数放在类外面,缺点是什么呢?就是当你出门在外(在别的模块里)发现你要用刀的时候,还要特地跑一趟去商店买一把刀(import -这个函数)。 - -另外,我觉得静态方法在业务和设计上的意义,会更多一些。 - -一般静态方法是做为类或者实例的一个工具函数,比如对变量的做一个合法性的检验,对数据进行序列化反序列化操作等等。 - -说完了 Python2 ,再来说说Python3. - -以前我觉得 Python2 对于方法和函数的界线更加清晰,但接触了 -Python3,我反而觉得Python3里方法和函数的区分似乎更加合理。 - -还是刚刚那段代码,我更改了解释器为Python3.6(以下在 -Python3.6中测试通过) + $ ./configure + $ make + $ sudo make install -|image2| +至此,你已经成功安装 了 Python3, pip3,setuptools -和Python2的唯一区别是,\ ``People.jump`` 在Python3 中变成了函数。 +requests.get(“https://www.baidu.com”) -这一下颠覆了你刚刚才建立起来的知识体系有木有? +:: -先别急,我再做个试验,也许你就知道了。 + python3 -m pip install --user requests aiohttp cryptography pymysql prettytable sh Fabric paramiko apscheduler bashplotlib httpie PathPicker -i https://pypi.douban.com/simple -**在 Python2中** - -执行People.jump(‘hello’),会报错说,jump的首参必须为People的实例对象,这可以理解,毕竟jump定义时,第一个参数为self。 - -|image3| - -**在 Python3中** - -你可以发现,这里的jump的首参不再要求是 People -的一个实例,而可以是任意的对象,比如我使用字符串对象,也没有报错。 - -|image4| - -也就是说,当你往jump中传入的首参为People的实例时,jump -就是方法,而当你传入的首参不是People的实例对象时,jump就是函数。 - -你看,多么灵活呀。 - -再总结一下,在Python3中: - -1、普通函数(未定位在类里),都是函数。 - -2、静态方法(@staticmethod),都是函数。 - -3、类方法(@classmethod),都是方法。 - -4、方法和函数区分没有那么明确,而是更加灵活了,一个函数有可能是方法也有可能是函数。 - -你肯定又要问了,那这是不是就意味着,Python3 -中静态方法,可以不用再使用@staticmethod -装饰了呢,反正Python3都可以识别。 - -这是个好问题,是的,可以不用指定,但是最好指定,如果你不指定,你调用这个方法只能通过People.jump,而不能通过 -self.jump了,因为首参不是 self,而如果使用@staticmethod -就可以使用self.jump。 - -所以说这是一个规范,就像类的私有方法,规范要求外部最好不要调用,但这不是强制要求,不是说外部就不能调用。 - -写这篇文章的起源,是前两天有位读者在交流里问到了相关的问题,正好没什么主题可以写,就拿过来做为素材整理一下,也正好没有写过静态方法、类方法的内容,没想到简单的东西,也能写出这么多的内容出来。 - -|image5| +|image1| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190630111243.png -.. |image2| image:: http://image.python-online.cn/20190630104956.png -.. |image3| image:: http://image.python-online.cn/20190630105735.png -.. |image4| image:: http://image.python-online.cn/20190630105600.png -.. |image5| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_21.md b/source/c01/c01_21.md index b4a4f30..cf1190c 100644 --- a/source/c01/c01_21.md +++ b/source/c01/c01_21.md @@ -1,88 +1,216 @@ -# 1.21 开发小技巧 +# 1.21 Python 炫技操作:连接列表的八种方法 ![](http://image.iswbm.com/20200602135014.png) -## 1. 解决网页鼠标限制 +Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 -``` -解决网页不能选中,在console中输入:document.onselectstart=true -解决网页不能复制,在console中输入:document.oncopy=true -解决网页不能右键,在console中输入:document.oncontextmenu=true -``` +但你要知道,在团队合作里,炫技是大忌。 -## 2. 在 linux 上看 json 文件 +为什么这么说呢?我说下自己的看法: +1. 越简洁的代码,越清晰的逻辑,就越不容易出错; +2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 +3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) + +该篇是「**炫技系列**」的第三篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 Python 发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 + +## 1. 最直观的相加 + +使用 `+` 对多个列表进行相加,你应该懂,不多说了。 + +```python +>>> list01 = [1,2,3] +>>> list02 = [4,5,6] +>>> list03 = [7,8,9] +>>> +>>> list01 + list02 + list03 +[1, 2, 3, 4, 5, 6, 7, 8, 9] +>>> ``` -cat test.json | python -m json.tool -``` -## 3. 在 Mac 上多开微信 + +## 2. 借助 itertools + +itertools 在 Python 里有一个非常强大的内置模块,它专门用于操作可迭代对象。 + +在前面的文章中也介绍过,使用 `itertools.chain()` 函数先可迭代对象(在这里指的是列表)串联起来,组成一个更大的可迭代对象。 + +最后你再利用 list 将其转化为 列表。 + +```python +>>> from itertools import chain +>>> list01 = [1,2,3] +>>> list02 = [4,5,6] +>>> list03 = [7,8,9] +>>> +>>> list(chain(list01, list02, list03)) +[1, 2, 3, 4, 5, 6, 7, 8, 9] +>>> ``` -open -n /Applications/WeChat.app + +## 3. 使用 * 解包 + +在 [Python 炫技操作(02):合并字典的七种方法](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486080&idx=1&sn=f1c5c4fc5363a1d787b9ae9baba0d07b&chksm=e8866a62dff1e374ad1e5ae2e51bc6cbeb41f631899cde980555ded61be840f0fe6c76c44b7d&token=688334198&lang=zh_CN#rd) 提到了使用 `**` 可解包字典。 + +与它相似的,使用 `*` 可以解包列表。 `*` 和 `**` 常用在函数定义时,设置可变参数。 + +现在我将它单独拿出来用在多个列表的合并。 + +示例如下: + +```python +>>> list01 = [1,2,3] +>>> list02 = [4,5,6] +>>> +>>> [*list01, *list02] +[1, 2, 3, 4, 5, 6] +>>> ``` -## 3. 微信测试是否被删除 + +## 4. 使用 extend + +在字典中,使用 update 可实现原地更新,而在列表中,使用 extend 可实现列表的自我扩展。 + +```python +>>> list01 = [1,2,3] +>>> list02 = [4,5,6] +>>> +>>> list01.extend(list02) +>>> list01 +[1, 2, 3, 4, 5, 6] ``` -1. 转账(不是好友不能点) -2. 拉群(不说话不会提示) + +## 5. 使用列表推导式 + +Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 + +那就是列表解析式,集合解析式和字典解析式,通常是 Python 发烧友的最爱,那么今天的主题:列表合并,列表推导式还能否胜任呢? + +当然可以,具体示例代码如下: + +```python +>>> list01 = [1,2,3] +>>> list02 = [4,5,6] +>>> list03 = [7,8,9] +>>> +>>> [x for l in (list01, list02, list03) for x in l] +[1, 2, 3, 4, 5, 6, 7, 8, 9] +>>> ``` -## 4. Mac 启动台 app 大小调整 + +## 6. 使用 heapq + +heapq 是 Python 的一个标准模块,它提供了堆排序算法的实现。 + +该模块里有一个 merge 方法,可以用于合并多个列表,如下所示 + +```python +>>> list01 = [1,2,3] +>>> list02 = [4,5,6] +>>> list03 = [7,8,9] +>>> +>>> from heapq import merge +>>> +>>> list(merge(list01, list02, list03)) +[1, 2, 3, 4, 5, 6, 7, 8, 9] +>>> ``` -1.终端运行命令:10代表一行显示10个图标,几个可以自定义 -defaults write com.apple.dock springboard-columns -int 10 -2.设置完需要重新启动一下 启动台 -killall Dock +要注意的是,heapq.merge 除了合并多个列表外,它还会将合并后的最终的列表进行排序。 + +```python +>>> list01 = [2,5,3] +>>> list02 = [1,4,6] +>>> list03 = [7,9,8] +>>> +>>> from heapq import merge +>>> +>>> list(merge(list01, list02, list03)) +[1, 2, 4, 5, 3, 6, 7, 9, 8] +>>> ``` -## 5. 重定向标准输出到文件 +它的效果等价于下面这行代码: +```python +sorted(itertools.chain(*iterables)) ``` -import contextlib -def unshelve_task(): - pass +如果你希望得到一个始终有序的列表,那请第一时间想到 heapq.merge,因为它采用堆排序,效率非常高。但若你不希望得到一个排过序的列表,就不要使用它了。 -@contextlib.contextmanager -def close_stdout(): - raw_stdout = sys.stdout - file = open(log_file, 'a+') - sys.stdout = file +## 7. 借助魔法方法 - yield +在之前的文章里,把魔法方法介绍得很全。 - sys.stdout = raw_stdout - file.close() - -with close_stdout(): - unshelve_task() -``` +[非常全的通俗易懂 Python 魔法方法指南(上)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485551&idx=1&sn=4c0983f22269a113bcdf83690e5e2b20&chksm=e886688ddff1e19b9ad230128a67ee1a9ee1eced0720c14b5d48f68943be10b1b85b23d8ca2d#rd) -## 6. 将子网掩码转换为cidr +[非常全的通俗易懂 Python 魔法方法指南(下)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485555&idx=1&sn=0a218b796e651b451a17112e22790d07&chksm=e8866891dff1e18771a9392da7f509732244ebc4d1a6e2427acd39ee8b59b146e3d4961a2a62#rd) -如何使用netaddr库将ipv4子网掩码转换为cidr表示法? -示例:255.255.255.0到/ 24 +其中有一个魔法方法是 `__add__`,实际 上当我们使用第一种方法 list01 + list02 的时候,内部实际上是作用在 `__add__` 这个魔法方法上的. -使用netaddr: +所以以下两种方法其实是等价的 ``` ->>> from netaddr import IPAddress ->>> IPAddress("255.255.255.0").netmask_bits() -24 +>>> list01 = [1,2,3] +>>> list02 = [4,5,6] +>>> +>>> list01 + list02 +[1, 2, 3, 4, 5, 6] +>>> +>>> +>>> list01.__add__(list02) +[1, 2, 3, 4, 5, 6] +>>> ``` -您也可以在不使用任何库的情况下执行此操作,只需在网络掩码的二进制表示中计算1位: +借用这个魔法特性,我们可以 reduce 这个方法来对多个列表进行合并,示例代码如下 +```python +>>> list01 = [1,2,3] +>>> list02 = [4,5,6] +>>> list03 = [7,8,9] +>>> +>>> from functools import reduce +>>> reduce(list.__add__, (list01, list02, list03)) +[1, 2, 3, 4, 5, 6, 7, 8, 9] +>>> ``` ->>> netmask = "255.255.255.0" ->>> sum([bin(int(x)).count("1") for x in netmask.split(".")]) -24 + + + +## 8. 使用 yield from + +在很早的一篇文章里([并发编程08|深入理解yield from语法](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485063&idx=1&sn=0ff7a99058320ff90a6237e7e03367fb&scene=21#wechat_redirect)),我很详细的介绍了 yield from 意义及使用方法。 + +在 yield from 后可接一个可迭代对象,用于迭代并返回其中的每一个元素。 + +因此,我们可以像下面这样自定义一个合并列表的工具函数。 + +```python +>>> list01 = [1,2,3] +>>> list02 = [4,5,6] +>>> list03 = [7,8,9] +>>> +>>> def merge(*lists): +... for l in lists: +... yield from l +... +>>> list(merge(list01, list02, list03)) +[1, 2, 3, 4, 5, 6, 7, 8, 9] +>>> ``` -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file +--- + + + +![](http://image.iswbm.com/20200607174235.png) + + diff --git a/source/c01/c01_21.rst b/source/c01/c01_21.rst index a3b4ae5..8302f52 100644 --- a/source/c01/c01_21.rst +++ b/source/c01/c01_21.rst @@ -1,95 +1,230 @@ -1.21 开发小技巧 -=============== +1.21 Python 炫技操作:连接列表的八种方法 +======================================== |image0| -1. 解决网页鼠标限制 -------------------- +Python 语言里有许多(而且是越来越多)的高级特性,是 Python +发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 -:: +但你要知道,在团队合作里,炫技是大忌。 - 解决网页不能选中,在console中输入:document.onselectstart=true - 解决网页不能复制,在console中输入:document.oncopy=true - 解决网页不能右键,在console中输入:document.oncontextmenu=true +为什么这么说呢?我说下自己的看法: -2. 在 linux 上看 json 文件 --------------------------- +1. 越简洁的代码,越清晰的逻辑,就越不容易出错; +2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 +3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) -:: +该篇是「\ **炫技系列**\ 」的第三篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 +Python +发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 - cat test.json | python -m json.tool +1. 最直观的相加 +--------------- -3. 在 Mac 上多开微信 --------------------- +使用 ``+`` 对多个列表进行相加,你应该懂,不多说了。 -:: +.. code:: python - open -n /Applications/WeChat.app + >>> list01 = [1,2,3] + >>> list02 = [4,5,6] + >>> list03 = [7,8,9] + >>> + >>> list01 + list02 + list03 + [1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> -3. 微信测试是否被删除 ---------------------- +2. 借助 itertools +----------------- -:: +itertools 在 Python +里有一个非常强大的内置模块,它专门用于操作可迭代对象。 - 1. 转账(不是好友不能点) - 2. 拉群(不说话不会提示) +在前面的文章中也介绍过,使用 ``itertools.chain()`` +函数先可迭代对象(在这里指的是列表)串联起来,组成一个更大的可迭代对象。 -4. Mac 启动台 app 大小调整 --------------------------- +最后你再利用 list 将其转化为 列表。 -:: +.. code:: python - 1.终端运行命令:10代表一行显示10个图标,几个可以自定义 - defaults write com.apple.dock springboard-columns -int 10 + >>> from itertools import chain + >>> list01 = [1,2,3] + >>> list02 = [4,5,6] + >>> list03 = [7,8,9] + >>> + >>> list(chain(list01, list02, list03)) + [1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> - 2.设置完需要重新启动一下 启动台 - killall Dock +3. 使用 \* 解包 +--------------- -5. 重定向标准输出到文件 ------------------------ +在 `Python +炫技操作(02):合并字典的七种方法 `__ +提到了使用 ``**`` 可解包字典。 -:: +与它相似的,使用 ``*`` 可以解包列表。 ``*`` 和 ``**`` +常用在函数定义时,设置可变参数。 - import contextlib +现在我将它单独拿出来用在多个列表的合并。 - def unshelve_task(): - pass +示例如下: - @contextlib.contextmanager - def close_stdout(): - raw_stdout = sys.stdout - file = open(log_file, 'a+') - sys.stdout = file +.. code:: python - yield + >>> list01 = [1,2,3] + >>> list02 = [4,5,6] + >>> + >>> [*list01, *list02] + [1, 2, 3, 4, 5, 6] + >>> - sys.stdout = raw_stdout - file.close() - - with close_stdout(): - unshelve_task() +4. 使用 extend +-------------- -6. 将子网掩码转换为cidr ------------------------ +在字典中,使用 update 可实现原地更新,而在列表中,使用 extend +可实现列表的自我扩展。 -如何使用netaddr库将ipv4子网掩码转换为cidr表示法? 示例:255.255.255.0到/ -24 +.. code:: python -使用netaddr: + >>> list01 = [1,2,3] + >>> list02 = [4,5,6] + >>> + >>> list01.extend(list02) + >>> list01 + [1, 2, 3, 4, 5, 6] -:: +5. 使用列表推导式 +----------------- + +Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 + +那就是列表解析式,集合解析式和字典解析式,通常是 Python +发烧友的最爱,那么今天的主题:列表合并,列表推导式还能否胜任呢? + +当然可以,具体示例代码如下: + +.. code:: python + + >>> list01 = [1,2,3] + >>> list02 = [4,5,6] + >>> list03 = [7,8,9] + >>> + >>> [x for l in (list01, list02, list03) for x in l] + [1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> + +6. 使用 heapq +------------- + +heapq 是 Python 的一个标准模块,它提供了堆排序算法的实现。 + +该模块里有一个 merge 方法,可以用于合并多个列表,如下所示 + +.. code:: python + + >>> list01 = [1,2,3] + >>> list02 = [4,5,6] + >>> list03 = [7,8,9] + >>> + >>> from heapq import merge + >>> + >>> list(merge(list01, list02, list03)) + [1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> + +要注意的是,heapq.merge +除了合并多个列表外,它还会将合并后的最终的列表进行排序。 + +.. code:: python + + >>> list01 = [2,5,3] + >>> list02 = [1,4,6] + >>> list03 = [7,9,8] + >>> + >>> from heapq import merge + >>> + >>> list(merge(list01, list02, list03)) + [1, 2, 4, 5, 3, 6, 7, 9, 8] + >>> + +它的效果等价于下面这行代码: + +.. code:: python + + sorted(itertools.chain(*iterables)) + +如果你希望得到一个始终有序的列表,那请第一时间想到 +heapq.merge,因为它采用堆排序,效率非常高。但若你不希望得到一个排过序的列表,就不要使用它了。 + +7. 借助魔法方法 +--------------- + +在之前的文章里,把魔法方法介绍得很全。 + +`非常全的通俗易懂 Python +魔法方法指南(上) `__ + +`非常全的通俗易懂 Python +魔法方法指南(下) `__ - >>> from netaddr import IPAddress - >>> IPAddress("255.255.255.0").netmask_bits() - 24 +其中有一个魔法方法是 ``__add__``\ ,实际 上当我们使用第一种方法 list01 + +list02 的时候,内部实际上是作用在 ``__add__`` 这个魔法方法上的. -您也可以在不使用任何库的情况下执行此操作,只需在网络掩码的二进制表示中计算1位: +所以以下两种方法其实是等价的 :: - >>> netmask = "255.255.255.0" - >>> sum([bin(int(x)).count("1") for x in netmask.split(".")]) - 24 + >>> list01 = [1,2,3] + >>> list02 = [4,5,6] + >>> + >>> list01 + list02 + [1, 2, 3, 4, 5, 6] + >>> + >>> + >>> list01.__add__(list02) + [1, 2, 3, 4, 5, 6] + >>> + +借用这个魔法特性,我们可以 reduce +这个方法来对多个列表进行合并,示例代码如下 + +.. code:: python + + >>> list01 = [1,2,3] + >>> list02 = [4,5,6] + >>> list03 = [7,8,9] + >>> + >>> from functools import reduce + >>> reduce(list.__add__, (list01, list02, list03)) + [1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> + +8. 使用 yield from +------------------ + +在很早的一篇文章里(\ `并发编程08|深入理解yield +from语法 `__\ ),我很详细的介绍了 +yield from 意义及使用方法。 + +在 yield from 后可接一个可迭代对象,用于迭代并返回其中的每一个元素。 + +因此,我们可以像下面这样自定义一个合并列表的工具函数。 + +.. code:: python + + >>> list01 = [1,2,3] + >>> list02 = [4,5,6] + >>> list03 = [7,8,9] + >>> + >>> def merge(*lists): + ... for l in lists: + ... yield from l + ... + >>> list(merge(list01, list02, list03)) + [1, 2, 3, 4, 5, 6, 7, 8, 9] + >>> + +-------------- |image1| diff --git a/source/c01/c01_22.md b/source/c01/c01_22.md index 6590721..39cd84d 100644 --- a/source/c01/c01_22.md +++ b/source/c01/c01_22.md @@ -1,193 +1,175 @@ -# 1.22 如何修改 CentOS 6.x 上默认Python +# 1.22 Python 炫技操作:海象运算符的三种用法 ![](http://image.iswbm.com/20200602135014.png) -最近在工作中遇到一个问题,就是有一个功能希望在各种服务器上实现,而服务器上的系统版本可能都不一样,有的是 CentOS 6.x,有的是 CentOS 7.x 。 +Python 版本发展非常快,如今最新的版本已经是 Pyhton 3.9,即便如此,有很多人甚至还停留在 3.6 或者 3.7,连 3.8 还没用上。 -需要说明的一点是,CentOS 6.x 上的 Python 版本是 2.6.x 的,而 CentOS 7.x 上的 Python 版本是 2.7.x 的,这意味着我要实现的功能要适配这两种版本的系统。 +很多 Python 3.8 的特性还没来得及了解,就已经成为旧知识了,比如今天要说的海象运算符。 -你可能会说,这有什么的,自己写的时候,注意一下就好了。 +海象运算符是在 PEP 572 被提出的,直到 3.8 版本合入发布。 -事情其实没有那么容易,我要实现的功能是基于一个框架进行定制,需要修改不少的框架代码。这个框架在不同的 Linux 版本上,是有不同的版本的,而且差异巨大,曾经想过在 CentOS 6.x 和 CentOS 7.x 将这个框架安装成同一个版本,最后还是失败了,无法安装,原因就是高版本需要 Python2.7,而 CentOS 6.x 上只有 Python2.6。 +它的英文原名叫 `Assignment Expressions`,翻译过来也就是 `赋值表达式`,不过现在大家更普遍地称之为海象运算符,就是因为它长得真的太像海象了。 -这个历史问题一直遗留到现在,由于这次的功能影响到的代码较多,如果要对两个版本的框架分别进行定制的话,需要花不少的时间,为了不维护两套版本,避免浪费多余的精力去做适配,我决定将 CentOS 6.x 上默认的 Python2.6 升级成 Python2.7。 +![](http://image.iswbm.com/image-20200418122739417.png) -下面是整个升级过程,别看步骤简单,这些精简步骤的背后可是有不少的坑,被我踩过后,你可以直接使用了。 +## 1. 第一个用法:if/else +可能有朋友是第一次接触这个新特性,所以还是简单的介绍一下这个海象运算符有什么用? +在 Golang 中的条件语句可以直接在 if 中运算变量的获取后直接对这个变量进行判断,可以让你少写一行代码 +```go +import "fmt" -1. 首先确认下你机器上的默认的 Python 版本 - -```shell -$ python -V -Python 2.6.6 - -$ whereis python -python: /usr/bin/python /usr/bin/python2.6 /usr/lib/python2.6 /usr/lib64/python2.6 /usr/local/bin/python /usr/include/python2.6 /usr/share/man/man1/python.1.gz +func main() { + if age := 20;age > 18 { + fmt.Println("已经成年了") + } +} ``` - - -2. 由于我们将使用编译安装的方式,所以要安装下 gcc,及一些工具包。 +若在 Python 3.8 之前,Python 必须得这样子写 -注意一定要全部安装,不然后面会发现有不少 python 的工具用不了。 +```python +age = 20 +if age > 18: + print("已经成年了") +``` -比如不安装 zlib 会无法安装 setuptools,不装 openssl 和 openssl-devel,会无法使用 pip 工具等 +但有了海象运算符之后,你可以和 Golang 一样(如果你没学过 Golang,那这里要注意,Golang 中的 `:=` 叫短变量声明,意思是声明并初始化,它和 Python 中的 `:=` 不是一个概念) -``` -$ yum install gcc -y -$ yum groupinstall "Development tools" -$ yum install zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel -y +```python +if (age:= 20) > 18: + print("已经成年了") ``` - 如果你这里未按照我的步骤来安装,你后面使用的时候出现了各种各样的问题,不要慌,只要再回来这里,把没安装的包装上,安装完成后,你需要进入第四步重新编译安装Python。 +## 2. 第二个用法:while -3. 下载最新的 Python2.7.x 安装包,解压并进入指定目录 +在不使用 海象运算符之前,使用 while 循环来读取文件的时候,你也许会这么写 -``` -$ wget https://www.python.org/ftp/python/2.7.14/Python-2.7.14.tgz -$ tar zxvf Python-2.7.14.tgz -$ cd Python-2.7.14 +```python +file = open("demo.txt", "r") +while True: + line = file.readline() + if not line: + break + print(line.strip()) ``` +但有了海象运算符之后,你可以这样 - -4. 配置,编译,安装 - -```shell -# --prefix 指定 python 安装的路径 -$ ./configure --prefix=/usr/local/python/python2.7 -$ make -$ make install +```python +file = open("demo.txt", "r") +while (line := file.readline()): + print(line.strip()) ``` -`./configure` 命令执行完毕之后创建一个文件creating Makefile,供下面的make命令使用 执行 `make install` 之后就会把程序安装到我们指定的目录中去。 +使用它替换以往的无限 while 循环写法更为惊艳 -Configure是一个可执行脚本,它有很多选项,在待安装的源码路径下使用命令./configure –help输出详细的选项列表。其中 `--prefix` 选项是配置安装的路径,如果不配置该选项,安装后可执行文件默认放在/usr /local/bin,库文件默认放在 `/usr/local/lib` ,配置文件默认放在 `/usr/local/etc` ,其它的资源文件放在 `/usr /local/share`。如果配置 `--prefix`,如:`./configure --prefix=/usr/local/test` 可以把所有资源文件放在/usr/local/test的路径中,不会杂乱。 +比如,实现一个需要命令行交互输入密码并检验的代码,你也许会这样子写 -用了 `--prefix` 选项的另一个好处是卸载软件或移植软件。当某个安装的软件不再需要时,只须简单的删除该安装目录,就可以把软件卸载得干干净净;移植软件只需拷贝整个目录到另外一个机器即可(相同的操作系统)。当然要卸载程序,也可以在原来的make目录下用一次 `make uninstall`,但前提是make文件指定过uninstall。 - - +```python +while True: + p = input("Enter the password: ") + if p == "youpassword": + break +``` -5. 查看系统的 Python 版本 +有了海象运算符之后,这样子写更为舒服 -```shell -$ python -V -Python 2.6.6 +```python +while (p := input("Enter the password: ")) != "youpassword": + continue ``` - 如果你查看还是 Python 2.6.6 版本,请继续看第六步。 +## 3. 第三个用法:推导式 -6. 修改系统默认的 Python 版本 +这个系列的文章,几乎每篇都能看到推导式的身影,这一篇依旧如此。 -查看新安装的Python版本,当前系统的Python版本,并将系统指向的Python从2.6.x修改为2.7.x,再次查看当前系统的Python版本,已经变更为2.7.x +在编码过程中,我很喜欢使用推导式,在简单的应用场景下,它简洁且不失高效。 -```shell -# 这是我们刚安装的 Python -$/usr/local/bin/python2.7 -V -Python 2.7.14 +如下这段代码中,我会使用列表推导式得出所有会员中过于肥胖的人的 bmi 指数 -# 这是系统默认 Python -$ /usr/bin/python -V -Python 2.6.6 +```python +members = [ + {"name": "小五", "age": 23, "height": 1.75, "weight": 72}, + {"name": "小李", "age": 17, "height": 1.72, "weight": 63}, + {"name": "小陈", "age": 20, "height": 1.78, "weight": 82}, +] -# 备份原来的 Python 文件 -$ mv /usr/bin/python /usr/bin/python.bak +count = 0 -# 建立软链接,将我们刚安装的 python2.7 做为系统默认版本 -ln -s /usr/local/bin/python2.7 /usr/bin/python +def get_bmi(info): + global count + count += 1 -# 再次查看 Python 版本,已经成功切换过来 -$ python -V -Python 2.7.14 -``` + print(f"执行了 {count} 次") -7. 重新指定 yum 的Python版本 + height = info["height"] + weight = info["weight"] -上面我们改了系统的默认 Python 版本,由于CentOS 6.x 的 yum 是基于Python2.6 的,为了不影响 yum 的使用,需单独将yum指向python2.6版本。 + return weight / (height**2) -编辑: vim /usr/bin/yum ,将` /usr/bin/python` 改成 `/usr/bin/python2.6` +# 查出所有会员中过于肥胖的人的 bmi 指数 +fat_bmis = [get_bmi(m) for m in members if get_bmi(m) > 24] -```python -#!/usr/bin/python2.6 +print(fat_bmis) ``` +输出如下 - -8. 安装 setuptools 及 pip - -pip是python的安装工具,很多python的常用工具,都可以通过pip进行安装。要安装pip,首先要安装setuptools。从这个链接,你可以得到相关信息:https://pypi.python.org/pypi/setuptools - -```shell -# 下载 setuptools -$ wget https://pypi.python.org/packages/ff/d4/209f4939c49e31f5524fa0027bf1c8ec3107abaf7c61fdaad704a648c281/setuptools-21.0.0.tar.gz#md5=81964fdb89534118707742e6d1a1ddb4 ``` - -同样的,进行安装: - -```shell -$ tar vxf setuptools-21.0.0.tar.gz -$ cd setuptools-21.0.0 -$ python setup.py install +执行了 1 次 +执行了 2 次 +执行了 3 次 +执行了 4 次 +[25.88057063502083] ``` -安装完成后,下载pip。其信息在如下网站:https://pypi.python.org/pypi/pip +可以看到,会员数只有 3 个,但是 get_bmi 函数却执行了 4 次,原因是在判断时执行了 3 次,而在构造新的列表时又重复执行了一遍。 -```shell -# 下载 pip -wget https://pypi.python.org/packages/41/27/9a8d24e1b55bd8c85e4d022da2922cb206f183e2d18fee4e320c9547e751/pip-8.1.1.tar.gz#md5=6b86f11841e89c8241d689956ba99ed7 -``` +如果所有会员都是过于肥胖的,那最终将执行 6 次,这种在大量的数据下是比较浪费性能的,因此对于这种结构,我通常会使用传统的for 循环 + if 判断。 -同样的,进行安装 +```python +fat_bmis = [] -```shell -$ tar vxf pip-8.1.1.tar.gz -$ cd pip-8.1.1 -$ python setup.py install +# 查出所有会员中过于肥胖的人的 bmi 指数 +for m in members: + bmi = get_bmi(m) + if bmi > 24: + fat_bmis.append(bmi) ``` -安装完成后,执行 `pip list` 查看一下安装的包,若无异常,则一切顺利。或者你也可以试着安装一下第三方包 `pip install requests` 。 - +在有了海象运算符之后,你就可以不用在这种场景下做出妥协。 +```python +# 查出所有会员中过于肥胖的人的 bmi 指数 +fat_bmis = [bmi for m in members if (bmi := get_bmi(m)) > 24] +``` -8. 转移cloudinit - -上面说的项目,其实就是 cloudinit。接下来就要将 centos 7.2 上的cloudinit 的目录整体拷贝到 centos 6.5 的/usr/local/lib/python2.7/site-packages/ 目录下 - -![](http://image.python-online.cn/20190831160317.png) +最终从输出结果可以看出,只执行了 3 次 -然后安装一些 cloudinit 的依赖包。 +``` +执行了 1 次 +执行了 2 次 +执行了 3 次 +[25.88057063502083] +``` -```shell -$ pip install six requests prettytable jsonpatch configobj +这里仅介绍了列表推导式,但在字典推导式和集合推导式中同样适用。不再演示。 -# 默认还是安装在 python2.6 下 -$ yum install PyYAML -y -# 将这些文件拷贝到 python2.7 目录下 -# 如果你不知道 python2.7 的目录,使用 import sys;print sys.path 就可以打印 -$ cd /usr/lib64/python2.6/site-packages -$ cp -r yaml/ /usr/local/lib/python2.7/site-packages/ -$ cp -p _yaml.so /usr/local/lib/python2.7/site-packages/ -$ cp -p PyYAML-3.10-py2.6.egg-info /usr/local/lib/python2.7/site-packages/ -``` -执行一下 cloudinit 的几个命令,没有问题,任务就完成了。 +海象运算符,是一个新奇的特性,有不少人觉得这样这种特性会破坏代码的可读性。确实在一个新鲜事物刚出来时是会这样,但我相信经过时间的沉淀后,越来越多的人使用它并享受它带来的便利时,这种争议也会慢慢消失在历史的长河中。 -```shell -$ cloud-init init -l -$ cloud-init init -``` +--- -**参考文章** -- https://www.cnblogs.com/stonehe/p/7944366.html -![](http://image.iswbm.com/20200607174235.png) +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_22.rst b/source/c01/c01_22.rst index ccd94f4..177b431 100644 --- a/source/c01/c01_22.rst +++ b/source/c01/c01_22.rst @@ -1,207 +1,183 @@ -1.22 如何修改 CentOS 6.x 上默认Python -===================================== +1.22 Python 炫技操作:海象运算符的三种用法 +========================================== |image0| -最近在工作中遇到一个问题,就是有一个功能希望在各种服务器上实现,而服务器上的系统版本可能都不一样,有的是 -CentOS 6.x,有的是 CentOS 7.x 。 +Python 版本发展非常快,如今最新的版本已经是 Pyhton +3.9,即便如此,有很多人甚至还停留在 3.6 或者 3.7,连 3.8 还没用上。 -需要说明的一点是,CentOS 6.x 上的 Python 版本是 2.6.x 的,而 CentOS 7.x -上的 Python 版本是 2.7.x -的,这意味着我要实现的功能要适配这两种版本的系统。 +很多 Python 3.8 +的特性还没来得及了解,就已经成为旧知识了,比如今天要说的海象运算符。 -你可能会说,这有什么的,自己写的时候,注意一下就好了。 +海象运算符是在 PEP 572 被提出的,直到 3.8 版本合入发布。 -事情其实没有那么容易,我要实现的功能是基于一个框架进行定制,需要修改不少的框架代码。这个框架在不同的 -Linux 版本上,是有不同的版本的,而且差异巨大,曾经想过在 CentOS 6.x 和 -CentOS 7.x -将这个框架安装成同一个版本,最后还是失败了,无法安装,原因就是高版本需要 -Python2.7,而 CentOS 6.x 上只有 Python2.6。 +它的英文原名叫 ``Assignment Expressions``\ ,翻译过来也就是 +``赋值表达式``\ ,不过现在大家更普遍地称之为海象运算符,就是因为它长得真的太像海象了。 -这个历史问题一直遗留到现在,由于这次的功能影响到的代码较多,如果要对两个版本的框架分别进行定制的话,需要花不少的时间,为了不维护两套版本,避免浪费多余的精力去做适配,我决定将 -CentOS 6.x 上默认的 Python2.6 升级成 Python2.7。 - -下面是整个升级过程,别看步骤简单,这些精简步骤的背后可是有不少的坑,被我踩过后,你可以直接使用了。 - -1. 首先确认下你机器上的默认的 Python 版本 - -.. code:: shell - - $ python -V - Python 2.6.6 +|image1| - $ whereis python - python: /usr/bin/python /usr/bin/python2.6 /usr/lib/python2.6 /usr/lib64/python2.6 /usr/local/bin/python /usr/include/python2.6 /usr/share/man/man1/python.1.gz +1. 第一个用法:if/else +---------------------- -2. 由于我们将使用编译安装的方式,所以要安装下 gcc,及一些工具包。 +可能有朋友是第一次接触这个新特性,所以还是简单的介绍一下这个海象运算符有什么用? -注意一定要全部安装,不然后面会发现有不少 python 的工具用不了。 +在 Golang 中的条件语句可以直接在 if +中运算变量的获取后直接对这个变量进行判断,可以让你少写一行代码 -比如不安装 zlib 会无法安装 setuptools,不装 openssl 和 -openssl-devel,会无法使用 pip 工具等 +.. code:: go -:: + import "fmt" - $ yum install gcc -y - $ yum groupinstall "Development tools" - $ yum install zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel -y + func main() { + if age := 20;age > 18 { + fmt.Println("已经成年了") + } + } -如果你这里未按照我的步骤来安装,你后面使用的时候出现了各种各样的问题,不要慌,只要再回来这里,把没安装的包装上,安装完成后,你需要进入第四步重新编译安装Python。 +若在 Python 3.8 之前,Python 必须得这样子写 -3. 下载最新的 Python2.7.x 安装包,解压并进入指定目录 - -:: +.. code:: python - $ wget https://www.python.org/ftp/python/2.7.14/Python-2.7.14.tgz - $ tar zxvf Python-2.7.14.tgz - $ cd Python-2.7.14 + age = 20 + if age > 18: + print("已经成年了") -4. 配置,编译,安装 +但有了海象运算符之后,你可以和 Golang 一样(如果你没学过 +Golang,那这里要注意,Golang 中的 ``:=`` +叫短变量声明,意思是声明并初始化,它和 Python 中的 ``:=`` 不是一个概念) -.. code:: shell +.. code:: python - # --prefix 指定 python 安装的路径 - $ ./configure --prefix=/usr/local/python/python2.7 - $ make - $ make install + if (age:= 20) > 18: + print("已经成年了") -``./configure`` 命令执行完毕之后创建一个文件creating -Makefile,供下面的make命令使用 执行 ``make install`` -之后就会把程序安装到我们指定的目录中去。 +2. 第二个用法:while +-------------------- -Configure是一个可执行脚本,它有很多选项,在待安装的源码路径下使用命令./configure -–help输出详细的选项列表。其中 ``--prefix`` -选项是配置安装的路径,如果不配置该选项,安装后可执行文件默认放在/usr -/local/bin,库文件默认放在 ``/usr/local/lib`` ,配置文件默认放在 -``/usr/local/etc`` ,其它的资源文件放在 -``/usr /local/share``\ 。如果配置 -``--prefix``\ ,如:\ ``./configure --prefix=/usr/local/test`` -可以把所有资源文件放在/usr/local/test的路径中,不会杂乱。 +在不使用 海象运算符之前,使用 while 循环来读取文件的时候,你也许会这么写 -用了 ``--prefix`` -选项的另一个好处是卸载软件或移植软件。当某个安装的软件不再需要时,只须简单的删除该安装目录,就可以把软件卸载得干干净净;移植软件只需拷贝整个目录到另外一个机器即可(相同的操作系统)。当然要卸载程序,也可以在原来的make目录下用一次 -``make uninstall``\ ,但前提是make文件指定过uninstall。 +.. code:: python -5. 查看系统的 Python 版本 + file = open("demo.txt", "r") + while True: + line = file.readline() + if not line: + break + print(line.strip()) -.. code:: shell +但有了海象运算符之后,你可以这样 - $ python -V - Python 2.6.6 +.. code:: python -如果你查看还是 Python 2.6.6 版本,请继续看第六步。 + file = open("demo.txt", "r") + while (line := file.readline()): + print(line.strip()) -6. 修改系统默认的 Python 版本 +使用它替换以往的无限 while 循环写法更为惊艳 -查看新安装的Python版本,当前系统的Python版本,并将系统指向的Python从2.6.x修改为2.7.x,再次查看当前系统的Python版本,已经变更为2.7.x +比如,实现一个需要命令行交互输入密码并检验的代码,你也许会这样子写 -.. code:: shell +.. code:: python - # 这是我们刚安装的 Python - $/usr/local/bin/python2.7 -V - Python 2.7.14 + while True: + p = input("Enter the password: ") + if p == "youpassword": + break - # 这是系统默认 Python - $ /usr/bin/python -V - Python 2.6.6 +有了海象运算符之后,这样子写更为舒服 - # 备份原来的 Python 文件 - $ mv /usr/bin/python /usr/bin/python.bak +.. code:: python - # 建立软链接,将我们刚安装的 python2.7 做为系统默认版本 - ln -s /usr/local/bin/python2.7 /usr/bin/python + while (p := input("Enter the password: ")) != "youpassword": + continue - # 再次查看 Python 版本,已经成功切换过来 - $ python -V - Python 2.7.14 +3. 第三个用法:推导式 +--------------------- -7. 重新指定 yum 的Python版本 +这个系列的文章,几乎每篇都能看到推导式的身影,这一篇依旧如此。 -上面我们改了系统的默认 Python 版本,由于CentOS 6.x 的 yum -是基于Python2.6 的,为了不影响 yum -的使用,需单独将yum指向python2.6版本。 +在编码过程中,我很喜欢使用推导式,在简单的应用场景下,它简洁且不失高效。 -编辑: vim /usr/bin/yum ,将\ ``/usr/bin/python`` 改成 -``/usr/bin/python2.6`` +如下这段代码中,我会使用列表推导式得出所有会员中过于肥胖的人的 bmi 指数 .. code:: python - #!/usr/bin/python2.6 - -8. 安装 setuptools 及 pip - -pip是python的安装工具,很多python的常用工具,都可以通过pip进行安装。要安装pip,首先要安装setuptools。从这个链接,你可以得到相关信息:https://pypi.python.org/pypi/setuptools - -.. code:: shell + members = [ + {"name": "小五", "age": 23, "height": 1.75, "weight": 72}, + {"name": "小李", "age": 17, "height": 1.72, "weight": 63}, + {"name": "小陈", "age": 20, "height": 1.78, "weight": 82}, + ] - # 下载 setuptools - $ wget https://pypi.python.org/packages/ff/d4/209f4939c49e31f5524fa0027bf1c8ec3107abaf7c61fdaad704a648c281/setuptools-21.0.0.tar.gz#md5=81964fdb89534118707742e6d1a1ddb4 + count = 0 -同样的,进行安装: + def get_bmi(info): + global count + count += 1 -.. code:: shell + print(f"执行了 {count} 次") - $ tar vxf setuptools-21.0.0.tar.gz - $ cd setuptools-21.0.0 - $ python setup.py install + height = info["height"] + weight = info["weight"] -安装完成后,下载pip。其信息在如下网站:https://pypi.python.org/pypi/pip + return weight / (height**2) -.. code:: shell + # 查出所有会员中过于肥胖的人的 bmi 指数 + fat_bmis = [get_bmi(m) for m in members if get_bmi(m) > 24] - # 下载 pip - wget https://pypi.python.org/packages/41/27/9a8d24e1b55bd8c85e4d022da2922cb206f183e2d18fee4e320c9547e751/pip-8.1.1.tar.gz#md5=6b86f11841e89c8241d689956ba99ed7 + print(fat_bmis) -同样的,进行安装 +输出如下 -.. code:: shell +:: - $ tar vxf pip-8.1.1.tar.gz - $ cd pip-8.1.1 - $ python setup.py install + 执行了 1 次 + 执行了 2 次 + 执行了 3 次 + 执行了 4 次 + [25.88057063502083] -安装完成后,执行 ``pip list`` -查看一下安装的包,若无异常,则一切顺利。或者你也可以试着安装一下第三方包 -``pip install requests`` 。 +可以看到,会员数只有 3 个,但是 get_bmi 函数却执行了 4 +次,原因是在判断时执行了 3 次,而在构造新的列表时又重复执行了一遍。 -8. 转移cloudinit +如果所有会员都是过于肥胖的,那最终将执行 6 +次,这种在大量的数据下是比较浪费性能的,因此对于这种结构,我通常会使用传统的for +循环 + if 判断。 -上面说的项目,其实就是 cloudinit。接下来就要将 centos 7.2 上的cloudinit -的目录整体拷贝到 centos 6.5 的/usr/local/lib/python2.7/site-packages/ -目录下 +.. code:: python -|image1| + fat_bmis = [] -然后安装一些 cloudinit 的依赖包。 + # 查出所有会员中过于肥胖的人的 bmi 指数 + for m in members: + bmi = get_bmi(m) + if bmi > 24: + fat_bmis.append(bmi) -.. code:: shell +在有了海象运算符之后,你就可以不用在这种场景下做出妥协。 - $ pip install six requests prettytable jsonpatch configobj +.. code:: python - # 默认还是安装在 python2.6 下 - $ yum install PyYAML -y + # 查出所有会员中过于肥胖的人的 bmi 指数 + fat_bmis = [bmi for m in members if (bmi := get_bmi(m)) > 24] - # 将这些文件拷贝到 python2.7 目录下 - # 如果你不知道 python2.7 的目录,使用 import sys;print sys.path 就可以打印 - $ cd /usr/lib64/python2.6/site-packages - $ cp -r yaml/ /usr/local/lib/python2.7/site-packages/ - $ cp -p _yaml.so /usr/local/lib/python2.7/site-packages/ - $ cp -p PyYAML-3.10-py2.6.egg-info /usr/local/lib/python2.7/site-packages/ +最终从输出结果可以看出,只执行了 3 次 -执行一下 cloudinit 的几个命令,没有问题,任务就完成了。 +:: -.. code:: shell + 执行了 1 次 + 执行了 2 次 + 执行了 3 次 + [25.88057063502083] - $ cloud-init init -l - $ cloud-init init +这里仅介绍了列表推导式,但在字典推导式和集合推导式中同样适用。不再演示。 -**参考文章** +海象运算符,是一个新奇的特性,有不少人觉得这样这种特性会破坏代码的可读性。确实在一个新鲜事物刚出来时是会这样,但我相信经过时间的沉淀后,越来越多的人使用它并享受它带来的便利时,这种争议也会慢慢消失在历史的长河中。 -- https://www.cnblogs.com/stonehe/p/7944366.html +-------------- |image2| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190831160317.png +.. |image1| image:: http://image.iswbm.com/image-20200418122739417.png .. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_23.md b/source/c01/c01_23.md index 1c7b47f..7142677 100644 --- a/source/c01/c01_23.md +++ b/source/c01/c01_23.md @@ -1,128 +1,142 @@ -# 1.23 Pythonista 学习 Js +# 1.23 Python 炫技操作:模块重载的五种方法 ![](http://image.iswbm.com/20200602135014.png) -1. JavaScript的设计者希望用`null`表示一个空的值,而`undefined`表示值未定义。事实证明,这并没有什么卵用,区分两者的意义不大。大多数情况下,我们都应该用`null`。`undefined`仅仅在判断函数参数是否传递的情况下有用。 - - - -2. 在JavaScript中,使用等号`=`对变量进行赋值。可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,但是要注意只能用`var`申明一次。 - - - -3. var 申明的变量不是全局变量,如果不使用 var 申明变量,就会变成全局变量,多个js文件会共享这个变量,互相影响。这个语言设计弊端,在 ECMA 推出 strict 模式后得到解决,不使用 var 申明变量的会运行出错。 - - - -4. 直接操作数组的长度,或者对超出数组长度的索引赋值,可以扩容和压缩数组的大小。 - - - -5. 对象之间的比较和Python差异巨大,`=` 表示值的比较,`===` 表示对象类型的比较,主要分为下面三种情况: - - ```javascript - // 1. 对于string,number等【基础类型】,==和===是有区别的。 - // 如果是不同类型,== 会将两边转化成同一类型再看值是否相等 - // 如果是相同类型,直接对值进行比较。 - alert('1'==1);//结果是true - alert('1'===1);//结果是false - - - // 2. 如果是 Array,Object等高级类型,==和===是没有区别的。 - // 直接比较指针地址。,跟 Python 里的 is 等同。 - - - // 3. 基础类型与高级类型,==和===是有区别的。 - // 对于==,将高级转化为基础类型,进行“值”比较。 - // 因为类型不同,===结果为false。 - var a = new String('1');//定义一个string的高级类型 - var b = '1';//定一个基础类型字符串 - alert(b==a);//为true - alert(b===a);//为false - ``` - -6. 定义一个函数,其参数个数可以少于传入的参数个数,基于可以不定义参数,还可以传入参数。那传入的多余的参数如何获取呢?可以使用 arguments 这个变量取得所有的参数,但这个变量仅在参数内起作用。 - - ```python - function foo(a, b, ...rest) { - console.log('a = ' + a); - console.log('b = ' + b); - console.log(rest); - } - ``` - -7. JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部,但是并不会提前为变量赋值。 - - ```javascript - 'use strict'; - - function foo() { - var x = 'Hello, ' + y; - console.log(x); - var y = 'Bob'; - } - - foo(); - - // 对于 javascript 引擎,看到代码相当于 - function foo() { - var y; // 提升变量y的申明,此时y为undefined - var x = 'Hello, ' + y; - console.log(x); - y = 'Bob'; - } - ``` - - - -8. This有个巨坑,需要新手注意。 - - 如下,当没有 `var that = this;` 这句时,函数内部的函数里的this指向的是 window 对象。 - - ```javascript - 'use strict'; - - var xiaoming = { - name: '小明', - birth: 1990, - age: function () { - var that = this; // 在方法内部一开始就捕获this - function getAgeFromBirth() { - var y = new Date().getFullYear(); - return y - that.birth; // 用that而不是this - } - return getAgeFromBirth(); - } - }; - - xiaoming.age(); // 25 - ``` - - 或者,可以用 apply 的方法来解决这个问题,apply 的第一个参数是人该函数要绑定的对象,第二个参数是一个列表,装的是要传递给这个函数据参数。 - - ```javascript - function getAge() { - var y = new Date().getFullYear(); - return y - this.birth; - } - - var xiaoming = { - name: '小明', - birth: 1990, - age: getAge - }; - - xiaoming.age(); // 29 - getAge.apply(xiaoming, []); // 29, this指向xiaoming, 参数为空 - ``` - - 假如,不想绑定给任何对象,第一个参数可以用 null。如 - - ```javascript - Math.max.apply(null, [3, 5, 4]); // 5 - Math.max.call(null, 3, 5, 4); // 5 - ``` - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file +## 环境准备 + +新建一个 foo 文件夹,其下包含一个 bar.py 文件 + +``` +$ tree foo +foo +└── bar.py + +0 directories, 1 file +``` + +bar.py 的内容非常简单,只写了个 print 语句 + +``` +print("successful to be imported") +``` + +只要 bar.py 被导入一次,就被执行一次 print + +## 禁止重复导入 + +'由于有 sys.modules 的存在,当你导入一个已导入的模块时,实际上是没有效果的。' + +``` +>>> from foo import bar +successful to be imported +>>> from foo import bar +>>> +``` + +## 重复导入方法一 + +如果你使用的 python2(记得前面在 foo 文件夹下加一个 `__init__.py`),有一个 reload 的方法可以直接使用 + +``` +>>> from foo import bar +successful to be imported +>>> from foo import bar +>>> +>>> reload(bar) +successful to be imported + +``` + +如果你使用的 python3 那方法就多了,详细请看下面 + +## 重复导入方法二 + +如果你使用 Python3.0 -> 3.3,那么可以使用 imp.reload 方法 + +``` +>>> from foo import bar +successful to be imported +>>> from foo import bar +>>> +>>> import imp +>>> imp.reload(bar) +successful to be imported + +``` + +但是这个方法在 Python 3.4+,就不推荐使用了 + +``` +:1: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses +``` + +## 重复导入方法三 + +如果你使用的 Python 3.4+,请使用 importlib.reload 方法 + +``` +>>> from foo import bar +successful to be imported +>>> from foo import bar +>>> +>>> import importlib +>>> importlib.reload(bar) +successful to be imported + +``` + +## 重复导入方法四 + +如果你对包的加载器有所了解(详细可以翻阅我以前写的文章:https://iswbm.com/84.html) + +还可以使用下面的方法 + +``` +>>> from foo import bar +successful to be imported +>>> from foo import bar +>>> +>>> bar.__spec__.loader.load_module() +successful to be imported + +``` + +## 重复导入方法五 + +既然影响我们重复导入的是 sys.modules,那我们只要将已导入的包从其中移除是不是就好了呢? + +``` +>>> import foo.bar +successful to be imported +>>> +>>> import foo.bar +>>> +>>> import sys +>>> sys.modules['foo.bar'] + +>>> del sys.modules['foo.bar'] +>>> +>>> import foo.bar +successful to be imported +``` + +有没有发现在前面的例子里我使用的都是 `from foo import bar`,在这个例子里,却使用 `import foo.bar`,这是为什么呢? + +这是因为如果你使用 `from foo import bar` 这种方式,想使用移除 sys.modules 来重载模块这种方法是失效的。 + +这应该算是一个小坑,不知道的人,会掉入坑中爬不出来。 + +``` +>>> import foo.bar +successful to be imported +>>> +>>> import foo.bar +>>> +>>> import sys +>>> del sys.modules['foo.bar'] +>>> from foo import bar +>>> +``` + + + diff --git a/source/c01/c01_23.rst b/source/c01/c01_23.rst index 6b03b45..c2f08a9 100644 --- a/source/c01/c01_23.rst +++ b/source/c01/c01_23.rst @@ -1,127 +1,156 @@ -1.23 Pythonista 学习 Js -======================= +1.23 Python 炫技操作:模块重载的五种方法 +======================================== |image0| -1. JavaScript的设计者希望用\ ``null``\ 表示一个空的值,而\ ``undefined``\ 表示值未定义。事实证明,这并没有什么卵用,区分两者的意义不大。大多数情况下,我们都应该用\ ``null``\ 。\ ``undefined``\ 仅仅在判断函数参数是否传递的情况下有用。 +环境准备 +-------- -2. 在JavaScript中,使用等号\ ``=``\ 对变量进行赋值。可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,但是要注意只能用\ ``var``\ 申明一次。 +新建一个 foo 文件夹,其下包含一个 bar.py 文件 -3. var 申明的变量不是全局变量,如果不使用 var - 申明变量,就会变成全局变量,多个js文件会共享这个变量,互相影响。这个语言设计弊端,在 - ECMA 推出 strict 模式后得到解决,不使用 var 申明变量的会运行出错。 +:: -4. 直接操作数组的长度,或者对超出数组长度的索引赋值,可以扩容和压缩数组的大小。 + $ tree foo + foo + └── bar.py -5. 对象之间的比较和Python差异巨大,\ ``=`` 表示值的比较,\ ``===`` - 表示对象类型的比较,主要分为下面三种情况: + 0 directories, 1 file - .. code:: javascript +bar.py 的内容非常简单,只写了个 print 语句 - // 1. 对于string,number等【基础类型】,==和===是有区别的。 - // 如果是不同类型,== 会将两边转化成同一类型再看值是否相等 - // 如果是相同类型,直接对值进行比较。 - alert('1'==1);//结果是true - alert('1'===1);//结果是false +:: + print("successful to be imported") - // 2. 如果是 Array,Object等高级类型,==和===是没有区别的。 - // 直接比较指针地址。,跟 Python 里的 is 等同。 +只要 bar.py 被导入一次,就被执行一次 print +禁止重复导入 +------------ - // 3. 基础类型与高级类型,==和===是有区别的。 - // 对于==,将高级转化为基础类型,进行“值”比较。 - // 因为类型不同,===结果为false。 - var a = new String('1');//定义一个string的高级类型 - var b = '1';//定一个基础类型字符串 - alert(b==a);//为true - alert(b===a);//为false +‘由于有 sys.modules +的存在,当你导入一个已导入的模块时,实际上是没有效果的。’ -6. 定义一个函数,其参数个数可以少于传入的参数个数,基于可以不定义参数,还可以传入参数。那传入的多余的参数如何获取呢?可以使用 - arguments 这个变量取得所有的参数,但这个变量仅在参数内起作用。 +:: - .. code:: python + >>> from foo import bar + successful to be imported + >>> from foo import bar + >>> - function foo(a, b, ...rest) { - console.log('a = ' + a); - console.log('b = ' + b); - console.log(rest); - } +重复导入方法一 +-------------- -7. JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部,但是并不会提前为变量赋值。 +如果你使用的 python2(记得前面在 foo 文件夹下加一个 +``__init__.py``\ ),有一个 reload 的方法可以直接使用 - .. code:: javascript +:: - 'use strict'; + >>> from foo import bar + successful to be imported + >>> from foo import bar + >>> + >>> reload(bar) + successful to be imported + - function foo() { - var x = 'Hello, ' + y; - console.log(x); - var y = 'Bob'; - } +如果你使用的 python3 那方法就多了,详细请看下面 - foo(); +重复导入方法二 +-------------- - // 对于 javascript 引擎,看到代码相当于 - function foo() { - var y; // 提升变量y的申明,此时y为undefined - var x = 'Hello, ' + y; - console.log(x); - y = 'Bob'; - } +如果你使用 Python3.0 -> 3.3,那么可以使用 imp.reload 方法 -8. This有个巨坑,需要新手注意。 +:: - 如下,当没有 ``var that = this;`` - 这句时,函数内部的函数里的this指向的是 window 对象。 + >>> from foo import bar + successful to be imported + >>> from foo import bar + >>> + >>> import imp + >>> imp.reload(bar) + successful to be imported + - .. code:: javascript +但是这个方法在 Python 3.4+,就不推荐使用了 - 'use strict'; +:: - var xiaoming = { - name: '小明', - birth: 1990, - age: function () { - var that = this; // 在方法内部一开始就捕获this - function getAgeFromBirth() { - var y = new Date().getFullYear(); - return y - that.birth; // 用that而不是this - } - return getAgeFromBirth(); - } - }; + :1: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses - xiaoming.age(); // 25 +重复导入方法三 +-------------- - 或者,可以用 apply 的方法来解决这个问题,apply - 的第一个参数是人该函数要绑定的对象,第二个参数是一个列表,装的是要传递给这个函数据参数。 +如果你使用的 Python 3.4+,请使用 importlib.reload 方法 - .. code:: javascript +:: - function getAge() { - var y = new Date().getFullYear(); - return y - this.birth; - } + >>> from foo import bar + successful to be imported + >>> from foo import bar + >>> + >>> import importlib + >>> importlib.reload(bar) + successful to be imported + - var xiaoming = { - name: '小明', - birth: 1990, - age: getAge - }; +重复导入方法四 +-------------- - xiaoming.age(); // 29 - getAge.apply(xiaoming, []); // 29, this指向xiaoming, 参数为空 +如果你对包的加载器有所了解(详细可以翻阅我以前写的文章:https://iswbm.com/84.html) - 假如,不想绑定给任何对象,第一个参数可以用 null。如 +还可以使用下面的方法 - .. code:: javascript +:: - Math.max.apply(null, [3, 5, 4]); // 5 - Math.max.call(null, 3, 5, 4); // 5 + >>> from foo import bar + successful to be imported + >>> from foo import bar + >>> + >>> bar.__spec__.loader.load_module() + successful to be imported + -|image1| +重复导入方法五 +-------------- + +既然影响我们重复导入的是 +sys.modules,那我们只要将已导入的包从其中移除是不是就好了呢? + +:: + + >>> import foo.bar + successful to be imported + >>> + >>> import foo.bar + >>> + >>> import sys + >>> sys.modules['foo.bar'] + + >>> del sys.modules['foo.bar'] + >>> + >>> import foo.bar + successful to be imported + +有没有发现在前面的例子里我使用的都是 +``from foo import bar``\ ,在这个例子里,却使用 +``import foo.bar``\ ,这是为什么呢? + +这是因为如果你使用 ``from foo import bar`` 这种方式,想使用移除 +sys.modules 来重载模块这种方法是失效的。 + +这应该算是一个小坑,不知道的人,会掉入坑中爬不出来。 + +:: + + >>> import foo.bar + successful to be imported + >>> + >>> import foo.bar + >>> + >>> import sys + >>> del sys.modules['foo.bar'] + >>> from foo import bar + >>> .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_24.md b/source/c01/c01_24.md index c82e2cf..ab429b8 100644 --- a/source/c01/c01_24.md +++ b/source/c01/c01_24.md @@ -1,725 +1,148 @@ -# 1.24 深入探讨 Python 的 import 机制:实现远程导入模块 +# 1.24 Python 炫技操作:条件语句的七种写法 ![](http://image.iswbm.com/20200602135014.png) -所谓的模块导入( `import` ),是指在一个模块中使用另一个模块的代码的操作,它有利于代码的复用。 - -在 Python 中使用 import 关键字来实现这个操作,但不是唯一的方法,还有 `importlib.import_module()` 和 `__import__()` 等。 - -也许你看到这个标题,会说我怎么会发这么基础的文章? - -与此相反。恰恰我觉得这篇文章的内容可以算是 Python 的进阶技能,会深入地探讨并以真实案例讲解 Python import Hook 的知识点。 - -当然为了使文章更系统、全面,前面会有小篇幅讲解基础知识点,但请你有耐心的往后读下去,因为后面才是本篇文章的精华所在,希望你不要错过。 - -![](http://image.python-online.cn/20191027192949.png) - -## 1. 导入系统的基础 - -### 1.1 导入单元构成 - -导入单元有多种,可以是模块、包及变量等。 - -对于这些基础的概念,对于新手还是有必要介绍一下它们的区别。 - -**模块**:类似 \*.py,\*.pyc, \*.pyd ,\*.so,\*.dll 这样的文件,是 Python 代码载体的最小单元。 - -**包** 还可以细分为两种: - -- Regular packages:是一个带有 `__init__.py` 文件的文件夹,此文件夹下可包含其他子包,或者模块 -- Namespace packages - -关于 Namespace packages,有的人会比较陌生,我这里摘抄官方文档的一段说明来解释一下。 - -Namespace packages 是由多个 部分 构成的,每个部分为父包增加一个子包。 各个部分可能处于文件系统的不同位置。 部分也可能处于 zip 文件中、网络上,或者 Python 在导入期间可以搜索的其他地方。 命名空间包并不一定会直接对应到文件系统中的对象;它们有可能是无实体表示的虚拟模块。 - -命名空间包的 `__path__ ` 属性不使用普通的列表。 而是使用定制的可迭代类型,如果其父包的路径 (或者最高层级包的 sys.path) 发生改变,这种对象会在该包内的下一次导入尝试时自动执行新的对包部分的搜索。 - -命名空间包没有 `parent/__init__.py` 文件。 实际上,在导入搜索期间可能找到多个 parent 目录,每个都由不同的部分所提供。 因此 parent/one 的物理位置不一定与 parent/two 相邻。 在这种情况下,Python 将为顶级的 parent 包创建一个命名空间包,无论是它本身还是它的某个子包被导入。 - - - -### 1.2 相对/绝对导入 - -当我们 import 导入模块或包时,Python 提供两种导入方式: - -- 相对导入(relative import ):from . import B 或 from ..A import B,其中.表示当前模块,..表示上层模块 -- 绝对导入(absolute import):import foo.bar 或者 from foo import bar - -你可以根据实际需要进行选择,但有必要说明的是,在早期的版本( Python2.6 之前),Python 默认使用的相对导入。而后来的版本中( Python2.6 之后),都以绝对导入为默认使用的导入方式。 - -使用绝对路径和相对路径各有利弊: - -- 当你在开发维护自己的项目时,应当使用相对路径导入,这样可以避免硬编码带来的麻烦。 -- 而使用绝对路径,会让你模块导入结构更加清晰,而且也避免了重名的包冲突而导入错误。 - -### 1.3 导入的标准写法 - -在 PEP8 中对模块的导入提出了要求,遵守 PEP8规范能让你的代码更具有可读性,我这边也列一下: - -- import 语句应当分行书写 - -```python -# bad -import os,sys - -# good -import os -import sys -``` - -- import语句应当使用absolute import - -```python -# bad -from ..bar import Bar - -# good -from foo.bar import test -``` - -- import语句应当放在文件头部,置于模块说明及docstring之后,全局变量之前 - -- import语句应该按照顺序排列,每组之间用一个空格分隔,按照内置模块,第三方模块,自己所写的模块调用顺序,同时每组内部按照字母表顺序排列 - -```python -# 内置模块 -import os -import sys - -# 第三方模块 -import flask - -# 本地模块 -from foo import bar -``` - -### 1.4 几个有用的 sys 变量 - -`sys.path` 可以列出 Python 模块查找的目录列表 - -```python ->>> import sys ->>> from pprint import pprint ->>> pprint(sys.path) -['', - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', - '/Users/MING/Library/Python/3.6/lib/python/site-packages', - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages'] ->>> -``` - -`sys.meta_path` 存放的是所有的查找器。 - -```python ->>> import sys ->>> from pprint import pprint ->>> pprint(sys.meta_path) -[, - , - ] -``` - - - -`sys.path_importer_cache` 比 `sys.path` 会更大点, 因为它会为所有被加载代码的目录记录它们的查找器。 这包括包的子目录,这些通常在 `sys.path` 中是不存在的。 - -```python ->>> import sys ->>> from pprint import pprint ->>> pprint(sys.path_importer_cache) -{'/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6'), - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/collections': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/collections'), - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/encodings': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/encodings'), - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload'), - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages'), - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip': None, - '/Users/MING': FileFinder('/Users/MING'), - '/Users/MING/Library/Python/3.6/lib/python/site-packages': FileFinder('/Users/MING/Library/Python/3.6/lib/python/site-packages')} -``` - - - -## 2. \__import__ 的妙用 - -import 关键字的使用,可以说是基础中的基础。 - -但这不是模块唯一的方法,还有 `importlib.import_module()` 和 `__import__()` 等。 - -和 import 不同的是,`__import__` 是一个函数,也正是因为这个原因,使得 `__import__` 的使用会更加灵活,常常用于框架中,对于插件的动态加载。 - -实际上,当我们调用 import 导入模块时,其内部也是调用了 `__import__` ,请看如下两种导入方法,他们是等价的。 - -```python -# 使用 import -import os - -# 使用 __import__ -os = __import__('os') -``` - -通过举一反三,下面两种方法同样也是等价的。 +## 原代码 +这是一段非常简单的通过年龄判断一个人是否成年的代码,由于代码行数过多,有些人就不太愿意这样写,因为这体现不出自己多年的 Python 功力。 ```python -# 使用 import .. as .. -import pandas as pd - -# 使用 __import__ -pd = __import__('pandas') +if age > 18: + return "已成年" +else: + return "未成年" ``` -上面我说 `__import__` 常常用于插件的动态,事实上也只有它能做到(相对于 import 来说)。 - -`插件`通常会位于某一特定的文件夹下,在使用过程中,可能你并不会用到全部的插件,也可能你会新增插件。 +下面我列举了六种这段代码的变异写法,一个比一个还 6 ,单独拿出来比较好理解,放在工程代码里,没用过这些学法的人,一定会看得一脸懵逼,理解了之后,又不经意大呼:**卧槽,还可以这样写?**,而后就要开始骂街了:**这是给人看的代码?** (除了第一种之外) -如果使用 import 关键字这种硬编码的方式,显然太不优雅了,当你要新增/修改插件的时候,都需要你修改代码。更合适的做法是,将这些插件以配置的方式,写在配置文件中,然后由代码去读取你的配置,动态导入你要使用的插件,即灵活又方便,也不容易出错。 +## 第一种 -假如我的一个项目中,有 `plugin01` 、`plugin02`、`plugin03 ` 、`plugin04` 四个插件,这些插件下都会实现一个核心方法 `run()` 。但有时候我不想使用全部的插件,只想使用 `plugin02`、`plugin04 ` ,那我就在配置文件中写我要使用的两个插件。 - -```shell -# my.conf -custom_plugins=['plugin02', 'plugin04'] -``` - -那我如何使用动态加载,并运行他们呢? +语法: ```python -# main.py - -for plugin in conf.custom_plugins: - __import__(plugin) - sys.modules[plugin].run() -``` - - - -## 3. 理解模块的缓存 - -在一个模块内部重复引用另一个相同模块,实际并不会导入两次,原因是在使用关键字 `import` 导入模块时,它会先检索 `sys.modules` 里是否已经载入这个模块了,如果已经载入,则不会再次导入,如果不存在,才会去检索导入这个模块。 - -来实验一下,在 `my_mod02` 这个模块里,我 import 两次 `my_mod01` 这个模块,按逻辑每一次 import 会一次 `my_mod01` 里的代码(即打印 `in mod01`),但是验证结果是,只打印了一次。 - -```shell -$ cat my_mod01.py -print('in mod01') - -$ cat my_mod02.py -import my_mod01 -import my_mod01 - -$ python my_mod02.py -in mod01 + if else ``` -该现象的解释是:因为有 `sys.modules` 的存在。 - -`sys.modules` 是一个字典(key:模块名,value:模块对象),它存放着在当前 namespace 所有已经导入的模块对象。 +例子 ```python -# test_module.py - -import sys -print(sys.modules.get('json', 'NotFound')) - -import json -print(sys.modules.get('json', 'NotFound')) -``` - -运行结果如下,可见在 导入后 json 模块后,`sys.modules` 才有了 json 模块的对象。 - -```shell -$ python test_module.py -NotFound - +>>> age1 = 20 +>>> age2 = 17 +>>> +>>> +>>> msg1 = "已成年" if age1 > 18 else "未成年" +>>> print msg1 +已成年 +>>> +>>> msg2 = "已成年" if age2 > 18 else "未成年" +>>> print msg2 +未成年 +>>> ``` - 由于有缓存的存在,使得我们无法重新载入一个模块。 +## 第二种 -但若你想反其道行之,可以借助 importlib 这个神奇的库来实现。事实也确实有此场景,比如在代码调试中,在发现代码有异常并修改后,我们通常要重启服务再次载入程序。这时候,若有了模块重载,就无比方便了,修改完代码后也无需服务的重启,就能继续调试。 - -还是以上面的例子来理解,`my_mod02.py` 改写成如下 +语法 ```python -# my_mod02.py - -import importlib -import my_mod01 -importlib.reload(my_mod01) -``` - -使用 python3 来执行这个模块,与上面不同的是,这边执行了两次 `my_mod01.py` - -```shell -$ python3 my_mod02.py -in mod01 -in mod01 + and or ``` - - -## 4. 查找器与加载器 - -如果指定名称的模块在 `sys.modules` 找不到,则将发起调用 Python 的导入协议以查找和加载该模块。 - -此协议由两个概念性模块构成,即 `查找器` 和 `加载器`。 - -一个 Python 的模块的导入,其实可以再细分为两个过程: - -1. 由查找器实现的模块查找 -2. 由加载器实现的模块加载 - -### 4.1 查找器是什么? - -查找器(finder),简单点说,查找器定义了一个模块查找机制,让程序知道该如何找到对应的模块。 - -其实 Python 内置了多个默认查找器,其存在于 sys.meta_path 中。 - -但这些查找器对应使用者来说,并不是那么重要,因此在 Python 3.3 之前, Python 解释将其隐藏了,我们称之为隐式查找器。 +例子 ```python -# Python 2.7 ->>> import sys ->>> sys.meta_path -[] +>>> msg1 = age1 > 18 and "已成年" or "未成年" +>>> msg2 = "已成年" if age2 > 18 else "未成年" >>> +>>> print(msg1) +已成年 +>>> +>>> print(msg2) +未成年 ``` -由于这点不利于开发者深入理解 import 机制,在 Python 3.3 后,所有的模块导入机制都会通过 sys.meta_path 暴露,不会在有任何隐式导入机制。 - -```python -# Python 3.6 ->>> import sys ->>> from pprint import pprint ->>> pprint(sys.meta_path) -[, - , - ] -``` - -观察一下 Python 默认的这几种查找器 (finder),可以分为三种: - -- 一种知道如何导入内置模块 -- 一种知道如何导入冻结模块 -- 一种知道如何导入来自 [import path](https://docs.python.org/zh-cn/3/glossary.html#term-import-path) 的模块 (即 [path based finder](https://docs.python.org/zh-cn/3/glossary.html#term-path-based-finder))。 - -那我们能不能自已定义一个查找器呢?当然可以,你只要 - -- 定义一个实现了 find_module 方法的类(py2和py3均可),或者实现 find_loader 类方法(仅 py3 有效),如果找到模块需要返回一个 loader 对象或者 ModuleSpec 对象(后面会讲),没找到需要返回 None -- 定义完后,要使用这个查找器,必须注册它,将其插入在 sys.meta_path 的首位,这样就能优先使用。 - -```python -import sys - -class MyFinder(object): - @classmethod - def find_module(cls, name, path, target=None): - print("Importing", name, path, target) - # 将在后面定义 - return MyLoader() - -# 由于 finder 是按顺序读取的,所以必须插入在首位 -sys.meta_path.insert(0, MyFinder) -``` - -查找器可以分为两种: - -```shell -object - +-- Finder (deprecated) - +-- MetaPathFinder - +-- PathEntryFinder -``` - -这里需要注意的是,在 3.4 版前,查找器会直接返回 加载器(Loader)对象,而在 3.4 版后,查找器则会返回模块规格说明(ModuleSpec),其中 包含加载器。 - -而关于什么是 加载器 和 模块规格说明, 请继续往后看。 - -### 4.2 加载器是什么? - -查找器只负责查找定位找模,而真正负责加载模块的,是加载器(loader)。 - -一般的 loader 必须定义名为 ` load_module() ` 的方法。 - -为什么这里说一般,因为 loader 还分多种: - -```shell -object - +-- Finder (deprecated) - | +-- MetaPathFinder - | +-- PathEntryFinder - +-- Loader - +-- ResourceLoader --------+ - +-- InspectLoader | - +-- ExecutionLoader --+ - +-- FileLoader - +-- SourceLoader -``` - -通过查看源码可知,不同的加载器的抽象方法各有不同。 - - - -加载器通常由一个 finder 返回。详情参见 PEP 302,对于 abstract base class 可参见 importlib.abc.Loader。 - -那如何自定义我们自己的加载器呢? - -你只要 - -- 定义一个实现了 load_module 方法的类 -- 对与导入有关的属性([点击查看详情](https://docs.python.org/zh-cn/3/reference/import.html#import-related-module-attributes))进行校验 -- 创建模块对象并绑定所有与导入相关的属性变量到该模块上 -- 将此模块保存到 sys.modules 中(顺序很重要,避免递归导入) -- 然后加载模块(这是核心) -- 若加载出错,需要能够处理抛出异常( ImportError) -- 若加载成功,则返回 module 对象 - -若你想看具体的例子,可以接着往后看。 - -### 4.3 模块规格说明 - -导入机制在导入期间会使用有关每个模块的多种信息,特别是加载之前。 大多数信息都是所有模块通用的。 模块规格说明的目的是基于每个模块来封装这些导入相关信息。 - -模块的规格说明会作为模块对象的 `__spec__` 属性对外公开。 有关模块规格的详细内容请参阅 [`ModuleSpec`](https://docs.python.org/zh-cn/3/library/importlib.html#importlib.machinery.ModuleSpec)。 - -在 Python 3.4 后,查找器不再返回加载器,而是返回 ModuleSpec 对象,它储存着更多的信息 - -- 模块名 -- 加载器 -- 模块绝对路径 - -那如何查看一个模块的 ModuleSpec ? - -这边举个例子 - -```shell -$ cat my_mod02.py -import my_mod01 -print(my_mod01.__spec__) - -$ python3 my_mod02.py -in mod01 -ModuleSpec(name='my_mod01', loader=<_frozen_importlib_external.SourceFileLoader object at 0x000000000392DBE0>, origin='/home/MING/my_mod01.py') -``` - -从 ModuleSpec 中可以看到,加载器是包含在内的,那我们如果要重新加载一个模块,是不是又有了另一种思路了? - -来一起验证一下。 +## 第三种 -现在有两个文件: - -一个是 my_info.py +语法 ```python -# my_info.py -name='wangbm' +(, )[condition] ``` - 另一个是:main.py +例子 ```python -# main.py -import my_info - -print(my_info.name) - -# 加一个断点 -import pdb;pdb.set_trace() - -# 再加载一次 -my_info.__spec__.loader.load_module() - -print(my_info.name) -``` - -在 `main.py` 处,我加了一个断点,目的是当运行到断点处时,我修改 my_info.py 里的 name 为 `ming` ,以便验证重载是否有效? - -```shell -$ python3 main.py -wangbm -> /home/MING/main.py(9)() --> my_info.__spec__.loader.load_module() -(Pdb) c -ming +>>> msg1 = ("未成年", "已成年")[age1 > 18] +>>> print(msg1) +已成年 +>>> +>>> +>>> msg2 = ("未成年", "已成年")[age2 > 18] +>>> print(msg2) +未成年 ``` -从结果来看,重载是有效的。 - - - -### 4.4 导入器是什么? - -导入器(importer),也许你在其他文章里会见到它,但其实它并不是个新鲜的东西。 +## 第四种 -它只是同时实现了查找器和加载器两种接口的对象,所以你可以说导入器(importer)是查找器(finder),也可以说它是加载器(loader)。 - - - -## 5. 远程导入模块 - -由于 Python 默认的 查找器和加载器 仅支持本地的模块的导入,并不支持实现远程模块的导入。 - -为了让你更好的理解 Python Import Hook 机制,我下面会通过实例演示,如何自己实现远程导入模块的导入器。 - -### 5.1 动手实现导入器 - -当导入一个包的时候,Python 解释器首先会从 sys.meta_path 中拿到查找器列表。 - -默认顺序是:内建模块查找器 -> 冻结模块查找器 -> 第三方模块路径(本地的 sys.path)查找器 - -若经过这三个查找器,仍然无法查找到所需的模块,则会抛出ImportError异常。 - - - -因此要实现远程导入模块,有两种思路。 - -- 一种是实现自己的元路径导入器; -- 另一种是编写一个钩子,添加到sys.path_hooks里,识别特定的目录命名模式。 - - - -我这里选择第一种方法来做为示例。 - -实现导入器,我们需要分别查找器和加载器。 - -**首先是查找器** - -由源码得知,路径查找器分为两种 - -- MetaPathFinder -- PathEntryFinder - -这里使用 MetaPathFinder 来进行查找器的编写。 - -在 Python 3.4 版本之前,查找器必须实现 `find_module()` 方法,而 Python 3.4+ 版,则推荐使用 `find_spec()` 方法,但这并不意味着你不能使用 `find_module()`,但是在没有 `find_spec()` 方法时,导入协议还是会尝试 `find_module()` 方法。 - -我先举例下使用 `find_module()` 该如何写。 +语法 ```python -from importlib import abc - -class UrlMetaFinder(abc.MetaPathFinder): - def __init__(self, baseurl): - self._baseurl = baseurl - - def find_module(self, fullname, path=None): - if path is None: - baseurl = self._baseurl - else: - # 不是原定义的url就直接返回不存在 - if not path.startswith(self._baseurl): - return None - baseurl = path - - try: - loader = UrlMetaLoader(baseurl) - loader.load_module(fullname) - return loader - except Exception: - return None +(lambda: , lambda:)[]() ``` -若使用 `find_spec()` ,要注意此方法的调用需要带有两到三个参数。 - -第一个是被导入模块的完整限定名称,例如 `foo.bar.baz`。 第二个参数是供模块搜索使用的路径条目。 对于最高层级模块,第二个参数为 `None`,但对于子模块或子包,第二个参数为父包 `__path__` 属性的值。 如果相应的 `__path__` 属性无法访问,将引发 [`ModuleNotFoundError`](https://docs.python.org/zh-cn/3/library/exceptions.html#ModuleNotFoundError)。 第三个参数是一个将被作为稍后加载目标的现有模块对象。 导入系统仅会在重加载期间传入一个目标模块。 +例子 ```python -from importlib import abc -from importlib.machinery import ModuleSpec - -class UrlMetaFinder(abc.MetaPathFinder): - def __init__(self, baseurl): - self._baseurl = baseurl - def find_spec(self, fullname, path=None, target=None): - if path is None: - baseurl = self._baseurl - else: - # 不是原定义的url就直接返回不存在 - if not path.startswith(self._baseurl): - return None - baseurl = path - - try: - loader = UrlMetaLoader(baseurl) - return ModuleSpec(fullname, loader, is_package=loader.is_package(fullname)) - except Exception: - return None +>>> msg1 = (lambda:"未成年", lambda:"已成年")[age1 > 18]() +>>> print(msg1) +已成年 +>>> +>>> msg2 = (lambda:"未成年", lambda:"已成年")[age2 > 18]() +>>> print(msg2) +未成年 ``` +## 第五种 - -**接下来是加载器** - -由源码得知,路径查找器分为三种 - -- FileLoader -- SourceLoader - -按理说,两种加载器都可以实现我们想要的功能,我这里选用 SourceLoader 来示范。 - -在 SourceLoader 这个抽象类里,有几个很重要的方法,在你写实现加载器的时候需要注意 - -- get_code:获取源代码,可以根据自己场景实现实现。 -- exec_module:执行源代码,并将变量赋值给 `module.__dict__` -- get_data:抽象方法,必须实现,返回指定路径的字节码。 -- get_filename:抽象方法,必须实现,返回文件名 - -在一些老的博客文章中,你会经常看到 加载器 要实现 `load_module()` ,而这个方法早已在 Python 3.4 的时候就被废弃了,当然为了兼容考虑,你若使用 `load_module()` 也是可以的。 +语法: ```python -from importlib import abc - -class UrlMetaLoader(abc.SourceLoader): - def __init__(self, baseurl): - self.baseurl = baseurl - - def get_code(self, fullname): - f = urllib2.urlopen(self.get_filename(fullname)) - return f.read() - - def load_module(self, fullname): - code = self.get_code(fullname) - mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) - mod.__file__ = self.get_filename(fullname) - mod.__loader__ = self - mod.__package__ = fullname - exec(code, mod.__dict__) - return None - - def get_data(self): - pass - - def execute_module(self, module): - pass - - def get_filename(self, fullname): - return self.baseurl + fullname + '.py' +{True: , False: }[] ``` -当你使用这种旧模式实现自己的加载时,你需要注意两点,很重要: - -- execute_module 必须重载,而且不应该有任何逻辑,即使它并不是抽象方法。 -- load_module,需要你在查找器里手动执行,才能实现模块的加载。。 - -做为替换,你应该使用 `execute_module()` 和 `create_module()` 。由于基类里已经实现了 `execute_module` 和 `create_module()`,并且满足我们的使用场景。我这边可以不用重复实现。和旧模式相比,这里也不需要在设查找器里手动执行 `execute_module()`。 +例子: ```python -import urllib.request as urllib2 - -class UrlMetaLoader(importlib.abc.SourceLoader): - def __init__(self, baseurl): - self.baseurl = baseurl - - def get_code(self, fullname): - f = urllib2.urlopen(self.get_filename(fullname)) - return f.read() - - def get_data(self): - pass - - def get_filename(self, fullname): - return self.baseurl + fullname + '.py' +>>> msg1 = {True: "已成年", False: "未成年"}[age1 > 18] +>>> print(msg1) +已成年 +>>> +>>> msg2 = {True: "已成年", False: "未成年"}[age2 > 18] +>>> print(msg2) +未成年 ``` -查找器和加载器都有了,别忘了往sys.meta_path 注册我们自定义的查找器(UrlMetaFinder)。 +## 第六种 -```python -def install_meta(address): - finder = UrlMetaFinder(address) - sys.meta_path.append(finder) -``` - -所有的代码都解析完毕后,我们将其整理在一个模块(my_importer.py)中 +语法 ```python -# my_importer.py -import sys -import importlib -import urllib.request as urllib2 - -class UrlMetaFinder(importlib.abc.MetaPathFinder): - def __init__(self, baseurl): - self._baseurl = baseurl - - - def find_module(self, fullname, path=None): - if path is None: - baseurl = self._baseurl - else: - # 不是原定义的url就直接返回不存在 - if not path.startswith(self._baseurl): - return None - baseurl = path - - try: - loader = UrlMetaLoader(baseurl) - return loader - except Exception: - return None - -class UrlMetaLoader(importlib.abc.SourceLoader): - def __init__(self, baseurl): - self.baseurl = baseurl - - def get_code(self, fullname): - f = urllib2.urlopen(self.get_filename(fullname)) - return f.read() - - def get_data(self): - pass - - def get_filename(self, fullname): - return self.baseurl + fullname + '.py' - -def install_meta(address): - finder = UrlMetaFinder(address) - sys.meta_path.append(finder) -``` - -### 5.2 搭建远程服务端 - -最开始我说了,要实现一个远程导入模块的方法。 - -我还缺一个在远端的服务器,来存放我的模块,为了方便,我使用python自带的 `http.server` 模块用一条命令即可实现。 - -```shell -$ mkdir httpserver && cd httpserver -$ cat>my_info.py) and (,) or (,))[0] ``` -一切准备好,我们就可以验证了。 +例子 ```python ->>> from my_importer import install_meta ->>> install_meta('http://localhost:12800/') # 往 sys.meta_path 注册 finder ->>> import my_info # 打印ok,说明导入成功 -ok ->>> my_info.name # 验证可以取得到变量 -'wangbm' +>>> msg1 = ((age1 > 18) and ("已成年",) or ("未成年",))[0] +>>> print(msg1) +已成年 +>>> +>>> msg2 = ((age2 > 18) and ("已成年",) or ("未成年",))[0] +>>> print(msg2) +未成年 ``` -至此,我实现了一个简易的可以导入远程服务器上的模块的导入器。 - - - -## 参考文档 - -- https://docs.python.org/zh-cn/3/reference/import.html -- https://docs.python.org/zh-cn/3/library/importlib.html#module-importlib.abc -- https://python3-cookbook.readthedocs.io/zh_CN/latest/c10/p11_load_modules_from_remote_machine_by_hooks.html - +以上代码,都比较简单,仔细看都能看懂,我就不做解释了。 +看到这里,有没有涨姿势了,学了这么久的 Python ,这么多骚操作,还真是活久见。。这六种写法里,我最推荐使用的是第一种,自己也经常在用,简洁直白,代码行还少。而其他的写法虽然能写,但是不会用,也不希望在我余生里碰到会在公共代码里用这些写法的同事。 -![](http://image.iswbm.com/20200607174235.png) +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_24.rst b/source/c01/c01_24.rst index e5c2fdd..4fa41c6 100644 --- a/source/c01/c01_24.rst +++ b/source/c01/c01_24.rst @@ -1,803 +1,165 @@ -1.24 深入探讨 Python 的 import 机制:实现远程导入模块 -===================================================== +1.24 Python 炫技操作:条件语句的七种写法 +======================================== |image0| -所谓的模块导入( ``import`` -),是指在一个模块中使用另一个模块的代码的操作,它有利于代码的复用。 +原代码 +------ -在 Python 中使用 import 关键字来实现这个操作,但不是唯一的方法,还有 -``importlib.import_module()`` 和 ``__import__()`` 等。 - -也许你看到这个标题,会说我怎么会发这么基础的文章? - -与此相反。恰恰我觉得这篇文章的内容可以算是 Python -的进阶技能,会深入地探讨并以真实案例讲解 Python import Hook 的知识点。 - -当然为了使文章更系统、全面,前面会有小篇幅讲解基础知识点,但请你有耐心的往后读下去,因为后面才是本篇文章的精华所在,希望你不要错过。 - -|image1| - -1. 导入系统的基础 ------------------ - -1.1 导入单元构成 -~~~~~~~~~~~~~~~~ - -导入单元有多种,可以是模块、包及变量等。 - -对于这些基础的概念,对于新手还是有必要介绍一下它们的区别。 - -**模块**\ :类似 \*.py,*.pyc, \*.pyd ,*.so,*.dll 这样的文件,是 -Python 代码载体的最小单元。 - -**包** 还可以细分为两种: - -- Regular packages:是一个带有 ``__init__.py`` - 文件的文件夹,此文件夹下可包含其他子包,或者模块 -- Namespace packages - -关于 Namespace -packages,有的人会比较陌生,我这里摘抄官方文档的一段说明来解释一下。 - -Namespace packages 是由多个 部分 构成的,每个部分为父包增加一个子包。 -各个部分可能处于文件系统的不同位置。 部分也可能处于 zip -文件中、网络上,或者 Python 在导入期间可以搜索的其他地方。 -命名空间包并不一定会直接对应到文件系统中的对象;它们有可能是无实体表示的虚拟模块。 - -命名空间包的 ``__path__`` 属性不使用普通的列表。 -而是使用定制的可迭代类型,如果其父包的路径 (或者最高层级包的 sys.path) -发生改变,这种对象会在该包内的下一次导入尝试时自动执行新的对包部分的搜索。 - -命名空间包没有 ``parent/__init__.py`` 文件。 -实际上,在导入搜索期间可能找到多个 parent -目录,每个都由不同的部分所提供。 因此 parent/one 的物理位置不一定与 -parent/two 相邻。 在这种情况下,Python 将为顶级的 parent -包创建一个命名空间包,无论是它本身还是它的某个子包被导入。 - -1.2 相对/绝对导入 -~~~~~~~~~~~~~~~~~ - -当我们 import 导入模块或包时,Python 提供两种导入方式: - -- 相对导入(relative import ):from . import B 或 from ..A import - B,其中.表示当前模块,..表示上层模块 -- 绝对导入(absolute import):import foo.bar 或者 from foo import bar - -你可以根据实际需要进行选择,但有必要说明的是,在早期的版本( Python2.6 -之前),Python 默认使用的相对导入。而后来的版本中( Python2.6 -之后),都以绝对导入为默认使用的导入方式。 - -使用绝对路径和相对路径各有利弊: - -- 当你在开发维护自己的项目时,应当使用相对路径导入,这样可以避免硬编码带来的麻烦。 -- 而使用绝对路径,会让你模块导入结构更加清晰,而且也避免了重名的包冲突而导入错误。 - -1.3 导入的标准写法 -~~~~~~~~~~~~~~~~~~ - -在 PEP8 中对模块的导入提出了要求,遵守 -PEP8规范能让你的代码更具有可读性,我这边也列一下: - -- import 语句应当分行书写 +这是一段非常简单的通过年龄判断一个人是否成年的代码,由于代码行数过多,有些人就不太愿意这样写,因为这体现不出自己多年的 +Python 功力。 .. code:: python - # bad - import os,sys - - # good - import os - import sys - -- import语句应当使用absolute import - -.. code:: python - - # bad - from ..bar import Bar - - # good - from foo.bar import test - -- import语句应当放在文件头部,置于模块说明及docstring之后,全局变量之前 - -- import语句应该按照顺序排列,每组之间用一个空格分隔,按照内置模块,第三方模块,自己所写的模块调用顺序,同时每组内部按照字母表顺序排列 - -.. code:: python - - # 内置模块 - import os - import sys - - # 第三方模块 - import flask - - # 本地模块 - from foo import bar - -1.4 几个有用的 sys 变量 -~~~~~~~~~~~~~~~~~~~~~~~ - -``sys.path`` 可以列出 Python 模块查找的目录列表 - -.. code:: python - - >>> import sys - >>> from pprint import pprint - >>> pprint(sys.path) - ['', - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', - '/Users/MING/Library/Python/3.6/lib/python/site-packages', - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages'] - >>> - -``sys.meta_path`` 存放的是所有的查找器。 - -.. code:: python - - >>> import sys - >>> from pprint import pprint - >>> pprint(sys.meta_path) - [, - , - ] - -``sys.path_importer_cache`` 比 ``sys.path`` 会更大点, -因为它会为所有被加载代码的目录记录它们的查找器。 -这包括包的子目录,这些通常在 ``sys.path`` 中是不存在的。 - -.. code:: python - - >>> import sys - >>> from pprint import pprint - >>> pprint(sys.path_importer_cache) - {'/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6'), - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/collections': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/collections'), - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/encodings': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/encodings'), - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload'), - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages'), - '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip': None, - '/Users/MING': FileFinder('/Users/MING'), - '/Users/MING/Library/Python/3.6/lib/python/site-packages': FileFinder('/Users/MING/Library/Python/3.6/lib/python/site-packages')} - -2. \__import_\_ 的妙用 ----------------------- - -import 关键字的使用,可以说是基础中的基础。 + if age > 18: + return "已成年" + else: + return "未成年" -但这不是模块唯一的方法,还有 ``importlib.import_module()`` 和 -``__import__()`` 等。 +下面我列举了六种这段代码的变异写法,一个比一个还 6 +,单独拿出来比较好理解,放在工程代码里,没用过这些学法的人,一定会看得一脸懵逼,理解了之后,又不经意大呼:\ **卧槽,还可以这样写?**\ ,而后就要开始骂街了:\ **这是给人看的代码?** +(除了第一种之外) -和 import 不同的是,\ ``__import__`` -是一个函数,也正是因为这个原因,使得 ``__import__`` -的使用会更加灵活,常常用于框架中,对于插件的动态加载。 +第一种 +------ -实际上,当我们调用 import 导入模块时,其内部也是调用了 ``__import__`` -,请看如下两种导入方法,他们是等价的。 +语法: .. code:: python - # 使用 import - import os + if else - # 使用 __import__ - os = __import__('os') - -通过举一反三,下面两种方法同样也是等价的。 +例子 .. code:: python - # 使用 import .. as .. - import pandas as pd - - # 使用 __import__ - pd = __import__('pandas') - -上面我说 ``__import__`` 常常用于插件的动态,事实上也只有它能做到(相对于 -import 来说)。 - -``插件``\ 通常会位于某一特定的文件夹下,在使用过程中,可能你并不会用到全部的插件,也可能你会新增插件。 - -如果使用 import -关键字这种硬编码的方式,显然太不优雅了,当你要新增/修改插件的时候,都需要你修改代码。更合适的做法是,将这些插件以配置的方式,写在配置文件中,然后由代码去读取你的配置,动态导入你要使用的插件,即灵活又方便,也不容易出错。 - -假如我的一个项目中,有 ``plugin01`` 、\ ``plugin02``\ 、\ ``plugin03`` -、\ ``plugin04`` 四个插件,这些插件下都会实现一个核心方法 ``run()`` -。但有时候我不想使用全部的插件,只想使用 ``plugin02``\ 、\ ``plugin04`` -,那我就在配置文件中写我要使用的两个插件。 - -.. code:: shell + >>> age1 = 20 + >>> age2 = 17 + >>> + >>> + >>> msg1 = "已成年" if age1 > 18 else "未成年" + >>> print msg1 + 已成年 + >>> + >>> msg2 = "已成年" if age2 > 18 else "未成年" + >>> print msg2 + 未成年 + >>> - # my.conf - custom_plugins=['plugin02', 'plugin04'] +第二种 +------ -那我如何使用动态加载,并运行他们呢? +语法 .. code:: python - # main.py - - for plugin in conf.custom_plugins: - __import__(plugin) - sys.modules[plugin].run() - -3. 理解模块的缓存 ------------------ - -在一个模块内部重复引用另一个相同模块,实际并不会导入两次,原因是在使用关键字 -``import`` 导入模块时,它会先检索 ``sys.modules`` -里是否已经载入这个模块了,如果已经载入,则不会再次导入,如果不存在,才会去检索导入这个模块。 + and or -来实验一下,在 ``my_mod02`` 这个模块里,我 import 两次 ``my_mod01`` -这个模块,按逻辑每一次 import 会一次 ``my_mod01`` 里的代码(即打印 -``in mod01``\ ),但是验证结果是,只打印了一次。 - -.. code:: shell - - $ cat my_mod01.py - print('in mod01') - - $ cat my_mod02.py - import my_mod01 - import my_mod01 - - $ python my_mod02.py - in mod01 - -该现象的解释是:因为有 ``sys.modules`` 的存在。 - -``sys.modules`` -是一个字典(key:模块名,value:模块对象),它存放着在当前 namespace -所有已经导入的模块对象。 +例子 .. code:: python - # test_module.py - - import sys - print(sys.modules.get('json', 'NotFound')) - - import json - print(sys.modules.get('json', 'NotFound')) - -运行结果如下,可见在 导入后 json 模块后,\ ``sys.modules`` 才有了 json -模块的对象。 - -.. code:: shell - - $ python test_module.py - NotFound - - -由于有缓存的存在,使得我们无法重新载入一个模块。 + >>> msg1 = age1 > 18 and "已成年" or "未成年" + >>> msg2 = "已成年" if age2 > 18 else "未成年" + >>> + >>> print(msg1) + 已成年 + >>> + >>> print(msg2) + 未成年 -但若你想反其道行之,可以借助 importlib -这个神奇的库来实现。事实也确实有此场景,比如在代码调试中,在发现代码有异常并修改后,我们通常要重启服务再次载入程序。这时候,若有了模块重载,就无比方便了,修改完代码后也无需服务的重启,就能继续调试。 +第三种 +------ -还是以上面的例子来理解,\ ``my_mod02.py`` 改写成如下 +语法 .. code:: python - # my_mod02.py - - import importlib - import my_mod01 - importlib.reload(my_mod01) - -使用 python3 来执行这个模块,与上面不同的是,这边执行了两次 -``my_mod01.py`` - -.. code:: shell - - $ python3 my_mod02.py - in mod01 - in mod01 - -4. 查找器与加载器 ------------------ - -如果指定名称的模块在 ``sys.modules`` 找不到,则将发起调用 Python -的导入协议以查找和加载该模块。 - -此协议由两个概念性模块构成,即 ``查找器`` 和 ``加载器``\ 。 - -一个 Python 的模块的导入,其实可以再细分为两个过程: - -1. 由查找器实现的模块查找 -2. 由加载器实现的模块加载 - -4.1 查找器是什么? -~~~~~~~~~~~~~~~~~~ - -查找器(finder),简单点说,查找器定义了一个模块查找机制,让程序知道该如何找到对应的模块。 - -其实 Python 内置了多个默认查找器,其存在于 sys.meta_path 中。 + (, )[condition] -但这些查找器对应使用者来说,并不是那么重要,因此在 Python 3.3 之前, -Python 解释将其隐藏了,我们称之为隐式查找器。 +例子 .. code:: python - # Python 2.7 - >>> import sys - >>> sys.meta_path - [] + >>> msg1 = ("未成年", "已成年")[age1 > 18] + >>> print(msg1) + 已成年 >>> + >>> + >>> msg2 = ("未成年", "已成年")[age2 > 18] + >>> print(msg2) + 未成年 -由于这点不利于开发者深入理解 import 机制,在 Python 3.3 -后,所有的模块导入机制都会通过 sys.meta_path -暴露,不会在有任何隐式导入机制。 - -.. code:: python - - # Python 3.6 - >>> import sys - >>> from pprint import pprint - >>> pprint(sys.meta_path) - [, - , - ] - -观察一下 Python 默认的这几种查找器 (finder),可以分为三种: - -- 一种知道如何导入内置模块 -- 一种知道如何导入冻结模块 -- 一种知道如何导入来自 `import - path `__ - 的模块 (即 `path based - finder `__)。 - -那我们能不能自已定义一个查找器呢?当然可以,你只要 - -- 定义一个实现了 find_module 方法的类(py2和py3均可),或者实现 - find_loader 类方法(仅 py3 有效),如果找到模块需要返回一个 loader - 对象或者 ModuleSpec 对象(后面会讲),没找到需要返回 None -- 定义完后,要使用这个查找器,必须注册它,将其插入在 sys.meta_path - 的首位,这样就能优先使用。 - -.. code:: python - - import sys - - class MyFinder(object): - @classmethod - def find_module(cls, name, path, target=None): - print("Importing", name, path, target) - # 将在后面定义 - return MyLoader() - - # 由于 finder 是按顺序读取的,所以必须插入在首位 - sys.meta_path.insert(0, MyFinder) - -查找器可以分为两种: - -.. code:: shell - - object - +-- Finder (deprecated) - +-- MetaPathFinder - +-- PathEntryFinder - -这里需要注意的是,在 3.4 版前,查找器会直接返回 -加载器(Loader)对象,而在 3.4 -版后,查找器则会返回模块规格说明(ModuleSpec),其中 包含加载器。 - -而关于什么是 加载器 和 模块规格说明, 请继续往后看。 - -4.2 加载器是什么? -~~~~~~~~~~~~~~~~~~ - -查找器只负责查找定位找模,而真正负责加载模块的,是加载器(loader)。 - -一般的 loader 必须定义名为 ``load_module()`` 的方法。 - -为什么这里说一般,因为 loader 还分多种: - -.. code:: shell - - object - +-- Finder (deprecated) - | +-- MetaPathFinder - | +-- PathEntryFinder - +-- Loader - +-- ResourceLoader --------+ - +-- InspectLoader | - +-- ExecutionLoader --+ - +-- FileLoader - +-- SourceLoader - -通过查看源码可知,不同的加载器的抽象方法各有不同。 - -加载器通常由一个 finder 返回。详情参见 PEP 302,对于 abstract base class -可参见 importlib.abc.Loader。 - -那如何自定义我们自己的加载器呢? - -你只要 - -- 定义一个实现了 load_module 方法的类 -- 对与导入有关的属性(\ `点击查看详情 `__\ )进行校验 -- 创建模块对象并绑定所有与导入相关的属性变量到该模块上 -- 将此模块保存到 sys.modules 中(顺序很重要,避免递归导入) -- 然后加载模块(这是核心) -- 若加载出错,需要能够处理抛出异常( ImportError) -- 若加载成功,则返回 module 对象 - -若你想看具体的例子,可以接着往后看。 - -4.3 模块规格说明 -~~~~~~~~~~~~~~~~ - -导入机制在导入期间会使用有关每个模块的多种信息,特别是加载之前。 -大多数信息都是所有模块通用的。 -模块规格说明的目的是基于每个模块来封装这些导入相关信息。 - -模块的规格说明会作为模块对象的 ``__spec__`` 属性对外公开。 -有关模块规格的详细内容请参阅 -```ModuleSpec`` `__\ 。 - -在 Python 3.4 后,查找器不再返回加载器,而是返回 ModuleSpec -对象,它储存着更多的信息 - -- 模块名 -- 加载器 -- 模块绝对路径 - -那如何查看一个模块的 ModuleSpec ? - -这边举个例子 - -.. code:: shell - - $ cat my_mod02.py - import my_mod01 - print(my_mod01.__spec__) - - $ python3 my_mod02.py - in mod01 - ModuleSpec(name='my_mod01', loader=<_frozen_importlib_external.SourceFileLoader object at 0x000000000392DBE0>, origin='/home/MING/my_mod01.py') - -从 ModuleSpec -中可以看到,加载器是包含在内的,那我们如果要重新加载一个模块,是不是又有了另一种思路了? - -来一起验证一下。 - -现在有两个文件: +第四种 +------ -一个是 my_info.py +语法 .. code:: python - # my_info.py - name='wangbm' + (lambda: , lambda:)[]() -另一个是:main.py +例子 .. code:: python - # main.py - import my_info - - print(my_info.name) - - # 加一个断点 - import pdb;pdb.set_trace() - - # 再加载一次 - my_info.__spec__.loader.load_module() - - print(my_info.name) - -在 ``main.py`` 处,我加了一个断点,目的是当运行到断点处时,我修改 -my_info.py 里的 name 为 ``ming`` ,以便验证重载是否有效? - -.. code:: shell - - $ python3 main.py - wangbm - > /home/MING/main.py(9)() - -> my_info.__spec__.loader.load_module() - (Pdb) c - ming - -从结果来看,重载是有效的。 - -4.4 导入器是什么? -~~~~~~~~~~~~~~~~~~ - -导入器(importer),也许你在其他文章里会见到它,但其实它并不是个新鲜的东西。 - -它只是同时实现了查找器和加载器两种接口的对象,所以你可以说导入器(importer)是查找器(finder),也可以说它是加载器(loader)。 - -5. 远程导入模块 ---------------- - -由于 Python 默认的 查找器和加载器 -仅支持本地的模块的导入,并不支持实现远程模块的导入。 - -为了让你更好的理解 Python Import Hook -机制,我下面会通过实例演示,如何自己实现远程导入模块的导入器。 - -5.1 动手实现导入器 -~~~~~~~~~~~~~~~~~~ - -当导入一个包的时候,Python 解释器首先会从 sys.meta_path -中拿到查找器列表。 - -默认顺序是:内建模块查找器 -> 冻结模块查找器 -> 第三方模块路径(本地的 -sys.path)查找器 - -若经过这三个查找器,仍然无法查找到所需的模块,则会抛出ImportError异常。 - -因此要实现远程导入模块,有两种思路。 - -- 一种是实现自己的元路径导入器; -- 另一种是编写一个钩子,添加到sys.path_hooks里,识别特定的目录命名模式。 - -我这里选择第一种方法来做为示例。 - -实现导入器,我们需要分别查找器和加载器。 - -**首先是查找器** - -由源码得知,路径查找器分为两种 - -- MetaPathFinder -- PathEntryFinder - -这里使用 MetaPathFinder 来进行查找器的编写。 - -在 Python 3.4 版本之前,查找器必须实现 ``find_module()`` 方法,而 Python -3.4+ 版,则推荐使用 ``find_spec()`` 方法,但这并不意味着你不能使用 -``find_module()``\ ,但是在没有 ``find_spec()`` -方法时,导入协议还是会尝试 ``find_module()`` 方法。 - -我先举例下使用 ``find_module()`` 该如何写。 - -.. code:: python - - from importlib import abc - - class UrlMetaFinder(abc.MetaPathFinder): - def __init__(self, baseurl): - self._baseurl = baseurl - - def find_module(self, fullname, path=None): - if path is None: - baseurl = self._baseurl - else: - # 不是原定义的url就直接返回不存在 - if not path.startswith(self._baseurl): - return None - baseurl = path - - try: - loader = UrlMetaLoader(baseurl) - loader.load_module(fullname) - return loader - except Exception: - return None - -若使用 ``find_spec()`` ,要注意此方法的调用需要带有两到三个参数。 - -第一个是被导入模块的完整限定名称,例如 ``foo.bar.baz``\ 。 -第二个参数是供模块搜索使用的路径条目。 对于最高层级模块,第二个参数为 -``None``\ ,但对于子模块或子包,第二个参数为父包 ``__path__`` 属性的值。 -如果相应的 ``__path__`` 属性无法访问,将引发 -```ModuleNotFoundError`` `__\ 。 -第三个参数是一个将被作为稍后加载目标的现有模块对象。 -导入系统仅会在重加载期间传入一个目标模块。 - -.. code:: python - - from importlib import abc - from importlib.machinery import ModuleSpec - - class UrlMetaFinder(abc.MetaPathFinder): - def __init__(self, baseurl): - self._baseurl = baseurl - def find_spec(self, fullname, path=None, target=None): - if path is None: - baseurl = self._baseurl - else: - # 不是原定义的url就直接返回不存在 - if not path.startswith(self._baseurl): - return None - baseurl = path - - try: - loader = UrlMetaLoader(baseurl) - return ModuleSpec(fullname, loader, is_package=loader.is_package(fullname)) - except Exception: - return None - -**接下来是加载器** - -由源码得知,路径查找器分为三种 - -- FileLoader -- SourceLoader - -按理说,两种加载器都可以实现我们想要的功能,我这里选用 SourceLoader -来示范。 - -在 SourceLoader -这个抽象类里,有几个很重要的方法,在你写实现加载器的时候需要注意 + >>> msg1 = (lambda:"未成年", lambda:"已成年")[age1 > 18]() + >>> print(msg1) + 已成年 + >>> + >>> msg2 = (lambda:"未成年", lambda:"已成年")[age2 > 18]() + >>> print(msg2) + 未成年 -- get_code:获取源代码,可以根据自己场景实现实现。 -- exec_module:执行源代码,并将变量赋值给 ``module.__dict__`` -- get_data:抽象方法,必须实现,返回指定路径的字节码。 -- get_filename:抽象方法,必须实现,返回文件名 +第五种 +------ -在一些老的博客文章中,你会经常看到 加载器 要实现 ``load_module()`` -,而这个方法早已在 Python 3.4 -的时候就被废弃了,当然为了兼容考虑,你若使用 ``load_module()`` -也是可以的。 +语法: .. code:: python - from importlib import abc - - class UrlMetaLoader(abc.SourceLoader): - def __init__(self, baseurl): - self.baseurl = baseurl - - def get_code(self, fullname): - f = urllib2.urlopen(self.get_filename(fullname)) - return f.read() - - def load_module(self, fullname): - code = self.get_code(fullname) - mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) - mod.__file__ = self.get_filename(fullname) - mod.__loader__ = self - mod.__package__ = fullname - exec(code, mod.__dict__) - return None - - def get_data(self): - pass - - def execute_module(self, module): - pass - - def get_filename(self, fullname): - return self.baseurl + fullname + '.py' - -当你使用这种旧模式实现自己的加载时,你需要注意两点,很重要: + {True: , False: }[] -- execute_module 必须重载,而且不应该有任何逻辑,即使它并不是抽象方法。 -- load_module,需要你在查找器里手动执行,才能实现模块的加载。。 - -做为替换,你应该使用 ``execute_module()`` 和 ``create_module()`` -。由于基类里已经实现了 ``execute_module`` 和 -``create_module()``\ ,并且满足我们的使用场景。我这边可以不用重复实现。和旧模式相比,这里也不需要在设查找器里手动执行 -``execute_module()``\ 。 +例子: .. code:: python - import urllib.request as urllib2 - - class UrlMetaLoader(importlib.abc.SourceLoader): - def __init__(self, baseurl): - self.baseurl = baseurl - - def get_code(self, fullname): - f = urllib2.urlopen(self.get_filename(fullname)) - return f.read() - - def get_data(self): - pass - - def get_filename(self, fullname): - return self.baseurl + fullname + '.py' - -查找器和加载器都有了,别忘了往sys.meta_path -注册我们自定义的查找器(UrlMetaFinder)。 - -.. code:: python + >>> msg1 = {True: "已成年", False: "未成年"}[age1 > 18] + >>> print(msg1) + 已成年 + >>> + >>> msg2 = {True: "已成年", False: "未成年"}[age2 > 18] + >>> print(msg2) + 未成年 - def install_meta(address): - finder = UrlMetaFinder(address) - sys.meta_path.append(finder) +第六种 +------ -所有的代码都解析完毕后,我们将其整理在一个模块(my_importer.py)中 +语法 .. code:: python - # my_importer.py - import sys - import importlib - import urllib.request as urllib2 - - class UrlMetaFinder(importlib.abc.MetaPathFinder): - def __init__(self, baseurl): - self._baseurl = baseurl - - - def find_module(self, fullname, path=None): - if path is None: - baseurl = self._baseurl - else: - # 不是原定义的url就直接返回不存在 - if not path.startswith(self._baseurl): - return None - baseurl = path - - try: - loader = UrlMetaLoader(baseurl) - return loader - except Exception: - return None + (() and (,) or (,))[0] - class UrlMetaLoader(importlib.abc.SourceLoader): - def __init__(self, baseurl): - self.baseurl = baseurl - - def get_code(self, fullname): - f = urllib2.urlopen(self.get_filename(fullname)) - return f.read() - - def get_data(self): - pass - - def get_filename(self, fullname): - return self.baseurl + fullname + '.py' - - def install_meta(address): - finder = UrlMetaFinder(address) - sys.meta_path.append(finder) - -5.2 搭建远程服务端 -~~~~~~~~~~~~~~~~~~ - -最开始我说了,要实现一个远程导入模块的方法。 - -我还缺一个在远端的服务器,来存放我的模块,为了方便,我使用python自带的 -``http.server`` 模块用一条命令即可实现。 - -.. code:: shell - - $ mkdir httpserver && cd httpserver - $ cat>my_info.py>> from my_importer import install_meta - >>> install_meta('http://localhost:12800/') # 往 sys.meta_path 注册 finder - >>> import my_info # 打印ok,说明导入成功 - ok - >>> my_info.name # 验证可以取得到变量 - 'wangbm' - -至此,我实现了一个简易的可以导入远程服务器上的模块的导入器。 + >>> msg1 = ((age1 > 18) and ("已成年",) or ("未成年",))[0] + >>> print(msg1) + 已成年 + >>> + >>> msg2 = ((age2 > 18) and ("已成年",) or ("未成年",))[0] + >>> print(msg2) + 未成年 -参考文档 --------- +以上代码,都比较简单,仔细看都能看懂,我就不做解释了。 -- https://docs.python.org/zh-cn/3/reference/import.html -- https://docs.python.org/zh-cn/3/library/importlib.html#module-importlib.abc -- https://python3-cookbook.readthedocs.io/zh_CN/latest/c10/p11_load_modules_from_remote_machine_by_hooks.html +看到这里,有没有涨姿势了,学了这么久的 Python +,这么多骚操作,还真是活久见。。这六种写法里,我最推荐使用的是第一种,自己也经常在用,简洁直白,代码行还少。而其他的写法虽然能写,但是不会用,也不希望在我余生里碰到会在公共代码里用这些写法的同事。 -|image2| +|image1| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20191027192949.png -.. |image2| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_25.md b/source/c01/c01_25.md index ea6cf52..428535f 100644 --- a/source/c01/c01_25.md +++ b/source/c01/c01_25.md @@ -1,110 +1,289 @@ -# 1.25 50% 的人不知道的Python 包与模块的知识盲区 +# 1.25 Python炫技操作:花式导包的八种方法 ![](http://image.iswbm.com/20200602135014.png) -## 1. 使用 \__all__ 控制可被导入的变量 -使用 `from module import *` 默认情况下会导入 module 里的所有变量,若你只想从模块中导入其中几个变量,可以在 module 中使用 `__all__` 来控制想要被其他模块导入的变量。 -```python -# profile.py -name='wangbm' -age=27 -gender='male' +## 1. 直接 import + +人尽皆知的方法,直接导入即可 -__all__=['name'] +```python +>>> import os +>>> os.getcwd() +'/home/wangbm' ``` -打开 python console 验证一下 +与此类似的还有,不再细讲 ```python ->>> from profile import * ->>> print(name) -wangbm ->>> print(age) -Traceback (most recent call last): - File "", line 1, in -NameError: name 'age' is not defined ->>> print(gender) -Traceback (most recent call last): - File "", line 1, in -NameError: name 'gender' is not defined +import ... +import ... as ... +from ... import ... +from ... import ... as ... ``` -`__all__` 仅对于使用`from module import *` 这种情况适用。 +一般情况下,使用 `import` 语句导入模块已经够用的。 -它经常在一个包的 `__init__.py` 中出现。 +但是在一些特殊场景中,可能还需要其他的导入方式。 +下面我会一一地给你介绍。 +## 2. 使用 \__import__ -## 2. 命名空间包的神奇之处 +`__import__` 函数可用于导入模块,import 语句也会调用函数。其定义为: -命名空间包,一个陌生的名字。 +``` +__import__(name[, globals[, locals[, fromlist[, level]]]]) +``` -与我们熟悉的常规包不同的是,它没有 `__init__.py` 文件。 +参数介绍: -更为特殊的是,它可以跨空间地将两个不相邻的子包,合并成一个虚拟机的包,我们将其称之为 `命名空间包`。 +- name (required): 被加载 module 的名称 +- globals (optional): 包含全局变量的字典,该选项很少使用,采用默认值 global() +- locals (optional): 包含局部变量的字典,内部标准实现未用到该变量,采用默认值 - local() +- fromlist (Optional): 被导入的 submodule 名称 +- level (Optional): 导入路径选项,Python 2 中默认为 -1,表示同时支持 absolute import 和 relative import。Python 3 中默认为 0,表示仅支持 absolute import。如果大于 0,则表示相对导入的父目录的级数,即 1 类似于 '.',2 类似于 '..'。 -例如,一个项目的部分代码布局如下 +使用示例如下: +```python +>>> os = __import__('os') +>>> os.getcwd() +'/home/wangbm' ``` -foo-package/ - spam/ - blah.py -bar-package/ - spam/ - grok.py +如果要实现 `import xx as yy` 的效果,只要修改左值即可 + +如下示例,等价于 `import os as myos`: + +```python +>>> myos = __import__('os') +>>> myos.getcwd() +'/home/wangbm' ``` -在这2个目录里,都有着共同的命名空间spam。在任何一个目录里都没有__init__.py文件。 -让我们看看,如果将foo-package和bar-package都加到python模块路径并尝试导入会发生什么? + +上面说过的 `__import__` 是一个内建函数,既然是内建函数的话,那么这个内建函数必将存在于 `__buildins__` 中,因此我们还可以这样导入 os 的模块: ```python ->>> import sys ->>> sys.path.extend(['foo-package', 'bar-package']) ->>> import spam.blah ->>> import spam.grok ->>> +>>> __builtins__.__dict__['__import__']('os').getcwd() +'/home/wangbm' ``` -当一个包为命名空间包时,他就不再和常规包一样具有 `__file_` 属性,取而代之的是 `__path__` +## 3. 使用 importlib 模块 + +importlib 是 Python 中的一个标准库,importlib 能提供的功能非常全面。 + +它的简单示例: ```python ->>> import sys ->>> sys.path.extend(['foo-package', 'bar-package']) ->>> import spam.blah ->>> import spam.grok ->>> spam.__path__ -_NamespacePath(['foo-package/spam', 'bar-package/spam']) ->>> spam.__file__ -Traceback (most recent call last): - File "", line 1, in -AttributeError: 'module' object has no attribute '__file__' +>>> import importlib +>>> myos=importlib.import_module("os") +>>> myos.getcwd() +'/home/wangbm' ``` +如果要实现 `import xx as yy`效果,可以这样 + +```python +>>> import importlib +>>> +>>> myos = importlib.import_module("os") +>>> myos.getcwd() +'/home/wangbm' +``` -## 3. 包重载就是一个坑 -第一种方法 +## 4. 使用 imp 模块 + +`imp` 模块提供了一些 import 语句内部实现的接口。例如模块查找(find_module)、模块加载(load_module)等等(模块的导入过程会包含模块查找、加载、缓存等步骤)。可以用该模块来简单实现内建的 `__import__` 函数功能: ```python ->>> import spam >>> import imp ->>> imp.reload(spam) - +>>> file, pathname, desc = imp.find_module('os') +>>> myos = imp.load_module('sep', file, pathname, desc) +>>> myos + +>>> myos.getcwd() +'/home/wangbm' +``` + +从 python 3 开始,内建的 reload 函数被移到了 imp 模块中。而从 Python 3.4 开始,imp 模块被否决,不再建议使用,其包含的功能被移到了 importlib 模块下。即从 Python 3.4 开始,importlib 模块是之前 imp 模块和 importlib 模块的合集。 + + + +## 5. 使用 execfile + +在 Python 2 中有一个 execfile 函数,利用它可以用来执行一个文件。 + +语法如下: + +``` +execfile(filename[, globals[, locals]]) +``` + +参数有这么几个: + +- filename:文件名。 +- globals:变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。 +- locals:变量作用域,局部命名空间,如果被提供,可以是任何映射对象。 + +```python +>>> execfile("/usr/lib64/python2.7/os.py") +>>> +>>> getcwd() +'/home/wangbm' +``` + + + +## 6. 使用 exec 执行 + +`execfile` 只能在 Python2 中使用,Python 3.x 里已经删除了这个函数。 + +但是原理值得借鉴,你可以使用 open ... read 读取文件内容,然后再用 exec 去执行模块。 + +示例如下: + +```python +>>> with open("/usr/lib64/python2.7/os.py", "r") as f: +... exec(f.read()) +... +>>> getcwd() +'/home/wangbm' +``` + + + +## 7. import_from_github_com + +有一个包叫做 **import_from_github_com**,从名字上很容易得知,它是一个可以从 github 下载安装并导入的包。为了使用它,你需要做的就是按照如下命令使用pip 先安装它。 + +```shell +$ python3 -m pip install import_from_github_com +``` + +这个包使用了PEP 302中新的引入钩子,允许你可以从github上引入包。这个包实际做的就是安装这个包并将它添加到本地。你需要 Python 3.2 或者更高的版本,并且 git 和 pip 都已经安装才能使用这个包。 + +pip 要保证是较新版本,如果不是请执行如下命令进行升级。 + +```shell +$ python3 -m pip install --upgrade pip +``` + +确保环境 ok 后,你就可以在 Python shell 中使用 import_from_github_com + +示例如下 + +```python +>>> from github_com.zzzeek import sqlalchemy +Collecting git+https://github.com/zzzeek/sqlalchemy +Cloning https://github.com/zzzeek/sqlalchemy to /tmp/pip-acfv7t06-build +Installing collected packages: SQLAlchemy +Running setup.py install for SQLAlchemy ... done +Successfully installed SQLAlchemy-1.1.0b1.dev0 +>>> locals() +{'__builtins__': , '__spec__': None, +'__package__': None, '__doc__': None, '__name__': '__main__', +'sqlalchemy': , +'__loader__': } >>> ``` -由于这种重载方法,只对 `import module` 有效,而使用 `from module import arg` 导入的 arg 并不会刷新。 +看了 import_from_github_com的源码后,你会注意到它并没有使用importlib。实际上,它的原理就是使用 pip 来安装那些没有安装的包,然后使用Python的`__import__()`函数来引入新安装的模块。 + + + +## 8. 远程导入模块 + +我在这篇文章里([深入探讨 Python 的 import 机制:实现远程导入模块](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484838&idx=1&sn=1e6fbf5d7546902c6965c60383f7b639&chksm=e8866544dff1ec52e01b6c9a982dfa150b8e34ad472acca35201373dc51dadb5a8630870982a&scene=21#wechat_redirect)),深入剖析了导入模块的内部原理,并在最后手动实现了从远程服务器上读取模块内容,并在本地成功将模块导入的导入器。 + +具体内容非常的多,你可以点击这个[链接](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484838&idx=1&sn=1e6fbf5d7546902c6965c60383f7b639&chksm=e8866544dff1ec52e01b6c9a982dfa150b8e34ad472acca35201373dc51dadb5a8630870982a&scene=21#wechat_redirect)进行深入学习。 + +示例代码如下: + +```python +# 新建一个 py 文件(my_importer.py),内容如下 +import sys +import importlib +import urllib.request as urllib2 + +class UrlMetaFinder(importlib.abc.MetaPathFinder): + def __init__(self, baseurl): + self._baseurl = baseurl + + + def find_module(self, fullname, path=None): + if path is None: + baseurl = self._baseurl + else: + # 不是原定义的url就直接返回不存在 + if not path.startswith(self._baseurl): + return None + baseurl = path + + try: + loader = UrlMetaLoader(baseurl) + return loader + except Exception: + return None + +class UrlMetaLoader(importlib.abc.SourceLoader): + def __init__(self, baseurl): + self.baseurl = baseurl + + def get_code(self, fullname): + f = urllib2.urlopen(self.get_filename(fullname)) + return f.read() + + def get_data(self): + pass + + def get_filename(self, fullname): + return self.baseurl + fullname + '.py' + +def install_meta(address): + finder = UrlMetaFinder(address) + sys.meta_path.append(finder) +``` + +并且在远程服务器上开启 http 服务(为了方便,我仅在本地进行演示),并且手动编辑一个名为 my_info 的 python 文件,如果后面导入成功会打印 `ok`。 + +```shell +$ mkdir httpserver && cd httpserver +$ cat>my_info.py>> from my_importer import install_meta +>>> install_meta('http://localhost:12800/') # 往 sys.meta_path 注册 finder +>>> import my_info # 打印ok,说明导入成功 +ok +>>> my_info.name # 验证可以取得到变量 +'wangbm' +``` -因此,在生产环境中可能需要避免重新加载模块。而在调试模式中,它会提供一定的便利,但你要知道这个重载的弊端,以免掉入坑里。 +好了,8 种方法都给大家介绍完毕,对于普通开发者来说,其实只要掌握 import 这种方法足够了,而对于那些想要自己开发框架的人来说,深入学习` __import__ `以及 importlib 是非常有必要的。 diff --git a/source/c01/c01_25.rst b/source/c01/c01_25.rst index f7fda8f..90d9581 100644 --- a/source/c01/c01_25.rst +++ b/source/c01/c01_25.rst @@ -1,111 +1,306 @@ -1.25 50% 的人不知道的Python 包与模块的知识盲区 -============================================== +1.25 Python炫技操作:花式导包的八种方法 +======================================= |image0| -1. 使用 \__all_\_ 控制可被导入的变量 ------------------------------------- +1. 直接 import +-------------- -使用 ``from module import *`` 默认情况下会导入 module -里的所有变量,若你只想从模块中导入其中几个变量,可以在 module 中使用 -``__all__`` 来控制想要被其他模块导入的变量。 +人尽皆知的方法,直接导入即可 .. code:: python - # profile.py - name='wangbm' - age=27 - gender='male' - - __all__=['name'] + >>> import os + >>> os.getcwd() + '/home/wangbm' -打开 python console 验证一下 +与此类似的还有,不再细讲 .. code:: python - >>> from profile import * - >>> print(name) - wangbm - >>> print(age) - Traceback (most recent call last): - File "", line 1, in - NameError: name 'age' is not defined - >>> print(gender) - Traceback (most recent call last): - File "", line 1, in - NameError: name 'gender' is not defined + import ... + import ... as ... + from ... import ... + from ... import ... as ... -``__all__`` 仅对于使用\ ``from module import *`` 这种情况适用。 +一般情况下,使用 ``import`` 语句导入模块已经够用的。 -它经常在一个包的 ``__init__.py`` 中出现。 +但是在一些特殊场景中,可能还需要其他的导入方式。 -2. 命名空间包的神奇之处 ------------------------ +下面我会一一地给你介绍。 -命名空间包,一个陌生的名字。 +2. 使用 \__import_\_ +-------------------- -与我们熟悉的常规包不同的是,它没有 ``__init__.py`` 文件。 +``__import__`` 函数可用于导入模块,import 语句也会调用函数。其定义为: -更为特殊的是,它可以跨空间地将两个不相邻的子包,合并成一个虚拟机的包,我们将其称之为 -``命名空间包``\ 。 +:: -例如,一个项目的部分代码布局如下 + __import__(name[, globals[, locals[, fromlist[, level]]]]) -:: +参数介绍: + +- name (required): 被加载 module 的名称 +- globals (optional): 包含全局变量的字典,该选项很少使用,采用默认值 + global() +- locals (optional): + 包含局部变量的字典,内部标准实现未用到该变量,采用默认值 - local() +- fromlist (Optional): 被导入的 submodule 名称 +- level (Optional): 导入路径选项,Python 2 中默认为 -1,表示同时支持 + absolute import 和 relative import。Python 3 中默认为 0,表示仅支持 + absolute import。如果大于 0,则表示相对导入的父目录的级数,即 1 + 类似于 ‘.’,2 类似于 ‘..’。 - foo-package/ - spam/ - blah.py +使用示例如下: - bar-package/ - spam/ - grok.py +.. code:: python + + >>> os = __import__('os') + >>> os.getcwd() + '/home/wangbm' -在这2个目录里,都有着共同的命名空间spam。在任何一个目录里都没有__init__.py文件。 +如果要实现 ``import xx as yy`` 的效果,只要修改左值即可 -让我们看看,如果将foo-package和bar-package都加到python模块路径并尝试导入会发生什么? +如下示例,等价于 ``import os as myos``\ : .. code:: python - >>> import sys - >>> sys.path.extend(['foo-package', 'bar-package']) - >>> import spam.blah - >>> import spam.grok - >>> + >>> myos = __import__('os') + >>> myos.getcwd() + '/home/wangbm' -当一个包为命名空间包时,他就不再和常规包一样具有 ``__file_`` -属性,取而代之的是 ``__path__`` +上面说过的 ``__import__`` +是一个内建函数,既然是内建函数的话,那么这个内建函数必将存在于 +``__buildins__`` 中,因此我们还可以这样导入 os 的模块: .. code:: python - >>> import sys - >>> sys.path.extend(['foo-package', 'bar-package']) - >>> import spam.blah - >>> import spam.grok - >>> spam.__path__ - _NamespacePath(['foo-package/spam', 'bar-package/spam']) - >>> spam.__file__ - Traceback (most recent call last): - File "", line 1, in - AttributeError: 'module' object has no attribute '__file__' + >>> __builtins__.__dict__['__import__']('os').getcwd() + '/home/wangbm' -3. 包重载就是一个坑 -------------------- +3. 使用 importlib 模块 +---------------------- -第一种方法 +importlib 是 Python 中的一个标准库,importlib 能提供的功能非常全面。 + +它的简单示例: + +.. code:: python + + >>> import importlib + >>> myos=importlib.import_module("os") + >>> myos.getcwd() + '/home/wangbm' + +如果要实现 ``import xx as yy``\ 效果,可以这样 + +.. code:: python + + >>> import importlib + >>> + >>> myos = importlib.import_module("os") + >>> myos.getcwd() + '/home/wangbm' + +4. 使用 imp 模块 +---------------- + +``imp`` 模块提供了一些 import +语句内部实现的接口。例如模块查找(find_module)、模块加载(load_module)等等(模块的导入过程会包含模块查找、加载、缓存等步骤)。可以用该模块来简单实现内建的 +``__import__`` 函数功能: .. code:: python - >>> import spam >>> import imp - >>> imp.reload(spam) - + >>> file, pathname, desc = imp.find_module('os') + >>> myos = imp.load_module('sep', file, pathname, desc) + >>> myos + + >>> myos.getcwd() + '/home/wangbm' + +从 python 3 开始,内建的 reload 函数被移到了 imp 模块中。而从 Python 3.4 +开始,imp 模块被否决,不再建议使用,其包含的功能被移到了 importlib +模块下。即从 Python 3.4 开始,importlib 模块是之前 imp 模块和 importlib +模块的合集。 + +5. 使用 execfile +---------------- + +在 Python 2 中有一个 execfile 函数,利用它可以用来执行一个文件。 + +语法如下: + +:: + + execfile(filename[, globals[, locals]]) + +参数有这么几个: + +- filename:文件名。 +- globals:变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。 +- locals:变量作用域,局部命名空间,如果被提供,可以是任何映射对象。 + +.. code:: python + + >>> execfile("/usr/lib64/python2.7/os.py") + >>> + >>> getcwd() + '/home/wangbm' + +6. 使用 exec 执行 +----------------- + +``execfile`` 只能在 Python2 中使用,Python 3.x 里已经删除了这个函数。 + +但是原理值得借鉴,你可以使用 open … read 读取文件内容,然后再用 exec +去执行模块。 + +示例如下: + +.. code:: python + + >>> with open("/usr/lib64/python2.7/os.py", "r") as f: + ... exec(f.read()) + ... + >>> getcwd() + '/home/wangbm' + +7. import_from_github_com +------------------------- + +有一个包叫做 +**import_from_github_com**\ ,从名字上很容易得知,它是一个可以从 github +下载安装并导入的包。为了使用它,你需要做的就是按照如下命令使用pip +先安装它。 + +.. code:: shell + + $ python3 -m pip install import_from_github_com + +这个包使用了PEP +302中新的引入钩子,允许你可以从github上引入包。这个包实际做的就是安装这个包并将它添加到本地。你需要 +Python 3.2 或者更高的版本,并且 git 和 pip 都已经安装才能使用这个包。 + +pip 要保证是较新版本,如果不是请执行如下命令进行升级。 + +.. code:: shell + + $ python3 -m pip install --upgrade pip + +确保环境 ok 后,你就可以在 Python shell 中使用 import_from_github_com + +示例如下 + +.. code:: python + + >>> from github_com.zzzeek import sqlalchemy + Collecting git+https://github.com/zzzeek/sqlalchemy + Cloning https://github.com/zzzeek/sqlalchemy to /tmp/pip-acfv7t06-build + Installing collected packages: SQLAlchemy + Running setup.py install for SQLAlchemy ... done + Successfully installed SQLAlchemy-1.1.0b1.dev0 + >>> locals() + {'__builtins__': , '__spec__': None, + '__package__': None, '__doc__': None, '__name__': '__main__', + 'sqlalchemy': , + '__loader__': } >>> -由于这种重载方法,只对 ``import module`` 有效,而使用 -``from module import arg`` 导入的 arg 并不会刷新。 +看了 +import_from_github_com的源码后,你会注意到它并没有使用importlib。实际上,它的原理就是使用 +pip +来安装那些没有安装的包,然后使用Python的\ ``__import__()``\ 函数来引入新安装的模块。 + +8. 远程导入模块 +--------------- + +我在这篇文章里(\ `深入探讨 Python 的 import +机制:实现远程导入模块 `__\ ),深入剖析了导入模块的内部原理,并在最后手动实现了从远程服务器上读取模块内容,并在本地成功将模块导入的导入器。 + +具体内容非常的多,你可以点击这个\ `链接 `__\ 进行深入学习。 + +示例代码如下: + +.. code:: python + + # 新建一个 py 文件(my_importer.py),内容如下 + import sys + import importlib + import urllib.request as urllib2 + + class UrlMetaFinder(importlib.abc.MetaPathFinder): + def __init__(self, baseurl): + self._baseurl = baseurl + + + def find_module(self, fullname, path=None): + if path is None: + baseurl = self._baseurl + else: + # 不是原定义的url就直接返回不存在 + if not path.startswith(self._baseurl): + return None + baseurl = path + + try: + loader = UrlMetaLoader(baseurl) + return loader + except Exception: + return None + + class UrlMetaLoader(importlib.abc.SourceLoader): + def __init__(self, baseurl): + self.baseurl = baseurl + + def get_code(self, fullname): + f = urllib2.urlopen(self.get_filename(fullname)) + return f.read() + + def get_data(self): + pass + + def get_filename(self, fullname): + return self.baseurl + fullname + '.py' + + def install_meta(address): + finder = UrlMetaFinder(address) + sys.meta_path.append(finder) + +并且在远程服务器上开启 http +服务(为了方便,我仅在本地进行演示),并且手动编辑一个名为 my_info 的 +python 文件,如果后面导入成功会打印 ``ok``\ 。 + +.. code:: shell + + $ mkdir httpserver && cd httpserver + $ cat>my_info.py>> from my_importer import install_meta + >>> install_meta('http://localhost:12800/') # 往 sys.meta_path 注册 finder + >>> import my_info # 打印ok,说明导入成功 + ok + >>> my_info.name # 验证可以取得到变量 + 'wangbm' -因此,在生产环境中可能需要避免重新加载模块。而在调试模式中,它会提供一定的便利,但你要知道这个重载的弊端,以免掉入坑里。 +好了,8 种方法都给大家介绍完毕,对于普通开发者来说,其实只要掌握 import +这种方法足够了,而对于那些想要自己开发框架的人来说,深入学习\ ``__import__``\ 以及 +importlib 是非常有必要的。 |image1| diff --git a/source/c01/c01_26.md b/source/c01/c01_26.md index c4dafae..d75731e 100644 --- a/source/c01/c01_26.md +++ b/source/c01/c01_26.md @@ -1,244 +1,219 @@ -# 1.26 C语言基础的学习 +# 1.26 Python 炫技操作:合并字典的七种方法 ![](http://image.iswbm.com/20200602135014.png) -## 1. 安装编译器 +Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 -C 语言编译器用于把源代码编译成最终的可执行程序。这里假设您已经对编程语言编译器有基本的了解了。 +但你要知道,在团队合作里,炫技是大忌。 -最常用的免费可用的编译器是 GNU 的 C/C++ 编译器。 +为什么这么说呢?我说下自己的看法: -### 1.1 windows +1. 越简洁的代码,越清晰的逻辑,就越不容易出错; +2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 +3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) -MinGw 是 Minimal GNU on Windows 的缩写,允许在 GNU/Linux 和 Windows 平台生成本地的 Windows 程序而不需要第三方运行时库。本文主要介绍 MinGw 的安装和使用。 +该篇是「**炫技系列**」的第二篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 Python 发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 -**安装** +## 1. 最简单的原地更新 -1.下载 [min-gw](https://osdn.net/projects/mingw/downloads/68260/mingw-get-setup.exe/) 安装程序,下载 mingw-get-setup.exe (86.5 kB) +字典对象内置了一个 update 方法,用于把另一个字典更新到自己身上。 -2.运行 mingw-get-setup.exe (86.5 kB) ,点击“运行”,continue等,注意记住安装的目录,如 **C:\MinGw**,下面修改环境变量时还会用到。 -3.修改环境变量: 选择计算机—属性---高级系统设置---环境变量,在系统变量中找到 Path 变量,在后面加入 min-gw的安装目录,如 **C:\MinGw\bin** -4.在开始菜单中,点击“运行”,输入 **cmd**,打开命令行:输入 **mingw-get.exe**,如果弹出 MinGw installation manager 窗口,说明安装正常。此时,关闭 MinGw installation manager 窗口,否则接下来的步骤会报错 -5.在cmd中输入命令 **mingw-get install gcc**,等待一会,gcc 就安装成功了。 +```python +>>> profile = {"name": "xiaoming", "age": 27} +>>> ext_info = {"gender": "male"} +>>> +>>> profile.update(ext_info) +>>> print(profile) +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} +``` -如果想安装 g++,gdb,只要输入命令 **mingw-get install g++** 和 **mingw-get install gdb** +如果想使用 update 这种最简单、最地道原生的方法,但又不想更新到自己身上,而是生成一个新的对象,那请使用深拷贝。 + +```python +>>> profile = {"name": "xiaoming", "age": 27} +>>> ext_info = {"gender": "male"} +>>> +>>> from copy import deepcopy +>>> +>>> full_profile = deepcopy(profile) +>>> full_profile.update(ext_info) +>>> +>>> print(full_profile) +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} +>>> print(profile) +{"name": "xiaoming", "age": 27} +``` -**使用** -在 cmd 的当前工作目录写 C 程序 hello.c: -``` -# include -int main() -{ - printf("%s\n","hello world"); - return 0; -} -``` +## 2. 先解包再合并字典 -在 cmd 中输入命令 +使用 `**` 可以解包字典,解包完后再使用 dict 或者 `{}` 就可以合并。 -```shell -$ gcc hello.c +```python +>>> profile = {"name": "xiaoming", "age": 27} +>>> ext_info = {"gender": "male"} +>>> +>>> full_profile01 = {**profile, **ext_info} +>>> print(full_profile01) +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} +>>> +>>> full_profile02 = dict(**profile, **ext_info) +>>> print(full_profile02) +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ``` -在当前目录下会生成 a.exe 的可执行文件,在 cmd 中输入 a.exe 就可以执行程序了。 +若你不知道 `dict(**profile, **ext_info)` 做了啥,你可以将它等价于 -如果想调试程序,可以输入 gdb a.exe +```python +>>> dict((("name", "xiaoming"), ("age", 27), ("gender", "male"))) +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} +``` -进入 gdb 的功能,使用 gdb 常用的命令就可以调试程序了。 -### Mac OSX 及 Linux -从苹果的网站上下载 [Xcode 开发环境](https://developer.apple.com/xcode/),并按照安装说明进行安装。一旦安装上 Xcode,您就能使用 GNU 编译器。 +## 3. 借助 itertools -**使用** +在 Python 里有一个非常强大的内置模块,它专门用于操作可迭代对象。 -打开终端,使用 vim 编辑文件 hello.c +正好我们字典也是可迭代对象,自然就可以想到,可以使用 `itertools.chain()` 函数先将多个字典(可迭代对象)串联起来,组成一个更大的可迭代对象,然后再使用 dict 转成字典。 -``` -# include -int main() -{ - printf("%s\n","hello world"); - return 0; -} +```python +>>> import itertools +>>> +>>> profile = {"name": "xiaoming", "age": 27} +>>> ext_info = {"gender": "male"} +>>> +>>> +>>> dict(itertools.chain(profile.items(), ext_info.items())) +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ``` -使用 gcc 工具进行编译完后,就会在当前目录下生成一个名为 a.out 的可执行文件,手动运行它即可 -```shell -$ gcc hello.c -$ ./a.out -``` +## 4. 借助 ChainMap +如果可以引入一个辅助包,那我就再提一个, `ChainMap` 也可以达到和 `itertools` 同样的效果。 -### 使用 VS code - -在 vscode 中引入标准库的头文件时,会出现波浪线,提示找不到头文件。 - -解决方法是在当前项目下的 .vscode/c_cpp_properties.json 中的 includePath 列表中添加包含标准头文件的路径,这个路径哪里来呢?你可以使用 everything 搜索一下,哪个路径下有此 stdio.h 文件。 - -比如我的是这样,注意路径不要使用 `\` 和 `\\` ,一定要使用 `\` - -```json -{ - "configurations": [ - { - "name": "Win32", - "includePath": [ - "${workspaceFolder}/**", - "E:/MinGW/lib/gcc/mingw32/8.2.0/include", - "E:/MinGW/include", - "E:/MinGW/lib/gcc/mingw32/8.2.0/include/c++/tr1", - "E:/MinGW/lib/gcc/mingw32/8.2.0/include/ssp", - "C:/Users/wangbm/AppData/Local/Programs/Common/Microsoft/Visual C++ for Python/9.0/VC/include" - ], - "defines": [ - "_DEBUG", - "UNICODE", - "_UNICODE" - ], - "intelliSenseMode": "msvc-x64" - } - ], - "version": 4 -} +```python +>>> from collections import ChainMap +>>> +>>> profile = {"name": "xiaoming", "age": 27} +>>> ext_info = {"gender": "male"} +>>> +>>> dict(ChainMap(profile, ext_info)) +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ``` +使用 ChainMap 有一点需要注意,当字典间有重复的键时,只会取第一个值,排在后面的键值并不会更新掉前面的(使用 itertools 就不会有这个问题)。 +```python +>>> from collections import ChainMap +>>> +>>> profile = {"name": "xiaoming", "age": 27} +>>> ext_info={"age": 30} +>>> dict(ChainMap(profile, ext_info)) +{'name': 'xiaoming', 'age': 27} +``` -## - -### 字符串声明定义 -字符串声明使用 `char` -```c -#include +## 5. 使用dict.items() 合并 -// 定义 name ,不设置大小 -char name[] = "wangbm"; +在 Python 3.9 之前,其实就已经有 `|` 操作符了,只不过它通常用于对集合(set)取并集。 -// 定义 gender ,设置大小为7个字节 -char gender[7] = "female"; +利用这一点,也可以将它用于字典的合并,只不过得绕个弯子,有点不好理解。 -int main() -{ - printf("name is %s\n", name); - printf("size of name: %lu\n", sizeof(name)); - printf("len of name: %lu\n", strlen(name)); +你得先利用 `items` 方法将 dict 转成 dict_items,再对这两个 dict_items 取并集,最后利用 dict 函数,转成字典。 - printf("gender is %s\n", gender); - printf("size of gender: %lu\n", sizeof(gender)); - printf("len of gender: %lu\n", strlen(gender)); - return 0; -} +```python +>>> profile = {"name": "xiaoming", "age": 27} +>>> ext_info = {"gender": "male"} +>>> +>>> full_profile = dict(profile.items() | ext_info.items()) +>>> full_profile +{'gender': 'male', 'age': 27, 'name': 'xiaoming'} ``` -### 字符串操作 -- strcat(s1,s2) :string catenate,连接s2到s1末尾 -- strcpy(s1,s2) :string copy,复制字符串s2到s1 -- strlen(s1) :(string length),返回s1字符串的长度 -- strlwr(s1) :string lowercase,将s1的字符串的字母全部大写返回 -- strupr(s1) :string upercase,将s1的字符串的字母全部小写返回 -- strcmp(s1,s2) :string compare,如果 s1 和 s2 是相同的,则返回 0;如果 s1s2 则返回大于 0。 -### 字符的输入 +当然了,你如果嫌这样太麻烦,也可以简单点,直接使用 list 函数再合并(示例为 Python 3.x ) -使用 `printf` 和 `scanf` 函数 +```python +>>> profile = {"name": "xiaoming", "age": 27} +>>> ext_info = {"gender": "male"} +>>> +>>> dict(list(profile.items()) + list(ext_info.items())) +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} +``` -```c -# include +若你在 Python 2.x 下,可以直接省去 list 函数。 -int main(int argc, char const *argv[]) -{ - char name[10]; - printf("Enter your name: "); - scanf("%s", name); - printf("Your name is: %s \n", name); - return 0; -} +```python +>>> profile = {"name": "xiaoming", "age": 27} +>>> ext_info = {"gender": "male"} +>>> +>>> dict(profile.items() + ext_info.items()) +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ``` -使用 fgets 和 fputs 函数 -```c -# include -int main(int argc, char const *argv[]) -{ - char name[10]; - printf("Enter your name: "); - fgets(name, 10, stdin); - printf("Your name is: "); - fputs(name, stdout); - return 0; -} -``` +## 6. 最酷炫的字典解析式 + +Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 -getchar() & putchar() +那就是列表解析式,集合解析式和字典解析式,通常是 Python 发烧友的最爱,那么今天的主题:字典合并,字典解析式还能否胜任呢? +当然可以,具体示例代码如下: +```python +>>> profile = {"name": "xiaoming", "age": 27} +>>> ext_info = {"gender": "male"} +>>> +>>> {k:v for d in [profile, ext_info] for k,v in d.items()} +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} +``` -### 指针相关的两个符号 -记住 `*` 有3个用途: -1. 乘号(Multiply): 2*3 就是6 -2. 声明指针(Pointer Statement): int a =5; int* ptr=&a;就是声明变量a是5,把a的地址附到指针ptr上 -3. 解引用 (Dereference): *ptr 单独拿出来就是找出 ptr指针指向的值,按照第二点的说法就是5. +## 7. Python 3.9 新特性 -`&`叫做取地址符号,一般指针只能接受一个内存地址而不能接受一个值 +在 2 月份发布的 Python 3.9.04a 版本中,新增了一个抓眼球的新操作符操作符: `|`, PEP584 将它称之为合并操作符(Union Operator),用它可以很直观地合并多个字典。 -```c -// 如下是错误的,指针不能接受一个值 -int a =5; int* ptr=a; +```python +>>> profile = {"name": "xiaoming", "age": 27} +>>> ext_info = {"gender": "male"} +>>> +>>> profile | ext_info +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} +>>> +>>> ext_info | profile +{'gender': 'male', 'name': 'xiaoming', 'age': 27} +>>> +>>> +``` -// 如下是正确的,a的地址给指针ptr -int a =5; int* ptr=&a; +除了 `|` 操作符之外,还有另外一个操作符 `|=`,类似于原地更新。 + +```python +>>> ext_info |= profile +>>> ext_info +{'gender': 'male', 'name': 'xiaoming', 'age': 27} +>>> +>>> +>>> profile |= ext_info +>>> profile +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ``` -### 可变参数的获取 - -以下写了一个函数 `get_sum` 来求得输入的所有参数的和(除了第一个参数外,第一个参数表示,对后面几个可变参数求和,在示例中是作为结束条件存在)。 - -```c -#include -#include - -int get_sum(int n, ...) -{ - va_list arglist; // 定义一个va_list类型的字符指针,用来指向当前参数,后面取参必须通过这个指针进行 - va_start(arglist, n); // 初始化这个指针,让其指向可变参数里的第一个参数,这里的参数应该填写 ... 的那个参数 - double sum; - - for (int i = 1; i <= n; i++) - { - sum += va_arg(arglist, int); - if (i >= 3) - { - break; - } - } - - va_end(arglist); // 养成好习惯,将这个指针关闭 - return sum; -} - -int main(int argc, char const *argv[]) -{ - /* code */ - printf("The sum is: %d", get_sum(3,1,1,1)); - return 0; -} -``` +看到这里,有没有涨姿势了,学了这么久的 Python ,没想到合并字典还有这么多的方法。本篇文章的主旨,并不在于让你全部掌握这 7 种合并字典的方法,实际在工作中,你只要选用一种最顺手的方式即可,但是在协同工作中,或者在阅读他人代码时,你不可避免地会碰到各式各样的写法,这时候你能下意识的知道这是在做合并字典的操作,那这篇文章就是有意义的。 + +--- diff --git a/source/c01/c01_26.rst b/source/c01/c01_26.rst index b576f57..cb9ad4c 100644 --- a/source/c01/c01_26.rst +++ b/source/c01/c01_26.rst @@ -1,265 +1,228 @@ -1.26 C语言基础的学习 -==================== +1.26 Python 炫技操作:合并字典的七种方法 +======================================== |image0| -1. 安装编译器 -------------- +Python 语言里有许多(而且是越来越多)的高级特性,是 Python +发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 -C -语言编译器用于把源代码编译成最终的可执行程序。这里假设您已经对编程语言编译器有基本的了解了。 +但你要知道,在团队合作里,炫技是大忌。 -最常用的免费可用的编译器是 GNU 的 C/C++ 编译器。 +为什么这么说呢?我说下自己的看法: -1.1 windows -~~~~~~~~~~~ +1. 越简洁的代码,越清晰的逻辑,就越不容易出错; +2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 +3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) -MinGw 是 Minimal GNU on Windows 的缩写,允许在 GNU/Linux 和 Windows -平台生成本地的 Windows 程序而不需要第三方运行时库。本文主要介绍 MinGw -的安装和使用。 +该篇是「\ **炫技系列**\ 」的第二篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 +Python +发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 -**安装** +1. 最简单的原地更新 +------------------- -1.下载 -`min-gw `__ -安装程序,下载 mingw-get-setup.exe (86.5 kB) +字典对象内置了一个 update 方法,用于把另一个字典更新到自己身上。 -2.运行 mingw-get-setup.exe (86.5 kB) -,点击“运行”,continue等,注意记住安装的目录,如 -\**C::raw-latex:`\MinGw*`\*,下面修改环境变量时还会用到。 3.修改环境变量: -选择计算机—属性—高级系统设置—环境变量,在系统变量中找到 Path -变量,在后面加入 min-gw的安装目录,如 -\**C::raw-latex:`\MinGw`:raw-latex:`\bin*`\* -4.在开始菜单中,点击“运行”,输入 **cmd**,打开命令行:输入 -**mingw-get.exe**,如果弹出 MinGw installation manager -窗口,说明安装正常。此时,关闭 MinGw installation manager -窗口,否则接下来的步骤会报错 5.在cmd中输入命令 **mingw-get install -gcc**,等待一会,gcc 就安装成功了。 +.. code:: python -如果想安装 g++,gdb,只要输入命令 **mingw-get install g++** 和 **mingw-get -install gdb** + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> profile.update(ext_info) + >>> print(profile) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} -**使用** +如果想使用 update +这种最简单、最地道原生的方法,但又不想更新到自己身上,而是生成一个新的对象,那请使用深拷贝。 -在 cmd 的当前工作目录写 C 程序 hello.c: +.. code:: python -:: + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> from copy import deepcopy + >>> + >>> full_profile = deepcopy(profile) + >>> full_profile.update(ext_info) + >>> + >>> print(full_profile) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} + >>> print(profile) + {"name": "xiaoming", "age": 27} - # include - int main() - { - printf("%s\n","hello world"); - return 0; - } +2. 先解包再合并字典 +------------------- -在 cmd 中输入命令 +使用 ``**`` 可以解包字典,解包完后再使用 dict 或者 ``{}`` 就可以合并。 -.. code:: shell +.. code:: python - $ gcc hello.c + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> full_profile01 = {**profile, **ext_info} + >>> print(full_profile01) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} + >>> + >>> full_profile02 = dict(**profile, **ext_info) + >>> print(full_profile02) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} -在当前目录下会生成 a.exe 的可执行文件,在 cmd 中输入 a.exe -就可以执行程序了。 +若你不知道 ``dict(**profile, **ext_info)`` 做了啥,你可以将它等价于 -如果想调试程序,可以输入 gdb a.exe +.. code:: python -进入 gdb 的功能,使用 gdb 常用的命令就可以调试程序了。 + >>> dict((("name", "xiaoming"), ("age", 27), ("gender", "male"))) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} -Mac OSX 及 Linux -~~~~~~~~~~~~~~~~ +3. 借助 itertools +----------------- -从苹果的网站上下载 `Xcode -开发环境 `__\ ,并按照安装说明进行安装。一旦安装上 -Xcode,您就能使用 GNU 编译器。 +在 Python 里有一个非常强大的内置模块,它专门用于操作可迭代对象。 -**使用** +正好我们字典也是可迭代对象,自然就可以想到,可以使用 +``itertools.chain()`` +函数先将多个字典(可迭代对象)串联起来,组成一个更大的可迭代对象,然后再使用 +dict 转成字典。 -打开终端,使用 vim 编辑文件 hello.c +.. code:: python -:: + >>> import itertools + >>> + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> + >>> dict(itertools.chain(profile.items(), ext_info.items())) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - # include - int main() - { - printf("%s\n","hello world"); - return 0; - } +4. 借助 ChainMap +---------------- -使用 gcc 工具进行编译完后,就会在当前目录下生成一个名为 a.out -的可执行文件,手动运行它即可 +如果可以引入一个辅助包,那我就再提一个, ``ChainMap`` 也可以达到和 +``itertools`` 同样的效果。 -.. code:: shell +.. code:: python - $ gcc hello.c - $ ./a.out + >>> from collections import ChainMap + >>> + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> dict(ChainMap(profile, ext_info)) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} -使用 VS code -~~~~~~~~~~~~ +使用 ChainMap +有一点需要注意,当字典间有重复的键时,只会取第一个值,排在后面的键值并不会更新掉前面的(使用 +itertools 就不会有这个问题)。 -在 vscode 中引入标准库的头文件时,会出现波浪线,提示找不到头文件。 +.. code:: python -解决方法是在当前项目下的 .vscode/c_cpp_properties.json 中的 includePath -列表中添加包含标准头文件的路径,这个路径哪里来呢?你可以使用 everything -搜索一下,哪个路径下有此 stdio.h 文件。 + >>> from collections import ChainMap + >>> + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info={"age": 30} + >>> dict(ChainMap(profile, ext_info)) + {'name': 'xiaoming', 'age': 27} -比如我的是这样,注意路径不要使用 ``\`` 和 ``\\`` ,一定要使用 ``\`` +5. 使用dict.items() 合并 +------------------------ -.. code:: json +在 Python 3.9 之前,其实就已经有 ``|`` +操作符了,只不过它通常用于对集合(set)取并集。 - { - "configurations": [ - { - "name": "Win32", - "includePath": [ - "${workspaceFolder}/**", - "E:/MinGW/lib/gcc/mingw32/8.2.0/include", - "E:/MinGW/include", - "E:/MinGW/lib/gcc/mingw32/8.2.0/include/c++/tr1", - "E:/MinGW/lib/gcc/mingw32/8.2.0/include/ssp", - "C:/Users/wangbm/AppData/Local/Programs/Common/Microsoft/Visual C++ for Python/9.0/VC/include" - ], - "defines": [ - "_DEBUG", - "UNICODE", - "_UNICODE" - ], - "intelliSenseMode": "msvc-x64" - } - ], - "version": 4 - } +利用这一点,也可以将它用于字典的合并,只不过得绕个弯子,有点不好理解。 -字符串声明定义 -~~~~~~~~~~~~~~ +你得先利用 ``items`` 方法将 dict 转成 dict_items,再对这两个 dict_items +取并集,最后利用 dict 函数,转成字典。 -字符串声明使用 ``char`` +.. code:: python -.. code:: c + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> full_profile = dict(profile.items() | ext_info.items()) + >>> full_profile + {'gender': 'male', 'age': 27, 'name': 'xiaoming'} - #include +当然了,你如果嫌这样太麻烦,也可以简单点,直接使用 list +函数再合并(示例为 Python 3.x ) - // 定义 name ,不设置大小 - char name[] = "wangbm"; +.. code:: python - // 定义 gender ,设置大小为7个字节 - char gender[7] = "female"; + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> dict(list(profile.items()) + list(ext_info.items())) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - int main() - { - printf("name is %s\n", name); - printf("size of name: %lu\n", sizeof(name)); - printf("len of name: %lu\n", strlen(name)); +若你在 Python 2.x 下,可以直接省去 list 函数。 - printf("gender is %s\n", gender); - printf("size of gender: %lu\n", sizeof(gender)); - printf("len of gender: %lu\n", strlen(gender)); - return 0; - } +.. code:: python -字符串操作 -~~~~~~~~~~ + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> dict(profile.items() + ext_info.items()) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} -- strcat(s1,s2) :string catenate,连接s2到s1末尾 -- strcpy(s1,s2) :string copy,复制字符串s2到s1 -- strlen(s1) :(string length),返回s1字符串的长度 -- strlwr(s1) :string lowercase,将s1的字符串的字母全部大写返回 -- strupr(s1) :string upercase,将s1的字符串的字母全部小写返回 -- strcmp(s1,s2) :string compare,如果 s1 和 s2 是相同的,则返回 - 0;如果 s1s2 则返回大于 0。 +6. 最酷炫的字典解析式 +--------------------- -字符的输入 -~~~~~~~~~~ +Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 -使用 ``printf`` 和 ``scanf`` 函数 +那就是列表解析式,集合解析式和字典解析式,通常是 Python +发烧友的最爱,那么今天的主题:字典合并,字典解析式还能否胜任呢? -.. code:: c +当然可以,具体示例代码如下: - # include - - int main(int argc, char const *argv[]) - { - char name[10]; - printf("Enter your name: "); - scanf("%s", name); - printf("Your name is: %s \n", name); - return 0; - } - -使用 fgets 和 fputs 函数 - -.. code:: c - - # include - - int main(int argc, char const *argv[]) - { - char name[10]; - printf("Enter your name: "); - fgets(name, 10, stdin); - printf("Your name is: "); - fputs(name, stdout); - return 0; - } - -getchar() & putchar() - -指针相关的两个符号 -~~~~~~~~~~~~~~~~~~ - -记住 ``*`` 有3个用途: - -1. 乘号(Multiply): 2*3 就是6 -2. 声明指针(Pointer Statement): int a =5; int\* - ptr=&a;就是声明变量a是5,把a的地址附到指针ptr上 -3. 解引用 (Dereference): \*ptr 单独拿出来就是找出 - ptr指针指向的值,按照第二点的说法就是5. - -``&``\ 叫做取地址符号,一般指针只能接受一个内存地址而不能接受一个值 - -.. code:: c - - // 如下是错误的,指针不能接受一个值 - int a =5; int* ptr=a; - - // 如下是正确的,a的地址给指针ptr - int a =5; int* ptr=&a; +.. code:: python -可变参数的获取 -~~~~~~~~~~~~~~ + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> {k:v for d in [profile, ext_info] for k,v in d.items()} + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} -以下写了一个函数 ``get_sum`` -来求得输入的所有参数的和(除了第一个参数外,第一个参数表示,对后面几个可变参数求和,在示例中是作为结束条件存在)。 +7. Python 3.9 新特性 +-------------------- -.. code:: c +在 2 月份发布的 Python 3.9.04a +版本中,新增了一个抓眼球的新操作符操作符: ``|``\ , PEP584 +将它称之为合并操作符(Union Operator),用它可以很直观地合并多个字典。 - #include - #include +.. code:: python - int get_sum(int n, ...) - { - va_list arglist; // 定义一个va_list类型的字符指针,用来指向当前参数,后面取参必须通过这个指针进行 - va_start(arglist, n); // 初始化这个指针,让其指向可变参数里的第一个参数,这里的参数应该填写 ... 的那个参数 - double sum; - - for (int i = 1; i <= n; i++) - { - sum += va_arg(arglist, int); - if (i >= 3) - { - break; - } - } - - va_end(arglist); // 养成好习惯,将这个指针关闭 - return sum; - } + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> profile | ext_info + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} + >>> + >>> ext_info | profile + {'gender': 'male', 'name': 'xiaoming', 'age': 27} + >>> + >>> - int main(int argc, char const *argv[]) - { - /* code */ - printf("The sum is: %d", get_sum(3,1,1,1)); - return 0; - } +除了 ``|`` 操作符之外,还有另外一个操作符 ``|=``\ ,类似于原地更新。 + +.. code:: python + + >>> ext_info |= profile + >>> ext_info + {'gender': 'male', 'name': 'xiaoming', 'age': 27} + >>> + >>> + >>> profile |= ext_info + >>> profile + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} + +看到这里,有没有涨姿势了,学了这么久的 Python +,没想到合并字典还有这么多的方法。本篇文章的主旨,并不在于让你全部掌握这 +7 +种合并字典的方法,实际在工作中,你只要选用一种最顺手的方式即可,但是在协同工作中,或者在阅读他人代码时,你不可避免地会碰到各式各样的写法,这时候你能下意识的知道这是在做合并字典的操作,那这篇文章就是有意义的。 + +-------------- |image1| diff --git a/source/c01/c01_27.md b/source/c01/c01_27.md index 5974033..e8b2220 100644 --- a/source/c01/c01_27.md +++ b/source/c01/c01_27.md @@ -1,654 +1,142 @@ -# 1.27 全面学习 Python 包:包的构建与分发 +# 1.27 Python 炫技操作:判断是否包含子串的七种方法 ![](http://image.iswbm.com/20200602135014.png) -> 首发于公众号:Python编程时光 +Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 +但你要知道,在团队合作里,炫技是大忌。 +为什么这么说呢?我说下自己的看法: -## 1. 为什么需要对项目分发打包? +1. 越简洁的代码,越清晰的逻辑,就越不容易出错; +2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 +3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) -平常我们习惯了使用 pip 来安装一些第三方模块,这个安装过程之所以简单,是因为模块开发者为我们默默地为我们做了所有繁杂的工作,而这个过程就是 `打包`。 +该篇是「**炫技系列**」的第三篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 Python 发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 -打包,就是将你的源代码进一步封装,并且将所有的项目部署工作都事先安排好,这样使用者拿到后即装即用,不用再操心如何部署的问题(如果你不想对照着一堆部署文档手工操作的话)。 +## 1. 使用 in 和 not in -不管你是在工作中,还是业余准备自己写一个可以上传到 PyPI 的项目,你都要学会如何打包你的项目。 +`in` 和 `not in` 在 Python 中是很常用的关键字,我们将它们归类为 `成员运算符`。 -Python 发展了这么些年了,项目打包工具也已经很成熟了。他们都有哪些呢? - -你可能听过 `disutils`、 `distutils` 、`distutils2`、`setuptools`等等,好像很熟悉,却又很陌生,他们都是什么关系呢? - -## 2. 包分发的始祖:distutils - -`distutils` 是 Python 的一个标准库,从命名上很容易看出它是一个分发(distribute)工具(utlis),它是 Python 官方开发的一个分发打包工具,所有后续的打包工具,全部都是基于它进行开发的。 - -`distutils` 的精髓在于编写 setup.py,它是模块分发与安装的指导文件。 - -那么如何编写 setup.py 呢?这里面的内容非常多,我会在后面进行详细的解析,请你耐心往下看。 - -你有可能没写过 setup.py ,但你绝对使用过 setup.py 来做一些事情,比如下面这条命令,我们经常用它来进行模块的安装。 - -```shell -$ python setup.py install -``` - -这样的安装方法是通过源码安装,与之对应的是通过二进制软件包的安装,同样我也会在后面进行介绍。 - -## 3. 分发工具升级:setuptools - -`setuptools` 是 distutils 增强版,不包括在标准库中。其扩展了很多功能,能够帮助开发者更好的创建和分发 Python 包。大部分 Python 用户都会使用更先进的 setuptools 模块。 - - **distribute**,或许你在其他地方也见过它,这里也提一下。 - -distribute 是 setuptools 有一个分支版本,分支的原因可能是有一部分开发者认为 setuptools 开发太慢了。但现在,distribute 又合并回了 setuptools 中。因此,我们可以认为它们是同一个东西。 - -还有一个大包分发工具是 **distutils2**,其试图尝试充分利用distutils,detuptools 和 distribute 并成为 Python 标准库中的标准工具。但该计划并没有达到预期的目的,且已经是一个废弃的项目。 - -因此,setuptools 是一个优秀的,可靠的 Python 包安装与分发工具。 - -那么如何在一个干净的环境中安装 setuptools 呢? - -主要有两种方法: - -- 源码安装:在 https://pypi.org/project/setuptools/#files 中下载 zip 包 解压执行 `python setup.py install` 安装 -- 通过引导程序安装:下载引导程序,它可以用来下载或者更新最新版本的 setuptools - -```shell -$ wget http://peak.telecommunity.com/dist/ez_setup.py - -# 安装 -$ python ez_setup.py - -# 更新,以下两种任选 -$ python ez_setup.py –U setuptools -$ pip install -U setuptools -``` - - -## 4. easy_install 使用指南 - -当你安装完 setuptools 后,就拥有了一个叫做 `easy_install` 的第三方管理工具,这也是它区分于 distutils 的一大改进。 - -这里简单介绍一下它的用法,虽然它已经用得非常少了。 - -先是包的安装 - -```shell -# 通过包名,从PyPI寻找最新版本,自动下载、编译、安装 -$ easy_install pkg_name - -# 通过包名从指定下载页寻找链接来安装或升级包 -$ easy_install -f http://pythonpaste.org/package_index.html - -# 指定线上的包地址安装 -$ easy_install http://example.com/path/to/MyPackage-1.2.3.tgz - -# 从本地的 .egg 文件安装 -$ easy_install xxx.egg - -# 在安装时你可以添加额外的参数 -指定安装目录:--install-dir=DIR, -d DIR -指定用户安装:--user -``` - -再者是包的升级 - -```shell -# 从 pypi 中搜索并升级包 -$ easy_install --upgrade pkg_name - -# 指定版本进行升级 -$ easy_install "SomePackage==2.0" -``` - -最后是包的删除 - -```shell -$ easy_install -m pkg_name -``` - -需要注意的是,这样的删除,仅是在 easy-install.pth 文件中删除,使其不能在 python 中使用 这个模块,但实际的包还在你的电脑中,若要删除彻底,需要你手动删除相关的 .egg 及 其他文件。 - - - -默认情况下,easy_install 只会从 pypi 上下载相关软件包,由于这个源在国外,下载包的速度并不理想,使用过pip的朋友自然会想,easy_install 是否能指定源进行安装呢? - -答案是,可以的。 - -编辑配置文件 `/root/.pydistutils.cfg` - -```ini -[easy_install] -index-url=http://mirrors.aliyun.com/pypi/simple/ -find-links=http://mirrors.aliyun.com/pypi/simple/ -``` - -以上仅介绍了 easy_install 的一些常用的方法,想要了解更多,你可以点击官方文档:https://setuptools.readthedocs.io/en/latest/easy_install.html - - - -总结一句:setuptools 是官方提供的一个专业用于包分发的工具,若只从安装的角度来看,它的功能确实简单。它更大的意义是对包的分发很有用,定制化程序非常高,我们现在也还在用它进行版本包的发布。 - - - -## 5. 源码包与二进制包什么区别? - -Python 包的分发可以分为两种: - -1. 以源码包的方式发布 - -源码包安装的过程,是先解压,再编译,最后才安装,所以它是跨平台的,由于每次安装都要进行编译,相对二进包安装方式来说安装速度较慢。 - -源码包的本质是一个压缩包,其常见的格式有: - -![](http://image.python-online.cn/20191218202833.png) - -2. 以二进制包形式发布 - -二进制包的安装过程省去了编译的过程,直接进行解压安装,所以安装速度较源码包来说更快。 - -由于不同平台的编译出来的包无法通用,所以在发布时,需事先编译好多个平台的包。 - -二进制包的常见格式有: - -![](http://image.python-online.cn/20191218203005.png) - -## 6. eggs 与 wheels 有什么区别? - -Egg 格式是由 setuptools 在 2004 年引入,而 Wheel 格式是由 PEP427 在 2012 年定义。Wheel 的出现是为了替代 Egg,它的本质是一个zip包,其现在被认为是 Python 的二进制包的标准格式。 - -以下是 Wheel 和 Egg 的主要区别: - -- Wheel 有一个官方的 PEP427 来定义,而 Egg 没有 PEP 定义 -- Wheel 是一种分发格式,即打包格式。而 Egg 既是一种分发格式,也是一种运行时安装的格式,并且是可以被直接 import -- Wheel 文件不会包含 .pyc 文件 -- Wheel 使用和 PEP376 兼容的 .dist-info 目录,而 Egg 使用 .egg-info 目录 -- Wheel 有着更丰富的命名规则。 -- Wheel 是有版本的。每个 Wheel 文件都包含 wheel 规范的版本和打包的实现 -- Wheel 在内部被 sysconfig path type 管理,因此转向其他格式也更容易 - -wheel 包可以通过 pip 来安装,只不过需要先安装 wheel 模块,然后再使用 pip 的命令。 - -```shell -$ pip install wheel -$ pip wheel --wheel-dir=/local/wheels pkg -``` - - - -## 7. 超详细讲解 setup.py 的编写? - -打包分发最关键的一步是编写 `setup.py` 文件。 - -以下是一个 setup.py 简单的使用示例 +使用这两个成员运算符,可以很让我们很直观清晰的判断一个对象是否在另一个对象中,示例如下: ```python -from setuptools import setup, find_packages - -setup( - name="mytest", - version="1.0", - author="wangbm", - author_email="wongbingming@163.com", - description="Learn to Pack Python Module -->公众号:Python编程时光", - - # 项目主页 - url="http://python-online.cn/", - - # 你要安装的包,通过 setuptools.find_packages 找到当前目录下有哪些包 - packages=find_packages() -) +>>> "llo" in "hello, python" +True +>>> +>>> "lol" in "hello, python" +False ``` -接下来,我将慢慢扩充这个setup函数,增加更多的参数,以便你能理解setup函数能做哪些事情。 - -**程序分类信息** - -`classifiers` 参数说明包的分类信息。所有支持的分类列表见:https://pypi.org/pypi?%3Aaction=list_classifiers -示例: - -```python -from setuptools import setup, find_packages - -setup( - classifiers = [ - # 发展时期,常见的如下 - # 3 - Alpha - # 4 - Beta - # 5 - Production/Stable - 'Development Status :: 3 - Alpha', - - # 开发的目标用户 - 'Intended Audience :: Developers', - - # 属于什么类型 - 'Topic :: Software Development :: Build Tools', - - # 许可证信息 - 'License :: OSI Approved :: MIT License', - - # 目标 Python 版本 - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - ] -) -``` +## 2. 使用 find 方法 - -**关于文件的分发** +使用 字符串 对象的 find 方法,如果有找到子串,就可以返回指定子串在字符串中的出现位置,如果没有找到,就返回 `-1` ```python -from setuptools import setup, find_packages - - -setup( - name="mytest", - version="1.0", - author="wangbm", - author_email="wongbingming@163.com", - description="Learn to Pack Python Module", - url="http://python-online.cn/", - packages=find_packages(), - - # 安装过程中,需要安装的静态文件,如配置文件、service文件、图片等 - data_files=[ - ('', ['conf/*.conf']), - ('/usr/lib/systemd/system/', ['bin/*.service']), - ], - - # 希望被打包的文件 - package_data={ - '':['*.txt'], - 'bandwidth_reporter':['*.txt'] - }, - # 不打包某些文件 - exclude_package_data={ - 'bandwidth_reporter':['*.txt'] - } -) -``` - -除了以上的参数配置之外,还可以使用一个叫做 `MANIFEST.in` 的文件,来控制文件的分发。 - -如下这是一个 `MANIFEST.in` 的样例: - -``` -include *.txt -recursive-include examples *.txt *.py -prune examples/sample?/build +>>> "hello, python".find("llo") != -1 +True +>>> "hello, python".find("lol") != -1 +False +>> ``` -这些配置,规定了如下几点 -- 所有根目录下的以 txt 为后缀名的文件,都会分发 -- 根目录下的 examples 目录 和 txt、py文件都会分发 -- 路径匹配上 examples/sample?/build 不会分发 -`MANIFEST.in` 需要放在和 setup.py 同级的顶级目录下,setuptools 会自动读取该文件。 +## 3. 使用 index 方法 - - -**关于依赖包下载安装** +字符串对象有一个 index 方法,可以返回指定子串在该字符串中第一次出现的索引,如果没有找到会抛出异常,因此使用时需要注意捕获。 ```python -from setuptools import setup, find_packages - - -setup( - ... - - # 表明当前模块依赖哪些包,若环境中没有,则会从pypi中下载安装 - install_requires=['docutils>=0.3'], - - # setup.py 本身要依赖的包,这通常是为一些setuptools的插件准备的配置 - # 这里列出的包,不会自动安装。 - setup_requires=['pbr'], - - # 仅在测试时需要使用的依赖,在正常发布的代码中是没有用的。 - # 在执行python setup.py test时,可以自动安装这三个库,确保测试的正常运行。 - tests_require=[ - 'pytest>=3.3.1', - 'pytest-cov>=2.5.1', - ], - - # 用于安装setup_requires或tests_require里的软件包 - # 这些信息会写入egg的 metadata 信息中 - dependency_links=[ - "http://example2.com/p/foobar-1.0.tar.gz", - ], - - # install_requires 在安装模块时会自动安装依赖包 - # 而 extras_require 不会,这里仅表示该模块会依赖这些包 - # 但是这些包通常不会使用到,只有当你深度使用模块时,才会用到,这里需要你手动安装 - extras_require={ - 'PDF': ["ReportLab>=1.2", "RXP"], - 'reST': ["docutils>=0.3"], - } -) +def is_in(full_str, sub_str): + try: + full_str.index(sub_str) + return True + except ValueError: + return False +print(is_in("hello, python", "llo")) # True +print(is_in("hello, python", "lol")) # False ``` -关于 `install_requires`, 有以下五种常用的表示方法: - -1. `'argparse'`,只包含包名。 这种形式只检查包的存在性,不检查版本。 方便,但不利于控制风险。 -2. `'setuptools==38.2.4'`,指定版本。 这种形式把风险降到了最低,确保了开发、测试与部署的版本一致,不会出现意外。 缺点是不利于更新,每次更新都需要改动代码。 -3. `'docutils >= 0.3'`,这是比较常用的形式。 当对某个库比较信任时,这种形式可以自动保持版本为最新。 -4. `'Django >= 1.11, != 1.11.1, <= 2'`,这是比较复杂的形式。 如这个例子,保证了Django的大版本在1.11和2之间,也即1.11.x;并且,排除了已知有问题的版本1.11.1(仅举例)。 对于一些大型、复杂的库,这种形式是最合适的。 -5. `'requests[security, socks] >= 2.18.4'`,这是包含了额外的可选依赖的形式。 正常安装requests会自动安装它的`install_requires`中指定的依赖,而不会安装`security`和`socks`这两组依赖。 这两组依赖是定义在它的`extras_require`中。 这种形式,用在深度使用某些库时。 +## 4. 使用 count 方法 -**关于安装环境的限制** +利用和 index 这种曲线救国的思路,同样我们可以使用 count 的方法来判断。 -有些库并不是在所以的 Python 版本中都适用的,若一个库安装在一个未兼容的 Python 环境中,理论上不应该在使用时才报错,而应该在安装过程就使其失败,提示禁止安装。 - -这样的功能,可以使用 `python_requires` 来实现。 +只要判断结果大于 0 就说明子串存在于字符串中。 ```python -setup( - ... - python_requires='>=2.7, <=3', -) -``` - +def is_in(full_str, sub_str): + return full_str.count(sub_str) > 0 - -**生成可执行文件的分发** - -```python -from setuptools import setup, find_packages - - -setup( - name="mytest", - version="1.0", - author="wangbm", - author_email="wongbingming@163.com", - description="Learn to Pack Python Module", - url="http://python-online.cn/", - packages=find_packages(), - - # 用来支持自动生成脚本,安装后会自动生成 /usr/bin/foo 的可执行文件 - # 该文件入口指向 foo/main.py 的main 函数 - entry_points={ - 'console_scripts': [ - 'foo = foo.main:main' - ] - }, - - # 将 bin/foo.sh 和 bar.py 脚本,生成到系统 PATH中 - # 执行 python setup.py install 后 - # 会生成 如 /usr/bin/foo.sh 和 如 /usr/bin/bar.py - scripts=['bin/foo.sh', 'bar.py'] -) +print(is_in("hello, python", "llo")) # True +print(is_in("hello, python", "lol")) # False ``` -上面的 scripts 里有的脚本中有 `sh` 和 `py` 后缀,那么安装后,setuptools 会原封不动的移动到 /usr/bin 中,并添加可执行权限。 -若你想对这些文件再作一些更改,比如去掉多余的后缀,可以这样做 - -```python -from setuptools.command.install_scripts import install_scripts - -class InstallScripts(install_scripts): - - def run(self): - setuptools.command.install_scripts.install_scripts.run(self) - - # Rename some script files - for script in self.get_outputs(): - if basename.endswith(".py") or basename.endswith(".sh"): - dest = script[:-3] - else: - continue - print("moving %s to %s" % (script, dest)) - shutil.move(script, dest) - -setup( - ... - scripts=['bin/foo.sh', 'bar.py'], - - cmdclass={ - "install_scripts": InstallScripts - } -) -``` +## 5. 通过魔法方法 +在第一种方法中,我们使用 in 和 not in 判断一个子串是否存在于另一个字符中,实际上当你使用 in 和 not in 时,Python 解释器会先去检查该对象是否有 `__contains__` 魔法方法。 -**ext_modules** +若有就执行它,若没有,Python 就自动会迭代整个序列,只要找到了需要的一项就返回 True 。 -`ext_modules` 参数用于构建 C 和 C++ 扩展扩展包。其是 Extension 实例的列表,每一个 Extension 实例描述了一个独立的扩展模块,扩展模块可以设置扩展包名,头文件、源文件、链接库及其路径、宏定义和编辑参数等。如: +示例如下; ```python -setup( - # other arguments here... - ext_modules=[ - Extension('foo', - glob(path.join(here, 'src', '*.c')), - libraries = [ 'rt' ], - include_dirs=[numpy.get_include()]) - ] -) -``` - -详细了解可参考:https://docs.python.org/3.6/distutils/setupscript.html#preprocessor-options - - - -**指定release** - -setup.py 里只能指定 version,而不能指定 release,如果你需要变更版本号,可以使用 `--release` 参数进行指定 - -```shell -python setup.py bdist_rpm --release=20200617 -``` - - - -setup.py 的参数非常多,能够不借助文档写好一个setup.py好像没那么简单。为了备忘,我整理了 setup 函数常用的一些参数: - -![](http://image.python-online.cn/20191218203255.png) - -更多参数可见:https://setuptools.readthedocs.io/en/latest/setuptools.html - -## 8. 打包辅助神器PBR 是什么? - -`pbr` 是 setuptools 的辅助工具,最初是为 OpenStack 开发(https://launchpad.net/pbr),基于`d2to1`。 - - - -`pbr` 会读取和过滤setup.cfg中的数据,然后将解析后的数据提供给 `setup.py` 作为参数。包含如下功能: - -1. 从git中获取Version、AUTHORS and ChangeLog信息 -2. Sphinx Autodoc。pbr 会扫描project,找到所有模块,生成stub files -3. Requirements。pbr会读取requirements.txt,生成setup函数需要的`install_requires/tests_require/dependency_links` - -这里需要注意,在 `requirements.txt` 文件的头部可以使用:`--index https://pypi.python.org/simple/`,这一行把一个抽象的依赖声明如 requests==1.2.0 转变为一个具体的依赖声明 requests 1.2.0 from pypi.python.org/simple/ - -4. long_description。从README.rst, README.txt or README file中生成`long_description`参数 - - - -使用pbr很简单: - -``` -from setuptools import setup - -setup( - setup_requires=['pbr'], - pbr=True, -) - -``` - -使用pbr时,setup.cfg中有一些配置。在[files]中,有三个key: -`packages`:指定需要包含的包,行为类似于setuptools.find_packages -`namespace_packages`:指定namespace packages -`data_files`: 指定目的目录和源文件路径,一个示例: - -``` -[files] -data_files = - etc/pbr = etc/pbr/* - etc/neutron = - etc/api-paste.ini - etc/dhcp-agent.ini - etc/init.d = neutron.init - -``` - -`[entry_points]` 段跟 setuptools 的方式相同。 - - - -到此,我讲了三种编写使用 setup.py 的方法 - -- 使用命令行参数指定,一个一个将参数传递进去(极不推荐) -- 在 setup.py 中的setup函数中指定(推荐使用) -- 使用 pbr ,在 setup.cfg 中指定(易于管理,更推荐) - -## 9. 如何使用 setup.py 构建包 - -1、构建源码发布包。 - -用于发布一个 Python 模块或项目,将源码打包成 tar.gz (用于 Linux 环境中)或者 zip 压缩包(用于 Windows 环境中) - -```shell -$ python setup.py sdist -``` - -那这种包如何安装呢? - -答案是,使用下一节即将介绍的 `setuptools` 中提供的 `easy_install` 工具。 - -```shell -$ easy_install xxx.tar.gz -``` - -使用 sdist 将根据当前平台创建默认格式的存档。在类 Unix 平台上,将创建后缀后为 `.tar.gz` 的 gzip 压缩的tar文件分发包,而在Windows上为 ZIP 文件。 - -当然,你也可以通过指定你要的发布包格式来打破这个默认行为 - -```shell -$ python setup.py sdist --formats=gztar,zip -``` - -你可以指定的格式有哪些呢? - -创建一个压缩的tarball和一个zip文件。可用格式为: - -![](http://image.python-online.cn/20191218203517.png) - -对以上的格式,有几点需要注意一下: - -- 在版本3.5中才添加了对 `xztar` 格式的支持 -- zip 格式需要你事先已安装相应的模块:zip程序或zipfile模块(已成为Python的标准库) -- ztar 格式正在弃用,请尽量不要使用 - -另外,如果您希望归档文件的所有文件归root拥有,可以这样指定 - -``` -python setup.py sdist --owner=root --group=root -``` - - - -2、构建二进制分发包。 - -在windows中我们习惯了双击 exe 进行软件的安装,Python 模块的安装也同样支持 打包成 exe 这样的二进制软件包。 - -```shell -$ python setup.py bdist_wininst -``` - -而在 Linux 中,大家也习惯了使用 rpm 来安装包,对此你可以使用这条命令实现 rpm 包的构建 - -```shell -$ python setup.py bdist_rpm +>>> "hello, python".__contains__("llo") +True +>>> +>>> "hello, python".__contains__("lol") +False +>>> ``` -若你喜欢使用 easy_install 或者 pip 来安装离线包。你可以将其打包成 egg 包 +这个用法与使用 in 和 not in 没有区别,但不排除有人会特意写成这样来增加代码的理解难度。 -```shell -$ python setup.py bdist_egg -``` - -若你的项目,需要安装多个平台下,既有 Windows 也有 Linux,按照上面的方法,多种格式我们要执行多次命令,为了方便,你可以一步到位,执行如下这条命令,即可生成多个格式的进制包 - -```shell -$ python setup.py bdist -``` - - - -## 10. 如何使用 setup.py 安装包 - -正常情况下,我们都是通过以上构建的源码包或者二进制包进行模块的安装。 - -但在编写 setup.py 的过程中,可能不能一步到位,需要多次调试,这时候如何测试自己写的 setup.py 文件是可用的呢? - -这时候你可以使用这条命令,它会将你的模块安装至系统全局环境中 - -```shell -$ python setup.py install -``` +## 6. 借助 operator -如若你的项目还处于开发阶段,频繁的安装模块,也是一个麻烦事。 +operator模块是python中内置的操作符函数接口,它定义了一些算术和比较内置操作的函数。operator模块是用c实现的,所以执行速度比 python 代码快。 -这时候你可以使用这条命令安装,该方法不会真正的安装包,而是在系统环境中创建一个软链接指向包实际所在目录。这边在修改包之后不用再安装就能生效,便于调试。 +在 operator 中有一个方法 `contains` 可以很方便地判断子串是否在字符串中。 -```shell -$ python setup.py develop +```python +>>> import operator +>>> +>>> operator.contains("hello, python", "llo") +True +>>> operator.contains("hello, python", "lol") +False +>>> ``` -## 11. 如何发布包到 PyPi? - -通过上面的学习,你一定已经学会了如何打包自己的项目,若你觉得自己开发的模块非常不错,想要 share 给其他人使用,你可以将其上传到 PyPi (Python Package Index)上,它是 Python 官方维护的第三方包仓库,用于统一存储和管理开发者发布的 Python 包。 - - - -如果要发布自己的包,需要先到 pypi 上注册账号。然后创建 `~/.pypirc` 文件,此文件中配置 PyPI 访问地址和账号。如的.pypirc文件内容请根据自己的账号来修改。 - -典型的 .pypirc 文件 +## 7. 使用正则匹配 -```ini -[distutils] -index-servers = pypi +说到查找功能,那正则绝对可以说是专业的工具,多复杂的查找规则,都能满足你。 -[pypi] -username:xxx -password:xxx -``` - -然后使用这条命令进行信息注册,完成后,你可以在 PyPi 上看到项目信息。 +对于判断字符串是否存在于另一个字符串中的这个需求,使用正则简直就是大材小用。 -```shell -$ python setup.py register -``` +```python +import re -注册完了后,你还要上传源码包,别人才使用下载安装 +def is_in(full_str, sub_str): + if re.findall(sub_str, full_str): + return True + else: + return False -```shell -$ python setup.py upload +print(is_in("hello, python", "llo")) # True +print(is_in("hello, python", "lol")) # False ``` -或者也可以使用 `twine` 工具注册上传,它是一个专门用于与 pypi 进行交互的工具,详情可以参考官网:https://www.ctolib.com/twine.html,这里不详细讲了。 - - -## 参考文章 -- http://blog.konghy.cn/2018/04/29/setup-dot-py/ -- https://note.qidong.name/2018/01/python-setup-requires/ +--- diff --git a/source/c01/c01_27.rst b/source/c01/c01_27.rst index 7676aa6..f65a018 100644 --- a/source/c01/c01_27.rst +++ b/source/c01/c01_27.rst @@ -1,705 +1,154 @@ -1.27 全面学习 Python 包:包的构建与分发 -======================================= +1.27 Python 炫技操作:判断是否包含子串的七种方法 +================================================ |image0| - 首发于公众号:Python编程时光 +Python 语言里有许多(而且是越来越多)的高级特性,是 Python +发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 -1. 为什么需要对项目分发打包? ------------------------------ +但你要知道,在团队合作里,炫技是大忌。 -平常我们习惯了使用 pip -来安装一些第三方模块,这个安装过程之所以简单,是因为模块开发者为我们默默地为我们做了所有繁杂的工作,而这个过程就是 -``打包``\ 。 +为什么这么说呢?我说下自己的看法: -打包,就是将你的源代码进一步封装,并且将所有的项目部署工作都事先安排好,这样使用者拿到后即装即用,不用再操心如何部署的问题(如果你不想对照着一堆部署文档手工操作的话)。 +1. 越简洁的代码,越清晰的逻辑,就越不容易出错; +2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 +3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) -不管你是在工作中,还是业余准备自己写一个可以上传到 PyPI -的项目,你都要学会如何打包你的项目。 - -Python 发展了这么些年了,项目打包工具也已经很成熟了。他们都有哪些呢? - -你可能听过 ``disutils``\ 、 ``distutils`` -、\ ``distutils2``\ 、\ ``setuptools``\ 等等,好像很熟悉,却又很陌生,他们都是什么关系呢? - -2. 包分发的始祖:distutils --------------------------- - -``distutils`` 是 Python -的一个标准库,从命名上很容易看出它是一个分发(distribute)工具(utlis),它是 +该篇是「\ **炫技系列**\ 」的第三篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 Python -官方开发的一个分发打包工具,所有后续的打包工具,全部都是基于它进行开发的。 - -``distutils`` 的精髓在于编写 setup.py,它是模块分发与安装的指导文件。 - -那么如何编写 setup.py -呢?这里面的内容非常多,我会在后面进行详细的解析,请你耐心往下看。 - -你有可能没写过 setup.py ,但你绝对使用过 setup.py -来做一些事情,比如下面这条命令,我们经常用它来进行模块的安装。 - -.. code:: shell - - $ python setup.py install - -这样的安装方法是通过源码安装,与之对应的是通过二进制软件包的安装,同样我也会在后面进行介绍。 - -3. 分发工具升级:setuptools ---------------------------- - -``setuptools`` 是 distutils -增强版,不包括在标准库中。其扩展了很多功能,能够帮助开发者更好的创建和分发 -Python 包。大部分 Python 用户都会使用更先进的 setuptools 模块。 - -**distribute**\ ,或许你在其他地方也见过它,这里也提一下。 - -distribute 是 setuptools -有一个分支版本,分支的原因可能是有一部分开发者认为 setuptools -开发太慢了。但现在,distribute 又合并回了 setuptools -中。因此,我们可以认为它们是同一个东西。 - -还有一个大包分发工具是 -**distutils2**\ ,其试图尝试充分利用distutils,detuptools 和 distribute -并成为 Python -标准库中的标准工具。但该计划并没有达到预期的目的,且已经是一个废弃的项目。 - -因此,setuptools 是一个优秀的,可靠的 Python 包安装与分发工具。 - -那么如何在一个干净的环境中安装 setuptools 呢? - -主要有两种方法: - -- 源码安装:在 https://pypi.org/project/setuptools/#files 中下载 zip 包 - 解压执行 ``python setup.py install`` 安装 -- 通过引导程序安装:下载引导程序,它可以用来下载或者更新最新版本的 - setuptools - -.. code:: shell - - $ wget http://peak.telecommunity.com/dist/ez_setup.py - - # 安装 - $ python ez_setup.py - - # 更新,以下两种任选 - $ python ez_setup.py –U setuptools - $ pip install -U setuptools - -4. easy_install 使用指南 ------------------------- - -当你安装完 setuptools 后,就拥有了一个叫做 ``easy_install`` -的第三方管理工具,这也是它区分于 distutils 的一大改进。 - -这里简单介绍一下它的用法,虽然它已经用得非常少了。 - -先是包的安装 - -.. code:: shell - - # 通过包名,从PyPI寻找最新版本,自动下载、编译、安装 - $ easy_install pkg_name - - # 通过包名从指定下载页寻找链接来安装或升级包 - $ easy_install -f http://pythonpaste.org/package_index.html - - # 指定线上的包地址安装 - $ easy_install http://example.com/path/to/MyPackage-1.2.3.tgz - - # 从本地的 .egg 文件安装 - $ easy_install xxx.egg - - # 在安装时你可以添加额外的参数 - 指定安装目录:--install-dir=DIR, -d DIR - 指定用户安装:--user - -再者是包的升级 - -.. code:: shell - - # 从 pypi 中搜索并升级包 - $ easy_install --upgrade pkg_name - - # 指定版本进行升级 - $ easy_install "SomePackage==2.0" - -最后是包的删除 - -.. code:: shell +发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 - $ easy_install -m pkg_name +1. 使用 in 和 not in +-------------------- -需要注意的是,这样的删除,仅是在 easy-install.pth 文件中删除,使其不能在 -python 中使用 -这个模块,但实际的包还在你的电脑中,若要删除彻底,需要你手动删除相关的 -.egg 及 其他文件。 +``in`` 和 ``not in`` 在 Python 中是很常用的关键字,我们将它们归类为 +``成员运算符``\ 。 -默认情况下,easy_install 只会从 pypi -上下载相关软件包,由于这个源在国外,下载包的速度并不理想,使用过pip的朋友自然会想,easy_install -是否能指定源进行安装呢? - -答案是,可以的。 - -编辑配置文件 ``/root/.pydistutils.cfg`` - -.. code:: ini - - [easy_install] - index-url=http://mirrors.aliyun.com/pypi/simple/ - find-links=http://mirrors.aliyun.com/pypi/simple/ - -以上仅介绍了 easy_install -的一些常用的方法,想要了解更多,你可以点击官方文档:https://setuptools.readthedocs.io/en/latest/easy_install.html - -总结一句:setuptools -是官方提供的一个专业用于包分发的工具,若只从安装的角度来看,它的功能确实简单。它更大的意义是对包的分发很有用,定制化程序非常高,我们现在也还在用它进行版本包的发布。 - -5. 源码包与二进制包什么区别? ------------------------------ - -Python 包的分发可以分为两种: - -1. 以源码包的方式发布 - -源码包安装的过程,是先解压,再编译,最后才安装,所以它是跨平台的,由于每次安装都要进行编译,相对二进包安装方式来说安装速度较慢。 - -源码包的本质是一个压缩包,其常见的格式有: - -|image1| - -2. 以二进制包形式发布 - -二进制包的安装过程省去了编译的过程,直接进行解压安装,所以安装速度较源码包来说更快。 - -由于不同平台的编译出来的包无法通用,所以在发布时,需事先编译好多个平台的包。 - -二进制包的常见格式有: - -|image2| - -6. eggs 与 wheels 有什么区别? ------------------------------- - -Egg 格式是由 setuptools 在 2004 年引入,而 Wheel 格式是由 PEP427 在 2012 -年定义。Wheel 的出现是为了替代 Egg,它的本质是一个zip包,其现在被认为是 -Python 的二进制包的标准格式。 - -以下是 Wheel 和 Egg 的主要区别: - -- Wheel 有一个官方的 PEP427 来定义,而 Egg 没有 PEP 定义 -- Wheel 是一种分发格式,即打包格式。而 Egg - 既是一种分发格式,也是一种运行时安装的格式,并且是可以被直接 import -- Wheel 文件不会包含 .pyc 文件 -- Wheel 使用和 PEP376 兼容的 .dist-info 目录,而 Egg 使用 .egg-info - 目录 -- Wheel 有着更丰富的命名规则。 -- Wheel 是有版本的。每个 Wheel 文件都包含 wheel 规范的版本和打包的实现 -- Wheel 在内部被 sysconfig path type 管理,因此转向其他格式也更容易 - -wheel 包可以通过 pip 来安装,只不过需要先安装 wheel 模块,然后再使用 pip -的命令。 - -.. code:: shell - - $ pip install wheel - $ pip wheel --wheel-dir=/local/wheels pkg - -7. 超详细讲解 setup.py 的编写? -------------------------------- - -打包分发最关键的一步是编写 ``setup.py`` 文件。 - -以下是一个 setup.py 简单的使用示例 +使用这两个成员运算符,可以很让我们很直观清晰的判断一个对象是否在另一个对象中,示例如下: .. code:: python - from setuptools import setup, find_packages - - setup( - name="mytest", - version="1.0", - author="wangbm", - author_email="wongbingming@163.com", - description="Learn to Pack Python Module -->公众号:Python编程时光", - - # 项目主页 - url="http://python-online.cn/", - - # 你要安装的包,通过 setuptools.find_packages 找到当前目录下有哪些包 - packages=find_packages() - ) - -接下来,我将慢慢扩充这个setup函数,增加更多的参数,以便你能理解setup函数能做哪些事情。 + >>> "llo" in "hello, python" + True + >>> + >>> "lol" in "hello, python" + False -**程序分类信息** +2. 使用 find 方法 +----------------- -``classifiers`` -参数说明包的分类信息。所有支持的分类列表见:https://pypi.org/pypi?%3Aaction=list_classifiers - -示例: +使用 字符串 对象的 find +方法,如果有找到子串,就可以返回指定子串在字符串中的出现位置,如果没有找到,就返回 +``-1`` .. code:: python - from setuptools import setup, find_packages - - setup( - classifiers = [ - # 发展时期,常见的如下 - # 3 - Alpha - # 4 - Beta - # 5 - Production/Stable - 'Development Status :: 3 - Alpha', - - # 开发的目标用户 - 'Intended Audience :: Developers', - - # 属于什么类型 - 'Topic :: Software Development :: Build Tools', - - # 许可证信息 - 'License :: OSI Approved :: MIT License', + >>> "hello, python".find("llo") != -1 + True + >>> "hello, python".find("lol") != -1 + False + >> - # 目标 Python 版本 - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - ] - ) +3. 使用 index 方法 +------------------ -**关于文件的分发** +字符串对象有一个 index +方法,可以返回指定子串在该字符串中第一次出现的索引,如果没有找到会抛出异常,因此使用时需要注意捕获。 .. code:: python - from setuptools import setup, find_packages - - - setup( - name="mytest", - version="1.0", - author="wangbm", - author_email="wongbingming@163.com", - description="Learn to Pack Python Module", - url="http://python-online.cn/", - packages=find_packages(), - - # 安装过程中,需要安装的静态文件,如配置文件、service文件、图片等 - data_files=[ - ('', ['conf/*.conf']), - ('/usr/lib/systemd/system/', ['bin/*.service']), - ], - - # 希望被打包的文件 - package_data={ - '':['*.txt'], - 'bandwidth_reporter':['*.txt'] - }, - # 不打包某些文件 - exclude_package_data={ - 'bandwidth_reporter':['*.txt'] - } - ) - -除了以上的参数配置之外,还可以使用一个叫做 ``MANIFEST.in`` -的文件,来控制文件的分发。 - -如下这是一个 ``MANIFEST.in`` 的样例: - -:: - - include *.txt - recursive-include examples *.txt *.py - prune examples/sample?/build - -这些配置,规定了如下几点 - -- 所有根目录下的以 txt 为后缀名的文件,都会分发 -- 根目录下的 examples 目录 和 txt、py文件都会分发 -- 路径匹配上 examples/sample?/build 不会分发 - -``MANIFEST.in`` 需要放在和 setup.py 同级的顶级目录下,setuptools -会自动读取该文件。 - -**关于依赖包下载安装** - -.. code:: python - - from setuptools import setup, find_packages - - - setup( - ... - - # 表明当前模块依赖哪些包,若环境中没有,则会从pypi中下载安装 - install_requires=['docutils>=0.3'], - - # setup.py 本身要依赖的包,这通常是为一些setuptools的插件准备的配置 - # 这里列出的包,不会自动安装。 - setup_requires=['pbr'], - - # 仅在测试时需要使用的依赖,在正常发布的代码中是没有用的。 - # 在执行python setup.py test时,可以自动安装这三个库,确保测试的正常运行。 - tests_require=[ - 'pytest>=3.3.1', - 'pytest-cov>=2.5.1', - ], - - # 用于安装setup_requires或tests_require里的软件包 - # 这些信息会写入egg的 metadata 信息中 - dependency_links=[ - "http://example2.com/p/foobar-1.0.tar.gz", - ], - - # install_requires 在安装模块时会自动安装依赖包 - # 而 extras_require 不会,这里仅表示该模块会依赖这些包 - # 但是这些包通常不会使用到,只有当你深度使用模块时,才会用到,这里需要你手动安装 - extras_require={ - 'PDF': ["ReportLab>=1.2", "RXP"], - 'reST': ["docutils>=0.3"], - } - ) - -关于 ``install_requires``\ , 有以下五种常用的表示方法: - -1. ``'argparse'``\ ,只包含包名。 这种形式只检查包的存在性,不检查版本。 - 方便,但不利于控制风险。 -2. ``'setuptools==38.2.4'``\ ,指定版本。 - 这种形式把风险降到了最低,确保了开发、测试与部署的版本一致,不会出现意外。 - 缺点是不利于更新,每次更新都需要改动代码。 -3. ``'docutils >= 0.3'``\ ,这是比较常用的形式。 - 当对某个库比较信任时,这种形式可以自动保持版本为最新。 -4. ``'Django >= 1.11, != 1.11.1, <= 2'``\ ,这是比较复杂的形式。 - 如这个例子,保证了Django的大版本在1.11和2之间,也即1.11.x;并且,排除了已知有问题的版本1.11.1(仅举例)。 - 对于一些大型、复杂的库,这种形式是最合适的。 -5. ``'requests[security, socks] >= 2.18.4'``\ ,这是包含了额外的可选依赖的形式。 - 正常安装requests会自动安装它的\ ``install_requires``\ 中指定的依赖,而不会安装\ ``security``\ 和\ ``socks``\ 这两组依赖。 - 这两组依赖是定义在它的\ ``extras_require``\ 中。 - 这种形式,用在深度使用某些库时。 - -**关于安装环境的限制** - -有些库并不是在所以的 Python 版本中都适用的,若一个库安装在一个未兼容的 -Python -环境中,理论上不应该在使用时才报错,而应该在安装过程就使其失败,提示禁止安装。 - -这样的功能,可以使用 ``python_requires`` 来实现。 + def is_in(full_str, sub_str): + try: + full_str.index(sub_str) + return True + except ValueError: + return False -.. code:: python + print(is_in("hello, python", "llo")) # True + print(is_in("hello, python", "lol")) # False - setup( - ... - python_requires='>=2.7, <=3', - ) +4. 使用 count 方法 +------------------ -**生成可执行文件的分发** +利用和 index 这种曲线救国的思路,同样我们可以使用 count 的方法来判断。 -.. code:: python - - from setuptools import setup, find_packages - - - setup( - name="mytest", - version="1.0", - author="wangbm", - author_email="wongbingming@163.com", - description="Learn to Pack Python Module", - url="http://python-online.cn/", - packages=find_packages(), - - # 用来支持自动生成脚本,安装后会自动生成 /usr/bin/foo 的可执行文件 - # 该文件入口指向 foo/main.py 的main 函数 - entry_points={ - 'console_scripts': [ - 'foo = foo.main:main' - ] - }, - - # 将 bin/foo.sh 和 bar.py 脚本,生成到系统 PATH中 - # 执行 python setup.py install 后 - # 会生成 如 /usr/bin/foo.sh 和 如 /usr/bin/bar.py - scripts=['bin/foo.sh', 'bar.py'] - ) - -上面的 scripts 里有的脚本中有 ``sh`` 和 ``py`` -后缀,那么安装后,setuptools 会原封不动的移动到 /usr/bin -中,并添加可执行权限。 - -若你想对这些文件再作一些更改,比如去掉多余的后缀,可以这样做 +只要判断结果大于 0 就说明子串存在于字符串中。 .. code:: python - from setuptools.command.install_scripts import install_scripts - - class InstallScripts(install_scripts): + def is_in(full_str, sub_str): + return full_str.count(sub_str) > 0 - def run(self): - setuptools.command.install_scripts.install_scripts.run(self) + print(is_in("hello, python", "llo")) # True + print(is_in("hello, python", "lol")) # False - # Rename some script files - for script in self.get_outputs(): - if basename.endswith(".py") or basename.endswith(".sh"): - dest = script[:-3] - else: - continue - print("moving %s to %s" % (script, dest)) - shutil.move(script, dest) +5. 通过魔法方法 +--------------- - setup( - ... - scripts=['bin/foo.sh', 'bar.py'], - - cmdclass={ - "install_scripts": InstallScripts - } - ) +在第一种方法中,我们使用 in 和 not in +判断一个子串是否存在于另一个字符中,实际上当你使用 in 和 not in +时,Python 解释器会先去检查该对象是否有 ``__contains__`` 魔法方法。 -**ext_modules** +若有就执行它,若没有,Python +就自动会迭代整个序列,只要找到了需要的一项就返回 True 。 -``ext_modules`` 参数用于构建 C 和 C++ 扩展扩展包。其是 Extension -实例的列表,每一个 Extension -实例描述了一个独立的扩展模块,扩展模块可以设置扩展包名,头文件、源文件、链接库及其路径、宏定义和编辑参数等。如: +示例如下; .. code:: python - setup( - # other arguments here... - ext_modules=[ - Extension('foo', - glob(path.join(here, 'src', '*.c')), - libraries = [ 'rt' ], - include_dirs=[numpy.get_include()]) - ] - ) - -详细了解可参考:https://docs.python.org/3.6/distutils/setupscript.html#preprocessor-options - -**指定release** - -setup.py 里只能指定 version,而不能指定 -release,如果你需要变更版本号,可以使用 ``--release`` 参数进行指定 - -.. code:: shell - - python setup.py bdist_rpm --release=20200617 - -setup.py -的参数非常多,能够不借助文档写好一个setup.py好像没那么简单。为了备忘,我整理了 -setup 函数常用的一些参数: - -|image3| - -更多参数可见:https://setuptools.readthedocs.io/en/latest/setuptools.html - -8. 打包辅助神器PBR 是什么? ---------------------------- - -``pbr`` 是 setuptools 的辅助工具,最初是为 OpenStack -开发(https://launchpad.net/pbr),基于\ ``d2to1``\ 。 - -``pbr`` 会读取和过滤setup.cfg中的数据,然后将解析后的数据提供给 -``setup.py`` 作为参数。包含如下功能: - -1. 从git中获取Version、AUTHORS and ChangeLog信息 -2. Sphinx Autodoc。pbr 会扫描project,找到所有模块,生成stub files -3. Requirements。pbr会读取requirements.txt,生成setup函数需要的\ ``install_requires/tests_require/dependency_links`` - -这里需要注意,在 ``requirements.txt`` -文件的头部可以使用:\ ``--index https://pypi.python.org/simple/``\ ,这一行把一个抽象的依赖声明如 -requests==1.2.0 转变为一个具体的依赖声明 requests 1.2.0 from -pypi.python.org/simple/ - -4. long_description。从README.rst, README.txt or README - file中生成\ ``long_description``\ 参数 - -使用pbr很简单: - -:: - - from setuptools import setup - - setup( - setup_requires=['pbr'], - pbr=True, - ) - -使用pbr时,setup.cfg中有一些配置。在[files]中,有三个key: -``packages``:指定需要包含的包,行为类似于setuptools.find_packages -``namespace_packages``:指定namespace packages ``data_files``: -指定目的目录和源文件路径,一个示例: - -:: - - [files] - data_files = - etc/pbr = etc/pbr/* - etc/neutron = - etc/api-paste.ini - etc/dhcp-agent.ini - etc/init.d = neutron.init - -``[entry_points]`` 段跟 setuptools 的方式相同。 - -到此,我讲了三种编写使用 setup.py 的方法 - -- 使用命令行参数指定,一个一个将参数传递进去(极不推荐) -- 在 setup.py 中的setup函数中指定(推荐使用) -- 使用 pbr ,在 setup.cfg 中指定(易于管理,更推荐) - -9. 如何使用 setup.py 构建包 ---------------------------- - -1、构建源码发布包。 - -用于发布一个 Python 模块或项目,将源码打包成 tar.gz (用于 Linux -环境中)或者 zip 压缩包(用于 Windows 环境中) - -.. code:: shell - - $ python setup.py sdist - -那这种包如何安装呢? - -答案是,使用下一节即将介绍的 ``setuptools`` 中提供的 ``easy_install`` -工具。 - -.. code:: shell - - $ easy_install xxx.tar.gz - -使用 sdist 将根据当前平台创建默认格式的存档。在类 Unix -平台上,将创建后缀后为 ``.tar.gz`` 的 gzip -压缩的tar文件分发包,而在Windows上为 ZIP 文件。 + >>> "hello, python".__contains__("llo") + True + >>> + >>> "hello, python".__contains__("lol") + False + >>> -当然,你也可以通过指定你要的发布包格式来打破这个默认行为 +这个用法与使用 in 和 not in +没有区别,但不排除有人会特意写成这样来增加代码的理解难度。 -.. code:: shell +6. 借助 operator +---------------- - $ python setup.py sdist --formats=gztar,zip +operator模块是python中内置的操作符函数接口,它定义了一些算术和比较内置操作的函数。operator模块是用c实现的,所以执行速度比 +python 代码快。 -你可以指定的格式有哪些呢? +在 operator 中有一个方法 ``contains`` +可以很方便地判断子串是否在字符串中。 -创建一个压缩的tarball和一个zip文件。可用格式为: - -|image4| - -对以上的格式,有几点需要注意一下: - -- 在版本3.5中才添加了对 ``xztar`` 格式的支持 -- zip - 格式需要你事先已安装相应的模块:zip程序或zipfile模块(已成为Python的标准库) -- ztar 格式正在弃用,请尽量不要使用 - -另外,如果您希望归档文件的所有文件归root拥有,可以这样指定 - -:: - - python setup.py sdist --owner=root --group=root - -2、构建二进制分发包。 - -在windows中我们习惯了双击 exe 进行软件的安装,Python -模块的安装也同样支持 打包成 exe 这样的二进制软件包。 - -.. code:: shell - - $ python setup.py bdist_wininst - -而在 Linux 中,大家也习惯了使用 rpm 来安装包,对此你可以使用这条命令实现 -rpm 包的构建 - -.. code:: shell - - $ python setup.py bdist_rpm - -若你喜欢使用 easy_install 或者 pip 来安装离线包。你可以将其打包成 egg 包 - -.. code:: shell - - $ python setup.py bdist_egg - -若你的项目,需要安装多个平台下,既有 Windows 也有 -Linux,按照上面的方法,多种格式我们要执行多次命令,为了方便,你可以一步到位,执行如下这条命令,即可生成多个格式的进制包 - -.. code:: shell - - $ python setup.py bdist - -10. 如何使用 setup.py 安装包 ----------------------------- - -正常情况下,我们都是通过以上构建的源码包或者二进制包进行模块的安装。 - -但在编写 setup.py -的过程中,可能不能一步到位,需要多次调试,这时候如何测试自己写的 -setup.py 文件是可用的呢? - -这时候你可以使用这条命令,它会将你的模块安装至系统全局环境中 - -.. code:: shell - - $ python setup.py install - -如若你的项目还处于开发阶段,频繁的安装模块,也是一个麻烦事。 - -这时候你可以使用这条命令安装,该方法不会真正的安装包,而是在系统环境中创建一个软链接指向包实际所在目录。这边在修改包之后不用再安装就能生效,便于调试。 - -.. code:: shell - - $ python setup.py develop - -11. 如何发布包到 PyPi? ------------------------ - -通过上面的学习,你一定已经学会了如何打包自己的项目,若你觉得自己开发的模块非常不错,想要 -share 给其他人使用,你可以将其上传到 PyPi (Python Package -Index)上,它是 Python -官方维护的第三方包仓库,用于统一存储和管理开发者发布的 Python 包。 - -如果要发布自己的包,需要先到 pypi 上注册账号。然后创建 ``~/.pypirc`` -文件,此文件中配置 PyPI -访问地址和账号。如的.pypirc文件内容请根据自己的账号来修改。 - -典型的 .pypirc 文件 - -.. code:: ini - - [distutils] - index-servers = pypi - - [pypi] - username:xxx - password:xxx +.. code:: python -然后使用这条命令进行信息注册,完成后,你可以在 PyPi 上看到项目信息。 + >>> import operator + >>> + >>> operator.contains("hello, python", "llo") + True + >>> operator.contains("hello, python", "lol") + False + >>> -.. code:: shell +7. 使用正则匹配 +--------------- - $ python setup.py register +说到查找功能,那正则绝对可以说是专业的工具,多复杂的查找规则,都能满足你。 -注册完了后,你还要上传源码包,别人才使用下载安装 +对于判断字符串是否存在于另一个字符串中的这个需求,使用正则简直就是大材小用。 -.. code:: shell +.. code:: python - $ python setup.py upload + import re -或者也可以使用 ``twine`` 工具注册上传,它是一个专门用于与 pypi -进行交互的工具,详情可以参考官网:https://www.ctolib.com/twine.html,这里不详细讲了。 + def is_in(full_str, sub_str): + if re.findall(sub_str, full_str): + return True + else: + return False -参考文章 --------- + print(is_in("hello, python", "llo")) # True + print(is_in("hello, python", "lol")) # False -- http://blog.konghy.cn/2018/04/29/setup-dot-py/ -- https://note.qidong.name/2018/01/python-setup-requires/ +-------------- -|image5| +|image1| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20191218202833.png -.. |image2| image:: http://image.python-online.cn/20191218203005.png -.. |image3| image:: http://image.python-online.cn/20191218203255.png -.. |image4| image:: http://image.python-online.cn/20191218203517.png -.. |image5| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_41.rst b/source/c01/c01_28.rst similarity index 100% rename from source/c01/c01_41.rst rename to source/c01/c01_28.rst diff --git a/source/c01/c01_29.md b/source/c01/c01_29.md deleted file mode 100644 index 16db089..0000000 --- a/source/c01/c01_29.md +++ /dev/null @@ -1,13 +0,0 @@ -# 1.27 如何阅读 CPython源码? - -![](http://image.iswbm.com/20200602135014.png) - - - -参考学习地址:https://realpython.com/cpython-source-code-guide/ - -基于 Python 3.6 的源码分析:https://he11olx.com/ - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_29.rst b/source/c01/c01_29.rst index f801300..9df2a34 100644 --- a/source/c01/c01_29.rst +++ b/source/c01/c01_29.rst @@ -1,14 +1,183 @@ -1.27 如何阅读 CPython源码? -=========================== +1.42 Python 炫技操作:海象运算符的三种用法 +========================================== |image0| -参考学习地址:https://realpython.com/cpython-source-code-guide/ +Python 版本发展非常快,如今最新的版本已经是 Pyhton +3.9,即便如此,有很多人甚至还停留在 3.6 或者 3.7,连 3.8 还没用上。 -基于 Python 3.6 的源码分析:https://he11olx.com/ +很多 Python 3.8 +的特性还没来得及了解,就已经成为旧知识了,比如今天要说的海象运算符。 + +海象运算符是在 PEP 572 被提出的,直到 3.8 版本合入发布。 + +它的英文原名叫 ``Assignment Expressions``\ ,翻译过来也就是 +``赋值表达式``\ ,不过现在大家更普遍地称之为海象运算符,就是因为它长得真的太像海象了。 |image1| +1. 第一个用法:if/else +---------------------- + +可能有朋友是第一次接触这个新特性,所以还是简单的介绍一下这个海象运算符有什么用? + +在 Golang 中的条件语句可以直接在 if +中运算变量的获取后直接对这个变量进行判断,可以让你少写一行代码 + +.. code:: go + + import "fmt" + + func main() { + if age := 20;age > 18 { + fmt.Println("已经成年了") + } + } + +若在 Python 3.8 之前,Python 必须得这样子写 + +.. code:: python + + age = 20 + if age > 18: + print("已经成年了") + +但有了海象运算符之后,你可以和 Golang 一样(如果你没学过 +Golang,那这里要注意,Golang 中的 ``:=`` +叫短变量声明,意思是声明并初始化,它和 Python 中的 ``:=`` 不是一个概念) + +.. code:: python + + if (age:= 20) > 18: + print("已经成年了") + +2. 第二个用法:while +-------------------- + +在不使用 海象运算符之前,使用 while 循环来读取文件的时候,你也许会这么写 + +.. code:: python + + file = open("demo.txt", "r") + while True: + line = file.readline() + if not line: + break + print(line.strip()) + +但有了海象运算符之后,你可以这样 + +.. code:: python + + file = open("demo.txt", "r") + while (line := file.readline()): + print(line.strip()) + +使用它替换以往的无限 while 循环写法更为惊艳 + +比如,实现一个需要命令行交互输入密码并检验的代码,你也许会这样子写 + +.. code:: python + + while True: + p = input("Enter the password: ") + if p == "youpassword": + break + +有了海象运算符之后,这样子写更为舒服 + +.. code:: python + + while (p := input("Enter the password: ")) != "youpassword": + continue + +3. 第三个用法:推导式 +--------------------- + +这个系列的文章,几乎每篇都能看到推导式的身影,这一篇依旧如此。 + +在编码过程中,我很喜欢使用推导式,在简单的应用场景下,它简洁且不失高效。 + +如下这段代码中,我会使用列表推导式得出所有会员中过于肥胖的人的 bmi 指数 + +.. code:: python + + members = [ + {"name": "小五", "age": 23, "height": 1.75, "weight": 72}, + {"name": "小李", "age": 17, "height": 1.72, "weight": 63}, + {"name": "小陈", "age": 20, "height": 1.78, "weight": 82}, + ] + + count = 0 + + def get_bmi(info): + global count + count += 1 + + print(f"执行了 {count} 次") + + height = info["height"] + weight = info["weight"] + + return weight / (height**2) + + # 查出所有会员中过于肥胖的人的 bmi 指数 + fat_bmis = [get_bmi(m) for m in members if get_bmi(m) > 24] + + print(fat_bmis) + +输出如下 + +:: + + 执行了 1 次 + 执行了 2 次 + 执行了 3 次 + 执行了 4 次 + [25.88057063502083] + +可以看到,会员数只有 3 个,但是 get_bmi 函数却执行了 4 +次,原因是在判断时执行了 3 次,而在构造新的列表时又重复执行了一遍。 + +如果所有会员都是过于肥胖的,那最终将执行 6 +次,这种在大量的数据下是比较浪费性能的,因此对于这种结构,我通常会使用传统的for +循环 + if 判断。 + +.. code:: python + + fat_bmis = [] + + # 查出所有会员中过于肥胖的人的 bmi 指数 + for m in members: + bmi = get_bmi(m) + if bmi > 24: + fat_bmis.append(bmi) + +在有了海象运算符之后,你就可以不用在这种场景下做出妥协。 + +.. code:: python + + # 查出所有会员中过于肥胖的人的 bmi 指数 + fat_bmis = [bmi for m in members if (bmi := get_bmi(m)) > 24] + +最终从输出结果可以看出,只执行了 3 次 + +:: + + 执行了 1 次 + 执行了 2 次 + 执行了 3 次 + [25.88057063502083] + +这里仅介绍了列表推导式,但在字典推导式和集合推导式中同样适用。不再演示。 + +海象运算符,是一个新奇的特性,有不少人觉得这样这种特性会破坏代码的可读性。确实在一个新鲜事物刚出来时是会这样,但我相信经过时间的沉淀后,越来越多的人使用它并享受它带来的便利时,这种争议也会慢慢消失在历史的长河中。 + +-------------- + +|image2| + .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/image-20200418122739417.png +.. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_31.md b/source/c01/c01_31.md deleted file mode 100644 index 870abb5..0000000 --- a/source/c01/c01_31.md +++ /dev/null @@ -1,25 +0,0 @@ -# 1.31 学习 Pillow 笔记 - -![](http://image.iswbm.com/20200602135014.png) - - - -## 1. 安装 pillow - -``` -pip install pillow -``` - - - -## 2. 使用 - -ARGB 是一种色彩模式,也就是RGB色彩模式附加上Alpha(透明度)通道,常见于32位位图的存储结构。 - -RGB 色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。 - - - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_31.rst b/source/c01/c01_31.rst deleted file mode 100644 index 536d407..0000000 --- a/source/c01/c01_31.rst +++ /dev/null @@ -1,26 +0,0 @@ -1.31 学习 Pillow 笔记 -===================== - -|image0| - -1. 安装 pillow --------------- - -:: - - pip install pillow - -2. 使用 -------- - -ARGB -是一种色彩模式,也就是RGB色彩模式附加上Alpha(透明度)通道,常见于32位位图的存储结构。 - -RGB -色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。 - -|image1| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_32.md b/source/c01/c01_32.md deleted file mode 100644 index 9d8e2f4..0000000 --- a/source/c01/c01_32.md +++ /dev/null @@ -1,39 +0,0 @@ -# 1.32 在 CentOS 7.2 上安装 Python3.7 - -![](http://image.iswbm.com/20200602135014.png) - -首先下载 python3.7的源码包,然后解压 - -```shell -$ cd ~ -$ wget -c https://www.python.org/ftp/python/3.7.1/Python-3.7.1.tgz -$ tar xf Python-3.7.1.tgz && cd Python-3.7.1 -``` - -安装 一些依赖包 - -```shell -$ yum install gcc zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel libffi-devel python3-devel -y -``` - -编译安装 - -```shell -$ ./configure -$ make -$ sudo make install -``` - -至此,你已经成功安装 了 Python3, pip3,setuptools - -requests.get("https://www.baidu.com") - -``` -python3 -m pip install --user requests aiohttp cryptography pymysql prettytable sh Fabric paramiko apscheduler bashplotlib httpie PathPicker -i https://pypi.douban.com/simple -``` - - - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_32.rst b/source/c01/c01_32.rst deleted file mode 100644 index 4317fad..0000000 --- a/source/c01/c01_32.rst +++ /dev/null @@ -1,40 +0,0 @@ -1.32 在 CentOS 7.2 上安装 Python3.7 -=================================== - -|image0| - -首先下载 python3.7的源码包,然后解压 - -.. code:: shell - - $ cd ~ - $ wget -c https://www.python.org/ftp/python/3.7.1/Python-3.7.1.tgz - $ tar xf Python-3.7.1.tgz && cd Python-3.7.1 - -安装 一些依赖包 - -.. code:: shell - - $ yum install gcc zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel libffi-devel python3-devel -y - -编译安装 - -.. code:: shell - - $ ./configure - $ make - $ sudo make install - -至此,你已经成功安装 了 Python3, pip3,setuptools - -requests.get(“https://www.baidu.com”) - -:: - - python3 -m pip install --user requests aiohttp cryptography pymysql prettytable sh Fabric paramiko apscheduler bashplotlib httpie PathPicker -i https://pypi.douban.com/simple - -|image1| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_34.md b/source/c01/c01_34.md deleted file mode 100644 index e5ad8a0..0000000 --- a/source/c01/c01_34.md +++ /dev/null @@ -1,92 +0,0 @@ -# 1.34 每日一库:sh,最优雅的命令调用方式 - -![](http://image.iswbm.com/20200602135014.png) - -在编写 Python 脚本的时候,很经常需要我们去调用系统的命令,方法有很多种,比如 os.popen,os.system,commands,还有 subprocess。 - -今天明哥要介绍一种更加优雅的方法,就是 `sh` 这个第三方库,它能让你像调用方法那样去调用系统中的命令。 - -使用前,当然是安装它,`sh` 支持 Python 2 也支持 Python3,这里以 Python 3 为例。 - -```shell -$ python3 -m pip install sh -``` - -这里要注意一点,虽然在 Windows 上也可以安装成功,但是并不能使用,如果你尝试在 Windows 导入 它,会友好地提示你,该库只适用于 Linux 及 OSX 系统,假如你也想要在 Windows 使用,它推荐你使用它的 兄弟库 - `pbs` (https://pypi.org/project/pbs/)。 - -![](http://image.python-online.cn/20200227201644.png) - - - -安装完成后,就可以直接使用它了,以下几个示例,非常简单,简单到我感觉只要 demo ,而不需要任何的中文解释就可以让你知道他是如何使用的。 - - - -### 1. 列出目录文件 - -使用 `ls` - -```python ->>> import sh ->>> sh.ls("/home", "-l", color="never") -total 4 -drwx------ 3 centos centos 4096 Mar 8 2019 centos -``` - -使用 `glob` - -```python ->>> sh.glob("/etc/*.conf") -['/etc/mke2fs.conf', '/etc/dnsmasq.conf', '/etc/asound.conf'] -``` - - - -### 调用程序 - -```python ->>> import sh ->>> r=sh.Command('/root/test.py') ->>> r() -hello,world -``` - -上面我们的 ls ,也可以通过这种方式执行,只是不能再加参数了。 - -```python -sh.Command("ls")() -``` - - - -### 管道 - -```python ->>> print(sh.sort(sh.du(sh.glob('*'),'-shc'),'-rn')) -712K distribute-0.6.49.tar.gz -672K setuptools-1.1.5.tar.gz -548K get-pip.py -``` - -管道是有序的,默认由内而外,但如果需要并行呢?加个_piped=True - -```python ->>> for line in sh.tr(sh.tail("-f", "/home/mysql/mysql/log/alert.log", _piped=True), "[:upper:]", "[:lower:]", _iter=True): -... print line -... -innodb: doublewrite buffer not found: creating new - -innodb: doublewrite buffer created - -innodb: 127 rollback segment(s) active. - -innodb: creating foreign key constraint system tables - -innodb: foreign key constraint system tables created -``` - - - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_34.rst b/source/c01/c01_34.rst deleted file mode 100644 index c020d68..0000000 --- a/source/c01/c01_34.rst +++ /dev/null @@ -1,98 +0,0 @@ -1.34 每日一库:sh,最优雅的命令调用方式 -======================================= - -|image0| - -在编写 Python -脚本的时候,很经常需要我们去调用系统的命令,方法有很多种,比如 -os.popen,os.system,commands,还有 subprocess。 - -今天明哥要介绍一种更加优雅的方法,就是 ``sh`` -这个第三方库,它能让你像调用方法那样去调用系统中的命令。 - -使用前,当然是安装它,\ ``sh`` 支持 Python 2 也支持 Python3,这里以 -Python 3 为例。 - -.. code:: shell - - $ python3 -m pip install sh - -这里要注意一点,虽然在 Windows -上也可以安装成功,但是并不能使用,如果你尝试在 Windows 导入 -它,会友好地提示你,该库只适用于 Linux 及 OSX 系统,假如你也想要在 -Windows 使用,它推荐你使用它的 兄弟库 - ``pbs`` -(https://pypi.org/project/pbs/)。 - -|image1| - -安装完成后,就可以直接使用它了,以下几个示例,非常简单,简单到我感觉只要 -demo ,而不需要任何的中文解释就可以让你知道他是如何使用的。 - -1. 列出目录文件 -~~~~~~~~~~~~~~~ - -使用 ``ls`` - -.. code:: python - - >>> import sh - >>> sh.ls("/home", "-l", color="never") - total 4 - drwx------ 3 centos centos 4096 Mar 8 2019 centos - -使用 ``glob`` - -.. code:: python - - >>> sh.glob("/etc/*.conf") - ['/etc/mke2fs.conf', '/etc/dnsmasq.conf', '/etc/asound.conf'] - -调用程序 -~~~~~~~~ - -.. code:: python - - >>> import sh - >>> r=sh.Command('/root/test.py') - >>> r() - hello,world - -上面我们的 ls ,也可以通过这种方式执行,只是不能再加参数了。 - -.. code:: python - - sh.Command("ls")() - -管道 -~~~~ - -.. code:: python - - >>> print(sh.sort(sh.du(sh.glob('*'),'-shc'),'-rn')) - 712K distribute-0.6.49.tar.gz - 672K setuptools-1.1.5.tar.gz - 548K get-pip.py - -管道是有序的,默认由内而外,但如果需要并行呢?加个_piped=True - -.. code:: python - - >>> for line in sh.tr(sh.tail("-f", "/home/mysql/mysql/log/alert.log", _piped=True), "[:upper:]", "[:lower:]", _iter=True): - ... print line - ... - innodb: doublewrite buffer not found: creating new - - innodb: doublewrite buffer created - - innodb: 127 rollback segment(s) active. - - innodb: creating foreign key constraint system tables - - innodb: foreign key constraint system tables created - -|image2| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20200227201644.png -.. |image2| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_35.md b/source/c01/c01_35.md deleted file mode 100644 index 3c72d22..0000000 --- a/source/c01/c01_35.md +++ /dev/null @@ -1,384 +0,0 @@ -# 1.35 使用 Python 远程登陆服务器的利器 - -![](http://image.iswbm.com/20200602135014.png) - -在使用 Python 写一些脚本的时候,在某些情况下,我们需要频繁登陆远程服务去执行一次命令,并返回一些结果。 - -在 shell 环境中,我们是这样子做的。 - -```shell -$ sshpass -p ${passwd} ssh -p ${port} -l ${user} -o StrictHostKeyChecking=no xx.xx.xx.xx "ls -l" -``` - -然后你会发现,你的输出有很多你并不需要,但是又不去不掉的一些信息(也许有方法,请留言交流),类似这样 - -```shell -host: xx.xx.xx.xx, port: xx -Warning: Permanently added '[xx.xx.xx.xx]:xx' (RSA) to the list of known hosts. -Login failure: [Errno 1] This server is not registered to rmp platform, please confirm whether cdn server. -total 4 --rw-r--r-- 1 root root 239 Mar 30 2018 admin-openrc -``` - -对于直接使用 shell 命令,来执行命令的,可以直接使用管道,或者将标准输出重定向到文件的方法取得执行命令返回的结果 - -## 1. 使用 subprocess - -若是使用 Python 来做这件事,通常我们会第一时间,想到使用 os.popen,os.system,commands,subprocess 等一些命令执行库来间接获取 。 - -但是据我所知,这些库获取的 output 不仅只有标准输出,还包含标准错误(也就是上面那些多余的信息) - -所以每次都要对 output 进行的数据清洗,然后整理格式化,才能得到我们想要的数据。 - -用 subprocess 举个例子,就像这样子 - -```python -import subprocess -ssh_cmd = "sshpass -p ${passwd} ssh -p 22 -l root -o StrictHostKeyChecking=no xx.xx.xx.xx 'ls -l'" -status, output = subprocess.getstatusoutput(ssh_cmd) - -# 数据清理,格式化的就不展示了 - -``` - - - -通过以上的文字 + 代码的展示 ,可以感觉到 ssh 登陆的几大痛点 - -- **痛点一**:需要额外安装 sshpass(如果不免密的话) -- **痛点二**:干扰信息太多,数据清理、格式化相当麻烦 -- **痛点三**:代码实现不够优雅(有点土),可读性太差 -- **痛点四**:ssh 连接不能复用,一次连接仅能执行一次 -- **痛点五**:代码无法全平台,仅能在 Linux 和 OSX 上使用 - - - -为了解决这几个问题,我搜索了全网关于 Python ssh 的文章,没有看到有完整介绍这方面的技巧的。 - - - -为此,我就翻阅了一个很火的 Github 项目: awesome-python-cn (https://github.com/BingmingWong/awesome-python-cn)。 - -期望在这里,找到有一些关于 远程连接 的一些好用的库。 - -还真的被我找到了两个 - -- sh.ssh -- Paramiko - - - -## 2. 使用 sh.ssh - -首先来介绍第一个,`sh.ssh` - -`sh` 是一个可以让你通过函数的调用来完成 Linxu/OSX 系统命令的一个库,非常好用,关于它有机会也写篇介绍。 - -```shell -$ python3 -m pip install sh -``` - - - -今天只介绍它其中的一个函数:`ssh` - -通常两台机器互访,为了方便,可设置免密登陆,这样就不需要输入密码。 - -这段代码可以实现免密登陆,并执行我们的命令 `ls -l` - -```python -from sh import ssh -output=ssh("root@xx.xx.xx.xx", "-p 22", "ls -l") -print(output) -``` - -但有可能 ,我们并不想设置互信免密,为了使这段代码更通用,我假定我们没有设置免密,只能使用密码进行登陆。 - -问题就来了,要输入密码,必须得使用交互式的方法来输入呀,在 Python 中要如何实现呢? - -原来 ssh 方法接收一个 `_out` 参数,这个参数可以为一个字符串,表示文件路径,也可以是一个文件对象(或者类文件对象),还可以是一个回调函数,意思是当有标准输出时,就会调用将输出内容传给这个函数。 - -这就好办了呀。 - -我只要识别到有 `password:` 字样,就往标准输入写入我的密码就好了呀。 - - - -完整代码如下: - -```python -import sys -from sh import ssh - -aggregated = "" -def ssh_interact(char, stdin): - global aggregated - sys.stdout.write(char.encode()) - sys.stdout.flush() - aggregated += char - if aggregated.endswith("password: "): - stdin.put("you_password\n") - -output=ssh("root@xx.xx.xx.xx", "-p 22", "ls -l",_tty_in=True, _out_bufsize=0, _out=ssh_interact) -print(output) -``` - -这是官方文档(http://amoffat.github.io/sh/tutorials/interacting_with_processes.html?highlight=ssh)给的一些信息,写的一个demo。 - -尝试运行后,发现程序会一直在运行中,永远不会返回,不会退出,回调函数也永远不会进入。 - -通过调试查看源代码,仍然查不到问题所在,于是去 [Github](https://github.com/amoffat/sh/issues/393) 上搜了下,原来在 2017 年就已经存在这个问题了,到现在 2020 年了还没有修复,看来使用 `sh.ssh` 的人并不多,于是我又“追问”了下,期望能得到回复。 - -![](http://image.python-online.cn/20200228085749.png) - -以上这个问题,只有在需要输入密码才会出现,如果设置了机器互信是没有问题的。 - -为了感受 `sh.ssh` 的使用效果,我设置了机器互信免密,然后使用如下这段代码。 - -```python -from sh import ssh - -my_server=ssh.bake("root@xx.xx.xx.xx", "-p 22") - -# 相当于执行登陆一次执行一次命令,执行完就退出登陆 -print(my_server.ls()) - -# 可在 sleep 期间,手动登陆服务器,使用 top ,查看当前有多少终端在连接 -time.sleep(5) - -# 再次执行这条命令时,登陆终端数将 +1,执行完后,又将 -1 -print(my_server.ifconfig()) -``` - -惊奇地发现使用 `bake` 这种方式,`my_server.ls()` 和 `my_server.ifconfig()` 这种看似是通过同一个ssh连接,执行两次命令,可实际上,你可以在远程机器上,执行 top 命令看到已连接的终端的变化,会先 `+1` 再 `-1`,说明两次命令的执行是通过两次连接实现的。 - -如此看来,使用 `sh.ssh` 可以解决痛点一(如果上述问题能得到解决)、痛点二、痛点三。 - -但是它仍然无法复用 ssh 连接,还是不太方便,不是我理想中的最佳方案。 - -最重要的一点是, `sh` 这个模块,仅支持 Linxu/OSX ,在 Windows 你得使用它的兄弟库 - `pbs` ,然后我又去 pypi 看了一眼 [pbs](https://pypi.org/project/pbs/),已经 “年久失修”,没人维护了。 - -![](http://image.python-online.cn/20200228093627.png) - -至此,我离 “卒”,就差最后一根稻草了。 - - - -## 3. 使用 paramiko - -带着最后一丝希望,我尝试使用了 `paramiko` 这个库,终于在 `paramiko` 这里,找回了本应属于 Python 的那种优雅。 - -你可以通过如下命令去安装它 - -``` -$ python3 -m pip install paramiko -``` - - - -然后接下来,就介绍几种常用的 ssh 登陆的方法 - -### 方法1:基于用户名和密码的 sshclient 方式登录 - -然后你可以参考如下这段代码,在 Linux/OSX 系统下进行远程连接 - -```python -import paramiko - -ssh = paramiko.SSHClient() -# 允许连接不在know_hosts文件中的主机 -ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - -# 建立连接 -ssh.connect("xx.xx.xx.xx", username="root", port=22, password="you_password") - -# 使用这个连接执行命令 -ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("ls -l") - -# 获取输出 -print(ssh_stdout.read()) - -# 关闭连接 -ssh.close() -``` - - - -### 方法2:基于用户名和密码的 transport 方式登录 - -方法1 是传统的连接服务器、执行命令、关闭的一个操作,多个操作需要连接多次,无法复用连接[**痛点四**]。 - -有时候需要登录上服务器执行多个操作,比如执行命令、上传/下载文件,方法1 则无法实现,那就可以使用 transport 的方法。 - -```python -import paramiko - -# 建立连接 -trans = paramiko.Transport(("xx.xx.xx.xx", 22)) -trans.connect(username="root", password="you_passwd") - -# 将sshclient的对象的transport指定为以上的trans -ssh = paramiko.SSHClient() -ssh._transport = trans - -# 剩下的就和上面一样了 -ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) -ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("ls -l") -print(ssh_stdout.read()) - -# 关闭连接 -trans.close() -``` - - - -### 方法3:基于公钥密钥的 SSHClient 方式登录 - -```python -import paramiko - -# 指定本地的RSA私钥文件 -# 如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数 -pkey = paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa', password='12345') - -# 建立连接 -ssh = paramiko.SSHClient() -ssh.connect(hostname='xx.xx.xx.xx', - port=22, - username='you_username', - pkey=pkey) - -# 执行命令 -stdin, stdout, stderr = ssh.exec_command('ls -l') - -# 结果放到stdout中,如果有错误将放到stderr中 -print(stdout.read()) - -# 关闭连接 -ssh.close() -``` - - - -### 方法4:基于密钥的 Transport 方式登录 - -```python -import paramiko - -# 指定本地的RSA私钥文件 -# 如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数 -pkey = paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa', password='12345') - -# 建立连接 -trans = paramiko.Transport(('xx.xx.xx.xx', 22)) -trans.connect(username='you_username', pkey=pkey) - -# 将sshclient的对象的transport指定为以上的trans -ssh = paramiko.SSHClient() -ssh._transport = trans - -# 执行命令,和传统方法一样 -stdin, stdout, stderr = ssh.exec_command('df -hl') -print(stdout.read().decode()) - -# 关闭连接 -trans.close() -``` - - - -以上四种方法,可以帮助你实现远程登陆服务器执行命令,如果需要复用连接:一次连接执行多次命令,可以使用 **方法二** 和 **方法四** - -用完后,记得关闭连接。 - -### 实现 sftp 文件传输 - -同时,paramiko 做为 ssh 的完美解决方案,它非常专业,利用它还可以实现 sftp 文件传输。 - -```python -import paramiko - -# 实例化一个trans对象# 实例化一个transport对象 -trans = paramiko.Transport(('xx.xx.xx.xx', 22)) - -# 建立连接 -trans.connect(username='you_username', password='you_passwd') - -# 实例化一个 sftp对象,指定连接的通道 -sftp = paramiko.SFTPClient.from_transport(trans) - -# 发送文件 -sftp.put(localpath='/tmp/11.txt', remotepath='/tmp/22.txt') - -# 下载文件 -sftp.get(remotepath='/tmp/22.txt', localpath='/tmp/33.txt') -trans.close() -``` - - - -到这里,Paramiko 已经完胜了,但是仍然有一个痛点我们没有提及,就是多平台,说的就是 Windows,这里就有一件好事,一件坏事了,。 - -好事就是:paramiko 支持 windows - -坏事就是:你需要做很多复杂的准备,你可 google 解决,但是我建议你直接放弃,坑太深了。 - -![](http://image.python-online.cn/20200228111654.png) - -### 注意事项 - -使用 paramiko 的时候,有一点需要注意一下,这个也是我自己 "踩坑" 后才发现的,其实我觉得这个设计挺好的,如果你不需要等待它返回数据,可以直接实现异步效果,只不过对于不知道这个设计的人,确实是个容易掉坑的点 - -就是在执行 `ssh.exec_command(cmd)` 时,这个命令并不是同步阻塞的。 - -比如下面这段代码,执行时,你会发现 脚本立马就结束退出了,并不会等待 5 s 后,再 执行 ssh.close() - -```python -import paramiko - -trans = paramiko.Transport(("172.20.42.1", 57891)) -trans.connect(username="root", password="youpassword") -ssh = paramiko.SSHClient() -ssh._transport = trans -stdin, stdout, stderr = ssh.exec_command("sleep 5;echo ok") -ssh.close() -``` - - - -但是如果改成这样,加上一行 stdout.read(), paramiko 就知道,你需要这个执行的结果,就会在 read() 进行阻塞。 - -```python -import paramiko - -trans = paramiko.Transport(("172.20.42.1", 57891)) -trans.connect(username="root", password="youpassword") -ssh = paramiko.SSHClient() -ssh._transport = trans -stdin, stdout, stderr = ssh.exec_command("sleep 5;echo ok") - -# 加上一行 read() -print(stdout.read()) -ssh.close() -``` - - - -## 4. 写在最后 - -经过了一番对比,和一些实例的展示,可以看出 Paramiko 是一个专业、让人省心的 ssh 利器,个人认为 Paramiko 模块是运维人员必学模块之一,如果你恰好需要在 Python 代码中实现 ssh 到远程服务器去获取一些信息,那么我把 Paramiko 推荐给你。 - -最后,希望这篇文章,能给你带来帮助。 - - - -## 5. 参考链接 - -- https://github.com/paramiko/paramiko -- http://docs.paramiko.org -- https://www.liujiangblog.com/blog/15/ -- http://docs.paramiko.org/en/stable/ - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_35.rst b/source/c01/c01_35.rst deleted file mode 100644 index c2ae53d..0000000 --- a/source/c01/c01_35.rst +++ /dev/null @@ -1,405 +0,0 @@ -1.35 使用 Python 远程登陆服务器的利器 -===================================== - -|image0| - -在使用 Python -写一些脚本的时候,在某些情况下,我们需要频繁登陆远程服务去执行一次命令,并返回一些结果。 - -在 shell 环境中,我们是这样子做的。 - -.. code:: shell - - $ sshpass -p ${passwd} ssh -p ${port} -l ${user} -o StrictHostKeyChecking=no xx.xx.xx.xx "ls -l" - -然后你会发现,你的输出有很多你并不需要,但是又不去不掉的一些信息(也许有方法,请留言交流),类似这样 - -.. code:: shell - - host: xx.xx.xx.xx, port: xx - Warning: Permanently added '[xx.xx.xx.xx]:xx' (RSA) to the list of known hosts. - Login failure: [Errno 1] This server is not registered to rmp platform, please confirm whether cdn server. - total 4 - -rw-r--r-- 1 root root 239 Mar 30 2018 admin-openrc - -对于直接使用 shell -命令,来执行命令的,可以直接使用管道,或者将标准输出重定向到文件的方法取得执行命令返回的结果 - -1. 使用 subprocess ------------------- - -若是使用 Python 来做这件事,通常我们会第一时间,想到使用 -os.popen,os.system,commands,subprocess 等一些命令执行库来间接获取 。 - -但是据我所知,这些库获取的 output -不仅只有标准输出,还包含标准错误(也就是上面那些多余的信息) - -所以每次都要对 output -进行的数据清洗,然后整理格式化,才能得到我们想要的数据。 - -用 subprocess 举个例子,就像这样子 - -.. code:: python - - import subprocess - ssh_cmd = "sshpass -p ${passwd} ssh -p 22 -l root -o StrictHostKeyChecking=no xx.xx.xx.xx 'ls -l'" - status, output = subprocess.getstatusoutput(ssh_cmd) - - # 数据清理,格式化的就不展示了 - - -通过以上的文字 + 代码的展示 ,可以感觉到 ssh 登陆的几大痛点 - -- **痛点一**\ :需要额外安装 sshpass(如果不免密的话) -- **痛点二**\ :干扰信息太多,数据清理、格式化相当麻烦 -- **痛点三**\ :代码实现不够优雅(有点土),可读性太差 -- **痛点四**\ :ssh 连接不能复用,一次连接仅能执行一次 -- **痛点五**\ :代码无法全平台,仅能在 Linux 和 OSX 上使用 - -为了解决这几个问题,我搜索了全网关于 Python ssh -的文章,没有看到有完整介绍这方面的技巧的。 - -为此,我就翻阅了一个很火的 Github 项目: awesome-python-cn -(https://github.com/BingmingWong/awesome-python-cn)。 - -期望在这里,找到有一些关于 远程连接 的一些好用的库。 - -还真的被我找到了两个 - -- sh.ssh -- Paramiko - -2. 使用 sh.ssh --------------- - -首先来介绍第一个,\ ``sh.ssh`` - -``sh`` 是一个可以让你通过函数的调用来完成 Linxu/OSX -系统命令的一个库,非常好用,关于它有机会也写篇介绍。 - -.. code:: shell - - $ python3 -m pip install sh - -今天只介绍它其中的一个函数:\ ``ssh`` - -通常两台机器互访,为了方便,可设置免密登陆,这样就不需要输入密码。 - -这段代码可以实现免密登陆,并执行我们的命令 ``ls -l`` - -.. code:: python - - from sh import ssh - output=ssh("root@xx.xx.xx.xx", "-p 22", "ls -l") - print(output) - -但有可能 -,我们并不想设置互信免密,为了使这段代码更通用,我假定我们没有设置免密,只能使用密码进行登陆。 - -问题就来了,要输入密码,必须得使用交互式的方法来输入呀,在 Python -中要如何实现呢? - -原来 ssh 方法接收一个 ``_out`` -参数,这个参数可以为一个字符串,表示文件路径,也可以是一个文件对象(或者类文件对象),还可以是一个回调函数,意思是当有标准输出时,就会调用将输出内容传给这个函数。 - -这就好办了呀。 - -我只要识别到有 ``password:`` 字样,就往标准输入写入我的密码就好了呀。 - -完整代码如下: - -.. code:: python - - import sys - from sh import ssh - - aggregated = "" - def ssh_interact(char, stdin): - global aggregated - sys.stdout.write(char.encode()) - sys.stdout.flush() - aggregated += char - if aggregated.endswith("password: "): - stdin.put("you_password\n") - - output=ssh("root@xx.xx.xx.xx", "-p 22", "ls -l",_tty_in=True, _out_bufsize=0, _out=ssh_interact) - print(output) - -这是官方文档(http://amoffat.github.io/sh/tutorials/interacting_with_processes.html?highlight=ssh)给的一些信息,写的一个demo。 - -尝试运行后,发现程序会一直在运行中,永远不会返回,不会退出,回调函数也永远不会进入。 - -通过调试查看源代码,仍然查不到问题所在,于是去 -`Github `__ 上搜了下,原来在 -2017 年就已经存在这个问题了,到现在 2020 年了还没有修复,看来使用 -``sh.ssh`` 的人并不多,于是我又“追问”了下,期望能得到回复。 - -|image1| - -以上这个问题,只有在需要输入密码才会出现,如果设置了机器互信是没有问题的。 - -为了感受 ``sh.ssh`` -的使用效果,我设置了机器互信免密,然后使用如下这段代码。 - -.. code:: python - - from sh import ssh - - my_server=ssh.bake("root@xx.xx.xx.xx", "-p 22") - - # 相当于执行登陆一次执行一次命令,执行完就退出登陆 - print(my_server.ls()) - - # 可在 sleep 期间,手动登陆服务器,使用 top ,查看当前有多少终端在连接 - time.sleep(5) - - # 再次执行这条命令时,登陆终端数将 +1,执行完后,又将 -1 - print(my_server.ifconfig()) - -惊奇地发现使用 ``bake`` 这种方式,\ ``my_server.ls()`` 和 -``my_server.ifconfig()`` -这种看似是通过同一个ssh连接,执行两次命令,可实际上,你可以在远程机器上,执行 -top 命令看到已连接的终端的变化,会先 ``+1`` 再 -``-1``\ ,说明两次命令的执行是通过两次连接实现的。 - -如此看来,使用 ``sh.ssh`` -可以解决痛点一(如果上述问题能得到解决)、痛点二、痛点三。 - -但是它仍然无法复用 ssh 连接,还是不太方便,不是我理想中的最佳方案。 - -最重要的一点是, ``sh`` 这个模块,仅支持 Linxu/OSX ,在 Windows -你得使用它的兄弟库 - ``pbs`` ,然后我又去 pypi 看了一眼 -`pbs `__\ ,已经 “年久失修”,没人维护了。 - -|image2| - -至此,我离 “卒”,就差最后一根稻草了。 - -3. 使用 paramiko ----------------- - -带着最后一丝希望,我尝试使用了 ``paramiko`` 这个库,终于在 ``paramiko`` -这里,找回了本应属于 Python 的那种优雅。 - -你可以通过如下命令去安装它 - -:: - - $ python3 -m pip install paramiko - -然后接下来,就介绍几种常用的 ssh 登陆的方法 - -方法1:基于用户名和密码的 sshclient 方式登录 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -然后你可以参考如下这段代码,在 Linux/OSX 系统下进行远程连接 - -.. code:: python - - import paramiko - - ssh = paramiko.SSHClient() - # 允许连接不在know_hosts文件中的主机 - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - - # 建立连接 - ssh.connect("xx.xx.xx.xx", username="root", port=22, password="you_password") - - # 使用这个连接执行命令 - ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("ls -l") - - # 获取输出 - print(ssh_stdout.read()) - - # 关闭连接 - ssh.close() - -方法2:基于用户名和密码的 transport 方式登录 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -方法1 -是传统的连接服务器、执行命令、关闭的一个操作,多个操作需要连接多次,无法复用连接[**痛点四**]。 - -有时候需要登录上服务器执行多个操作,比如执行命令、上传/下载文件,方法1 -则无法实现,那就可以使用 transport 的方法。 - -.. code:: python - - import paramiko - - # 建立连接 - trans = paramiko.Transport(("xx.xx.xx.xx", 22)) - trans.connect(username="root", password="you_passwd") - - # 将sshclient的对象的transport指定为以上的trans - ssh = paramiko.SSHClient() - ssh._transport = trans - - # 剩下的就和上面一样了 - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("ls -l") - print(ssh_stdout.read()) - - # 关闭连接 - trans.close() - -方法3:基于公钥密钥的 SSHClient 方式登录 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code:: python - - import paramiko - - # 指定本地的RSA私钥文件 - # 如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数 - pkey = paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa', password='12345') - - # 建立连接 - ssh = paramiko.SSHClient() - ssh.connect(hostname='xx.xx.xx.xx', - port=22, - username='you_username', - pkey=pkey) - - # 执行命令 - stdin, stdout, stderr = ssh.exec_command('ls -l') - - # 结果放到stdout中,如果有错误将放到stderr中 - print(stdout.read()) - - # 关闭连接 - ssh.close() - -方法4:基于密钥的 Transport 方式登录 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code:: python - - import paramiko - - # 指定本地的RSA私钥文件 - # 如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数 - pkey = paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa', password='12345') - - # 建立连接 - trans = paramiko.Transport(('xx.xx.xx.xx', 22)) - trans.connect(username='you_username', pkey=pkey) - - # 将sshclient的对象的transport指定为以上的trans - ssh = paramiko.SSHClient() - ssh._transport = trans - - # 执行命令,和传统方法一样 - stdin, stdout, stderr = ssh.exec_command('df -hl') - print(stdout.read().decode()) - - # 关闭连接 - trans.close() - -以上四种方法,可以帮助你实现远程登陆服务器执行命令,如果需要复用连接:一次连接执行多次命令,可以使用 -**方法二** 和 **方法四** - -用完后,记得关闭连接。 - -实现 sftp 文件传输 -~~~~~~~~~~~~~~~~~~ - -同时,paramiko 做为 ssh 的完美解决方案,它非常专业,利用它还可以实现 -sftp 文件传输。 - -.. code:: python - - import paramiko - - # 实例化一个trans对象# 实例化一个transport对象 - trans = paramiko.Transport(('xx.xx.xx.xx', 22)) - - # 建立连接 - trans.connect(username='you_username', password='you_passwd') - - # 实例化一个 sftp对象,指定连接的通道 - sftp = paramiko.SFTPClient.from_transport(trans) - - # 发送文件 - sftp.put(localpath='/tmp/11.txt', remotepath='/tmp/22.txt') - - # 下载文件 - sftp.get(remotepath='/tmp/22.txt', localpath='/tmp/33.txt') - trans.close() - -到这里,Paramiko -已经完胜了,但是仍然有一个痛点我们没有提及,就是多平台,说的就是 -Windows,这里就有一件好事,一件坏事了,。 - -好事就是:paramiko 支持 windows - -坏事就是:你需要做很多复杂的准备,你可 google -解决,但是我建议你直接放弃,坑太深了。 - -|image3| - -注意事项 -~~~~~~~~ - -使用 paramiko 的时候,有一点需要注意一下,这个也是我自己 “踩坑” -后才发现的,其实我觉得这个设计挺好的,如果你不需要等待它返回数据,可以直接实现异步效果,只不过对于不知道这个设计的人,确实是个容易掉坑的点 - -就是在执行 ``ssh.exec_command(cmd)`` 时,这个命令并不是同步阻塞的。 - -比如下面这段代码,执行时,你会发现 脚本立马就结束退出了,并不会等待 5 s -后,再 执行 ssh.close() - -.. code:: python - - import paramiko - - trans = paramiko.Transport(("172.20.42.1", 57891)) - trans.connect(username="root", password="youpassword") - ssh = paramiko.SSHClient() - ssh._transport = trans - stdin, stdout, stderr = ssh.exec_command("sleep 5;echo ok") - ssh.close() - -但是如果改成这样,加上一行 stdout.read(), paramiko -就知道,你需要这个执行的结果,就会在 read() 进行阻塞。 - -.. code:: python - - import paramiko - - trans = paramiko.Transport(("172.20.42.1", 57891)) - trans.connect(username="root", password="youpassword") - ssh = paramiko.SSHClient() - ssh._transport = trans - stdin, stdout, stderr = ssh.exec_command("sleep 5;echo ok") - - # 加上一行 read() - print(stdout.read()) - ssh.close() - -4. 写在最后 ------------ - -经过了一番对比,和一些实例的展示,可以看出 Paramiko -是一个专业、让人省心的 ssh 利器,个人认为 Paramiko -模块是运维人员必学模块之一,如果你恰好需要在 Python 代码中实现 ssh -到远程服务器去获取一些信息,那么我把 Paramiko 推荐给你。 - -最后,希望这篇文章,能给你带来帮助。 - -5. 参考链接 ------------ - -- https://github.com/paramiko/paramiko -- http://docs.paramiko.org -- https://www.liujiangblog.com/blog/15/ -- http://docs.paramiko.org/en/stable/ - -|image4| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20200228085749.png -.. |image2| image:: http://image.python-online.cn/20200228093627.png -.. |image3| image:: http://image.python-online.cn/20200228111654.png -.. |image4| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_36.md b/source/c01/c01_36.md deleted file mode 100644 index e6f136b..0000000 --- a/source/c01/c01_36.md +++ /dev/null @@ -1,253 +0,0 @@ -# 1.36 每日一库:pretty_errors 解决bug 洁癖 - -![](http://image.iswbm.com/20200602135014.png) - -当我们写的一个脚本或程序发生各种不可预知的异常时,如果我们没有进行捕获处理的时候,通常都会致使程序崩溃退出,并且会在终端打印出一堆 **密密麻麻** 的 traceback 堆栈信息来告诉我们,是哪个地方出了问题。 - -就像这样子,天呐,密集恐惧症要犯了都 - -![](http://image.python-online.cn/image-20200307210853246.png) - -上面这段 traceback - -- 只有黑白两个颜色,无法像代码高亮那样,对肉眼实现太不友好了 -- 无法直接显示报错的代码,排查问题慢人一步,效率太低 - -那有没有一种办法,可以解决这些问题呢? - -当然有了,在 Python 中,没有什么问题是一个库解决不了的,如果有,那就等你去开发这个库。 - -今天要介绍的这个库呢,叫做 `pretty-errors` ,从名字上就可以知道它的用途,是用来美化错误信息的。 - -通过这条命令你可以安装它 - -```shell -$ python3 -m pip install pretty-errors -``` - - - -## 1. 环境要求 - -由于使用了 `pretty-errors` 后,你的 traceback 信息输出,会有代码高亮那样的效果,因此当你在使用测试使用 `pretty-error` 时,请确保你使用的终端可以输出带有颜色的字体。 - -在 windows 上你可以使用 Powershell,cmder 等 - -在 Mac 上你可以使用自带的终端,或者安装一个更好用的 iTerm2 - -## 2. 效果对比 - ------- - -随便写一个没有使用 pretty-errors ,并且报错了的程序,是这样子的。 - -![](http://image.python-online.cn/image-20200307212823345.png) - -而使用了 pretty_errors 后,报错信息被美化成这样了。 - -![](http://image.python-online.cn/image-20200307213534278.png) - -是不是感觉清楚了不少,那种密密麻麻带来的焦虑感是不是都消失了呢? - -当然这段代码少,你可能还没感受到,那就来看下 该项目在 Github上的一张效果对比图吧 - -![](https://warehouse-camo.cmh1.psfhosted.org/31399c5a034c3989b9e99b35249e8f2f0d40e102/68747470733a2f2f692e696d6775722e636f6d2f306a7045716f622e706e67) - - - -## 3. 配置全局可用 - -可以看到使用了 pretty_errors 后,无非就是把过滤掉了一些干扰我们视线的无用信息,然后把有用的关键信息给我们高亮显示。 - -既然既然这样,那 pretty_errors 应该也能支持我们如何自定义我们选用什么样的颜色,怎么排版吧? - -答案是显而易见的。 - -pretty_errors 和其他库不太一样,在一定程度上(如果你使用全局配置的话),它并不是开箱即用的,你在使用它之前可能需要做一下配置。 - -使用这一条命令,会让你进行配置,可以让你在该环境中运行其他脚本时的 traceback 输出都自动美化。 - -```shell -$ python3 -m pretty_errors -``` - -![](http://image.python-online.cn/image-20200307214742135.png) - -配置完成后,你再运行任何脚本,traceback 都会自动美化了。 - -不仅是在我的 iTerm 终端下 - -![](http://image.python-online.cn/image-20200307213534278.png) - -在 PyCharm 中也会 - -![](http://image.python-online.cn/image-20200307215530270.png) - -唯一的缺点就是,原先在 PyCharm 中的 traceback 可以直接点击 `文件路径` 直接跳转到对应错误文件代码行,而你如果是在 VSCode 可以使用 下面自定义配置的方案解决这个问题(下面会讲到,参数是:`display_link`)。 - -![](http://image.python-online.cn/image-20200307215834623.png) - -因此,有些情况下,你并不想设置 `pretty_errors` 全局可用。 - -那怎么取消之前的配置呢? - -只需要再次输出 `python -m pretty_errors`,输出入 `C` 即可清除。 - -![](http://image.python-online.cn/image-20200307214600749.png) - - - -## 4. 单文件中使用 - -取消全局可用后,你可以根据自己需要,在你需要使用 `pretty-errors` 的脚本文件中导入` pretty_errors `,即可使用 - -```python -import pretty_errors -``` - -就像这样 - -```python -import pretty_errors - -def foo(): - 1/0 - -if __name__ == "__main__": - foo() -``` - -值得一提的是,使用这种方式,若是你的脚本中,出现语法错误,则输出的异常信息还是按照之前的方式展示,并不会被美化。 - -因此,为了让美化更彻底,官方推荐你使用 `python -m pretty_errors` - -## 5. 自定义设置 - -上面的例子里,我们使用的都是 `pretty_errors` 的默认美化格式,展示的信息并没有那么全。 - -比如 - -- 它并没有展示报错文件的绝对路径,这将使我们很难定位到是哪个文件里的代码出现错误。 -- 如果能把具体报错的代码,给我们展示在终端屏幕上,就不需要我们再到源码文件中排查原因了。 - -如果使用了 `pretty_errors` 导致异常信息有丢失,那还不如不使用 `pretty_errors` 呢。 - -不过,可以告诉你的是,`pretty_errors` 并没有你想象的那么简单。 - -它足够开放,支持自定义配置,可以由你选择你需要展示哪些信息,怎么展示? - -这里举一个例子 - -```python -import pretty_errors - -# 【重点】进行配置 -pretty_errors.configure( - separator_character = '*', - filename_display = pretty_errors.FILENAME_EXTENDED, - line_number_first = True, - display_link = True, - lines_before = 5, - lines_after = 2, - line_color = pretty_errors.RED + '> ' + pretty_errors.default_config.line_color, - code_color = ' ' + pretty_errors.default_config.line_color, -) - -# 原来的代码 -def foo(): - 1/0 - -if __name__ == "__main__": - foo() -``` - -在你像上面这样使用 `pretty_errrs.configure` 进行配置时,抛出的的异常信息就变成这样了。 - -![](http://image.python-online.cn/image-20200308121949011.png) - - - -当然了,`pretty_errors.configure()` 还可以接收很多的参数,你可以根据你自己的需要进行配置。 - -### 5.1 设置颜色 - -- `header_color`:设置标题行的颜色。 -- `timestamp_color`:设置时间戳颜色 -- `default_color`:设置默认的颜色 -- `filename_color`:设置文件名颜色 -- `line_number_color`:设置行号颜色。 -- `function_color`:设置函数颜色。 -- `link_color`:设置链接的颜色。 - -在设置颜色的时候,`pretty_errors` 提供了一些常用的 颜色常量供你直接调取。 - -- `BLACK`:黑色 -- `GREY`:灰色 -- `RED`:红色 -- `GREEN`:绿色 -- `YELLOW`:黄色 -- `BLUE`:蓝色 -- `MAGENTA`:品红色 -- `CYAN`:蓝绿色 -- `WHITE`:白色 - -而每一种颜色,都相应的匹配的 `BRIGHT_` 变体 和 `_BACKGROUND` 变体, - -其中,`_BACKGROUND` 用于设置背景色,举个例子如下。 - -![](http://image.python-online.cn/image-20200308125431779.png) - -### 5.2 设置显示内容 - -- `line_number_first` - 启用后,将首先显示行号,而不是文件名。 -- `lines_before` : 显示发生异常处的前几行代码 -- `lines_after`: 显示发生异常处的后几行代码 -- `display_link`:启用后,将在错误位置下方写入链接,VScode将允许您单击该链接。 -- `separator_character`:用于创建标题行的字符。默认情况下使用连字符。如果设置为 `''` 或者 `None` ,标题将被禁用。 -- `display_timestamp`:启用时,时间戳将写入回溯头中。 -- `display_locals` - 启用后,将显示在顶部堆栈框架代码中的局部变量及其值。 - -- `display_trace_locals` - 启用后,其他堆栈框架代码中出现的局部变量将与它们的值一起显示。 - -### 5.3 设置怎么显示 - -- `line_length`:设置每行的长度,默认为0,表示每行的输出将与控制台尺寸相匹配,如果你设置的长度将好与控制台宽度匹配,则可能需要禁用`full_line_newline`,以防止出现明显的双换行符。 - -- `full_line_newline`:当输出的字符满行时,是否要插入换行符。 - -- `timestamp_function` - 调用该函数以生成时间戳。默认值为`time.perf_counter`。 - -- `top_first` - 启用后,堆栈跟踪将反转,首先显示堆栈顶部。 - -- `display_arrow` - 启用后,将针对语法错误显示一个箭头,指向有问题的令牌。 - -- `truncate_code` - 启用后,每行代码将被截断以适合行长。 - -- `stack_depth` - 要显示的堆栈跟踪的最大条目数。什么时候`0`将显示整个堆栈,这是默认值。 - -- `exception_above` - 启用后,异常将显示在堆栈跟踪上方。 - -- `exception_below`: - 启用后,异常显示在堆栈跟踪下方。 - -- `reset_stdout` - 启用后,重置转义序列将写入stdout和stderr;如果您的控制台留下错误的颜色,请启用此选项。 - -- `filename_display` - - 设置文件名的展示方式,有三个选项: `pretty_errors.FILENAME_COMPACT` 、`pretty_errors.FILENAME_EXTENDED`,或者`pretty_errors.FILENAME_FULL` - - - -以上,就是我对 `pretty_errors` 的使用体验,总的来说,这个库功能非常强大,使用效果也特别酷炫,它就跟 PEP8 规范一样,没有它是可以,但是有了它会更好一样。对于某些想自定义错误输出场景的人,`pretty_errors` 会是一个不错的解决方案,明哥把它推荐给你。 - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_36.rst b/source/c01/c01_36.rst deleted file mode 100644 index bbed8dd..0000000 --- a/source/c01/c01_36.rst +++ /dev/null @@ -1,286 +0,0 @@ -1.36 每日一库:pretty_errors 解决bug 洁癖 -========================================= - -|image0| - -当我们写的一个脚本或程序发生各种不可预知的异常时,如果我们没有进行捕获处理的时候,通常都会致使程序崩溃退出,并且会在终端打印出一堆 -**密密麻麻** 的 traceback 堆栈信息来告诉我们,是哪个地方出了问题。 - -就像这样子,天呐,密集恐惧症要犯了都 - -|image1| - -上面这段 traceback - -- 只有黑白两个颜色,无法像代码高亮那样,对肉眼实现太不友好了 -- 无法直接显示报错的代码,排查问题慢人一步,效率太低 - -那有没有一种办法,可以解决这些问题呢? - -当然有了,在 Python -中,没有什么问题是一个库解决不了的,如果有,那就等你去开发这个库。 - -今天要介绍的这个库呢,叫做 ``pretty-errors`` -,从名字上就可以知道它的用途,是用来美化错误信息的。 - -通过这条命令你可以安装它 - -.. code:: shell - - $ python3 -m pip install pretty-errors - -1. 环境要求 ------------ - -由于使用了 ``pretty-errors`` 后,你的 traceback -信息输出,会有代码高亮那样的效果,因此当你在使用测试使用 -``pretty-error`` 时,请确保你使用的终端可以输出带有颜色的字体。 - -在 windows 上你可以使用 Powershell,cmder 等 - -在 Mac 上你可以使用自带的终端,或者安装一个更好用的 iTerm2 - -2. 效果对比 ------------ - --------------- - -随便写一个没有使用 pretty-errors ,并且报错了的程序,是这样子的。 - -|image2| - -而使用了 pretty_errors 后,报错信息被美化成这样了。 - -|image3| - -是不是感觉清楚了不少,那种密密麻麻带来的焦虑感是不是都消失了呢? - -当然这段代码少,你可能还没感受到,那就来看下 该项目在 -Github上的一张效果对比图吧 - -|image4| - -3. 配置全局可用 ---------------- - -可以看到使用了 pretty_errors -后,无非就是把过滤掉了一些干扰我们视线的无用信息,然后把有用的关键信息给我们高亮显示。 - -既然既然这样,那 pretty_errors -应该也能支持我们如何自定义我们选用什么样的颜色,怎么排版吧? - -答案是显而易见的。 - -pretty_errors -和其他库不太一样,在一定程度上(如果你使用全局配置的话),它并不是开箱即用的,你在使用它之前可能需要做一下配置。 - -使用这一条命令,会让你进行配置,可以让你在该环境中运行其他脚本时的 -traceback 输出都自动美化。 - -.. code:: shell - - $ python3 -m pretty_errors - -|image5| - -配置完成后,你再运行任何脚本,traceback 都会自动美化了。 - -不仅是在我的 iTerm 终端下 - -|image6| - -在 PyCharm 中也会 - -|image7| - -唯一的缺点就是,原先在 PyCharm 中的 traceback 可以直接点击 ``文件路径`` -直接跳转到对应错误文件代码行,而你如果是在 VSCode 可以使用 -下面自定义配置的方案解决这个问题(下面会讲到,参数是:\ ``display_link``\ )。 - -|image8| - -因此,有些情况下,你并不想设置 ``pretty_errors`` 全局可用。 - -那怎么取消之前的配置呢? - -只需要再次输出 ``python -m pretty_errors``\ ,输出入 ``C`` 即可清除。 - -|image9| - -4. 单文件中使用 ---------------- - -取消全局可用后,你可以根据自己需要,在你需要使用 ``pretty-errors`` -的脚本文件中导入\ ``pretty_errors``\ ,即可使用 - -.. code:: python - - import pretty_errors - -就像这样 - -.. code:: python - - import pretty_errors - - def foo(): - 1/0 - - if __name__ == "__main__": - foo() - -值得一提的是,使用这种方式,若是你的脚本中,出现语法错误,则输出的异常信息还是按照之前的方式展示,并不会被美化。 - -因此,为了让美化更彻底,官方推荐你使用 ``python -m pretty_errors`` - -5. 自定义设置 -------------- - -上面的例子里,我们使用的都是 ``pretty_errors`` -的默认美化格式,展示的信息并没有那么全。 - -比如 - -- 它并没有展示报错文件的绝对路径,这将使我们很难定位到是哪个文件里的代码出现错误。 -- 如果能把具体报错的代码,给我们展示在终端屏幕上,就不需要我们再到源码文件中排查原因了。 - -如果使用了 ``pretty_errors`` 导致异常信息有丢失,那还不如不使用 -``pretty_errors`` 呢。 - -不过,可以告诉你的是,\ ``pretty_errors`` 并没有你想象的那么简单。 - -它足够开放,支持自定义配置,可以由你选择你需要展示哪些信息,怎么展示? - -这里举一个例子 - -.. code:: python - - import pretty_errors - - # 【重点】进行配置 - pretty_errors.configure( - separator_character = '*', - filename_display = pretty_errors.FILENAME_EXTENDED, - line_number_first = True, - display_link = True, - lines_before = 5, - lines_after = 2, - line_color = pretty_errors.RED + '> ' + pretty_errors.default_config.line_color, - code_color = ' ' + pretty_errors.default_config.line_color, - ) - - # 原来的代码 - def foo(): - 1/0 - - if __name__ == "__main__": - foo() - -在你像上面这样使用 ``pretty_errrs.configure`` -进行配置时,抛出的的异常信息就变成这样了。 - -|image10| - -当然了,\ ``pretty_errors.configure()`` -还可以接收很多的参数,你可以根据你自己的需要进行配置。 - -5.1 设置颜色 -~~~~~~~~~~~~ - -- ``header_color``\ :设置标题行的颜色。 -- ``timestamp_color``\ :设置时间戳颜色 -- ``default_color``\ :设置默认的颜色 -- ``filename_color``\ :设置文件名颜色 -- ``line_number_color``\ :设置行号颜色。 -- ``function_color``\ :设置函数颜色。 -- ``link_color``\ :设置链接的颜色。 - -在设置颜色的时候,\ ``pretty_errors`` 提供了一些常用的 -颜色常量供你直接调取。 - -- ``BLACK``\ :黑色 -- ``GREY``\ :灰色 -- ``RED``\ :红色 -- ``GREEN``\ :绿色 -- ``YELLOW``\ :黄色 -- ``BLUE``\ :蓝色 -- ``MAGENTA``\ :品红色 -- ``CYAN``\ :蓝绿色 -- ``WHITE``\ :白色 - -而每一种颜色,都相应的匹配的 ``BRIGHT_`` 变体 和 ``_BACKGROUND`` 变体, - -其中,\ ``_BACKGROUND`` 用于设置背景色,举个例子如下。 - -|image11| - -5.2 设置显示内容 -~~~~~~~~~~~~~~~~ - -- ``line_number_first`` 启用后,将首先显示行号,而不是文件名。 -- ``lines_before`` : 显示发生异常处的前几行代码 -- ``lines_after``\ : 显示发生异常处的后几行代码 -- ``display_link``\ :启用后,将在错误位置下方写入链接,VScode将允许您单击该链接。 -- ``separator_character``\ :用于创建标题行的字符。默认情况下使用连字符。如果设置为 - ``''`` 或者 ``None`` ,标题将被禁用。 -- ``display_timestamp``\ :启用时,时间戳将写入回溯头中。 -- ``display_locals`` - 启用后,将显示在顶部堆栈框架代码中的局部变量及其值。 - -- ``display_trace_locals`` - 启用后,其他堆栈框架代码中出现的局部变量将与它们的值一起显示。 - -5.3 设置怎么显示 -~~~~~~~~~~~~~~~~ - -- ``line_length``\ :设置每行的长度,默认为0,表示每行的输出将与控制台尺寸相匹配,如果你设置的长度将好与控制台宽度匹配,则可能需要禁用\ ``full_line_newline``\ ,以防止出现明显的双换行符。 - -- ``full_line_newline``\ :当输出的字符满行时,是否要插入换行符。 - -- ``timestamp_function`` - 调用该函数以生成时间戳。默认值为\ ``time.perf_counter``\ 。 - -- ``top_first`` 启用后,堆栈跟踪将反转,首先显示堆栈顶部。 - -- ``display_arrow`` - 启用后,将针对语法错误显示一个箭头,指向有问题的令牌。 - -- ``truncate_code`` 启用后,每行代码将被截断以适合行长。 - -- ``stack_depth`` - 要显示的堆栈跟踪的最大条目数。什么时候\ ``0``\ 将显示整个堆栈,这是默认值。 - -- ``exception_above`` 启用后,异常将显示在堆栈跟踪上方。 - -- ``exception_below``\ : 启用后,异常显示在堆栈跟踪下方。 - -- ``reset_stdout`` - 启用后,重置转义序列将写入stdout和stderr;如果您的控制台留下错误的颜色,请启用此选项。 - -- ``filename_display`` - - 设置文件名的展示方式,有三个选项: ``pretty_errors.FILENAME_COMPACT`` - 、\ ``pretty_errors.FILENAME_EXTENDED``\ ,或者\ ``pretty_errors.FILENAME_FULL`` - -以上,就是我对 ``pretty_errors`` -的使用体验,总的来说,这个库功能非常强大,使用效果也特别酷炫,它就跟 -PEP8 -规范一样,没有它是可以,但是有了它会更好一样。对于某些想自定义错误输出场景的人,\ ``pretty_errors`` -会是一个不错的解决方案,明哥把它推荐给你。 - -|image12| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/image-20200307210853246.png -.. |image2| image:: http://image.python-online.cn/image-20200307212823345.png -.. |image3| image:: http://image.python-online.cn/image-20200307213534278.png -.. |image4| image:: https://warehouse-camo.cmh1.psfhosted.org/31399c5a034c3989b9e99b35249e8f2f0d40e102/68747470733a2f2f692e696d6775722e636f6d2f306a7045716f622e706e67 -.. |image5| image:: http://image.python-online.cn/image-20200307214742135.png -.. |image6| image:: http://image.python-online.cn/image-20200307213534278.png -.. |image7| image:: http://image.python-online.cn/image-20200307215530270.png -.. |image8| image:: http://image.python-online.cn/image-20200307215834623.png -.. |image9| image:: http://image.python-online.cn/image-20200307214600749.png -.. |image10| image:: http://image.python-online.cn/image-20200308121949011.png -.. |image11| image:: http://image.python-online.cn/image-20200308125431779.png -.. |image12| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_37.md b/source/c01/c01_37.md deleted file mode 100644 index b206844..0000000 --- a/source/c01/c01_37.md +++ /dev/null @@ -1,161 +0,0 @@ -# 1.37 Python 炫技操作:条件语句的七种写法 - -![](http://image.iswbm.com/20200602135014.png) - -有的人说 Python 是一门 入门容易,但是精通难的语言,这一点我非常赞同。 - -Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 - -但你要知道,在团队合作里,炫技是大忌。 - -为什么这么说呢?我说下自己的看法: -1. 越简洁的代码,越清晰的逻辑,就越不容易出错; -2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 -3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) - -在这个系列里,我将总结列举一下,我所见过的那些炫技操作,今天先来个热身,写一写很简单的条件判断语句里有哪些让人想骂街的炫技操作,在这里,如果你是 Python 发烧友,你可以学到一些写出超酷的代码书写技巧,但学习归学习,希望你区分场景使用。 - -## 原代码 - -这是一段非常简单的通过年龄判断一个人是否成年的代码,由于代码行数过多,有些人就不太愿意这样写,因为这体现不出自己多年的 Python 功力。 -```python -if age > 18: - return "已成年" -else: - return "未成年" -``` - -下面我列举了六种这段代码的变异写法,一个比一个还 6 ,单独拿出来比较好理解,放在工程代码里,没用过这些学法的人,一定会看得一脸懵逼,理解了之后,又不经意大呼:**卧槽,还可以这样写?**,而后就要开始骂街了:**这是给人看的代码?** (除了第一种之外) - -## 第一种 - -语法: - -```python - if else -``` - -例子 - -```python ->>> age1 = 20 ->>> age2 = 17 ->>> ->>> ->>> msg1 = "已成年" if age1 > 18 else "未成年" ->>> print msg1 -已成年 ->>> ->>> msg2 = "已成年" if age2 > 18 else "未成年" ->>> print msg2 -未成年 ->>> -``` - -## 第二种 - -语法 - -```python - and or -``` - -例子 - -```python ->>> msg1 = age1 > 18 and "已成年" or "未成年" ->>> msg2 = "已成年" if age2 > 18 else "未成年" ->>> ->>> print(msg1) -已成年 ->>> ->>> print(msg2) -未成年 -``` - -## 第三种 - -语法 - -```python -(, )[condition] -``` - -例子 - -```python ->>> msg1 = ("未成年", "已成年")[age1 > 18] ->>> print(msg1) -已成年 ->>> ->>> ->>> msg2 = ("未成年", "已成年")[age2 > 18] ->>> print(msg2) -未成年 -``` - -## 第四种 - -语法 - -```python -(lambda: , lambda:)[]() -``` - -例子 - -```python ->>> msg1 = (lambda:"未成年", lambda:"已成年")[age1 > 18]() ->>> print(msg1) -已成年 ->>> ->>> msg2 = (lambda:"未成年", lambda:"已成年")[age2 > 18]() ->>> print(msg2) -未成年 -``` - -## 第五种 - -语法: - -```python -{True: , False: }[] -``` - -例子: - -```python ->>> msg1 = {True: "已成年", False: "未成年"}[age1 > 18] ->>> print(msg1) -已成年 ->>> ->>> msg2 = {True: "已成年", False: "未成年"}[age2 > 18] ->>> print(msg2) -未成年 -``` - -## 第六种 - -语法 - -```python -(() and (,) or (,))[0] -``` - -例子 - -```python ->>> msg1 = ((age1 > 18) and ("已成年",) or ("未成年",))[0] ->>> print(msg1) -已成年 ->>> ->>> msg2 = ((age2 > 18) and ("已成年",) or ("未成年",))[0] ->>> print(msg2) -未成年 -``` - -以上代码,都比较简单,仔细看都能看懂,我就不做解释了。 - -看到这里,有没有涨姿势了,学了这么久的 Python ,这么多骚操作,还真是活久见。。这六种写法里,我最推荐使用的是第一种,自己也经常在用,简洁直白,代码行还少。而其他的写法虽然能写,但是不会用,也不希望在我余生里碰到会在公共代码里用这些写法的同事。 - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_37.rst b/source/c01/c01_37.rst deleted file mode 100644 index 15c3b09..0000000 --- a/source/c01/c01_37.rst +++ /dev/null @@ -1,182 +0,0 @@ -1.37 Python 炫技操作:条件语句的七种写法 -======================================== - -|image0| - -有的人说 Python 是一门 入门容易,但是精通难的语言,这一点我非常赞同。 - -Python 语言里有许多(而且是越来越多)的高级特性,是 Python -发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 - -但你要知道,在团队合作里,炫技是大忌。 - -为什么这么说呢?我说下自己的看法: 1. -越简洁的代码,越清晰的逻辑,就越不容易出错; 2. -在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 -3. -简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) - -在这个系列里,我将总结列举一下,我所见过的那些炫技操作,今天先来个热身,写一写很简单的条件判断语句里有哪些让人想骂街的炫技操作,在这里,如果你是 -Python -发烧友,你可以学到一些写出超酷的代码书写技巧,但学习归学习,希望你区分场景使用。 - -原代码 ------- - -这是一段非常简单的通过年龄判断一个人是否成年的代码,由于代码行数过多,有些人就不太愿意这样写,因为这体现不出自己多年的 -Python 功力。 - -.. code:: python - - if age > 18: - return "已成年" - else: - return "未成年" - -下面我列举了六种这段代码的变异写法,一个比一个还 6 -,单独拿出来比较好理解,放在工程代码里,没用过这些学法的人,一定会看得一脸懵逼,理解了之后,又不经意大呼:\ **卧槽,还可以这样写?**\ ,而后就要开始骂街了:\ **这是给人看的代码?** -(除了第一种之外) - -第一种 ------- - -语法: - -.. code:: python - - if else - -例子 - -.. code:: python - - >>> age1 = 20 - >>> age2 = 17 - >>> - >>> - >>> msg1 = "已成年" if age1 > 18 else "未成年" - >>> print msg1 - 已成年 - >>> - >>> msg2 = "已成年" if age2 > 18 else "未成年" - >>> print msg2 - 未成年 - >>> - -第二种 ------- - -语法 - -.. code:: python - - and or - -例子 - -.. code:: python - - >>> msg1 = age1 > 18 and "已成年" or "未成年" - >>> msg2 = "已成年" if age2 > 18 else "未成年" - >>> - >>> print(msg1) - 已成年 - >>> - >>> print(msg2) - 未成年 - -第三种 ------- - -语法 - -.. code:: python - - (, )[condition] - -例子 - -.. code:: python - - >>> msg1 = ("未成年", "已成年")[age1 > 18] - >>> print(msg1) - 已成年 - >>> - >>> - >>> msg2 = ("未成年", "已成年")[age2 > 18] - >>> print(msg2) - 未成年 - -第四种 ------- - -语法 - -.. code:: python - - (lambda: , lambda:)[]() - -例子 - -.. code:: python - - >>> msg1 = (lambda:"未成年", lambda:"已成年")[age1 > 18]() - >>> print(msg1) - 已成年 - >>> - >>> msg2 = (lambda:"未成年", lambda:"已成年")[age2 > 18]() - >>> print(msg2) - 未成年 - -第五种 ------- - -语法: - -.. code:: python - - {True: , False: }[] - -例子: - -.. code:: python - - >>> msg1 = {True: "已成年", False: "未成年"}[age1 > 18] - >>> print(msg1) - 已成年 - >>> - >>> msg2 = {True: "已成年", False: "未成年"}[age2 > 18] - >>> print(msg2) - 未成年 - -第六种 ------- - -语法 - -.. code:: python - - (() and (,) or (,))[0] - -例子 - -.. code:: python - - >>> msg1 = ((age1 > 18) and ("已成年",) or ("未成年",))[0] - >>> print(msg1) - 已成年 - >>> - >>> msg2 = ((age2 > 18) and ("已成年",) or ("未成年",))[0] - >>> print(msg2) - 未成年 - -以上代码,都比较简单,仔细看都能看懂,我就不做解释了。 - -看到这里,有没有涨姿势了,学了这么久的 Python -,这么多骚操作,还真是活久见。。这六种写法里,我最推荐使用的是第一种,自己也经常在用,简洁直白,代码行还少。而其他的写法虽然能写,但是不会用,也不希望在我余生里碰到会在公共代码里用这些写法的同事。 - -|image1| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_38.rst b/source/c01/c01_38.rst deleted file mode 100644 index 94438e8..0000000 --- a/source/c01/c01_38.rst +++ /dev/null @@ -1,76 +0,0 @@ -1.38 /usr/bin/env python 有什么用? -=================================== - -|image0| - -我们经常会在别人的脚本或者项目的入口文件里看到第一行是下面这样 - -.. code:: json - - #!/usr/bin/python - -或者这样 - -.. code:: json - - #!/usr/bin/env python - -这两者有什么区别呢? - -稍微接触过 linux 的人都知道 ``/usr/bin/python`` 就是我们执行 ``python`` -进入console 模式里的 ``python`` - -|image1| - -而当你在可执行文件头里使用 ``#!`` + ``/usr/bin/python`` -,意思就是说你得用哪个软件 (python)来执行这个文件。 - -那么加和不加有什么区别呢? - -不加的话,你每次执行这个脚本时,都得这样: ``python xx.py`` , - -|image2| - -有没有一种方式?可以省去每次都加 ``python`` 呢? - -当然有,你可以文件头里加上\ ``#!/usr/bin/python`` -,那么当这个文件有可执行权限 时,只直接写这个脚本文件,就像下面这样。 - -|image3| - -明白了这个后,再来看看 ``!/usr/bin/env python`` 这个 又是什么意思 ? - -当我执行 ``env python`` 时,自动进入了 python console 的模式。 - -|image4| - -这是为什么?和 直接执行 python 好像没什么区别呀 - -当你执行 ``env python`` 时,它其实会去 ``env | grep PATH`` 里(也就是 -/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin -)这几个路径里去依次查找名为python的可执行文件。 - -找到一个就直接执行,上面我们的 python 路径是在 ``/usr/bin/python`` -里,在 ``PATH`` 列表里倒数第二个目录下,所以当我在 ``/usr/local/sbin`` -下创建一个名字也为 python 的可执行文件时,就会执行 ``/usr/bin/python`` -了。 - -具体演示过程,你可以看下面。 - -|image5| - -那么对于这两者,我们应该使用哪个呢? - -个人感觉应该优先使用 ``#!/usr/bin/env python``\ ,因为不是所有的机器的 -python 解释器都是 ``/usr/bin/python`` 。 - -|image6| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20200331184021.png -.. |image2| image:: http://image.python-online.cn/20200331185034.png -.. |image3| image:: http://image.python-online.cn/20200331184755.png -.. |image4| image:: http://image.python-online.cn/20200331185741.png -.. |image5| image:: http://image.python-online.cn/20200331190224.png -.. |image6| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_39.md b/source/c01/c01_39.md deleted file mode 100644 index 3f37049..0000000 --- a/source/c01/c01_39.md +++ /dev/null @@ -1,220 +0,0 @@ -# 1.39 Python 炫技操作:合并字典的七种方法 - -![](http://image.iswbm.com/20200602135014.png) - -Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 - -但你要知道,在团队合作里,炫技是大忌。 - -为什么这么说呢?我说下自己的看法: - -1. 越简洁的代码,越清晰的逻辑,就越不容易出错; -2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 -3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) - -该篇是「**炫技系列**」的第二篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 Python 发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 - -## 1. 最简单的原地更新 - -字典对象内置了一个 update 方法,用于把另一个字典更新到自己身上。 - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> profile.update(ext_info) ->>> print(profile) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - -如果想使用 update 这种最简单、最地道原生的方法,但又不想更新到自己身上,而是生成一个新的对象,那请使用深拷贝。 - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> from copy import deepcopy ->>> ->>> full_profile = deepcopy(profile) ->>> full_profile.update(ext_info) ->>> ->>> print(full_profile) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ->>> print(profile) -{"name": "xiaoming", "age": 27} -``` - - - -## 2. 先解包再合并字典 - -使用 `**` 可以解包字典,解包完后再使用 dict 或者 `{}` 就可以合并。 - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> full_profile01 = {**profile, **ext_info} ->>> print(full_profile01) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ->>> ->>> full_profile02 = dict(**profile, **ext_info) ->>> print(full_profile02) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - -若你不知道 `dict(**profile, **ext_info)` 做了啥,你可以将它等价于 - -```python ->>> dict((("name", "xiaoming"), ("age", 27), ("gender", "male"))) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - - - -## 3. 借助 itertools - -在 Python 里有一个非常强大的内置模块,它专门用于操作可迭代对象。 - -正好我们字典也是可迭代对象,自然就可以想到,可以使用 `itertools.chain()` 函数先将多个字典(可迭代对象)串联起来,组成一个更大的可迭代对象,然后再使用 dict 转成字典。 - -```python ->>> import itertools ->>> ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> ->>> dict(itertools.chain(profile.items(), ext_info.items())) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - - - -## 4. 借助 ChainMap - -如果可以引入一个辅助包,那我就再提一个, `ChainMap` 也可以达到和 `itertools` 同样的效果。 - -```python ->>> from collections import ChainMap ->>> ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> dict(ChainMap(profile, ext_info)) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - -使用 ChainMap 有一点需要注意,当字典间有重复的键时,只会取第一个值,排在后面的键值并不会更新掉前面的(使用 itertools 就不会有这个问题)。 - -```python ->>> from collections import ChainMap ->>> ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info={"age": 30} ->>> dict(ChainMap(profile, ext_info)) -{'name': 'xiaoming', 'age': 27} -``` - - - -## 5. 使用dict.items() 合并 - -在 Python 3.9 之前,其实就已经有 `|` 操作符了,只不过它通常用于对集合(set)取并集。 - -利用这一点,也可以将它用于字典的合并,只不过得绕个弯子,有点不好理解。 - -你得先利用 `items` 方法将 dict 转成 dict_items,再对这两个 dict_items 取并集,最后利用 dict 函数,转成字典。 - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> full_profile = dict(profile.items() | ext_info.items()) ->>> full_profile -{'gender': 'male', 'age': 27, 'name': 'xiaoming'} -``` - - - -当然了,你如果嫌这样太麻烦,也可以简单点,直接使用 list 函数再合并(示例为 Python 3.x ) - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> dict(list(profile.items()) + list(ext_info.items())) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - -若你在 Python 2.x 下,可以直接省去 list 函数。 - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> dict(profile.items() + ext_info.items()) -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - - - -## 6. 最酷炫的字典解析式 - -Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 - -那就是列表解析式,集合解析式和字典解析式,通常是 Python 发烧友的最爱,那么今天的主题:字典合并,字典解析式还能否胜任呢? - -当然可以,具体示例代码如下: - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> {k:v for d in [profile, ext_info] for k,v in d.items()} -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - - - -## 7. Python 3.9 新特性 - -在 2 月份发布的 Python 3.9.04a 版本中,新增了一个抓眼球的新操作符操作符: `|`, PEP584 将它称之为合并操作符(Union Operator),用它可以很直观地合并多个字典。 - -```python ->>> profile = {"name": "xiaoming", "age": 27} ->>> ext_info = {"gender": "male"} ->>> ->>> profile | ext_info -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ->>> ->>> ext_info | profile -{'gender': 'male', 'name': 'xiaoming', 'age': 27} ->>> ->>> -``` - -除了 `|` 操作符之外,还有另外一个操作符 `|=`,类似于原地更新。 - -```python ->>> ext_info |= profile ->>> ext_info -{'gender': 'male', 'name': 'xiaoming', 'age': 27} ->>> ->>> ->>> profile |= ext_info ->>> profile -{'name': 'xiaoming', 'age': 27, 'gender': 'male'} -``` - - - -看到这里,有没有涨姿势了,学了这么久的 Python ,没想到合并字典还有这么多的方法。本篇文章的主旨,并不在于让你全部掌握这 7 种合并字典的方法,实际在工作中,你只要选用一种最顺手的方式即可,但是在协同工作中,或者在阅读他人代码时,你不可避免地会碰到各式各样的写法,这时候你能下意识的知道这是在做合并字典的操作,那这篇文章就是有意义的。 - - - ---- - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_39.rst b/source/c01/c01_39.rst deleted file mode 100644 index 89a0d3d..0000000 --- a/source/c01/c01_39.rst +++ /dev/null @@ -1,231 +0,0 @@ -1.39 Python 炫技操作:合并字典的七种方法 -======================================== - -|image0| - -Python 语言里有许多(而且是越来越多)的高级特性,是 Python -发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 - -但你要知道,在团队合作里,炫技是大忌。 - -为什么这么说呢?我说下自己的看法: - -1. 越简洁的代码,越清晰的逻辑,就越不容易出错; -2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 -3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) - -该篇是「\ **炫技系列**\ 」的第二篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 -Python -发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 - -1. 最简单的原地更新 -------------------- - -字典对象内置了一个 update 方法,用于把另一个字典更新到自己身上。 - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> profile.update(ext_info) - >>> print(profile) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -如果想使用 update -这种最简单、最地道原生的方法,但又不想更新到自己身上,而是生成一个新的对象,那请使用深拷贝。 - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> from copy import deepcopy - >>> - >>> full_profile = deepcopy(profile) - >>> full_profile.update(ext_info) - >>> - >>> print(full_profile) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - >>> print(profile) - {"name": "xiaoming", "age": 27} - -2. 先解包再合并字典 -------------------- - -使用 ``**`` 可以解包字典,解包完后再使用 dict 或者 ``{}`` 就可以合并。 - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> full_profile01 = {**profile, **ext_info} - >>> print(full_profile01) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - >>> - >>> full_profile02 = dict(**profile, **ext_info) - >>> print(full_profile02) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -若你不知道 ``dict(**profile, **ext_info)`` 做了啥,你可以将它等价于 - -.. code:: python - - >>> dict((("name", "xiaoming"), ("age", 27), ("gender", "male"))) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -3. 借助 itertools ------------------ - -在 Python 里有一个非常强大的内置模块,它专门用于操作可迭代对象。 - -正好我们字典也是可迭代对象,自然就可以想到,可以使用 -``itertools.chain()`` -函数先将多个字典(可迭代对象)串联起来,组成一个更大的可迭代对象,然后再使用 -dict 转成字典。 - -.. code:: python - - >>> import itertools - >>> - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> - >>> dict(itertools.chain(profile.items(), ext_info.items())) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -4. 借助 ChainMap ----------------- - -如果可以引入一个辅助包,那我就再提一个, ``ChainMap`` 也可以达到和 -``itertools`` 同样的效果。 - -.. code:: python - - >>> from collections import ChainMap - >>> - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> dict(ChainMap(profile, ext_info)) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -使用 ChainMap -有一点需要注意,当字典间有重复的键时,只会取第一个值,排在后面的键值并不会更新掉前面的(使用 -itertools 就不会有这个问题)。 - -.. code:: python - - >>> from collections import ChainMap - >>> - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info={"age": 30} - >>> dict(ChainMap(profile, ext_info)) - {'name': 'xiaoming', 'age': 27} - -5. 使用dict.items() 合并 ------------------------- - -在 Python 3.9 之前,其实就已经有 ``|`` -操作符了,只不过它通常用于对集合(set)取并集。 - -利用这一点,也可以将它用于字典的合并,只不过得绕个弯子,有点不好理解。 - -你得先利用 ``items`` 方法将 dict 转成 dict_items,再对这两个 dict_items -取并集,最后利用 dict 函数,转成字典。 - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> full_profile = dict(profile.items() | ext_info.items()) - >>> full_profile - {'gender': 'male', 'age': 27, 'name': 'xiaoming'} - -当然了,你如果嫌这样太麻烦,也可以简单点,直接使用 list -函数再合并(示例为 Python 3.x ) - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> dict(list(profile.items()) + list(ext_info.items())) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -若你在 Python 2.x 下,可以直接省去 list 函数。 - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> dict(profile.items() + ext_info.items()) - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -6. 最酷炫的字典解析式 ---------------------- - -Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 - -那就是列表解析式,集合解析式和字典解析式,通常是 Python -发烧友的最爱,那么今天的主题:字典合并,字典解析式还能否胜任呢? - -当然可以,具体示例代码如下: - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> {k:v for d in [profile, ext_info] for k,v in d.items()} - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -7. Python 3.9 新特性 --------------------- - -在 2 月份发布的 Python 3.9.04a -版本中,新增了一个抓眼球的新操作符操作符: ``|``\ , PEP584 -将它称之为合并操作符(Union Operator),用它可以很直观地合并多个字典。 - -.. code:: python - - >>> profile = {"name": "xiaoming", "age": 27} - >>> ext_info = {"gender": "male"} - >>> - >>> profile | ext_info - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - >>> - >>> ext_info | profile - {'gender': 'male', 'name': 'xiaoming', 'age': 27} - >>> - >>> - -除了 ``|`` 操作符之外,还有另外一个操作符 ``|=``\ ,类似于原地更新。 - -.. code:: python - - >>> ext_info |= profile - >>> ext_info - {'gender': 'male', 'name': 'xiaoming', 'age': 27} - >>> - >>> - >>> profile |= ext_info - >>> profile - {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - -看到这里,有没有涨姿势了,学了这么久的 Python -,没想到合并字典还有这么多的方法。本篇文章的主旨,并不在于让你全部掌握这 -7 -种合并字典的方法,实际在工作中,你只要选用一种最顺手的方式即可,但是在协同工作中,或者在阅读他人代码时,你不可避免地会碰到各式各样的写法,这时候你能下意识的知道这是在做合并字典的操作,那这篇文章就是有意义的。 - --------------- - -|image1| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_40.md b/source/c01/c01_40.md deleted file mode 100644 index 2d70011..0000000 --- a/source/c01/c01_40.md +++ /dev/null @@ -1,143 +0,0 @@ -# 1.40 Python 炫技操作:判断是否包含子串的七种方法 - -![](http://image.iswbm.com/20200602135014.png) - -Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 - -但你要知道,在团队合作里,炫技是大忌。 - -为什么这么说呢?我说下自己的看法: - -1. 越简洁的代码,越清晰的逻辑,就越不容易出错; -2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 -3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) - -该篇是「**炫技系列**」的第三篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 Python 发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 - -## 1. 使用 in 和 not in - -`in` 和 `not in` 在 Python 中是很常用的关键字,我们将它们归类为 `成员运算符`。 - -使用这两个成员运算符,可以很让我们很直观清晰的判断一个对象是否在另一个对象中,示例如下: - -```python ->>> "llo" in "hello, python" -True ->>> ->>> "lol" in "hello, python" -False -``` - - - -## 2. 使用 find 方法 - -使用 字符串 对象的 find 方法,如果有找到子串,就可以返回指定子串在字符串中的出现位置,如果没有找到,就返回 `-1` - -```python ->>> "hello, python".find("llo") != -1 -True ->>> "hello, python".find("lol") != -1 -False ->> -``` - - - -## 3. 使用 index 方法 - -字符串对象有一个 index 方法,可以返回指定子串在该字符串中第一次出现的索引,如果没有找到会抛出异常,因此使用时需要注意捕获。 - -```python -def is_in(full_str, sub_str): - try: - full_str.index(sub_str) - return True - except ValueError: - return False - -print(is_in("hello, python", "llo")) # True -print(is_in("hello, python", "lol")) # False -``` - - - -## 4. 使用 count 方法 - -利用和 index 这种曲线救国的思路,同样我们可以使用 count 的方法来判断。 - -只要判断结果大于 0 就说明子串存在于字符串中。 - -```python -def is_in(full_str, sub_str): - return full_str.count(sub_str) > 0 - -print(is_in("hello, python", "llo")) # True -print(is_in("hello, python", "lol")) # False -``` - - - -## 5. 通过魔法方法 - -在第一种方法中,我们使用 in 和 not in 判断一个子串是否存在于另一个字符中,实际上当你使用 in 和 not in 时,Python 解释器会先去检查该对象是否有 `__contains__` 魔法方法。 - -若有就执行它,若没有,Python 就自动会迭代整个序列,只要找到了需要的一项就返回 True 。 - -示例如下; - -```python ->>> "hello, python".__contains__("llo") -True ->>> ->>> "hello, python".__contains__("lol") -False ->>> -``` - -这个用法与使用 in 和 not in 没有区别,但不排除有人会特意写成这样来增加代码的理解难度。 - -## 6. 借助 operator - -operator模块是python中内置的操作符函数接口,它定义了一些算术和比较内置操作的函数。operator模块是用c实现的,所以执行速度比 python 代码快。 - -在 operator 中有一个方法 `contains` 可以很方便地判断子串是否在字符串中。 - -```python ->>> import operator ->>> ->>> operator.contains("hello, python", "llo") -True ->>> operator.contains("hello, python", "lol") -False ->>> -``` - - - -## 7. 使用正则匹配 - -说到查找功能,那正则绝对可以说是专业的工具,多复杂的查找规则,都能满足你。 - -对于判断字符串是否存在于另一个字符串中的这个需求,使用正则简直就是大材小用。 - -```python -import re - -def is_in(full_str, sub_str): - if re.findall(sub_str, full_str): - return True - else: - return False - -print(is_in("hello, python", "llo")) # True -print(is_in("hello, python", "lol")) # False -``` - - - ---- - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_40.rst b/source/c01/c01_40.rst deleted file mode 100644 index 7bd620a..0000000 --- a/source/c01/c01_40.rst +++ /dev/null @@ -1,154 +0,0 @@ -1.40 Python 炫技操作:判断是否包含子串的七种方法 -================================================ - -|image0| - -Python 语言里有许多(而且是越来越多)的高级特性,是 Python -发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 - -但你要知道,在团队合作里,炫技是大忌。 - -为什么这么说呢?我说下自己的看法: - -1. 越简洁的代码,越清晰的逻辑,就越不容易出错; -2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 -3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) - -该篇是「\ **炫技系列**\ 」的第三篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 -Python -发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 - -1. 使用 in 和 not in --------------------- - -``in`` 和 ``not in`` 在 Python 中是很常用的关键字,我们将它们归类为 -``成员运算符``\ 。 - -使用这两个成员运算符,可以很让我们很直观清晰的判断一个对象是否在另一个对象中,示例如下: - -.. code:: python - - >>> "llo" in "hello, python" - True - >>> - >>> "lol" in "hello, python" - False - -2. 使用 find 方法 ------------------ - -使用 字符串 对象的 find -方法,如果有找到子串,就可以返回指定子串在字符串中的出现位置,如果没有找到,就返回 -``-1`` - -.. code:: python - - >>> "hello, python".find("llo") != -1 - True - >>> "hello, python".find("lol") != -1 - False - >> - -3. 使用 index 方法 ------------------- - -字符串对象有一个 index -方法,可以返回指定子串在该字符串中第一次出现的索引,如果没有找到会抛出异常,因此使用时需要注意捕获。 - -.. code:: python - - def is_in(full_str, sub_str): - try: - full_str.index(sub_str) - return True - except ValueError: - return False - - print(is_in("hello, python", "llo")) # True - print(is_in("hello, python", "lol")) # False - -4. 使用 count 方法 ------------------- - -利用和 index 这种曲线救国的思路,同样我们可以使用 count 的方法来判断。 - -只要判断结果大于 0 就说明子串存在于字符串中。 - -.. code:: python - - def is_in(full_str, sub_str): - return full_str.count(sub_str) > 0 - - print(is_in("hello, python", "llo")) # True - print(is_in("hello, python", "lol")) # False - -5. 通过魔法方法 ---------------- - -在第一种方法中,我们使用 in 和 not in -判断一个子串是否存在于另一个字符中,实际上当你使用 in 和 not in -时,Python 解释器会先去检查该对象是否有 ``__contains__`` 魔法方法。 - -若有就执行它,若没有,Python -就自动会迭代整个序列,只要找到了需要的一项就返回 True 。 - -示例如下; - -.. code:: python - - >>> "hello, python".__contains__("llo") - True - >>> - >>> "hello, python".__contains__("lol") - False - >>> - -这个用法与使用 in 和 not in -没有区别,但不排除有人会特意写成这样来增加代码的理解难度。 - -6. 借助 operator ----------------- - -operator模块是python中内置的操作符函数接口,它定义了一些算术和比较内置操作的函数。operator模块是用c实现的,所以执行速度比 -python 代码快。 - -在 operator 中有一个方法 ``contains`` -可以很方便地判断子串是否在字符串中。 - -.. code:: python - - >>> import operator - >>> - >>> operator.contains("hello, python", "llo") - True - >>> operator.contains("hello, python", "lol") - False - >>> - -7. 使用正则匹配 ---------------- - -说到查找功能,那正则绝对可以说是专业的工具,多复杂的查找规则,都能满足你。 - -对于判断字符串是否存在于另一个字符串中的这个需求,使用正则简直就是大材小用。 - -.. code:: python - - import re - - def is_in(full_str, sub_str): - if re.findall(sub_str, full_str): - return True - else: - return False - - print(is_in("hello, python", "llo")) # True - print(is_in("hello, python", "lol")) # False - --------------- - -|image1| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_41.md b/source/c01/c01_41.md deleted file mode 100644 index 185dfc3..0000000 --- a/source/c01/c01_41.md +++ /dev/null @@ -1,216 +0,0 @@ -# 1.41 Python 炫技操作:连接列表的八种方法 - -![](http://image.iswbm.com/20200602135014.png) - -Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 - -但你要知道,在团队合作里,炫技是大忌。 - -为什么这么说呢?我说下自己的看法: - -1. 越简洁的代码,越清晰的逻辑,就越不容易出错; -2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 -3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) - -该篇是「**炫技系列**」的第三篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 Python 发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 - -## 1. 最直观的相加 - -使用 `+` 对多个列表进行相加,你应该懂,不多说了。 - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> list03 = [7,8,9] ->>> ->>> list01 + list02 + list03 -[1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> -``` - - - -## 2. 借助 itertools - -itertools 在 Python 里有一个非常强大的内置模块,它专门用于操作可迭代对象。 - -在前面的文章中也介绍过,使用 `itertools.chain()` 函数先可迭代对象(在这里指的是列表)串联起来,组成一个更大的可迭代对象。 - -最后你再利用 list 将其转化为 列表。 - -```python ->>> from itertools import chain ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> list03 = [7,8,9] ->>> ->>> list(chain(list01, list02, list03)) -[1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> -``` - -## 3. 使用 * 解包 - -在 [Python 炫技操作(02):合并字典的七种方法](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247486080&idx=1&sn=f1c5c4fc5363a1d787b9ae9baba0d07b&chksm=e8866a62dff1e374ad1e5ae2e51bc6cbeb41f631899cde980555ded61be840f0fe6c76c44b7d&token=688334198&lang=zh_CN#rd) 提到了使用 `**` 可解包字典。 - -与它相似的,使用 `*` 可以解包列表。 `*` 和 `**` 常用在函数定义时,设置可变参数。 - -现在我将它单独拿出来用在多个列表的合并。 - -示例如下: - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> ->>> [*list01, *list02] -[1, 2, 3, 4, 5, 6] ->>> -``` - - - -## 4. 使用 extend - -在字典中,使用 update 可实现原地更新,而在列表中,使用 extend 可实现列表的自我扩展。 - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> ->>> list01.extend(list02) ->>> list01 -[1, 2, 3, 4, 5, 6] -``` - -## 5. 使用列表推导式 - -Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 - -那就是列表解析式,集合解析式和字典解析式,通常是 Python 发烧友的最爱,那么今天的主题:列表合并,列表推导式还能否胜任呢? - -当然可以,具体示例代码如下: - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> list03 = [7,8,9] ->>> ->>> [x for l in (list01, list02, list03) for x in l] -[1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> -``` - - - -## 6. 使用 heapq - -heapq 是 Python 的一个标准模块,它提供了堆排序算法的实现。 - -该模块里有一个 merge 方法,可以用于合并多个列表,如下所示 - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> list03 = [7,8,9] ->>> ->>> from heapq import merge ->>> ->>> list(merge(list01, list02, list03)) -[1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> -``` - -要注意的是,heapq.merge 除了合并多个列表外,它还会将合并后的最终的列表进行排序。 - -```python ->>> list01 = [2,5,3] ->>> list02 = [1,4,6] ->>> list03 = [7,9,8] ->>> ->>> from heapq import merge ->>> ->>> list(merge(list01, list02, list03)) -[1, 2, 4, 5, 3, 6, 7, 9, 8] ->>> -``` - -它的效果等价于下面这行代码: - -```python -sorted(itertools.chain(*iterables)) -``` - -如果你希望得到一个始终有序的列表,那请第一时间想到 heapq.merge,因为它采用堆排序,效率非常高。但若你不希望得到一个排过序的列表,就不要使用它了。 - -## 7. 借助魔法方法 - -在之前的文章里,把魔法方法介绍得很全。 - -[非常全的通俗易懂 Python 魔法方法指南(上)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485551&idx=1&sn=4c0983f22269a113bcdf83690e5e2b20&chksm=e886688ddff1e19b9ad230128a67ee1a9ee1eced0720c14b5d48f68943be10b1b85b23d8ca2d#rd) - -[非常全的通俗易懂 Python 魔法方法指南(下)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485555&idx=1&sn=0a218b796e651b451a17112e22790d07&chksm=e8866891dff1e18771a9392da7f509732244ebc4d1a6e2427acd39ee8b59b146e3d4961a2a62#rd) - -其中有一个魔法方法是 `__add__`,实际 上当我们使用第一种方法 list01 + list02 的时候,内部实际上是作用在 `__add__` 这个魔法方法上的. - -所以以下两种方法其实是等价的 - -``` ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> ->>> list01 + list02 -[1, 2, 3, 4, 5, 6] ->>> ->>> ->>> list01.__add__(list02) -[1, 2, 3, 4, 5, 6] ->>> -``` - -借用这个魔法特性,我们可以 reduce 这个方法来对多个列表进行合并,示例代码如下 - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> list03 = [7,8,9] ->>> ->>> from functools import reduce ->>> reduce(list.__add__, (list01, list02, list03)) -[1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> -``` - - - -## 8. 使用 yield from - -在很早的一篇文章里([并发编程08|深入理解yield from语法](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485063&idx=1&sn=0ff7a99058320ff90a6237e7e03367fb&scene=21#wechat_redirect)),我很详细的介绍了 yield from 意义及使用方法。 - -在 yield from 后可接一个可迭代对象,用于迭代并返回其中的每一个元素。 - -因此,我们可以像下面这样自定义一个合并列表的工具函数。 - -```python ->>> list01 = [1,2,3] ->>> list02 = [4,5,6] ->>> list03 = [7,8,9] ->>> ->>> def merge(*lists): -... for l in lists: -... yield from l -... ->>> list(merge(list01, list02, list03)) -[1, 2, 3, 4, 5, 6, 7, 8, 9] ->>> -``` - - - ---- - - - -![](http://image.iswbm.com/20200607174235.png) - - diff --git a/source/c01/c01_42.md b/source/c01/c01_42.md deleted file mode 100644 index 5c92a8e..0000000 --- a/source/c01/c01_42.md +++ /dev/null @@ -1,175 +0,0 @@ -# 1.42 Python 炫技操作:海象运算符的三种用法 - -![](http://image.iswbm.com/20200602135014.png) - -Python 版本发展非常快,如今最新的版本已经是 Pyhton 3.9,即便如此,有很多人甚至还停留在 3.6 或者 3.7,连 3.8 还没用上。 - -很多 Python 3.8 的特性还没来得及了解,就已经成为旧知识了,比如今天要说的海象运算符。 - -海象运算符是在 PEP 572 被提出的,直到 3.8 版本合入发布。 - -它的英文原名叫 `Assignment Expressions`,翻译过来也就是 `赋值表达式`,不过现在大家更普遍地称之为海象运算符,就是因为它长得真的太像海象了。 - -![](http://image.iswbm.com/image-20200418122739417.png) - -## 1. 第一个用法:if/else - -可能有朋友是第一次接触这个新特性,所以还是简单的介绍一下这个海象运算符有什么用? - -在 Golang 中的条件语句可以直接在 if 中运算变量的获取后直接对这个变量进行判断,可以让你少写一行代码 - -```go -import "fmt" - -func main() { - if age := 20;age > 18 { - fmt.Println("已经成年了") - } -} -``` - -若在 Python 3.8 之前,Python 必须得这样子写 - -```python -age = 20 -if age > 18: - print("已经成年了") -``` - -但有了海象运算符之后,你可以和 Golang 一样(如果你没学过 Golang,那这里要注意,Golang 中的 `:=` 叫短变量声明,意思是声明并初始化,它和 Python 中的 `:=` 不是一个概念) - -```python -if (age:= 20) > 18: - print("已经成年了") -``` - - - -## 2. 第二个用法:while - -在不使用 海象运算符之前,使用 while 循环来读取文件的时候,你也许会这么写 - -```python -file = open("demo.txt", "r") -while True: - line = file.readline() - if not line: - break - print(line.strip()) -``` - -但有了海象运算符之后,你可以这样 - -```python -file = open("demo.txt", "r") -while (line := file.readline()): - print(line.strip()) -``` - -使用它替换以往的无限 while 循环写法更为惊艳 - -比如,实现一个需要命令行交互输入密码并检验的代码,你也许会这样子写 - -```python -while True: - p = input("Enter the password: ") - if p == "youpassword": - break -``` - -有了海象运算符之后,这样子写更为舒服 - -```python -while (p := input("Enter the password: ")) != "youpassword": - continue -``` - - - -## 3. 第三个用法:推导式 - -这个系列的文章,几乎每篇都能看到推导式的身影,这一篇依旧如此。 - -在编码过程中,我很喜欢使用推导式,在简单的应用场景下,它简洁且不失高效。 - -如下这段代码中,我会使用列表推导式得出所有会员中过于肥胖的人的 bmi 指数 - -```python -members = [ - {"name": "小五", "age": 23, "height": 1.75, "weight": 72}, - {"name": "小李", "age": 17, "height": 1.72, "weight": 63}, - {"name": "小陈", "age": 20, "height": 1.78, "weight": 82}, -] - -count = 0 - -def get_bmi(info): - global count - count += 1 - - print(f"执行了 {count} 次") - - height = info["height"] - weight = info["weight"] - - return weight / (height**2) - -# 查出所有会员中过于肥胖的人的 bmi 指数 -fat_bmis = [get_bmi(m) for m in members if get_bmi(m) > 24] - -print(fat_bmis) -``` - -输出如下 - -``` -执行了 1 次 -执行了 2 次 -执行了 3 次 -执行了 4 次 -[25.88057063502083] -``` - -可以看到,会员数只有 3 个,但是 get_bmi 函数却执行了 4 次,原因是在判断时执行了 3 次,而在构造新的列表时又重复执行了一遍。 - -如果所有会员都是过于肥胖的,那最终将执行 6 次,这种在大量的数据下是比较浪费性能的,因此对于这种结构,我通常会使用传统的for 循环 + if 判断。 - -```python -fat_bmis = [] - -# 查出所有会员中过于肥胖的人的 bmi 指数 -for m in members: - bmi = get_bmi(m) - if bmi > 24: - fat_bmis.append(bmi) -``` - -在有了海象运算符之后,你就可以不用在这种场景下做出妥协。 - -```python -# 查出所有会员中过于肥胖的人的 bmi 指数 -fat_bmis = [bmi for m in members if (bmi := get_bmi(m)) > 24] -``` - -最终从输出结果可以看出,只执行了 3 次 - -``` -执行了 1 次 -执行了 2 次 -执行了 3 次 -[25.88057063502083] -``` - -这里仅介绍了列表推导式,但在字典推导式和集合推导式中同样适用。不再演示。 - - - -海象运算符,是一个新奇的特性,有不少人觉得这样这种特性会破坏代码的可读性。确实在一个新鲜事物刚出来时是会这样,但我相信经过时间的沉淀后,越来越多的人使用它并享受它带来的便利时,这种争议也会慢慢消失在历史的长河中。 - - - ---- - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_42.rst b/source/c01/c01_42.rst deleted file mode 100644 index 9df2a34..0000000 --- a/source/c01/c01_42.rst +++ /dev/null @@ -1,183 +0,0 @@ -1.42 Python 炫技操作:海象运算符的三种用法 -========================================== - -|image0| - -Python 版本发展非常快,如今最新的版本已经是 Pyhton -3.9,即便如此,有很多人甚至还停留在 3.6 或者 3.7,连 3.8 还没用上。 - -很多 Python 3.8 -的特性还没来得及了解,就已经成为旧知识了,比如今天要说的海象运算符。 - -海象运算符是在 PEP 572 被提出的,直到 3.8 版本合入发布。 - -它的英文原名叫 ``Assignment Expressions``\ ,翻译过来也就是 -``赋值表达式``\ ,不过现在大家更普遍地称之为海象运算符,就是因为它长得真的太像海象了。 - -|image1| - -1. 第一个用法:if/else ----------------------- - -可能有朋友是第一次接触这个新特性,所以还是简单的介绍一下这个海象运算符有什么用? - -在 Golang 中的条件语句可以直接在 if -中运算变量的获取后直接对这个变量进行判断,可以让你少写一行代码 - -.. code:: go - - import "fmt" - - func main() { - if age := 20;age > 18 { - fmt.Println("已经成年了") - } - } - -若在 Python 3.8 之前,Python 必须得这样子写 - -.. code:: python - - age = 20 - if age > 18: - print("已经成年了") - -但有了海象运算符之后,你可以和 Golang 一样(如果你没学过 -Golang,那这里要注意,Golang 中的 ``:=`` -叫短变量声明,意思是声明并初始化,它和 Python 中的 ``:=`` 不是一个概念) - -.. code:: python - - if (age:= 20) > 18: - print("已经成年了") - -2. 第二个用法:while --------------------- - -在不使用 海象运算符之前,使用 while 循环来读取文件的时候,你也许会这么写 - -.. code:: python - - file = open("demo.txt", "r") - while True: - line = file.readline() - if not line: - break - print(line.strip()) - -但有了海象运算符之后,你可以这样 - -.. code:: python - - file = open("demo.txt", "r") - while (line := file.readline()): - print(line.strip()) - -使用它替换以往的无限 while 循环写法更为惊艳 - -比如,实现一个需要命令行交互输入密码并检验的代码,你也许会这样子写 - -.. code:: python - - while True: - p = input("Enter the password: ") - if p == "youpassword": - break - -有了海象运算符之后,这样子写更为舒服 - -.. code:: python - - while (p := input("Enter the password: ")) != "youpassword": - continue - -3. 第三个用法:推导式 ---------------------- - -这个系列的文章,几乎每篇都能看到推导式的身影,这一篇依旧如此。 - -在编码过程中,我很喜欢使用推导式,在简单的应用场景下,它简洁且不失高效。 - -如下这段代码中,我会使用列表推导式得出所有会员中过于肥胖的人的 bmi 指数 - -.. code:: python - - members = [ - {"name": "小五", "age": 23, "height": 1.75, "weight": 72}, - {"name": "小李", "age": 17, "height": 1.72, "weight": 63}, - {"name": "小陈", "age": 20, "height": 1.78, "weight": 82}, - ] - - count = 0 - - def get_bmi(info): - global count - count += 1 - - print(f"执行了 {count} 次") - - height = info["height"] - weight = info["weight"] - - return weight / (height**2) - - # 查出所有会员中过于肥胖的人的 bmi 指数 - fat_bmis = [get_bmi(m) for m in members if get_bmi(m) > 24] - - print(fat_bmis) - -输出如下 - -:: - - 执行了 1 次 - 执行了 2 次 - 执行了 3 次 - 执行了 4 次 - [25.88057063502083] - -可以看到,会员数只有 3 个,但是 get_bmi 函数却执行了 4 -次,原因是在判断时执行了 3 次,而在构造新的列表时又重复执行了一遍。 - -如果所有会员都是过于肥胖的,那最终将执行 6 -次,这种在大量的数据下是比较浪费性能的,因此对于这种结构,我通常会使用传统的for -循环 + if 判断。 - -.. code:: python - - fat_bmis = [] - - # 查出所有会员中过于肥胖的人的 bmi 指数 - for m in members: - bmi = get_bmi(m) - if bmi > 24: - fat_bmis.append(bmi) - -在有了海象运算符之后,你就可以不用在这种场景下做出妥协。 - -.. code:: python - - # 查出所有会员中过于肥胖的人的 bmi 指数 - fat_bmis = [bmi for m in members if (bmi := get_bmi(m)) > 24] - -最终从输出结果可以看出,只执行了 3 次 - -:: - - 执行了 1 次 - 执行了 2 次 - 执行了 3 次 - [25.88057063502083] - -这里仅介绍了列表推导式,但在字典推导式和集合推导式中同样适用。不再演示。 - -海象运算符,是一个新奇的特性,有不少人觉得这样这种特性会破坏代码的可读性。确实在一个新鲜事物刚出来时是会这样,但我相信经过时间的沉淀后,越来越多的人使用它并享受它带来的便利时,这种争议也会慢慢消失在历史的长河中。 - --------------- - -|image2| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/image-20200418122739417.png -.. |image2| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_43.md b/source/c01/c01_43.md deleted file mode 100644 index f5c9bc6..0000000 --- a/source/c01/c01_43.md +++ /dev/null @@ -1,353 +0,0 @@ -# 1.43 求你了,别再使用 pprint 打印字典了 - -![](http://image.iswbm.com/20200602135014.png) - -## 1. 吐槽问题 - -pprint 你应该很熟悉了吧? - -随便在搜索引擎上搜索如何打印漂亮的字典或者格式化字符串时,大部分人都会推荐你使用这货 。 - -比如这下面这个 json 字符串或者说字典(我随便在网上找的),如果不格式化美化一下,根本无法阅读。 - -```json -[{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017 你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"斗鱼271934 走过路过不要错过,这里有最好的鸡儿"}] -``` - -如果你不想看到一堆密密麻麻的字,那就使用大伙都极力推荐的 pprint 看下什么效果(以下在 Python 2 中演示,Python 3 中是不一样的效果)。 - -```python ->>> info=[{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017 你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"斗鱼271934 走过路过不要错过,这里有最好的鸡儿"}] ->>> ->>> from pprint import pprint ->>> pprint(info) -[{'des': '2011-2017 \xe4\xbd\xa0\xe7\x9a\x84\xe9\x93\x81\xe5\xa4\xb4\xe5\xa8\x83\xe4\xb8\x80\xe7\x9b\xb4\xe5\x9c\xa8\xe8\xbf\x99\xe5\x84\xbf\xe3\x80\x82\xe4\xb8\xad\xe5\x9b\xbd\xe6\x9c\x80\xe5\xa4\xa7\xe7\x9a\x84\xe5\xae\x9e\xe5\x90\x8d\xe5\x88\xb6SNS\xe7\xbd\x91\xe7\xbb\x9c\xe5\xb9\xb3\xe5\x8f\xb0\xef\xbc\x8c\xe5\xab\xa9\xe5\xa4\xb4\xe9\x9d\x92', - 'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk', - 'iconUrl': 'app/com.renren.mobile.android/icon.jpg', - 'id': 1580615, - 'name': '\xe7\x9a\xae\xe7\x9a\x84\xe5\x98\x9b', - 'packageName': 'com.renren.mobile.android', - 'size': 21803987, - 'stars': 2}, - {'des': '\xe6\x96\x97\xe9\xb1\xbc271934 \xe8\xb5\xb0\xe8\xbf\x87\xe8\xb7\xaf\xe8\xbf\x87\xe4\xb8\x8d\xe8\xa6\x81\xe9\x94\x99\xe8\xbf\x87\xef\xbc\x8c\xe8\xbf\x99\xe9\x87\x8c\xe6\x9c\x89\xe6\x9c\x80\xe5\xa5\xbd\xe7\x9a\x84\xe9\xb8\xa1\xe5\x84\xbf', - 'downloadUrl': 'app/com.ct.client/com.ct.client.apk', - 'iconUrl': 'app/com.ct.client/icon.jpg', - 'id': 1540629, - 'name': '\xe4\xb8\x8d\xe5\xad\x98\xe5\x9c\xa8\xe7\x9a\x84', - 'packageName': 'com.ct.client', - 'size': 4794202, - 'stars': 2}] -``` - -好像有点效果,真的是 “神器”呀。 - -但是你告诉我, **\xe4\xbd\xa0\xe7\x9a** 这些是什么玩意?本来想提高可读性的,现在变成完全不可读了。 - -好在我懂点 Python 2 的编码,知道 Python 2 中默认(不带u)的字符串格式都是 str 类型,也是 bytes 类型,它是以 byte 存储的。 - -行吧,好像是我错了,我改了下,使用 unicode 类型来定义中文字符串吧。 - -```python ->>> info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] ->>> ->>> from pprint import pprint ->>> pprint(info) -[{'des': u'2011-2017\u4f60\u7684\u94c1\u5934\u5a03\u4e00\u76f4\u5728\u8fd9\u513f\u3002\u4e2d\u56fd\u6700\u5927\u7684\u5b9e\u540d\u5236SNS\u7f51\u7edc\u5e73\u53f0\uff0c\u5ae9\u5934\u9752', - 'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk', - 'iconUrl': 'app/com.renren.mobile.android/icon.jpg', - 'id': 1580615, - 'name': u'\u76ae\u7684\u561b', - 'packageName': 'com.renren.mobile.android', - 'size': 21803987, - 'stars': 2}, - {'des': u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u6700\u597d\u7684\u9e21\u513f', - 'downloadUrl': 'app/com.ct.client/com.ct.client.apk', - 'iconUrl': 'app/com.ct.client/icon.jpg', - 'id': 1540629, - 'name': u'\u4e0d\u5b58\u5728\u7684', - 'packageName': 'com.ct.client', - 'size': 4794202, - 'stars': 2}] -``` - -确实是有好点了,但是看到下面这些,我崩溃了,我哪里知道这是什么鬼,难道是我太菜了吗?当我是计算机呀? - -``` -u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u6700\u597d\u7684\u9e21\u513f' -``` - -除此之外,我们知道 json 的严格要求必须使用 **双引号**,而我定义字典时,也使用了双引号了,为什么打印出来的为什么是 **单引号**?我也太难了吧,我连自己的代码都无法控制了吗? - -到这里,我们知道了 pprint 带来的两个问题: - -1. 没法在 Python 2 下正常打印中文 -2. 没法输出 JSON 标准格式的格式化内容(双引号) - -## 2. 解决问题 - -### 打印中文 - -如果你是在 Python 3 下使用,你会发现中文是可以正常显示的。 - -```python -# Python3.7 ->>> info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] ->>> ->>> from pprint import pprint ->>> pprint(info) -[{'des': '2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青', - 'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk', - 'iconUrl': 'app/com.renren.mobile.android/icon.jpg', - 'id': 1580615, - 'name': '皮的嘛', - 'packageName': 'com.renren.mobile.android', - 'size': 21803987, - 'stars': 2}, - {'des': '斗鱼271934走过路过不要错过,这里有最好的鸡儿', - 'downloadUrl': 'app/com.ct.client/com.ct.client.apk', - 'iconUrl': 'app/com.ct.client/icon.jpg', - 'id': 1540629, - 'name': '不存在的', - 'packageName': 'com.ct.client', - 'size': 4794202, - 'stars': 2}] ->>> - -``` - -但是很多时候(在公司的一些服务器)你无法选择自己使用哪个版本的 Python,本来我可以选择不用的,因为有更好的替代方案(**这个后面会讲**)。 - -但是我出于猎奇,正好前两天不是写过一篇关于 编码 的文章吗,我自认为自己对于 编码还是掌握比较熟练的,就想着来解决一下这个问题。 - -索性就来看下 pprint 的源代码,还真被我找到了解决方法,如果你也想挑战一下,不防在这里停住,自己研究一下如何实现,我相信对你阅读源码会有帮助。 - -**以下是我的解决方案,供你参考**: - -写一个自己的 printer 对象,继承自 PrettyPrinter (pprint 使用的printer) - -并且复写 format 方法,判断传进来的字符串对象是否 str 类型,如果不是 str 类型,而是 unicode 类型,就用 uft8 编码成 str 类型。 - -```python -# coding: utf-8 -from pprint import PrettyPrinter - -# 继承 PrettyPrinter,复写 format 方法 -class MyPrettyPrinter(PrettyPrinter): - def format(self, object, context, maxlevels, level): - if isinstance(object, unicode): - return (object.encode('utf8'), True, False) - return PrettyPrinter.format(self, object, context, maxlevels, level) - -info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] - -MyPrettyPrinter().pprint(info) -``` - -输出如下,已经解决了中文的显示问题: - -![](http://image.iswbm.com/20200507171451.png) - -### 打印双引号 - -解决了中文问题后,再来看看如何让 pprint 打印双引号。 - -在实例化 PrettyPrinter 对象的时候,可以接收一个 stream 对象,它表示你要将内容输出到哪里,默认是使用 sys.stdout 这个 stream,也就是标准输出。 - -现在我们要修改输出的内容,也就是将输出的单引号替换成双引号。 - -那我们完全可以自己定义一个 stream 类型的对象,该对象不需要继承任何父类,只要你实现 write 方法就可以。 - -有了思路,就可以开始写代码了,如下: - -```python -# coding: utf-8 -from pprint import PrettyPrinter - -class MyPrettyPrinter(PrettyPrinter): - def format(self, object, context, maxlevels, level): - if isinstance(object, unicode): - return (object.encode('utf8'), True, False) - return PrettyPrinter.format(self, object, context, maxlevels, level) - -class MyStream(): - def write(self, text): - print text.replace('\'', '"') - -info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] -MyPrettyPrinter(stream=MyStream()).pprint(info) -``` - -尝试执行了下,我的天,怎么是这样子的。 - -```json -[ -{ -"des" -: -2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青 -, - "downloadUrl": -"app/com.renren.mobile.android/com.renren.mobile.android.apk" -, - "iconUrl": -"app/com.renren.mobile.android/icon.jpg" -, - "id": -1580615 -, - "name": -皮的嘛 -, - "packageName": -"com.renren.mobile.android" -, - "size": -21803987 -, - "stars": -2 -} -, - -{ -"des" -: -斗鱼271934走过路过不要错过,这里有最好的鸡儿 -, - "downloadUrl": -"app/com.ct.client/com.ct.client.apk" -, - "iconUrl": -"app/com.ct.client/icon.jpg" -, - "id": -1540629 -, - "name": -不存在的 -, - "packageName": -"com.ct.client" -, - "size": -4794202 -, - "stars": -2 -} -] -``` - -经过一番研究,才知道是因为 print 函数默认会将打印的内容后面加个 **换行符**。 - -那如何将使 print 函数打印的内容,不进行换行呢? - -方法很简单,但是我相信很多人都不知道,只要在 print 的内容后加一个 **逗号** 就行。 - -就像下面这样。 - -![](http://image.iswbm.com/20200507174459.png) - -知道了问题所在,再修改下代码 - -```python -# coding: utf-8 -from pprint import PrettyPrinter - -class MyPrettyPrinter(PrettyPrinter): - def format(self, object, context, maxlevels, level): - if isinstance(object, unicode): - return (object.encode('utf8'), True, False) - return PrettyPrinter.format(self, object, context, maxlevels, level) - -class MyStream(): - def write(self, text): - print text.replace('\'', '"'), - -info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] - -MyPrettyPrinter(stream=MyStream()).pprint(info) -``` - -终于成功了,太不容易了吧。 - -![](http://image.iswbm.com/20200507174802.png) - -## 3. 何必折腾 - -通过上面的一番折腾,我终于实现了我 **梦寐以求** 的需求。 - -代价就是我整整花费了两个小时,才得以实现,而对于小白来说,可能没有信心,也没有耐心去做这样的事情。 - -**所以我想说的是,Python 2 下的 pprint ,真的不要再用了**。 - -为什么我要用这么 说,因为明明有更好的替代品,人生苦短,既然用了 Python ,当然是怎么简单怎么来咯,何必为难自己呢,一行代码可以解决的事情,偏偏要去写两个类,那不是自讨苦吃吗?(我这是在骂自己吗? - -如果你愿意抛弃 pprint ,那我推荐你用 json.dumps ,我保证你再也不想用 pprint 了。 - -### 打印中文 - -其实无法打印中文,是 Python 2 引来的大坑,并不能全怪 pprint 。 - -但是同样的问题,在 json.dumps 这里,却只要加个参数就好了,可比 pprint 简单得不要太多。 - -具体的代码示例如下: - -```python ->>> info = [{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] ->>> ->>> import json ->>> ->>> ->>> print json.dumps(info, indent=4, ensure_ascii=False) -[ - { - "downloadUrl": "app/com.renren.mobile.android/com.renren.mobile.android.apk", - "iconUrl": "app/com.renren.mobile.android/icon.jpg", - "name": "皮的嘛", - "stars": 2, - "packageName": "com.renren.mobile.android", - "des": "2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青", - "id": 1580615, - "size": 21803987 - }, - { - "downloadUrl": "app/com.ct.client/com.ct.client.apk", - "iconUrl": "app/com.ct.client/icon.jpg", - "name": "不存在的", - "stars": 2, - "packageName": "com.ct.client", - "des": "斗鱼271934走过路过不要错过,这里有最好的鸡儿", - "id": 1540629, - "size": 4794202 - } -] ->>> -``` - -json.dumps 的关键参数有两个: - -- **indent=4**:以 4 个空格缩进单位 -- **ensure_ascii=False**:接收非 ASCII 编码的字符,这样才能使用中文 - -与 pprint 相比 json.dumps 可以说完胜: - -1. 两个参数就能实现所有我的需求(打印中文与双引号) -2. 就算在 Python 2 下,使用中文也不需要用 `u'中文'` 这种写法 -3. Python2 和 Python3 的写法完全一致,对于这一点不需要考虑兼容问题 - -## 4. 总结一下 - -本来很简单的一个观点,我为了证明 pprint 实现那两个需求有多么困难,花了很多的时间去研究了 pprint 的源码(各种处理其实还是挺复杂的),不过好在最后也能有所收获。 - -本文的分享就到这里,阅读本文,我认为你可以获取到三个知识点 - -1. 核心观点:Python2 下不要再使用 pprint -2. 若真要使用,且有和一样的改造需求,可以参考我的实现 -3. Python 2 中的 print 语句后居然可以加 逗号 - -以上。希望此文能对你有帮助。 - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_43.rst b/source/c01/c01_43.rst deleted file mode 100644 index cb4ba3b..0000000 --- a/source/c01/c01_43.rst +++ /dev/null @@ -1,391 +0,0 @@ -1.43 求你了,别再使用 pprint 打印字典了 -======================================= - -|image0| - -1. 吐槽问题 ------------ - -pprint 你应该很熟悉了吧? - -随便在搜索引擎上搜索如何打印漂亮的字典或者格式化字符串时,大部分人都会推荐你使用这货 -。 - -比如这下面这个 json -字符串或者说字典(我随便在网上找的),如果不格式化美化一下,根本无法阅读。 - -.. code:: json - - [{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017 你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"斗鱼271934 走过路过不要错过,这里有最好的鸡儿"}] - -如果你不想看到一堆密密麻麻的字,那就使用大伙都极力推荐的 pprint -看下什么效果(以下在 Python 2 中演示,Python 3 中是不一样的效果)。 - -.. code:: python - - >>> info=[{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017 你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"斗鱼271934 走过路过不要错过,这里有最好的鸡儿"}] - >>> - >>> from pprint import pprint - >>> pprint(info) - [{'des': '2011-2017 \xe4\xbd\xa0\xe7\x9a\x84\xe9\x93\x81\xe5\xa4\xb4\xe5\xa8\x83\xe4\xb8\x80\xe7\x9b\xb4\xe5\x9c\xa8\xe8\xbf\x99\xe5\x84\xbf\xe3\x80\x82\xe4\xb8\xad\xe5\x9b\xbd\xe6\x9c\x80\xe5\xa4\xa7\xe7\x9a\x84\xe5\xae\x9e\xe5\x90\x8d\xe5\x88\xb6SNS\xe7\xbd\x91\xe7\xbb\x9c\xe5\xb9\xb3\xe5\x8f\xb0\xef\xbc\x8c\xe5\xab\xa9\xe5\xa4\xb4\xe9\x9d\x92', - 'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk', - 'iconUrl': 'app/com.renren.mobile.android/icon.jpg', - 'id': 1580615, - 'name': '\xe7\x9a\xae\xe7\x9a\x84\xe5\x98\x9b', - 'packageName': 'com.renren.mobile.android', - 'size': 21803987, - 'stars': 2}, - {'des': '\xe6\x96\x97\xe9\xb1\xbc271934 \xe8\xb5\xb0\xe8\xbf\x87\xe8\xb7\xaf\xe8\xbf\x87\xe4\xb8\x8d\xe8\xa6\x81\xe9\x94\x99\xe8\xbf\x87\xef\xbc\x8c\xe8\xbf\x99\xe9\x87\x8c\xe6\x9c\x89\xe6\x9c\x80\xe5\xa5\xbd\xe7\x9a\x84\xe9\xb8\xa1\xe5\x84\xbf', - 'downloadUrl': 'app/com.ct.client/com.ct.client.apk', - 'iconUrl': 'app/com.ct.client/icon.jpg', - 'id': 1540629, - 'name': '\xe4\xb8\x8d\xe5\xad\x98\xe5\x9c\xa8\xe7\x9a\x84', - 'packageName': 'com.ct.client', - 'size': 4794202, - 'stars': 2}] - -好像有点效果,真的是 “神器”呀。 - -但是你告诉我, -**:raw-latex:`\xe`4:raw-latex:`\xbd`:raw-latex:`\xa`0:raw-latex:`\xe`7:raw-latex:`\x`9a** -这些是什么玩意?本来想提高可读性的,现在变成完全不可读了。 - -好在我懂点 Python 2 的编码,知道 Python 2 -中默认(不带u)的字符串格式都是 str 类型,也是 bytes 类型,它是以 byte -存储的。 - -行吧,好像是我错了,我改了下,使用 unicode 类型来定义中文字符串吧。 - -.. code:: python - - >>> info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] - >>> - >>> from pprint import pprint - >>> pprint(info) - [{'des': u'2011-2017\u4f60\u7684\u94c1\u5934\u5a03\u4e00\u76f4\u5728\u8fd9\u513f\u3002\u4e2d\u56fd\u6700\u5927\u7684\u5b9e\u540d\u5236SNS\u7f51\u7edc\u5e73\u53f0\uff0c\u5ae9\u5934\u9752', - 'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk', - 'iconUrl': 'app/com.renren.mobile.android/icon.jpg', - 'id': 1580615, - 'name': u'\u76ae\u7684\u561b', - 'packageName': 'com.renren.mobile.android', - 'size': 21803987, - 'stars': 2}, - {'des': u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u6700\u597d\u7684\u9e21\u513f', - 'downloadUrl': 'app/com.ct.client/com.ct.client.apk', - 'iconUrl': 'app/com.ct.client/icon.jpg', - 'id': 1540629, - 'name': u'\u4e0d\u5b58\u5728\u7684', - 'packageName': 'com.ct.client', - 'size': 4794202, - 'stars': 2}] - -确实是有好点了,但是看到下面这些,我崩溃了,我哪里知道这是什么鬼,难道是我太菜了吗?当我是计算机呀? - -:: - - u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u6700\u597d\u7684\u9e21\u513f' - -除此之外,我们知道 json 的严格要求必须使用 -**双引号**\ ,而我定义字典时,也使用了双引号了,为什么打印出来的为什么是 -**单引号**\ ?我也太难了吧,我连自己的代码都无法控制了吗? - -到这里,我们知道了 pprint 带来的两个问题: - -1. 没法在 Python 2 下正常打印中文 -2. 没法输出 JSON 标准格式的格式化内容(双引号) - -2. 解决问题 ------------ - -打印中文 -~~~~~~~~ - -如果你是在 Python 3 下使用,你会发现中文是可以正常显示的。 - -.. code:: python - - # Python3.7 - >>> info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] - >>> - >>> from pprint import pprint - >>> pprint(info) - [{'des': '2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青', - 'downloadUrl': 'app/com.renren.mobile.android/com.renren.mobile.android.apk', - 'iconUrl': 'app/com.renren.mobile.android/icon.jpg', - 'id': 1580615, - 'name': '皮的嘛', - 'packageName': 'com.renren.mobile.android', - 'size': 21803987, - 'stars': 2}, - {'des': '斗鱼271934走过路过不要错过,这里有最好的鸡儿', - 'downloadUrl': 'app/com.ct.client/com.ct.client.apk', - 'iconUrl': 'app/com.ct.client/icon.jpg', - 'id': 1540629, - 'name': '不存在的', - 'packageName': 'com.ct.client', - 'size': 4794202, - 'stars': 2}] - >>> - -但是很多时候(在公司的一些服务器)你无法选择自己使用哪个版本的 -Python,本来我可以选择不用的,因为有更好的替代方案(\ **这个后面会讲**\ )。 - -但是我出于猎奇,正好前两天不是写过一篇关于 编码 -的文章吗,我自认为自己对于 -编码还是掌握比较熟练的,就想着来解决一下这个问题。 - -索性就来看下 pprint -的源代码,还真被我找到了解决方法,如果你也想挑战一下,不防在这里停住,自己研究一下如何实现,我相信对你阅读源码会有帮助。 - -**以下是我的解决方案,供你参考**\ : - -写一个自己的 printer 对象,继承自 PrettyPrinter (pprint 使用的printer) - -并且复写 format 方法,判断传进来的字符串对象是否 str 类型,如果不是 str -类型,而是 unicode 类型,就用 uft8 编码成 str 类型。 - -.. code:: python - - # coding: utf-8 - from pprint import PrettyPrinter - - # 继承 PrettyPrinter,复写 format 方法 - class MyPrettyPrinter(PrettyPrinter): - def format(self, object, context, maxlevels, level): - if isinstance(object, unicode): - return (object.encode('utf8'), True, False) - return PrettyPrinter.format(self, object, context, maxlevels, level) - - info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] - - MyPrettyPrinter().pprint(info) - -输出如下,已经解决了中文的显示问题: - -|image1| - -打印双引号 -~~~~~~~~~~ - -解决了中文问题后,再来看看如何让 pprint 打印双引号。 - -在实例化 PrettyPrinter 对象的时候,可以接收一个 stream -对象,它表示你要将内容输出到哪里,默认是使用 sys.stdout 这个 -stream,也就是标准输出。 - -现在我们要修改输出的内容,也就是将输出的单引号替换成双引号。 - -那我们完全可以自己定义一个 stream -类型的对象,该对象不需要继承任何父类,只要你实现 write 方法就可以。 - -有了思路,就可以开始写代码了,如下: - -.. code:: python - - # coding: utf-8 - from pprint import PrettyPrinter - - class MyPrettyPrinter(PrettyPrinter): - def format(self, object, context, maxlevels, level): - if isinstance(object, unicode): - return (object.encode('utf8'), True, False) - return PrettyPrinter.format(self, object, context, maxlevels, level) - - class MyStream(): - def write(self, text): - print text.replace('\'', '"') - - info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] - MyPrettyPrinter(stream=MyStream()).pprint(info) - -尝试执行了下,我的天,怎么是这样子的。 - -.. code:: json - - [ - { - "des" - : - 2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青 - , - "downloadUrl": - "app/com.renren.mobile.android/com.renren.mobile.android.apk" - , - "iconUrl": - "app/com.renren.mobile.android/icon.jpg" - , - "id": - 1580615 - , - "name": - 皮的嘛 - , - "packageName": - "com.renren.mobile.android" - , - "size": - 21803987 - , - "stars": - 2 - } - , - - { - "des" - : - 斗鱼271934走过路过不要错过,这里有最好的鸡儿 - , - "downloadUrl": - "app/com.ct.client/com.ct.client.apk" - , - "iconUrl": - "app/com.ct.client/icon.jpg" - , - "id": - 1540629 - , - "name": - 不存在的 - , - "packageName": - "com.ct.client" - , - "size": - 4794202 - , - "stars": - 2 - } - ] - -经过一番研究,才知道是因为 print 函数默认会将打印的内容后面加个 -**换行符**\ 。 - -那如何将使 print 函数打印的内容,不进行换行呢? - -方法很简单,但是我相信很多人都不知道,只要在 print 的内容后加一个 -**逗号** 就行。 - -就像下面这样。 - -|image2| - -知道了问题所在,再修改下代码 - -.. code:: python - - # coding: utf-8 - from pprint import PrettyPrinter - - class MyPrettyPrinter(PrettyPrinter): - def format(self, object, context, maxlevels, level): - if isinstance(object, unicode): - return (object.encode('utf8'), True, False) - return PrettyPrinter.format(self, object, context, maxlevels, level) - - class MyStream(): - def write(self, text): - print text.replace('\'', '"'), - - info = [{"id":1580615,"name":u"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":u"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":u"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":u"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] - - MyPrettyPrinter(stream=MyStream()).pprint(info) - -终于成功了,太不容易了吧。 - -|image3| - -3. 何必折腾 ------------ - -通过上面的一番折腾,我终于实现了我 **梦寐以求** 的需求。 - -代价就是我整整花费了两个小时,才得以实现,而对于小白来说,可能没有信心,也没有耐心去做这样的事情。 - -**所以我想说的是,Python 2 下的 pprint ,真的不要再用了**\ 。 - -为什么我要用这么 说,因为明明有更好的替代品,人生苦短,既然用了 Python -,当然是怎么简单怎么来咯,何必为难自己呢,一行代码可以解决的事情,偏偏要去写两个类,那不是自讨苦吃吗?(我这是在骂自己吗? - -如果你愿意抛弃 pprint ,那我推荐你用 json.dumps ,我保证你再也不想用 -pprint 了。 - -.. _打印中文-1: - -打印中文 -~~~~~~~~ - -其实无法打印中文,是 Python 2 引来的大坑,并不能全怪 pprint 。 - -但是同样的问题,在 json.dumps 这里,却只要加个参数就好了,可比 pprint -简单得不要太多。 - -具体的代码示例如下: - -.. code:: python - - >>> info = [{"id":1580615,"name":"皮的嘛","packageName":"com.renren.mobile.android","iconUrl":"app/com.renren.mobile.android/icon.jpg","stars":2,"size":21803987,"downloadUrl":"app/com.renren.mobile.android/com.renren.mobile.android.apk","des":"2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青"},{"id":1540629,"name":"不存在的","packageName":"com.ct.client","iconUrl":"app/com.ct.client/icon.jpg","stars":2,"size":4794202,"downloadUrl":"app/com.ct.client/com.ct.client.apk","des":"斗鱼271934走过路过不要错过,这里有最好的鸡儿"}] - >>> - >>> import json - >>> - >>> - >>> print json.dumps(info, indent=4, ensure_ascii=False) - [ - { - "downloadUrl": "app/com.renren.mobile.android/com.renren.mobile.android.apk", - "iconUrl": "app/com.renren.mobile.android/icon.jpg", - "name": "皮的嘛", - "stars": 2, - "packageName": "com.renren.mobile.android", - "des": "2011-2017你的铁头娃一直在这儿。中国最大的实名制SNS网络平台,嫩头青", - "id": 1580615, - "size": 21803987 - }, - { - "downloadUrl": "app/com.ct.client/com.ct.client.apk", - "iconUrl": "app/com.ct.client/icon.jpg", - "name": "不存在的", - "stars": 2, - "packageName": "com.ct.client", - "des": "斗鱼271934走过路过不要错过,这里有最好的鸡儿", - "id": 1540629, - "size": 4794202 - } - ] - >>> - -json.dumps 的关键参数有两个: - -- **indent=4**\ :以 4 个空格缩进单位 -- **ensure_ascii=False**\ :接收非 ASCII 编码的字符,这样才能使用中文 - -与 pprint 相比 json.dumps 可以说完胜: - -1. 两个参数就能实现所有我的需求(打印中文与双引号) -2. 就算在 Python 2 下,使用中文也不需要用 ``u'中文'`` 这种写法 -3. Python2 和 Python3 的写法完全一致,对于这一点不需要考虑兼容问题 - -4. 总结一下 ------------ - -本来很简单的一个观点,我为了证明 pprint -实现那两个需求有多么困难,花了很多的时间去研究了 pprint -的源码(各种处理其实还是挺复杂的),不过好在最后也能有所收获。 - -本文的分享就到这里,阅读本文,我认为你可以获取到三个知识点 - -1. 核心观点:Python2 下不要再使用 pprint -2. 若真要使用,且有和一样的改造需求,可以参考我的实现 -3. Python 2 中的 print 语句后居然可以加 逗号 - -以上。希望此文能对你有帮助。 - -|image4| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200507171451.png -.. |image2| image:: http://image.iswbm.com/20200507174459.png -.. |image3| image:: http://image.iswbm.com/20200507174802.png -.. |image4| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_44.md b/source/c01/c01_44.md deleted file mode 100644 index 5f818e4..0000000 --- a/source/c01/c01_44.md +++ /dev/null @@ -1,274 +0,0 @@ -# 1.44 详解 Python 中的编码问题 - -![](http://image.iswbm.com/20200602135014.png) - -Python 中编码问题,一直是很多 Python 开发者的噩梦,尽管你是工作多年的 Python 开发者,也肯定会经常遇到令人神烦的编码问题,好不容易花了半天搞明白了。 - -一段时间后,又全都忘光光了,一脸懵逼的你又开始你找各种博客、帖子,从头搞清楚什么是编码?什么是 unicode?它和 ASCII 有什么区别?为什么 decode encode 老是报错?python2 里和 python3 的字符串类型怎么都不一样,怎么对应起来?如何检测编码格式? - -反反复复,这个过程真是太痛苦了。 - -今天我把大家在 Python 上会遇到的一些编码问题都讲清楚了,以后你可以不用再 Google,收藏这篇文章就行。 - - - -## 1. Python 3 中 str 与 bytes - -在 Python3中,字符串有两种类型 ,str 和 bytes。 - -今天就来说一说这二者的区别: - -- `unicode string(str 类型)`:以 Unicode code points 形式存储,**人类认识的形式** -- `byte string(bytes 类型)`:以 byte 形式存储,**机器认识的形式** - -在 Python 3 中你定义的所有字符串,都是 unicode string类型,使用 `type` 和 `isinstance` 可以判别 - -```python -# python3 - ->>> str_obj = "你好" ->>> ->>> type(str_obj) - ->>> ->>> isinstance("你好", str) -True ->>> ->>> isinstance("你好", bytes) -False ->>> -``` - -而 bytes 是一个二进制序列对象,你只要你在定义字符串时前面加一个 `b`,就表示你要定义一个 bytes 类型的字符串对象。 - -```python -# python3 ->>> byte_obj = b"Hello World!" ->>> type(byte_obj) - ->>> ->>> isinstance(byte_obj, str) -False ->>> ->>> isinstance(byte_obj, bytes) -True ->>> -``` - -但是在定义中文字符串时,你就不能直接在前面加 `b` 了,而应该使用 `encode` 转一下。 - -```python ->>> byte_obj=b"你好" - File "", line 1 -SyntaxError: bytes can only contain ASCII literal characters. ->>> ->>> str_obj="你好" ->>> ->>> str_obj.encode("utf-8") -b'\xe4\xbd\xa0\xe5\xa5\xbd' ->>> -``` - -## 2. Python 2 中 str 与 unicode - -而在 Python2 中,字符串的类型又与 Python3 不一样,需要仔细区分。 - -在 Python2 里,字符串也只有两种类型,unicode 和 str 。 - -只有 unicode object 和 非unicode object(其实应该叫 str object) 的区别: - -- `unicode string(unicode类型)`:以 Unicode code points 形式存储,**人类认识的形式** -- `byte string(str 类型)`:以 byte 形式存储,**机器认识的形式** - -当我们直接使用双引号或单引号包含字符的方式来定义字符串时,就是 str 字符串对象,比如这样 - -```python -# python2 - ->>> str_obj="你好" ->>> ->>> type(str_obj) - ->>> ->>> str_obj -'\xe4\xbd\xa0\xe5\xa5\xbd' ->>> ->>> isinstance(str_obj, bytes) -True ->>> isinstance(str_obj, str) -True ->>> isinstance(str_obj, unicode) -False ->>> ->>> str is bytes -True -``` - -而当我们在双引号或单引号前面加个 `u`,就表明我们定义的是 unicode 字符串对象,比如这样 - -```python -# python2 - ->>> unicode_obj = u"你好" ->>> ->>> unicode_obj -u'\u4f60\u597d' ->>> ->>> type(unicode_obj) - ->>> ->>> isinstance(unicode_obj, bytes) -False ->>> isinstance(unicode_obj, str) -False ->>> ->>> isinstance(unicode_obj, unicode) -True -``` - - - -## 3. 如何检测对象的编码 - -所有的字符,在 unicode 字符集中都有对应的编码值(英文叫做:`code point`) - -而把这些编码值按照一定的规则保存成二进制字节码,就是我们说的编码方式,常见的有:UTF-8,GB2312 等。 - -也就是说,当我们要将内存中的字符串持久化到硬盘中的时候,都要指定编码方法,而反过来,读取的时候,也要指定正确的编码方法(这个过程叫解码),不然会出现乱码。 - -那问题就来了,当我们知道了其对应的编码方法,我们就可以正常解码,但并不是所有时候我们都能知道应该用什么编码方式去解码? - -这时候就要介绍到一个 python 的库 -- `chardet` ,使用它之前 需要先安装 - -``` -python3 -m pip install chardet -``` - -chardet 有一个 detect 方法,可以 `预测`其其编码格式 - -```python ->>> import chardet ->>> chardet.detect('微信公众号:Python编程时光'.encode('gbk')) -{'encoding': 'GB2312', 'confidence': 0.99, 'language': 'Chinese'} -``` - -为什么说是预测呢,通过上面的输出来看,你会看到有一个 confidence 字段,其表示预测的可信度,或者说成功率。 - -但是使用它时,若你的字符数较少,就有可能 “`误诊`”),比如只有 `中文` 两个字,就像下面这样,我们是 使用 gbk 编码的,使用 chardet 却识别成 KOI8-R 编码。 - -```python ->>> str_obj = "中文" ->>> byte_obj = bytes(a, encoding='gbk') # 先得到一个 gbk 编码的 bytes ->>> ->>> chardet.detect(byte_obj) -{'encoding': 'KOI8-R', 'confidence': 0.682639754276994, 'language': 'Russian'} ->>> ->>> str_obj2 = str(byte_obj, encoding='KOI8-R') ->>> str_obj2 -'жпнд' -``` - -所以为了编码诊断的准确,要尽量使用足够多的字符。 - -chardet 支持多国的语言,从官方文档中可以看到支持如下这些语言(https://chardet.readthedocs.io/en/latest/supported-encodings.html) - -![](http://image.iswbm.com/20200423185819.png) - - - -## 4. 编码与解码的区别 - -编码和解码,其实就是 str 与 bytes 的相互转化的过程(Python 2 已经远去,这里以及后面都只用 Python 3 举例) - -- **编码**:encode 方法,把字符串对象转化为二进制字节序列 - -- **解码**:decode 方法,把二进制字节序列转化为字符串对象 - -![](http://image.iswbm.com/20200423190331.png) - - - -那么假如我们真知道了其编码格式,如何来转成 unicode 呢? - -**有两种方法** - -**第一种**是,直接使用 decode 方法 - -```python ->>> byte_obj.decode('gbk') -'中文' ->>> -``` - -**第二种**是,使用 str 类来转 - -```python ->>> str_obj = str(byte_obj, encoding='gbk') ->>> str_obj -'中文' ->>> -``` - - - -## 5. 如何设置文件编码 - -在 Python 2 中,默认使用的是 ASCII 编码来读取的,因此,我们在使用 Python 2 的时候,如果你的 python 文件里有中文,运行是会报错的。 - -``` -SyntaxError: Non-ASCII character '\xe4' in file demo.py -``` - -原因就是 ASCII 编码表太小,无法解释中文。 - -而在 Python 3 中,默认使用的是 uft-8 来读取,所以省了不少的事。 - -对于这个问题,通常解决方法有两种: - -**第一种方法** - -在 python2 中,可以使用在头部指定 - -可以这样写,虽然很好看 - -``` -# -*- coding: utf-8 -*- -``` - -但这样写太麻烦了,我通常使用下面两种写法 - -``` -# coding:utf-8 -# coding=utf-8 -``` - - - -**第二种方法** - -``` -import sys - -reload(sys) -sys.setdefaultencoding('utf-8') -``` - -这里在调用sys.setdefaultencoding(‘utf-8’) 设置默认的解码方式之前,执行了reload(sys),这是必须的,因为python在加载完sys之后,会删除 sys.setdefaultencoding 这个方法,我们需要重新载入sys,才能调用 sys.setdefaultencoding 这个方法。 - - - -## 6. 参考文章 - - - -- [阮一峰老师文章的常识性错误之 Unicode 与 UTF-8](https://foofish.net/unicode_utf-8.html) -- [Strings, Bytes, and Unicode in Python 2 and 3](https://timothybramlett.com/Strings_Bytes_and_Unicode_in_Python_2_and_3.html) -- [字符编码笔记:ASCII,Unicode 和 UTF-8](http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html) - - - ---- - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_44.rst b/source/c01/c01_44.rst deleted file mode 100644 index 70b3276..0000000 --- a/source/c01/c01_44.rst +++ /dev/null @@ -1,295 +0,0 @@ -1.44 详解 Python 中的编码问题 -============================= - -|image0| - -Python 中编码问题,一直是很多 Python 开发者的噩梦,尽管你是工作多年的 -Python -开发者,也肯定会经常遇到令人神烦的编码问题,好不容易花了半天搞明白了。 - -一段时间后,又全都忘光光了,一脸懵逼的你又开始你找各种博客、帖子,从头搞清楚什么是编码?什么是 -unicode?它和 ASCII 有什么区别?为什么 decode encode 老是报错?python2 -里和 python3 的字符串类型怎么都不一样,怎么对应起来?如何检测编码格式? - -反反复复,这个过程真是太痛苦了。 - -今天我把大家在 Python 上会遇到的一些编码问题都讲清楚了,以后你可以不用再 -Google,收藏这篇文章就行。 - -1. Python 3 中 str 与 bytes ---------------------------- - -在 Python3中,字符串有两种类型 ,str 和 bytes。 - -今天就来说一说这二者的区别: - -- ``unicode string(str 类型)``\ :以 Unicode code points - 形式存储,\ **人类认识的形式** -- ``byte string(bytes 类型)``\ :以 byte - 形式存储,\ **机器认识的形式** - -在 Python 3 中你定义的所有字符串,都是 unicode string类型,使用 ``type`` -和 ``isinstance`` 可以判别 - -.. code:: python - - # python3 - - >>> str_obj = "你好" - >>> - >>> type(str_obj) - - >>> - >>> isinstance("你好", str) - True - >>> - >>> isinstance("你好", bytes) - False - >>> - -而 bytes 是一个二进制序列对象,你只要你在定义字符串时前面加一个 -``b``\ ,就表示你要定义一个 bytes 类型的字符串对象。 - -.. code:: python - - # python3 - >>> byte_obj = b"Hello World!" - >>> type(byte_obj) - - >>> - >>> isinstance(byte_obj, str) - False - >>> - >>> isinstance(byte_obj, bytes) - True - >>> - -但是在定义中文字符串时,你就不能直接在前面加 ``b`` 了,而应该使用 -``encode`` 转一下。 - -.. code:: python - - >>> byte_obj=b"你好" - File "", line 1 - SyntaxError: bytes can only contain ASCII literal characters. - >>> - >>> str_obj="你好" - >>> - >>> str_obj.encode("utf-8") - b'\xe4\xbd\xa0\xe5\xa5\xbd' - >>> - -2. Python 2 中 str 与 unicode ------------------------------ - -而在 Python2 中,字符串的类型又与 Python3 不一样,需要仔细区分。 - -在 Python2 里,字符串也只有两种类型,unicode 和 str 。 - -只有 unicode object 和 非unicode object(其实应该叫 str object) -的区别: - -- ``unicode string(unicode类型)``\ :以 Unicode code points - 形式存储,\ **人类认识的形式** -- ``byte string(str 类型)``\ :以 byte 形式存储,\ **机器认识的形式** - -当我们直接使用双引号或单引号包含字符的方式来定义字符串时,就是 str -字符串对象,比如这样 - -.. code:: python - - # python2 - - >>> str_obj="你好" - >>> - >>> type(str_obj) - - >>> - >>> str_obj - '\xe4\xbd\xa0\xe5\xa5\xbd' - >>> - >>> isinstance(str_obj, bytes) - True - >>> isinstance(str_obj, str) - True - >>> isinstance(str_obj, unicode) - False - >>> - >>> str is bytes - True - -而当我们在双引号或单引号前面加个 ``u``\ ,就表明我们定义的是 unicode -字符串对象,比如这样 - -.. code:: python - - # python2 - - >>> unicode_obj = u"你好" - >>> - >>> unicode_obj - u'\u4f60\u597d' - >>> - >>> type(unicode_obj) - - >>> - >>> isinstance(unicode_obj, bytes) - False - >>> isinstance(unicode_obj, str) - False - >>> - >>> isinstance(unicode_obj, unicode) - True - -3. 如何检测对象的编码 ---------------------- - -所有的字符,在 unicode -字符集中都有对应的编码值(英文叫做:\ ``code point``\ ) - -而把这些编码值按照一定的规则保存成二进制字节码,就是我们说的编码方式,常见的有:UTF-8,GB2312 -等。 - -也就是说,当我们要将内存中的字符串持久化到硬盘中的时候,都要指定编码方法,而反过来,读取的时候,也要指定正确的编码方法(这个过程叫解码),不然会出现乱码。 - -那问题就来了,当我们知道了其对应的编码方法,我们就可以正常解码,但并不是所有时候我们都能知道应该用什么编码方式去解码? - -这时候就要介绍到一个 python 的库 – ``chardet`` ,使用它之前 需要先安装 - -:: - - python3 -m pip install chardet - -chardet 有一个 detect 方法,可以 ``预测``\ 其其编码格式 - -.. code:: python - - >>> import chardet - >>> chardet.detect('微信公众号:Python编程时光'.encode('gbk')) - {'encoding': 'GB2312', 'confidence': 0.99, 'language': 'Chinese'} - -为什么说是预测呢,通过上面的输出来看,你会看到有一个 confidence -字段,其表示预测的可信度,或者说成功率。 - -但是使用它时,若你的字符数较少,就有可能 “``误诊``”),比如只有 ``中文`` -两个字,就像下面这样,我们是 使用 gbk 编码的,使用 chardet 却识别成 -KOI8-R 编码。 - -.. code:: python - - >>> str_obj = "中文" - >>> byte_obj = bytes(a, encoding='gbk') # 先得到一个 gbk 编码的 bytes - >>> - >>> chardet.detect(byte_obj) - {'encoding': 'KOI8-R', 'confidence': 0.682639754276994, 'language': 'Russian'} - >>> - >>> str_obj2 = str(byte_obj, encoding='KOI8-R') - >>> str_obj2 - 'жпнд' - -所以为了编码诊断的准确,要尽量使用足够多的字符。 - -chardet -支持多国的语言,从官方文档中可以看到支持如下这些语言(https://chardet.readthedocs.io/en/latest/supported-encodings.html) - -|image1| - -4. 编码与解码的区别 -------------------- - -编码和解码,其实就是 str 与 bytes 的相互转化的过程(Python 2 -已经远去,这里以及后面都只用 Python 3 举例) - -- **编码**\ :encode 方法,把字符串对象转化为二进制字节序列 - -- **解码**\ :decode 方法,把二进制字节序列转化为字符串对象 - -|image2| - -那么假如我们真知道了其编码格式,如何来转成 unicode 呢? - -**有两种方法** - -**第一种**\ 是,直接使用 decode 方法 - -.. code:: python - - >>> byte_obj.decode('gbk') - '中文' - >>> - -**第二种**\ 是,使用 str 类来转 - -.. code:: python - - >>> str_obj = str(byte_obj, encoding='gbk') - >>> str_obj - '中文' - >>> - -5. 如何设置文件编码 -------------------- - -在 Python 2 中,默认使用的是 ASCII 编码来读取的,因此,我们在使用 Python -2 的时候,如果你的 python 文件里有中文,运行是会报错的。 - -:: - - SyntaxError: Non-ASCII character '\xe4' in file demo.py - -原因就是 ASCII 编码表太小,无法解释中文。 - -而在 Python 3 中,默认使用的是 uft-8 来读取,所以省了不少的事。 - -对于这个问题,通常解决方法有两种: - -**第一种方法** - -在 python2 中,可以使用在头部指定 - -可以这样写,虽然很好看 - -:: - - # -*- coding: utf-8 -*- - -但这样写太麻烦了,我通常使用下面两种写法 - -:: - - # coding:utf-8 - # coding=utf-8 - -**第二种方法** - -:: - - import sys - - reload(sys) - sys.setdefaultencoding('utf-8') - -这里在调用sys.setdefaultencoding(‘utf-8’) -设置默认的解码方式之前,执行了reload(sys),这是必须的,因为python在加载完sys之后,会删除 -sys.setdefaultencoding 这个方法,我们需要重新载入sys,才能调用 -sys.setdefaultencoding 这个方法。 - -6. 参考文章 ------------ - -- `阮一峰老师文章的常识性错误之 Unicode 与 - UTF-8 `__ -- `Strings, Bytes, and Unicode in Python 2 and - 3 `__ -- `字符编码笔记:ASCII,Unicode 和 - UTF-8 `__ - --------------- - -|image3| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200423185819.png -.. |image2| image:: http://image.iswbm.com/20200423190331.png -.. |image3| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_45.md b/source/c01/c01_45.md deleted file mode 100644 index caa4153..0000000 --- a/source/c01/c01_45.md +++ /dev/null @@ -1,290 +0,0 @@ -# 1.45 Python炫技操作:花式导包的八种方法 - -![](http://image.iswbm.com/20200602135014.png) - - - -## 1. 直接 import - -人尽皆知的方法,直接导入即可 - -```python ->>> import os ->>> os.getcwd() -'/home/wangbm' -``` - -与此类似的还有,不再细讲 - -```python -import ... -import ... as ... -from ... import ... -from ... import ... as ... -``` - -一般情况下,使用 `import` 语句导入模块已经够用的。 - -但是在一些特殊场景中,可能还需要其他的导入方式。 - -下面我会一一地给你介绍。 - -## 2. 使用 \__import__ - -`__import__` 函数可用于导入模块,import 语句也会调用函数。其定义为: - -``` -__import__(name[, globals[, locals[, fromlist[, level]]]]) -``` - -参数介绍: - -- name (required): 被加载 module 的名称 -- globals (optional): 包含全局变量的字典,该选项很少使用,采用默认值 global() -- locals (optional): 包含局部变量的字典,内部标准实现未用到该变量,采用默认值 - local() -- fromlist (Optional): 被导入的 submodule 名称 -- level (Optional): 导入路径选项,Python 2 中默认为 -1,表示同时支持 absolute import 和 relative import。Python 3 中默认为 0,表示仅支持 absolute import。如果大于 0,则表示相对导入的父目录的级数,即 1 类似于 '.',2 类似于 '..'。 - -使用示例如下: - -```python ->>> os = __import__('os') ->>> os.getcwd() -'/home/wangbm' -``` - -如果要实现 `import xx as yy` 的效果,只要修改左值即可 - -如下示例,等价于 `import os as myos`: - -```python ->>> myos = __import__('os') ->>> myos.getcwd() -'/home/wangbm' -``` - - - -上面说过的 `__import__` 是一个内建函数,既然是内建函数的话,那么这个内建函数必将存在于 `__buildins__` 中,因此我们还可以这样导入 os 的模块: - -```python ->>> __builtins__.__dict__['__import__']('os').getcwd() -'/home/wangbm' -``` - - - -## 3. 使用 importlib 模块 - -importlib 是 Python 中的一个标准库,importlib 能提供的功能非常全面。 - -它的简单示例: - -```python ->>> import importlib ->>> myos=importlib.import_module("os") ->>> myos.getcwd() -'/home/wangbm' -``` - -如果要实现 `import xx as yy`效果,可以这样 - -```python ->>> import importlib ->>> ->>> myos = importlib.import_module("os") ->>> myos.getcwd() -'/home/wangbm' -``` - - - -## 4. 使用 imp 模块 - -`imp` 模块提供了一些 import 语句内部实现的接口。例如模块查找(find_module)、模块加载(load_module)等等(模块的导入过程会包含模块查找、加载、缓存等步骤)。可以用该模块来简单实现内建的 `__import__` 函数功能: - -```python ->>> import imp ->>> file, pathname, desc = imp.find_module('os') ->>> myos = imp.load_module('sep', file, pathname, desc) ->>> myos - ->>> myos.getcwd() -'/home/wangbm' -``` - -从 python 3 开始,内建的 reload 函数被移到了 imp 模块中。而从 Python 3.4 开始,imp 模块被否决,不再建议使用,其包含的功能被移到了 importlib 模块下。即从 Python 3.4 开始,importlib 模块是之前 imp 模块和 importlib 模块的合集。 - - - -## 5. 使用 execfile - -在 Python 2 中有一个 execfile 函数,利用它可以用来执行一个文件。 - -语法如下: - -``` -execfile(filename[, globals[, locals]]) -``` - -参数有这么几个: - -- filename:文件名。 -- globals:变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。 -- locals:变量作用域,局部命名空间,如果被提供,可以是任何映射对象。 - -```python ->>> execfile("/usr/lib64/python2.7/os.py") ->>> ->>> getcwd() -'/home/wangbm' -``` - - - -## 6. 使用 exec 执行 - -`execfile` 只能在 Python2 中使用,Python 3.x 里已经删除了这个函数。 - -但是原理值得借鉴,你可以使用 open ... read 读取文件内容,然后再用 exec 去执行模块。 - -示例如下: - -```python ->>> with open("/usr/lib64/python2.7/os.py", "r") as f: -... exec(f.read()) -... ->>> getcwd() -'/home/wangbm' -``` - - - -## 7. import_from_github_com - -有一个包叫做 **import_from_github_com**,从名字上很容易得知,它是一个可以从 github 下载安装并导入的包。为了使用它,你需要做的就是按照如下命令使用pip 先安装它。 - -```shell -$ python3 -m pip install import_from_github_com -``` - -这个包使用了PEP 302中新的引入钩子,允许你可以从github上引入包。这个包实际做的就是安装这个包并将它添加到本地。你需要 Python 3.2 或者更高的版本,并且 git 和 pip 都已经安装才能使用这个包。 - -pip 要保证是较新版本,如果不是请执行如下命令进行升级。 - -```shell -$ python3 -m pip install --upgrade pip -``` - -确保环境 ok 后,你就可以在 Python shell 中使用 import_from_github_com - -示例如下 - -```python ->>> from github_com.zzzeek import sqlalchemy -Collecting git+https://github.com/zzzeek/sqlalchemy -Cloning https://github.com/zzzeek/sqlalchemy to /tmp/pip-acfv7t06-build -Installing collected packages: SQLAlchemy -Running setup.py install for SQLAlchemy ... done -Successfully installed SQLAlchemy-1.1.0b1.dev0 ->>> locals() -{'__builtins__': , '__spec__': None, -'__package__': None, '__doc__': None, '__name__': '__main__', -'sqlalchemy': , -'__loader__': } ->>> -``` - -看了 import_from_github_com的源码后,你会注意到它并没有使用importlib。实际上,它的原理就是使用 pip 来安装那些没有安装的包,然后使用Python的`__import__()`函数来引入新安装的模块。 - - - -## 8. 远程导入模块 - -我在这篇文章里([深入探讨 Python 的 import 机制:实现远程导入模块](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484838&idx=1&sn=1e6fbf5d7546902c6965c60383f7b639&chksm=e8866544dff1ec52e01b6c9a982dfa150b8e34ad472acca35201373dc51dadb5a8630870982a&scene=21#wechat_redirect)),深入剖析了导入模块的内部原理,并在最后手动实现了从远程服务器上读取模块内容,并在本地成功将模块导入的导入器。 - -具体内容非常的多,你可以点击这个[链接](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484838&idx=1&sn=1e6fbf5d7546902c6965c60383f7b639&chksm=e8866544dff1ec52e01b6c9a982dfa150b8e34ad472acca35201373dc51dadb5a8630870982a&scene=21#wechat_redirect)进行深入学习。 - -示例代码如下: - -```python -# 新建一个 py 文件(my_importer.py),内容如下 -import sys -import importlib -import urllib.request as urllib2 - -class UrlMetaFinder(importlib.abc.MetaPathFinder): - def __init__(self, baseurl): - self._baseurl = baseurl - - - def find_module(self, fullname, path=None): - if path is None: - baseurl = self._baseurl - else: - # 不是原定义的url就直接返回不存在 - if not path.startswith(self._baseurl): - return None - baseurl = path - - try: - loader = UrlMetaLoader(baseurl) - return loader - except Exception: - return None - -class UrlMetaLoader(importlib.abc.SourceLoader): - def __init__(self, baseurl): - self.baseurl = baseurl - - def get_code(self, fullname): - f = urllib2.urlopen(self.get_filename(fullname)) - return f.read() - - def get_data(self): - pass - - def get_filename(self, fullname): - return self.baseurl + fullname + '.py' - -def install_meta(address): - finder = UrlMetaFinder(address) - sys.meta_path.append(finder) -``` - -并且在远程服务器上开启 http 服务(为了方便,我仅在本地进行演示),并且手动编辑一个名为 my_info 的 python 文件,如果后面导入成功会打印 `ok`。 - -```shell -$ mkdir httpserver && cd httpserver -$ cat>my_info.py>> from my_importer import install_meta ->>> install_meta('http://localhost:12800/') # 往 sys.meta_path 注册 finder ->>> import my_info # 打印ok,说明导入成功 -ok ->>> my_info.name # 验证可以取得到变量 -'wangbm' -``` - - - -好了,8 种方法都给大家介绍完毕,对于普通开发者来说,其实只要掌握 import 这种方法足够了,而对于那些想要自己开发框架的人来说,深入学习` __import__ `以及 importlib 是非常有必要的。 - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_45.rst b/source/c01/c01_45.rst deleted file mode 100644 index 052224e..0000000 --- a/source/c01/c01_45.rst +++ /dev/null @@ -1,309 +0,0 @@ -1.45 Python炫技操作:花式导包的八种方法 -======================================= - -|image0| - -1. 直接 import --------------- - -人尽皆知的方法,直接导入即可 - -.. code:: python - - >>> import os - >>> os.getcwd() - '/home/wangbm' - -与此类似的还有,不再细讲 - -.. code:: python - - import ... - import ... as ... - from ... import ... - from ... import ... as ... - -一般情况下,使用 ``import`` 语句导入模块已经够用的。 - -但是在一些特殊场景中,可能还需要其他的导入方式。 - -下面我会一一地给你介绍。 - -2. 使用 \__import_\_ --------------------- - -``__import__`` 函数可用于导入模块,import 语句也会调用函数。其定义为: - -:: - - __import__(name[, globals[, locals[, fromlist[, level]]]]) - -参数介绍: - -- name (required): 被加载 module 的名称 -- globals (optional): 包含全局变量的字典,该选项很少使用,采用默认值 - global() -- locals (optional): - 包含局部变量的字典,内部标准实现未用到该变量,采用默认值 - local() -- fromlist (Optional): 被导入的 submodule 名称 -- level (Optional): 导入路径选项,Python 2 中默认为 -1,表示同时支持 - absolute import 和 relative import。Python 3 中默认为 0,表示仅支持 - absolute import。如果大于 0,则表示相对导入的父目录的级数,即 1 - 类似于 ‘.’,2 类似于 ‘..’。 - -使用示例如下: - -.. code:: python - - >>> os = __import__('os') - >>> os.getcwd() - '/home/wangbm' - -如果要实现 ``import xx as yy`` 的效果,只要修改左值即可 - -如下示例,等价于 ``import os as myos``\ : - -.. code:: python - - >>> myos = __import__('os') - >>> myos.getcwd() - '/home/wangbm' - -上面说过的 ``__import__`` -是一个内建函数,既然是内建函数的话,那么这个内建函数必将存在于 -``__buildins__`` 中,因此我们还可以这样导入 os 的模块: - -.. code:: python - - >>> __builtins__.__dict__['__import__']('os').getcwd() - '/home/wangbm' - -3. 使用 importlib 模块 ----------------------- - -importlib 是 Python 中的一个标准库,importlib 能提供的功能非常全面。 - -它的简单示例: - -.. code:: python - - >>> import importlib - >>> myos=importlib.import_module("os") - >>> myos.getcwd() - '/home/wangbm' - -如果要实现 ``import xx as yy``\ 效果,可以这样 - -.. code:: python - - >>> import importlib - >>> - >>> myos = importlib.import_module("os") - >>> myos.getcwd() - '/home/wangbm' - -4. 使用 imp 模块 ----------------- - -``imp`` 模块提供了一些 import -语句内部实现的接口。例如模块查找(find_module)、模块加载(load_module)等等(模块的导入过程会包含模块查找、加载、缓存等步骤)。可以用该模块来简单实现内建的 -``__import__`` 函数功能: - -.. code:: python - - >>> import imp - >>> file, pathname, desc = imp.find_module('os') - >>> myos = imp.load_module('sep', file, pathname, desc) - >>> myos - - >>> myos.getcwd() - '/home/wangbm' - -从 python 3 开始,内建的 reload 函数被移到了 imp 模块中。而从 Python 3.4 -开始,imp 模块被否决,不再建议使用,其包含的功能被移到了 importlib -模块下。即从 Python 3.4 开始,importlib 模块是之前 imp 模块和 importlib -模块的合集。 - -5. 使用 execfile ----------------- - -在 Python 2 中有一个 execfile 函数,利用它可以用来执行一个文件。 - -语法如下: - -:: - - execfile(filename[, globals[, locals]]) - -参数有这么几个: - -- filename:文件名。 -- globals:变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。 -- locals:变量作用域,局部命名空间,如果被提供,可以是任何映射对象。 - -.. code:: python - - >>> execfile("/usr/lib64/python2.7/os.py") - >>> - >>> getcwd() - '/home/wangbm' - -6. 使用 exec 执行 ------------------ - -``execfile`` 只能在 Python2 中使用,Python 3.x 里已经删除了这个函数。 - -但是原理值得借鉴,你可以使用 open … read 读取文件内容,然后再用 exec -去执行模块。 - -示例如下: - -.. code:: python - - >>> with open("/usr/lib64/python2.7/os.py", "r") as f: - ... exec(f.read()) - ... - >>> getcwd() - '/home/wangbm' - -7. import_from_github_com -------------------------- - -有一个包叫做 -**import_from_github_com**\ ,从名字上很容易得知,它是一个可以从 github -下载安装并导入的包。为了使用它,你需要做的就是按照如下命令使用pip -先安装它。 - -.. code:: shell - - $ python3 -m pip install import_from_github_com - -这个包使用了PEP -302中新的引入钩子,允许你可以从github上引入包。这个包实际做的就是安装这个包并将它添加到本地。你需要 -Python 3.2 或者更高的版本,并且 git 和 pip 都已经安装才能使用这个包。 - -pip 要保证是较新版本,如果不是请执行如下命令进行升级。 - -.. code:: shell - - $ python3 -m pip install --upgrade pip - -确保环境 ok 后,你就可以在 Python shell 中使用 import_from_github_com - -示例如下 - -.. code:: python - - >>> from github_com.zzzeek import sqlalchemy - Collecting git+https://github.com/zzzeek/sqlalchemy - Cloning https://github.com/zzzeek/sqlalchemy to /tmp/pip-acfv7t06-build - Installing collected packages: SQLAlchemy - Running setup.py install for SQLAlchemy ... done - Successfully installed SQLAlchemy-1.1.0b1.dev0 - >>> locals() - {'__builtins__': , '__spec__': None, - '__package__': None, '__doc__': None, '__name__': '__main__', - 'sqlalchemy': , - '__loader__': } - >>> - -看了 -import_from_github_com的源码后,你会注意到它并没有使用importlib。实际上,它的原理就是使用 -pip -来安装那些没有安装的包,然后使用Python的\ ``__import__()``\ 函数来引入新安装的模块。 - -8. 远程导入模块 ---------------- - -我在这篇文章里(\ `深入探讨 Python 的 import -机制:实现远程导入模块 `__\ ),深入剖析了导入模块的内部原理,并在最后手动实现了从远程服务器上读取模块内容,并在本地成功将模块导入的导入器。 - -具体内容非常的多,你可以点击这个\ `链接 `__\ 进行深入学习。 - -示例代码如下: - -.. code:: python - - # 新建一个 py 文件(my_importer.py),内容如下 - import sys - import importlib - import urllib.request as urllib2 - - class UrlMetaFinder(importlib.abc.MetaPathFinder): - def __init__(self, baseurl): - self._baseurl = baseurl - - - def find_module(self, fullname, path=None): - if path is None: - baseurl = self._baseurl - else: - # 不是原定义的url就直接返回不存在 - if not path.startswith(self._baseurl): - return None - baseurl = path - - try: - loader = UrlMetaLoader(baseurl) - return loader - except Exception: - return None - - class UrlMetaLoader(importlib.abc.SourceLoader): - def __init__(self, baseurl): - self.baseurl = baseurl - - def get_code(self, fullname): - f = urllib2.urlopen(self.get_filename(fullname)) - return f.read() - - def get_data(self): - pass - - def get_filename(self, fullname): - return self.baseurl + fullname + '.py' - - def install_meta(address): - finder = UrlMetaFinder(address) - sys.meta_path.append(finder) - -并且在远程服务器上开启 http -服务(为了方便,我仅在本地进行演示),并且手动编辑一个名为 my_info 的 -python 文件,如果后面导入成功会打印 ``ok``\ 。 - -.. code:: shell - - $ mkdir httpserver && cd httpserver - $ cat>my_info.py>> from my_importer import install_meta - >>> install_meta('http://localhost:12800/') # 往 sys.meta_path 注册 finder - >>> import my_info # 打印ok,说明导入成功 - ok - >>> my_info.name # 验证可以取得到变量 - 'wangbm' - -好了,8 种方法都给大家介绍完毕,对于普通开发者来说,其实只要掌握 import -这种方法足够了,而对于那些想要自己开发框架的人来说,深入学习\ ``__import__``\ 以及 -importlib 是非常有必要的。 - -|image1| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c01/c01_47.md b/source/c01/c01_47.md deleted file mode 100644 index 3b8caf0..0000000 --- a/source/c01/c01_47.md +++ /dev/null @@ -1,185 +0,0 @@ -# 1.47 少有人知的 Python "重试机制" - -为了避免由于一些网络或等其他不可控因素,而引起的功能性问题。比如在发送请求时,会因为网络不稳定,往往会有请求超时的问题。 - -这种情况下,我们通常会在代码中加入重试的代码。重试的代码本身不难实现,但如何写得优雅、易用,是我们要考虑的问题。 - -这里要给大家介绍的是一个第三方库 - `Tenacity` ,它实现了几乎我们可以使用到的所有重试场景,比如: - -1. 在什么情况下才进行重试? -2. 重试几次呢? -3. 重试多久后结束? -4. 每次重试的间隔多长呢? -5. 重试失败后的回调? - -在使用它之前 ,先要安装它 - -```shell -$ pip install tenacity -``` - - - -### **最基本的重试** - -无条件重试,重试之间无间隔 - -```python -from tenacity import retry - -@retry -def test_retry(): - print("等待重试,重试无间隔执行...") - raise Exception - -test_retry() -``` - -无条件重试,但是在重试之前要等待 2 秒 - -```python -from tenacity import retry, wait_fixed - -@retry(wait=wait_fixed(2)) -def test_retry(): - print("等待重试...") - raise Exception - -test_retry() -``` - - - -### 设置停止基本条件 - -只重试7 次 - -```python -from tenacity import retry, stop_after_attempt - -@retry(stop=stop_after_attempt(7)) -def test_retry(): - print("等待重试...") - raise Exception - -test_retry() -``` - -重试 10 秒后不再重试 - -```python -from tenacity import retry, stop_after_delay - -@retry(stop=stop_after_delay(10)) -def test_retry(): - print("等待重试...") - raise Exception - -test_retry() -``` - -或者上面两个条件满足一个就结束重试 - -```python -from tenacity import retry, stop_after_delay, stop_after_attempt - -@retry(stop=(stop_after_delay(10) | stop_after_attempt(7))) -def test_retry(): - print("等待重试...") - raise Exception - -test_retry() -``` - -### 设置何时进行重试 - -在出现特定错误/异常(比如请求超时)的情况下,再进行重试 - -```python -from requests import exceptions -from tenacity import retry, retry_if_exception_type - -@retry(retry=retry_if_exception_type(exceptions.Timeout)) -def test_retry(): - print("等待重试...") - raise exceptions.Timeout - -test_retry() -``` - -在满足自定义条件时,再进行重试。 - -如下示例,当 `test_retry` 函数返回值为 False 时,再进行重试 - -```python -from tenacity import retry, stop_after_attempt, retry_if_result - -def is_false(value): - return value is False - -@retry(stop=stop_after_attempt(3), - retry=retry_if_result(is_false)) -def test_retry(): - return False - -test_retry() -``` - - - -### 重试后错误重新抛出 - -当出现异常后,tenacity 会进行重试,若重试后还是失败,默认情况下,往上抛出的异常会变成 RetryError,而不是最根本的原因。 - -因此可以加一个参数(`reraise=True`),使得当重试失败后,往外抛出的异常还是原来的那个。 - -```python -from tenacity import retry, stop_after_attempt - -@retry(stop=stop_after_attempt(7), reraise=True) -def test_retry(): - print("等待重试...") - raise Exception - -test_retry() -``` - - - -### 设置回调函数 - -当最后一次重试失败后,可以执行一个回调函数 - -```python -from tenacity import * - -def return_last_value(retry_state): - print("执行回调函数") - return retry_state.outcome.result() # 表示返回原函数的返回值 - -def is_false(value): - return value is False - -@retry(stop=stop_after_attempt(3), - retry_error_callback=return_last_value, - retry=retry_if_result(is_false)) -def test_retry(): - print("等待重试中...") - return False - -print(test_retry()) -``` - -输出如下 - -```shell -等待重试中... -等待重试中... -等待重试中... -执行回调函数 -False -``` - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_47.rst b/source/c01/c01_47.rst deleted file mode 100644 index 234bde4..0000000 --- a/source/c01/c01_47.rst +++ /dev/null @@ -1,187 +0,0 @@ -1.47 少有人知的 Python “重试机制” -================================= - -为了避免由于一些网络或等其他不可控因素,而引起的功能性问题。比如在发送请求时,会因为网络不稳定,往往会有请求超时的问题。 - -这种情况下,我们通常会在代码中加入重试的代码。重试的代码本身不难实现,但如何写得优雅、易用,是我们要考虑的问题。 - -这里要给大家介绍的是一个第三方库 - ``Tenacity`` -,它实现了几乎我们可以使用到的所有重试场景,比如: - -1. 在什么情况下才进行重试? -2. 重试几次呢? -3. 重试多久后结束? -4. 每次重试的间隔多长呢? -5. 重试失败后的回调? - -在使用它之前 ,先要安装它 - -.. code:: shell - - $ pip install tenacity - -**最基本的重试** -~~~~~~~~~~~~~~~~ - -无条件重试,重试之间无间隔 - -.. code:: python - - from tenacity import retry - - @retry - def test_retry(): - print("等待重试,重试无间隔执行...") - raise Exception - - test_retry() - -无条件重试,但是在重试之前要等待 2 秒 - -.. code:: python - - from tenacity import retry, wait_fixed - - @retry(wait=wait_fixed(2)) - def test_retry(): - print("等待重试...") - raise Exception - - test_retry() - -设置停止基本条件 -~~~~~~~~~~~~~~~~ - -只重试7 次 - -.. code:: python - - from tenacity import retry, stop_after_attempt - - @retry(stop=stop_after_attempt(7)) - def test_retry(): - print("等待重试...") - raise Exception - - test_retry() - -重试 10 秒后不再重试 - -.. code:: python - - from tenacity import retry, stop_after_delay - - @retry(stop=stop_after_delay(10)) - def test_retry(): - print("等待重试...") - raise Exception - - test_retry() - -或者上面两个条件满足一个就结束重试 - -.. code:: python - - from tenacity import retry, stop_after_delay, stop_after_attempt - - @retry(stop=(stop_after_delay(10) | stop_after_attempt(7))) - def test_retry(): - print("等待重试...") - raise Exception - - test_retry() - -设置何时进行重试 -~~~~~~~~~~~~~~~~ - -在出现特定错误/异常(比如请求超时)的情况下,再进行重试 - -.. code:: python - - from requests import exceptions - from tenacity import retry, retry_if_exception_type - - @retry(retry=retry_if_exception_type(exceptions.Timeout)) - def test_retry(): - print("等待重试...") - raise exceptions.Timeout - - test_retry() - -在满足自定义条件时,再进行重试。 - -如下示例,当 ``test_retry`` 函数返回值为 False 时,再进行重试 - -.. code:: python - - from tenacity import retry, stop_after_attempt, retry_if_result - - def is_false(value): - return value is False - - @retry(stop=stop_after_attempt(3), - retry=retry_if_result(is_false)) - def test_retry(): - return False - - test_retry() - -重试后错误重新抛出 -~~~~~~~~~~~~~~~~~~ - -当出现异常后,tenacity -会进行重试,若重试后还是失败,默认情况下,往上抛出的异常会变成 -RetryError,而不是最根本的原因。 - -因此可以加一个参数(\ ``reraise=True``\ ),使得当重试失败后,往外抛出的异常还是原来的那个。 - -.. code:: python - - from tenacity import retry, stop_after_attempt - - @retry(stop=stop_after_attempt(7), reraise=True) - def test_retry(): - print("等待重试...") - raise Exception - - test_retry() - -设置回调函数 -~~~~~~~~~~~~ - -当最后一次重试失败后,可以执行一个回调函数 - -.. code:: python - - from tenacity import * - - def return_last_value(retry_state): - print("执行回调函数") - return retry_state.outcome.result() # 表示返回原函数的返回值 - - def is_false(value): - return value is False - - @retry(stop=stop_after_attempt(3), - retry_error_callback=return_last_value, - retry=retry_if_result(is_false)) - def test_retry(): - print("等待重试中...") - return False - - print(test_retry()) - -输出如下 - -.. code:: shell - - 等待重试中... - 等待重试中... - 等待重试中... - 执行回调函数 - False - -|image0| - -.. |image0| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c02/c02_01.md b/source/c02/c02_01.md index 521c3cd..0024d85 100644 --- a/source/c02/c02_01.md +++ b/source/c02/c02_01.md @@ -2,25 +2,8 @@ ![](http://image.iswbm.com/20200602135014.png) ---- - -作为进阶系列的一个分支「`并发编程`」,我觉得这是每个程序员都应该会的。 - -`并发编程` 这个系列,我准备了将近一个星期,从知识点梳理,到思考要举哪些例子才能更加让人容易吃透这些知识点。希望呈现出来的效果真能如想象中的那样,对小白也一样的友好。 - -昨天大致整理了下,这个系列我大概会讲如下内容(后期可能调整): -![课程大纲](https://i.loli.net/2018/05/27/5b0a1523a0730.png) - - -对于并发编程,Python 的实现,总结了一下,大致有如下三种方法: -- 多线程 -- 多进程 -- 协程(生成器) - -在之后的章节里,将陆陆续续地给大家介绍到这三个知识点。 - -## 2.1.1 基本概念 +## 1. 基本概念 在开始讲解理论知识之前,先过一下几个基本概念。虽然咱是进阶教程,但我也希望写得更小白,更通俗易懂。 @@ -44,13 +27,13 @@ .![](https://i.loli.net/2018/05/08/5af1781f05c29.jpg) -## 2.1.2 单线程VS多线程VS多进程 +## 2. 单线程VS多线程VS多进程 文字总是苍白无力的,千言万语不如几行代码来得孔武有力。 首先,我的实验环境配置如下 -![](http://image.python-online.cn/20190112205155.png) +![](http://image.iswbm.com/20190112205155.png) **注意** 以下代码,若要理解,对小白有如下知识点要求: @@ -215,11 +198,11 @@ multi_process(io_simulation, type="模拟IO密集型") 【多进程】-模拟IO密集型花费时间:2.0076842308044434秒 ``` -## 2.1.3 性能对比成果总结 +## 3. 性能对比成果总结 将结果汇总一下,制成表格。 -![](http://image.python-online.cn/20190112204930.png) +![](http://image.iswbm.com/20190112204930.png) 我们来分析下这个表格。 diff --git a/source/c02/c02_01.rst b/source/c02/c02_01.rst old mode 100755 new mode 100644 index eeb14f5..3b42f98 --- a/source/c02/c02_01.rst +++ b/source/c02/c02_01.rst @@ -3,23 +3,8 @@ |image0| --------------- - -作为进阶系列的一个分支「\ ``并发编程``\ 」,我觉得这是每个程序员都应该会的。 - -``并发编程`` -这个系列,我准备了将近一个星期,从知识点梳理,到思考要举哪些例子才能更加让人容易吃透这些知识点。希望呈现出来的效果真能如想象中的那样,对小白也一样的友好。 - -昨天大致整理了下,这个系列我大概会讲如下内容(后期可能调整): -|课程大纲| - -对于并发编程,Python 的实现,总结了一下,大致有如下三种方法: - 多线程 - -多进程 - 协程(生成器) - -在之后的章节里,将陆陆续续地给大家介绍到这三个知识点。 - -2.1.1 基本概念 --------------- +1. 基本概念 +----------- 在开始讲解理论知识之前,先过一下几个基本概念。虽然咱是进阶教程,但我也希望写得更小白,更通俗易懂。 @@ -41,20 +26,20 @@ - ``多线程``\ ,交替执行,另一种意义上的串行。 -.\ |image2| +.\ |image1| - ``多进程``\ ,并行执行,真正意义上的并发。 -.\ |image3| +.\ |image2| -2.1.2 单线程VS多线程VS多进程 ----------------------------- +2. 单线程VS多线程VS多进程 +------------------------- 文字总是苍白无力的,千言万语不如几行代码来得孔武有力。 首先,我的实验环境配置如下 -|image4| +|image3| **注意** 以下代码,若要理解,对小白有如下知识点要求: @@ -227,12 +212,12 @@ 【多进程】-网络IO密集型花费时间:0.13074755668640137秒 【多进程】-模拟IO密集型花费时间:2.0076842308044434秒 -2.1.3 性能对比成果总结 ----------------------- +3. 性能对比成果总结 +------------------- 将结果汇总一下,制成表格。 -|image5| +|image4| 我们来分析下这个表格。 @@ -250,13 +235,12 @@ -------------- -|image6| +|image5| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |课程大纲| image:: https://i.loli.net/2018/05/27/5b0a1523a0730.png -.. |image2| image:: https://i.loli.net/2018/05/08/5af1781dbad7c.jpg -.. |image3| image:: https://i.loli.net/2018/05/08/5af1781f05c29.jpg -.. |image4| image:: http://image.python-online.cn/20190112205155.png -.. |image5| image:: http://image.python-online.cn/20190112204930.png -.. |image6| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: https://i.loli.net/2018/05/08/5af1781dbad7c.jpg +.. |image2| image:: https://i.loli.net/2018/05/08/5af1781f05c29.jpg +.. |image3| image:: http://image.iswbm.com/20190112205155.png +.. |image4| image:: http://image.iswbm.com/20190112204930.png +.. |image5| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c02/c02_02.md b/source/c02/c02_02.md index e451a67..78af0fb 100644 --- a/source/c02/c02_02.md +++ b/source/c02/c02_02.md @@ -10,7 +10,7 @@ 经过总结,Python创建多线程主要有如下两种方法: -- - 函数 +- 函数 - 类 接下来,我们就来揭开多线程的神秘面纱。 @@ -130,7 +130,7 @@ t.daemon = False t.name = "My-Thread" ``` -至此,Python线程基础知识,我们大概都介绍完了。 + ---- ![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c02/c02_02.rst b/source/c02/c02_02.rst old mode 100755 new mode 100644 index 14ee3ea..2c9b6e2 --- a/source/c02/c02_02.rst +++ b/source/c02/c02_02.rst @@ -9,10 +9,7 @@ 经过总结,Python创建多线程主要有如下两种方法: -- - - - 函数 - +- 函数 - 类 接下来,我们就来揭开多线程的神秘面纱。 @@ -143,8 +140,6 @@ # 设置线程名 t.name = "My-Thread" -至此,Python线程基础知识,我们大概都介绍完了。 - -------------- |image1| diff --git a/source/c02/c02_03.md b/source/c02/c02_03.md index 095a76c..519ff92 100644 --- a/source/c02/c02_03.md +++ b/source/c02/c02_03.md @@ -307,7 +307,7 @@ t2.start() ## 6. 饱受争议的GIL(全局锁) -在第一章的时候,我就和大家介绍到,多线程和多进程是不一样的。 +在第一节的时候,我就和大家介绍到,多线程和多进程是不一样的。 多进程是真正的并行,而多线程是伪并行,实际上他只是交替执行。 diff --git a/source/c02/c02_03.rst b/source/c02/c02_03.rst old mode 100755 new mode 100644 index 03cde42..eea5c95 --- a/source/c02/c02_03.rst +++ b/source/c02/c02_03.rst @@ -335,7 +335,7 @@ lock.release()必须成对出现。否则就有可能造成死锁。 6. 饱受争议的GIL(全局锁) -------------------------- -在第一章的时候,我就和大家介绍到,多线程和多进程是不一样的。 +在第一节的时候,我就和大家介绍到,多线程和多进程是不一样的。 多进程是真正的并行,而多线程是伪并行,实际上他只是交替执行。 diff --git a/source/c02/c02_04.md b/source/c02/c02_04.md index 4049c74..c6f70dc 100644 --- a/source/c02/c02_04.md +++ b/source/c02/c02_04.md @@ -21,7 +21,7 @@ --- -## 2.4.1 Event事件 +## 1. Event事件 Python提供了非常简单的通信机制 `Threading.Event`,通用的条件变量。多个线程可以`等待某个事件的发生`,在事件发生后,`所有的线程`都会被`激活`。 @@ -93,7 +93,7 @@ Thread: 3 finish at Sun May 13 20:38:13 2018 可见在所有线程都启动(`start()`)后,并不会执行完,而是都在`self.event.wait()`止住了,需要我们通过`event.set()`来给所有线程发送执行指令才能往下执行。 -## 2.4.2 Condition +## 2. Condition Condition和Event 是类似的,并没有多大区别。 @@ -154,8 +154,8 @@ class Seeker(threading.Thread): cond = threading.Condition() seeker = Seeker(cond, 'seeker') hider = Hider(cond, 'hider') -seeker.run() -hider.run() +seeker.start() +hider.start() ``` 通过cond来通信,阻塞自己,并使对方执行。从而,达到有顺序的执行。 看下结果 @@ -167,11 +167,11 @@ hider: 我赢了 seeker: 被你找到了,哎~~~ ``` -## 2.4.3 Queue队列 +## 3. Queue队列 最后一个,队列,它是本节的重点,因为它是我们日常开发中最使用频率最高的。 -从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 Queue 对象,这些线程通过使用`put()` 和 `get()` 操作来向队列中添加或者删除元素。 +从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 Queue 对象,这些线程通过使用`put()` 和 `get()` 操作来向队列中发送和获取元素。 同样,对于Queue,我们也只需要掌握几个函数即可。 ```python @@ -180,73 +180,119 @@ from queue import Queue # 一旦>0,而消息数又达到限制,q.put()也将阻塞 q = Queue(maxsize=0) -# 阻塞程序,等待队列消息。 -q.get() +# 默认阻塞程序,等待队列消息,可设置超时时间 +q.get(block=True, timeout=None) -# 获取消息,设置超时时间 -q.get(timeout=5.0) - -# 发送消息 -q.put() +# 发送消息:默认会阻塞程序至队列中有空闲位置放入数据 +q.put(item, block=True, timeout=None) # 等待所有的消息都被消费完 q.join() -# 以下三个方法,知道就好,代码中不要使用 +# 通知队列任务处理已经完成,当所有任务都处理完成时,join() 阻塞将会解除 +q.task_done() + +``` +以下三个方法,知道就好,一般不需要使用 + +```python # 查询当前队列的消息个数 q.qsize() -# 队列消息是否都被消费完,True/False +# 队列消息是否都被消费完,返回 True/False q.empty() # 检测队列里消息是否已满 q.full() ``` + + + 函数会比之前的多一些,同时也从另一方面说明了其功能更加丰富。 我来举个老师点名的例子。 ```python +# coding=utf-8 +# /usr/bin/env python + +''' +Author: wangbm +Email: wongbingming@163.com +Wechat: mrbensonwon +Blog: python-online.cn +公众号:Python编程时光 + + +date: 2020/9/20 下午7:30 +desc: +''' + +__author__ = 'wangbm' + + from queue import Queue from threading import Thread import time -class Student(Thread): - def __init__(self, name, queue): - super().__init__() +class Student: + def __init__(self, name): self.name = name - self.queue = queue - def run(self): - while True: - # 阻塞程序,时刻监听老师,接收消息 - msg = self.queue.get() - # 一旦发现点到自己名字,就赶紧答到 - if msg == self.name: - print("{}:到!".format(self.name)) + def speak(self): + print("{}:到!".format(self.name)) class Teacher: def __init__(self, queue): + super().__init__() self.queue=queue def call(self, student_name): - print("老师:{}来了没?".format(student_name)) - # 发送消息,要点谁的名 + if student_name == "exit": + print("点名结束,开始上课..") + else: + print("老师:{}来了没?".format(student_name)) + # 发送消息,要点谁的名 self.queue.put(student_name) +class CallManager(Thread): + def __init__(self, queue): + super().__init__() + self.students = {} + self.queue = queue + + def put(self, student): + self.students.setdefault(student.name, student) + + def run(self): + while True: + # 阻塞程序,时刻监听老师,接收消息 + student_name = queue.get() + if student_name == "exit": + break + elif student_name in self.students: + self.students[student_name].speak() + else: + print("老师,咱班,没有 {} 这个人".format(student_name)) queue = Queue() teacher = Teacher(queue=queue) -s1 = Student(name="小明", queue=queue) -s2 = Student(name="小亮", queue=queue) -s1.start() -s2.start() + +s1 = Student(name="小明") +s2 = Student(name="小亮") + +cm = CallManager(queue) +cm.put(s1) +cm.put(s2) +cm.start() print('开始点名~') teacher.call('小明') time.sleep(1) teacher.call('小亮') +time.sleep(1) +teacher.call("exit") ``` 运行结果如下 ```python @@ -255,6 +301,7 @@ teacher.call('小亮') 小明:到! 老师:小亮来了没? 小亮:到! +点名结束,开始上课.. ``` 其实 queue 还有一个很重要的方法,Queue.task_done() @@ -271,9 +318,119 @@ teacher.call('小亮') 当队列内部的任务计数器归于零时,调用 Queue.join() 就不会再阻塞了。 -要理解这个过程,请参考 http://python.iswbm.com/en/latest/c02/c02_06.html 里自定义线程池的的例子。 +要理解这个过程,请参考 http://pythontime.iswbm.com/en/latest/c02/c02_06.html 里自定义线程池的的例子。 + +## 4. 消息队列的先进先出 + +消息队列可不是只有`queue.Queue`这一个类,除它之外,还有`queue.LifoQueue`和`queue.PriorityQueue`这两个类。 + +从名字上,对于他们之间的区别,你大概也能猜到一二吧。 + +> `queue.Queue`:先进先出队列 +> `queue.LifoQueue`:后进先出队列 +> `queue.PriorityQueue`:优先级队列 + +先来看看,我们的老朋友,`queue.Queue`。 +所谓的`先进先出`(FIFO,First in First Out),就是先进入队列的消息,将优先被消费。 +这和我们日常排队买菜是一样的,先排队的人肯定是先买到菜。 + +用代码来说明一下 + +```python +import queue + +q = queue.Queue() + +for i in range(5): + q.put(i) + +while not q.empty(): + print q.get() +``` + +看看输出,符合我们先进先出的预期。存入队列的顺序是`01234`,被消费的顺序也是`01234`。 + +``` +0 +1 +2 +3 +4 +``` + +再来看看`Queue.LifoQueue`,后进先出,就是后进入消息队列的,将优先被消费。 + +这和我们羽毛球筒是一样的,最后放进羽毛球筒的球,会被第一个取出使用。 + +用代码来看下 + +```python +import queue + +q = queue.LifoQueue() + +for i in range(5): + q.put(i) + +while not q.empty(): + print q.get() +``` + +来看看输出,符合我们后进后出的预期。存入队列的顺序是`01234`,被消费的顺序也是`43210`。 + +``` +4 +3 +2 +1 +0 +``` + +最后来看看`Queue.PriorityQueue`,优先级队列。 +这和我们日常生活中的会员机制有些类似,办了金卡的人比银卡的服务优先,办了银卡的人比不办卡的人服务优先。 + +来用代码看一下 + +```python +from queue import PriorityQueue + +# 重新定义一个类,继承自PriorityQueue +class MyPriorityQueue(PriorityQueue): + def __init__(self): + PriorityQueue.__init__(self) + self.counter = 0 + + def put(self, item, priority): + PriorityQueue.put(self, (priority, self.counter, item)) + self.counter += 1 + + def get(self, *args, **kwargs): + _, _, item = PriorityQueue.get(self, *args, **kwargs) + return item + + +queue = MyPriorityQueue() +queue.put('item2', 2) +queue.put('item5', 5) +queue.put('item3', 3) +queue.put('item4', 4) +queue.put('item1', 1) + +while True: + print(queue.get()) +``` + +来看看输出,符合我们的预期。我们存入入队列的顺序是`25341`,对应的优先级也是`25341`,可是被消费的顺序丝毫不受传入顺序的影响,而是根据指定的优先级来消费。 + +```python +item1 +item2 +item3 +item4 +item5 +``` -## 2.4.4 总结一下 +## 5. 总结一下 学习了以上三种通信方法,我们很容易就能发现`Event` 和 `Condition` 是threading模块原生提供的模块,原理简单,功能单一,它能发送 `True` 和 `False` 的指令,所以只能适用于某些简单的场景中。 diff --git a/source/c02/c02_04.rst b/source/c02/c02_04.rst old mode 100755 new mode 100644 index 85e6579..408c43a --- a/source/c02/c02_04.rst +++ b/source/c02/c02_04.rst @@ -20,8 +20,8 @@ threading.Condition - queue.Queue -------------- -2.4.1 Event事件 ---------------- +1. Event事件 +------------ Python提供了非常简单的通信机制 ``Threading.Event``\ ,通用的条件变量。多个线程可以\ ``等待某个事件的发生``\ ,在事件发生后,\ ``所有的线程``\ 都会被\ ``激活``\ 。 @@ -99,8 +99,8 @@ Python提供了非常简单的通信机制 可见在所有线程都启动(\ ``start()``\ )后,并不会执行完,而是都在\ ``self.event.wait()``\ 止住了,需要我们通过\ ``event.set()``\ 来给所有线程发送执行指令才能往下执行。 -2.4.2 Condition ---------------- +2. Condition +------------ Condition和Event 是类似的,并没有多大区别。 @@ -165,8 +165,8 @@ Condition和Event 是类似的,并没有多大区别。 cond = threading.Condition() seeker = Seeker(cond, 'seeker') hider = Hider(cond, 'hider') - seeker.run() - hider.run() + seeker.start() + hider.start() 通过cond来通信,阻塞自己,并使对方执行。从而,达到有顺序的执行。 看下结果 @@ -179,15 +179,15 @@ Condition和Event 是类似的,并没有多大区别。 hider: 我赢了 seeker: 被你找到了,哎~~~ -2.4.3 Queue队列 ---------------- +3. Queue队列 +------------ 最后一个,队列,它是本节的重点,因为它是我们日常开发中最使用频率最高的。 从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 Queue 对象,这些线程通过使用\ ``put()`` 和 ``get()`` -操作来向队列中添加或者删除元素。 +操作来向队列中发送和获取元素。 同样,对于Queue,我们也只需要掌握几个函数即可。 @@ -198,24 +198,27 @@ Condition和Event 是类似的,并没有多大区别。 # 一旦>0,而消息数又达到限制,q.put()也将阻塞 q = Queue(maxsize=0) - # 阻塞程序,等待队列消息。 - q.get() + # 默认阻塞程序,等待队列消息,可设置超时时间 + q.get(block=True, timeout=None) - # 获取消息,设置超时时间 - q.get(timeout=5.0) - - # 发送消息 - q.put() + # 发送消息:默认会阻塞程序至队列中有空闲位置放入数据 + q.put(item, block=True, timeout=None) # 等待所有的消息都被消费完 q.join() - # 以下三个方法,知道就好,代码中不要使用 + + # 通知队列任务处理已经完成,当所有任务都处理完成时,join() 阻塞将会解除 + q.task_done() + +以下三个方法,知道就好,一般不需要使用 + +.. code:: python # 查询当前队列的消息个数 q.qsize() - # 队列消息是否都被消费完,True/False + # 队列消息是否都被消费完,返回 True/False q.empty() # 检测队列里消息是否已满 @@ -227,46 +230,86 @@ Condition和Event 是类似的,并没有多大区别。 .. code:: python + # coding=utf-8 + # /usr/bin/env python + + ''' + Author: wangbm + Email: wongbingming@163.com + Wechat: mrbensonwon + Blog: python-online.cn + 公众号:Python编程时光 + + + date: 2020/9/20 下午7:30 + desc: + ''' + + __author__ = 'wangbm' + + from queue import Queue from threading import Thread import time - class Student(Thread): - def __init__(self, name, queue): - super().__init__() + class Student: + def __init__(self, name): self.name = name - self.queue = queue - def run(self): - while True: - # 阻塞程序,时刻监听老师,接收消息 - msg = self.queue.get() - # 一旦发现点到自己名字,就赶紧答到 - if msg == self.name: - print("{}:到!".format(self.name)) + def speak(self): + print("{}:到!".format(self.name)) class Teacher: def __init__(self, queue): + super().__init__() self.queue=queue def call(self, student_name): - print("老师:{}来了没?".format(student_name)) - # 发送消息,要点谁的名 + if student_name == "exit": + print("点名结束,开始上课..") + else: + print("老师:{}来了没?".format(student_name)) + # 发送消息,要点谁的名 self.queue.put(student_name) + class CallManager(Thread): + def __init__(self, queue): + super().__init__() + self.students = {} + self.queue = queue + + def put(self, student): + self.students.setdefault(student.name, student) + + def run(self): + while True: + # 阻塞程序,时刻监听老师,接收消息 + student_name = queue.get() + if student_name == "exit": + break + elif student_name in self.students: + self.students[student_name].speak() + else: + print("老师,咱班,没有 {} 这个人".format(student_name)) queue = Queue() teacher = Teacher(queue=queue) - s1 = Student(name="小明", queue=queue) - s2 = Student(name="小亮", queue=queue) - s1.start() - s2.start() + + s1 = Student(name="小明") + s2 = Student(name="小亮") + + cm = CallManager(queue) + cm.put(s1) + cm.put(s2) + cm.start() print('开始点名~') teacher.call('小明') time.sleep(1) teacher.call('小亮') + time.sleep(1) + teacher.call("exit") 运行结果如下 @@ -277,6 +320,7 @@ Condition和Event 是类似的,并没有多大区别。 小明:到! 老师:小亮来了没? 小亮:到! + 点名结束,开始上课.. 其实 queue 还有一个很重要的方法,Queue.task_done() @@ -295,11 +339,123 @@ Queue.task_done(),说明队列这个任务已经结束了。 当队列内部的任务计数器归于零时,调用 Queue.join() 就不会再阻塞了。 -要理解这个过程,请参考 http://python.iswbm.com/en/latest/c02/c02_06.html +要理解这个过程,请参考 +http://pythontime.iswbm.com/en/latest/c02/c02_06.html 里自定义线程池的的例子。 -2.4.4 总结一下 --------------- +4. 消息队列的先进先出 +--------------------- + +消息队列可不是只有\ ``queue.Queue``\ 这一个类,除它之外,还有\ ``queue.LifoQueue``\ 和\ ``queue.PriorityQueue``\ 这两个类。 + +从名字上,对于他们之间的区别,你大概也能猜到一二吧。 + + ``queue.Queue``\ :先进先出队列 ``queue.LifoQueue``\ :后进先出队列 + ``queue.PriorityQueue``\ :优先级队列 + +先来看看,我们的老朋友,\ ``queue.Queue``\ 。 +所谓的\ ``先进先出``\ (FIFO,First in First +Out),就是先进入队列的消息,将优先被消费。 +这和我们日常排队买菜是一样的,先排队的人肯定是先买到菜。 + +用代码来说明一下 + +.. code:: python + + import queue + + q = queue.Queue() + + for i in range(5): + q.put(i) + + while not q.empty(): + print q.get() + +看看输出,符合我们先进先出的预期。存入队列的顺序是\ ``01234``\ ,被消费的顺序也是\ ``01234``\ 。 + +:: + + 0 + 1 + 2 + 3 + 4 + +再来看看\ ``Queue.LifoQueue``\ ,后进先出,就是后进入消息队列的,将优先被消费。 + +这和我们羽毛球筒是一样的,最后放进羽毛球筒的球,会被第一个取出使用。 + +用代码来看下 + +.. code:: python + + import queue + + q = queue.LifoQueue() + + for i in range(5): + q.put(i) + + while not q.empty(): + print q.get() + +来看看输出,符合我们后进后出的预期。存入队列的顺序是\ ``01234``\ ,被消费的顺序也是\ ``43210``\ 。 + +:: + + 4 + 3 + 2 + 1 + 0 + +最后来看看\ ``Queue.PriorityQueue``\ ,优先级队列。 +这和我们日常生活中的会员机制有些类似,办了金卡的人比银卡的服务优先,办了银卡的人比不办卡的人服务优先。 + +来用代码看一下 + +.. code:: python + + from queue import PriorityQueue + + # 重新定义一个类,继承自PriorityQueue + class MyPriorityQueue(PriorityQueue): + def __init__(self): + PriorityQueue.__init__(self) + self.counter = 0 + + def put(self, item, priority): + PriorityQueue.put(self, (priority, self.counter, item)) + self.counter += 1 + + def get(self, *args, **kwargs): + _, _, item = PriorityQueue.get(self, *args, **kwargs) + return item + + + queue = MyPriorityQueue() + queue.put('item2', 2) + queue.put('item5', 5) + queue.put('item3', 3) + queue.put('item4', 4) + queue.put('item1', 1) + + while True: + print(queue.get()) + +来看看输出,符合我们的预期。我们存入入队列的顺序是\ ``25341``\ ,对应的优先级也是\ ``25341``\ ,可是被消费的顺序丝毫不受传入顺序的影响,而是根据指定的优先级来消费。 + +.. code:: python + + item1 + item2 + item3 + item4 + item5 + +5. 总结一下 +----------- 学习了以上三种通信方法,我们很容易就能发现\ ``Event`` 和 ``Condition`` 是threading模块原生提供的模块,原理简单,功能单一,它能发送 ``True`` 和 diff --git a/source/c02/c02_05.md b/source/c02/c02_05.md index f87c9db..52d86d8 100644 --- a/source/c02/c02_05.md +++ b/source/c02/c02_05.md @@ -7,10 +7,7 @@ 上一篇我们说,线程与线程之间要通过消息通信来控制程序的执行。 讲完了消息通信,今天就来探讨下线程里的`信息隔离`是如何做到的。 ->**大家注意**: ->`信息隔离`,这并不是官方命名的名词,也不是网上广为流传的名词。是我为了方便理解而自创的,大家知道就好咯。 - -## 2.5.1 初步认识信息隔离 +## 1. 初步认识信息隔离 什么是`信息隔离`? @@ -76,7 +73,7 @@ if __name__ == '__main__': 所以如果想在当前线程保存一个全局值,并且各自线程(包括主线程)互不干扰,使用local类吧。 -## 2.5.2 信息隔离的意义何在 +## 2. 信息隔离的意义何在 细心的你,一定已经发现了,上面那个例子,即使我们不用`threading.local`来做信息隔离,两个线程`self.getName()`本身就是隔离的,没有任何关系的。因为这两个线程是由一个class实例出的两个不同的实例对象。自然是可以不用做隔离,因为其本身就是隔离的。 @@ -139,116 +136,4 @@ Got 513469 bytes 如果是在这种场景下,要做到线程之间的状态信息的隔离,就肯定要借助`threading.local`,所以`threading.local`的存在是有存在的意义的。其他还有很多场景是必须借助`threading.local`才能实现的,而这些就要靠你们在真正的业务开发中去发现咯。 -## 2.5.3 消息队列的先进先出 - -首先,要告诉大家的事,消息队列可不是只有`queue.Queue`这一个类,除它之外,还有`queue.LifoQueue`和`queue.PriorityQueue`这两个类。 - -从名字上,对于他们之间的区别,你大概也能猜到一二吧。 - -> `queue.Queue`:先进先出队列 -> `queue.LifoQueue`:后进先出队列 -> `queue.PriorityQueue`:优先级队列 - -先来看看,我们的老朋友,`queue.Queue`。 -所谓的`先进先出`(FIFO,First in First Out),就是先进入队列的消息,将优先被消费。 -这和我们日常排队买菜是一样的,先排队的人肯定是先买到菜。 - -用代码来说明一下 - -```python -import queue - -q = queue.Queue() - -for i in range(5): - q.put(i) - -while not q.empty(): - print q.get() -``` - -看看输出,符合我们先进先出的预期。存入队列的顺序是`01234`,被消费的顺序也是`01234`。 - -``` -0 -1 -2 -3 -4 -``` - -再来看看`Queue.LifoQueue`,后进先出,就是后进入消息队列的,将优先被消费。 - -这和我们羽毛球筒是一样的,最后放进羽毛球筒的球,会被第一个取出使用。 - -用代码来看下 - -```python -import queue - -q = queue.LifoQueue() - -for i in range(5): - q.put(i) - -while not q.empty(): - print q.get() -``` - -来看看输出,符合我们后进后出的预期。存入队列的顺序是`01234`,被消费的顺序也是`43210`。 - -``` -4 -3 -2 -1 -0 -``` - -最后来看看`Queue.PriorityQueue`,优先级队列。 -这和我们日常生活中的会员机制有些类似,办了金卡的人比银卡的服务优先,办了银卡的人比不办卡的人服务优先。 - -来用代码看一下 - -```python -from queue import PriorityQueue - -# 重新定义一个类,继承自PriorityQueue -class MyPriorityQueue(PriorityQueue): - def __init__(self): - PriorityQueue.__init__(self) - self.counter = 0 - - def put(self, item, priority): - PriorityQueue.put(self, (priority, self.counter, item)) - self.counter += 1 - - def get(self, *args, **kwargs): - _, _, item = PriorityQueue.get(self, *args, **kwargs) - return item - - -queue = MyPriorityQueue() -queue.put('item2', 2) -queue.put('item5', 5) -queue.put('item3', 3) -queue.put('item4', 4) -queue.put('item1', 1) - -while True: - print(queue.get()) -``` - -来看看输出,符合我们的预期。我们存入入队列的顺序是`25341`,对应的优先级也是`25341`,可是被消费的顺序丝毫不受传入顺序的影响,而是根据指定的优先级来消费。 - -```python -item1 -item2 -item3 -item4 -item5 -``` - ----- - ![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c02/c02_05.rst b/source/c02/c02_05.rst old mode 100755 new mode 100644 index 4df48c5..e9038f3 --- a/source/c02/c02_05.rst +++ b/source/c02/c02_05.rst @@ -7,12 +7,8 @@ 上一篇我们说,线程与线程之间要通过消息通信来控制程序的执行。 -讲完了消息通信,今天就来探讨下线程里的\ ``信息隔离``\ 是如何做到的。 ->\ **大家注意**\ : ->\ ``信息隔离``\ ,这并不是官方命名的名词,也不是网上广为流传的名词。是我为了方便理解而自创的,大家知道就好咯。 - -2.5.1 初步认识信息隔离 ----------------------- +讲完了消息通信,今天就来探讨下线程里的\ ``信息隔离``\ 是如何做到的。 ## +1. 初步认识信息隔离 什么是\ ``信息隔离``\ ? @@ -82,8 +78,8 @@ 所以如果想在当前线程保存一个全局值,并且各自线程(包括主线程)互不干扰,使用local类吧。 -2.5.2 信息隔离的意义何在 ------------------------- +2. 信息隔离的意义何在 +--------------------- 细心的你,一定已经发现了,上面那个例子,即使我们不用\ ``threading.local``\ 来做信息隔离,两个线程\ ``self.getName()``\ 本身就是隔离的,没有任何关系的。因为这两个线程是由一个class实例出的两个不同的实例对象。自然是可以不用做隔离,因为其本身就是隔离的。 @@ -148,119 +144,6 @@ 如果是在这种场景下,要做到线程之间的状态信息的隔离,就肯定要借助\ ``threading.local``\ ,所以\ ``threading.local``\ 的存在是有存在的意义的。其他还有很多场景是必须借助\ ``threading.local``\ 才能实现的,而这些就要靠你们在真正的业务开发中去发现咯。 -2.5.3 消息队列的先进先出 ------------------------- - -首先,要告诉大家的事,消息队列可不是只有\ ``queue.Queue``\ 这一个类,除它之外,还有\ ``queue.LifoQueue``\ 和\ ``queue.PriorityQueue``\ 这两个类。 - -从名字上,对于他们之间的区别,你大概也能猜到一二吧。 - - ``queue.Queue``\ :先进先出队列 ``queue.LifoQueue``\ :后进先出队列 - ``queue.PriorityQueue``\ :优先级队列 - -先来看看,我们的老朋友,\ ``queue.Queue``\ 。 -所谓的\ ``先进先出``\ (FIFO,First in First -Out),就是先进入队列的消息,将优先被消费。 -这和我们日常排队买菜是一样的,先排队的人肯定是先买到菜。 - -用代码来说明一下 - -.. code:: python - - import queue - - q = queue.Queue() - - for i in range(5): - q.put(i) - - while not q.empty(): - print q.get() - -看看输出,符合我们先进先出的预期。存入队列的顺序是\ ``01234``\ ,被消费的顺序也是\ ``01234``\ 。 - -:: - - 0 - 1 - 2 - 3 - 4 - -再来看看\ ``Queue.LifoQueue``\ ,后进先出,就是后进入消息队列的,将优先被消费。 - -这和我们羽毛球筒是一样的,最后放进羽毛球筒的球,会被第一个取出使用。 - -用代码来看下 - -.. code:: python - - import queue - - q = queue.LifoQueue() - - for i in range(5): - q.put(i) - - while not q.empty(): - print q.get() - -来看看输出,符合我们后进后出的预期。存入队列的顺序是\ ``01234``\ ,被消费的顺序也是\ ``43210``\ 。 - -:: - - 4 - 3 - 2 - 1 - 0 - -最后来看看\ ``Queue.PriorityQueue``\ ,优先级队列。 -这和我们日常生活中的会员机制有些类似,办了金卡的人比银卡的服务优先,办了银卡的人比不办卡的人服务优先。 - -来用代码看一下 - -.. code:: python - - from queue import PriorityQueue - - # 重新定义一个类,继承自PriorityQueue - class MyPriorityQueue(PriorityQueue): - def __init__(self): - PriorityQueue.__init__(self) - self.counter = 0 - - def put(self, item, priority): - PriorityQueue.put(self, (priority, self.counter, item)) - self.counter += 1 - - def get(self, *args, **kwargs): - _, _, item = PriorityQueue.get(self, *args, **kwargs) - return item - - - queue = MyPriorityQueue() - queue.put('item2', 2) - queue.put('item5', 5) - queue.put('item3', 3) - queue.put('item4', 4) - queue.put('item1', 1) - - while True: - print(queue.get()) - -来看看输出,符合我们的预期。我们存入入队列的顺序是\ ``25341``\ ,对应的优先级也是\ ``25341``\ ,可是被消费的顺序丝毫不受传入顺序的影响,而是根据指定的优先级来消费。 - -.. code:: python - - item1 - item2 - item3 - item4 - item5 - --------------- - |image1| .. |image0| image:: http://image.iswbm.com/20200602135014.png diff --git a/source/c02/c02_06.md b/source/c02/c02_06.md index 19ba6bf..95e51f5 100644 --- a/source/c02/c02_06.md +++ b/source/c02/c02_06.md @@ -1,12 +1,7 @@ -# 2.6 线程池与进程池的创建 +# 2.6 线程池创建的几种方法 ![](http://image.iswbm.com/20200602135014.png) ---- - ->**友情提醒**: ->本系列所有的代码均在Python3下编写。Python2中可能有所差异。 - ## 1. 线程池的创建 ### 使用内置模块 @@ -144,9 +139,5 @@ running thread-123145485651968:1 -## 2. 进程池的创建 - - - ---- ![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c02/c02_06.rst b/source/c02/c02_06.rst old mode 100755 new mode 100644 index 35abcb7..78be5e5 --- a/source/c02/c02_06.rst +++ b/source/c02/c02_06.rst @@ -1,13 +1,8 @@ -2.6 线程池与进程池的创建 +2.6 线程池创建的几种方法 ======================== |image0| --------------- - - **友情提醒**\ : - 本系列所有的代码均在Python3下编写。Python2中可能有所差异。 - 1. 线程池的创建 --------------- @@ -151,9 +146,6 @@ 构建线程池的方法,是可以很灵活的,大家有空可以自己多研究。但是建议只要掌握一种自己熟悉的,能快速上手的就好了。 -2. 进程池的创建 ---------------- - -------------- |image1| diff --git a/source/c02/c02_07.md b/source/c02/c02_07.md index a743540..a8af238 100644 --- a/source/c02/c02_07.md +++ b/source/c02/c02_07.md @@ -15,7 +15,7 @@ >**友情提醒**: >本系列所有的代码均在Python3下编写。Python2中可能有所差异。 -## 2.7.1 可迭代、迭代器、生成器 +## 1. 可迭代、迭代器、生成器 初学Python的时候,对于这三货真的是傻傻分不清。甚至还认为他们是等价的。 @@ -89,7 +89,7 @@ False 2. 判断是否可迭代,不能仅看是否有`__iter__` 来草率决定,因为只实现了`__getitem__` 方法的也有可能是可迭代的。因为当没有`__iter__`时, Python 解释器会去找`__getitem__`,尝试按顺序(从索引0开始)获取元素,不抛异常,即是可迭代。 3. 所以,最好的判断方法应该是通过 `for循环`或者` iter()` 去真实运行。 -![](http://image.python-online.cn/20190527123516.png) +![](http://image.iswbm.com/20190527123516.png) @@ -223,7 +223,7 @@ if __name__ == '__main__': 可迭代对象和迭代器,是将所有的值都生成存放在内存中,而`生成器`则是需要元素才临时生成,节省时间,节省空间。 -## 2.7.2 如何运行/激活生成器 +## 2. 如何运行/激活生成器 由于生成器并不是一次生成所有元素,而是一次一次的执行返回,那么如何刺激生成器执行(或者说激活)呢? @@ -256,7 +256,7 @@ if __name__ == '__main__': 3 ``` -## 2.7.3 生成器的执行状态 +## 3. 生成器的执行状态 生成器在其生命周期中,会有如下四个状态 >`GEN_CREATED` # 等待开始执行 @@ -295,7 +295,7 @@ GEN_SUSPENDED GEN_CLOSED ``` -## 2.7.4 生成器的异常处理 +## 4. 生成器的异常处理 在生成器工作过程中,若生成器不满足生成元素的条件,就`会`/`应该` 抛出异常(`StopIteration`)。 @@ -319,7 +319,7 @@ if __name__ == '__main__': next(gen) ``` -## 2.7.5 从生成器过渡到协程:yield +## 5. 从生成器过渡到协程:yield 通过上面的介绍,我们知道生成器为我们引入了暂停函数执行(`yield`)的功能。当有了暂停的功能之后,人们就想能不能在生成器暂停的时候向其发送一点东西(其实上面也有提及:`send(None)`)。这种向暂停的生成器发送信息的功能通过 `PEP 342` 进入 `Python 2.5` 中,并催生了 `Python` 中`协程`的诞生。根据 `wikipedia` 中的定义 >协程是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序。 diff --git a/source/c02/c02_07.rst b/source/c02/c02_07.rst old mode 100755 new mode 100644 index ec51a27..93f6a58 --- a/source/c02/c02_07.rst +++ b/source/c02/c02_07.rst @@ -16,8 +16,8 @@ **友情提醒**\ : 本系列所有的代码均在Python3下编写。Python2中可能有所差异。 -2.7.1 可迭代、迭代器、生成器 ----------------------------- +1. 可迭代、迭代器、生成器 +------------------------- 初学Python的时候,对于这三货真的是傻傻分不清。甚至还认为他们是等价的。 @@ -234,8 +234,8 @@ 可迭代对象和迭代器,是将所有的值都生成存放在内存中,而\ ``生成器``\ 则是需要元素才临时生成,节省时间,节省空间。 -2.7.2 如何运行/激活生成器 -------------------------- +2. 如何运行/激活生成器 +---------------------- 由于生成器并不是一次生成所有元素,而是一次一次的执行返回,那么如何刺激生成器执行(或者说激活)呢? @@ -269,8 +269,8 @@ 2 3 -2.7.3 生成器的执行状态 ----------------------- +3. 生成器的执行状态 +------------------- 生成器在其生命周期中,会有如下四个状态 >\ ``GEN_CREATED`` # 等待开始执行 >\ ``GEN_RUNNING`` # @@ -310,8 +310,8 @@ 1 GEN_CLOSED -2.7.4 生成器的异常处理 ----------------------- +4. 生成器的异常处理 +------------------- 在生成器工作过程中,若生成器不满足生成元素的条件,就\ ``会``/``应该`` 抛出异常(\ ``StopIteration``\ )。 @@ -337,8 +337,8 @@ next(gen) next(gen) -2.7.5 从生成器过渡到协程:yield -------------------------------- +5. 从生成器过渡到协程:yield +---------------------------- 通过上面的介绍,我们知道生成器为我们引入了暂停函数执行(\ ``yield``\ )的功能。当有了暂停的功能之后,人们就想能不能在生成器暂停的时候向其发送一点东西(其实上面也有提及:\ ``send(None)``\ )。这种向暂停的生成器发送信息的功能通过 ``PEP 342`` 进入 ``Python 2.5`` 中,并催生了 ``Python`` @@ -395,7 +395,7 @@ |image3| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190527123516.png +.. |image1| image:: http://image.iswbm.com/20190527123516.png .. |image2| image:: https://i.loli.net/2018/05/19/5affd48c34e3f.png .. |image3| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c02/c02_08.md b/source/c02/c02_08.md index ea82375..144925f 100644 --- a/source/c02/c02_08.md +++ b/source/c02/c02_08.md @@ -11,7 +11,7 @@ >**友情提醒**: >本系列所有的代码均在Python3下编写。Python2中可能有所差异。 -## 2.8.1 为什么要使用协程 +## 1. 为什么要使用协程 在上一篇中,我们从生成器的基本认识与使用,成功过渡到了协程。 @@ -51,13 +51,13 @@ def spider_02(url): 2. 利用同步的方式去实现异步 3. 不再需要锁,提高了并发性能 -## 2.8.2 yield from的用法详解 +## 2. yield from的用法详解 `yield from` 是在Python3.3才出现的语法。所以这个特性在Python2中是没有的。 `yield from` 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。 -### 简单应用:拼接可迭代对象 +### 2.1 简单应用:拼接可迭代对象 我们可以用一个使用`yield`和一个使用`yield from`的例子来对比看下。 @@ -104,7 +104,7 @@ print(list(new_list)) 由上面两种方式对比,可以看出,yield from后面加上可迭代对象,他可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰。 -### 复杂应用:生成器的嵌套 +### 2.2 复杂应用:生成器的嵌套 如果你认为只是 `yield from` 仅仅只有上述的功能的话,那你就太小瞧了它,它的更强大的功能还在后面。 @@ -215,7 +215,7 @@ if __name__ == '__main__': ``` -## 2.8.3 为什么要使用yield from +## 3. 为什么要使用yield from 学到这里,我相信你肯定要问,既然委托生成器,起到的只是一个双向通道的作用,我还需要委托生成器做什么?我调用方直接调用子生成器不就好啦? @@ -223,7 +223,7 @@ if __name__ == '__main__': 下面我们来一起探讨一下,到底yield from 有什么过人之处,让我们非要用它不可。 -### 因为它可以帮我们处理异常 +### 3.1 因为它可以帮我们处理异常 如果我们去掉委托生成器,而直接调用子生成器。那我们就需要把代码改成像下面这样,我们需要自己捕获异常并处理。而不像使`yield from`那样省心。 diff --git a/source/c02/c02_08.rst b/source/c02/c02_08.rst old mode 100755 new mode 100644 index 21019c2..2c142be --- a/source/c02/c02_08.rst +++ b/source/c02/c02_08.rst @@ -12,8 +12,8 @@ **友情提醒**\ : 本系列所有的代码均在Python3下编写。Python2中可能有所差异。 -2.8.1 为什么要使用协程 ----------------------- +1. 为什么要使用协程 +------------------- 在上一篇中,我们从生成器的基本认识与使用,成功过渡到了协程。 @@ -54,8 +54,8 @@ 协程是在单线程里实现任务的切换的 2. 利用同步的方式去实现异步 3. 不再需要锁,提高了并发性能 -2.8.2 yield from的用法详解 --------------------------- +2. yield from的用法详解 +----------------------- ``yield from`` 是在Python3.3才出现的语法。所以这个特性在Python2中是没有的。 @@ -63,8 +63,8 @@ ``yield from`` 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。 -简单应用:拼接可迭代对象 -~~~~~~~~~~~~~~~~~~~~~~~~ +2.1 简单应用:拼接可迭代对象 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 我们可以用一个使用\ ``yield``\ 和一个使用\ ``yield from``\ 的例子来对比看下。 @@ -114,8 +114,8 @@ 由上面两种方式对比,可以看出,yield from后面加上可迭代对象,他可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰。 -复杂应用:生成器的嵌套 -~~~~~~~~~~~~~~~~~~~~~~ +2.2 复杂应用:生成器的嵌套 +~~~~~~~~~~~~~~~~~~~~~~~~~~ 如果你认为只是 ``yield from`` 仅仅只有上述的功能的话,那你就太小瞧了它,它的更强大的功能还在后面。 @@ -231,8 +231,8 @@ from后面加上可迭代对象,他可以把可迭代对象里的每个元素 计算完毕!! 总共传入 3 个数值, 总和:60,平均数:20.0 -2.8.3 为什么要使用yield from ----------------------------- +3. 为什么要使用yield from +------------------------- 学到这里,我相信你肯定要问,既然委托生成器,起到的只是一个双向通道的作用,我还需要委托生成器做什么?我调用方直接调用子生成器不就好啦? @@ -241,8 +241,8 @@ from后面加上可迭代对象,他可以把可迭代对象里的每个元素 下面我们来一起探讨一下,到底yield from 有什么过人之处,让我们非要用它不可。 -因为它可以帮我们处理异常 -~~~~~~~~~~~~~~~~~~~~~~~~ +3.1 因为它可以帮我们处理异常 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 如果我们去掉委托生成器,而直接调用子生成器。那我们就需要把代码改成像下面这样,我们需要自己捕获异常并处理。而不像使\ ``yield from``\ 那样省心。 diff --git a/source/c02/c02_09.md b/source/c02/c02_09.md index 540d0c8..bde67f4 100644 --- a/source/c02/c02_09.md +++ b/source/c02/c02_09.md @@ -19,7 +19,7 @@ 那是因为,我们现在还缺少一个成熟的框架,帮助你完成那些复杂的动作。这个时候,`ayncio`就这么应运而生了。 -## 2.9.1 如何定义/创建协程 +## 1. 如何定义/创建协程 还记得在前两章节的时候,我们创建了生成器,是如何去检验我们创建的是不是生成器对象吗? @@ -67,7 +67,7 @@ if __name__ == '__main__': print(isinstance(coroutine, Coroutine)) # False ``` -## 2.9.2 asyncio的几个概念 +## 2. asyncio的几个概念 在了解`asyncio`的使用方法前,首先有必要先介绍一下,这几个贯穿始终的概念。 @@ -79,7 +79,7 @@ if __name__ == '__main__': 这几个概念,干看可能很难以理解,没事,往下看实例,然后再回来,我相信你一定能够理解。 -## 2.9.3 学习协程是如何工作的 +## 3. 学习协程是如何工作的 协程完整的工作流程是这样的 - 定义/创建协程对象 @@ -112,7 +112,7 @@ loop.run_until_complete(task) Hello, World ``` -## 2.9.4 await与yield对比 +## 4. await与yield对比 前面我们说,`await`用于挂起阻塞的异步调用接口。其作用在`一定程度上`类似于yield。 @@ -162,7 +162,7 @@ print(isinstance(task, Future)) # True 好了,接下来,开始验证。 ![验证通过](https://i.loli.net/2018/05/26/5b09814dc4714.png) -## 2.9.5 绑定回调函数 +## 5. 绑定回调函数 异步IO的实现原理,就是在IO高的地方挂起,等IO结束后,再继续执行。在绝大部分时候,我们后续的代码的执行是需要依赖IO的返回值的,这就要用到回调了。 diff --git a/source/c02/c02_09.rst b/source/c02/c02_09.rst old mode 100755 new mode 100644 index 42ec222..7edb981 --- a/source/c02/c02_09.rst +++ b/source/c02/c02_09.rst @@ -22,8 +22,8 @@ 那是因为,我们现在还缺少一个成熟的框架,帮助你完成那些复杂的动作。这个时候,\ ``ayncio``\ 就这么应运而生了。 -2.9.1 如何定义/创建协程 ------------------------ +1. 如何定义/创建协程 +-------------------- 还记得在前两章节的时候,我们创建了生成器,是如何去检验我们创建的是不是生成器对象吗? @@ -75,8 +75,8 @@ print(isinstance(coroutine, Generator)) # True print(isinstance(coroutine, Coroutine)) # False -2.9.2 asyncio的几个概念 ------------------------ +2. asyncio的几个概念 +-------------------- 在了解\ ``asyncio``\ 的使用方法前,首先有必要先介绍一下,这几个贯穿始终的概念。 @@ -92,8 +92,8 @@ 这几个概念,干看可能很难以理解,没事,往下看实例,然后再回来,我相信你一定能够理解。 -2.9.3 学习协程是如何工作的 --------------------------- +3. 学习协程是如何工作的 +----------------------- 协程完整的工作流程是这样的 - 定义/创建协程对象 - 将协程转为task任务 - 定义事件循环对象容器 - 将task任务扔进事件循环对象中触发 @@ -126,8 +126,8 @@ Hello, World -2.9.4 await与yield对比 ----------------------- +4. await与yield对比 +------------------- 前面我们说,\ ``await``\ 用于挂起阻塞的异步调用接口。其作用在\ ``一定程度上``\ 类似于yield。 @@ -179,8 +179,8 @@ 好了,接下来,开始验证。 |验证通过| -2.9.5 绑定回调函数 ------------------- +5. 绑定回调函数 +--------------- 异步IO的实现原理,就是在IO高的地方挂起,等IO结束后,再继续执行。在绝大部分时候,我们后续的代码的执行是需要依赖IO的返回值的,这就要用到回调了。 diff --git a/source/c02/c02_10.md b/source/c02/c02_10.md index 82a253e..e219c04 100644 --- a/source/c02/c02_10.md +++ b/source/c02/c02_10.md @@ -27,7 +27,7 @@ 那么这一节,我们就来看下,协程中的`多任务`。 -## 2.10.1 协程中的并发 +## 1. 协程中的并发 协程的并发,和线程一样。举个例子来说,就好像 一个人同时吃三个馒头,咬了第一个馒头一口,就得等这口咽下去,才能去啃第其他两个馒头。就这样交替换着吃。 @@ -114,7 +114,7 @@ Task ret: Done after 2s Task ret: Done after 4s ``` -## 2.10.2 协程中的嵌套 +## 2. 协程中的嵌套 使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来。 @@ -236,7 +236,7 @@ async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED): return await _wait(fs, timeout, return_when, loop) ``` -## 2.10.3协程中的状态 +## 3. 协程中的状态 还记得我们在讲生成器的时候,有提及过生成器的状态。同样,在协程这里,我们也了解一下协程(准确的说,应该是Future对象,或者Task任务)有哪些状态。 @@ -289,7 +289,7 @@ if __name__ == '__main__': 假如,执行后 立马按下 Ctrl+C,则会触发task取消,就会打印 `Pending` -> `Cancelling` -> `Cancelling` 的状态变化。 -## 2.10.4 gather与wait +## 4. gather与wait 还记得上面我说,把多个协程注册进一个事件循环中有两种方法吗? - 使用`asyncio.wait()` @@ -320,7 +320,7 @@ async def factorial(name, number): print("Task %s: factorial(%s) = %s" % (name, number, f)) ``` -## 2.10.5 接收参数方式 +## 5. 接收参数方式 ### asyncio.wait @@ -388,7 +388,7 @@ group3 = asyncio.gather(*[factorial("B", i) for i in range(1, 7)]) loop.run_until_complete(asyncio.gather(group1, group2, group3)) ``` -## 2.10.6 返回结果不同 +## 6. 返回结果不同 ### asyncio.wait @@ -414,7 +414,7 @@ for result in results: print('Task ret: ', result) ``` -## 2.10.7wait有控制功能 +## 7. wait有控制功能 ```python import asyncio diff --git a/source/c02/c02_10.rst b/source/c02/c02_10.rst old mode 100755 new mode 100644 index ea98273..0451b3a --- a/source/c02/c02_10.rst +++ b/source/c02/c02_10.rst @@ -25,8 +25,8 @@ 那么这一节,我们就来看下,协程中的\ ``多任务``\ 。 -2.10.1 协程中的并发 -------------------- +1. 协程中的并发 +--------------- 协程的并发,和线程一样。举个例子来说,就好像 一个人同时吃三个馒头,咬了第一个馒头一口,就得等这口咽下去,才能去啃第其他两个馒头。就这样交替换着吃。 @@ -121,8 +121,8 @@ Task ret: Done after 2s Task ret: Done after 4s -2.10.2 协程中的嵌套 -------------------- +2. 协程中的嵌套 +--------------- 使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来。 @@ -254,8 +254,8 @@ # 【重点】:await一个内部协程 return await _wait(fs, timeout, return_when, loop) -2.10.3协程中的状态 ------------------- +3. 协程中的状态 +--------------- 还记得我们在讲生成器的时候,有提及过生成器的状态。同样,在协程这里,我们也了解一下协程(准确的说,应该是Future对象,或者Task任务)有哪些状态。 @@ -310,8 +310,8 @@ 假如,执行后 立马按下 Ctrl+C,则会触发task取消,就会打印 ``Pending`` -> ``Cancelling`` -> ``Cancelling`` 的状态变化。 -2.10.4 gather与wait -------------------- +4. gather与wait +--------------- 还记得上面我说,把多个协程注册进一个事件循环中有两种方法吗? - 使用\ ``asyncio.wait()`` @@ -346,8 +346,8 @@ f *= i print("Task %s: factorial(%s) = %s" % (name, number, f)) -2.10.5 接收参数方式 -------------------- +5. 接收参数方式 +--------------- asyncio.wait ~~~~~~~~~~~~ @@ -426,8 +426,8 @@ asyncio.gather loop.run_until_complete(asyncio.gather(group1, group2, group3)) -2.10.6 返回结果不同 -------------------- +6. 返回结果不同 +--------------- .. _asyncio.wait-1: @@ -460,8 +460,8 @@ asyncio.gather for result in results: print('Task ret: ', result) -2.10.7wait有控制功能 --------------------- +7. wait有控制功能 +----------------- .. code:: python diff --git a/source/c02/c02_11.md b/source/c02/c02_11.md index 602e681..34dba9b 100644 --- a/source/c02/c02_11.md +++ b/source/c02/c02_11.md @@ -16,7 +16,7 @@ - Redis的基本使用 - asyncio的使用 -## 2.11.1 动态添加协程 +## 1. 动态添加协程 在实战之前,我们要先了解下在asyncio中如何将协程态添加到事件循环中的。这是前提。 如何实现呢,有两种方法: @@ -114,7 +114,7 @@ Thu May 31 22:23:38 2018 Thu May 31 22:23:41 2018 ``` -## 2.11.2 利用redis实现动态添加任务 +## 2. 利用redis实现动态添加任务 对于并发任务,通常是用生成消费模型,对队列的处理可以使用类似master-worker的方式,master主要用户获取队列的msg,worker用户处理消息。 为了简单起见,并且协程更适合单线程的方式,我们的主线程用来监听队列,子线程用于处理队列。这里使用redis的队列。主线程中有一个是无限循环,用户消费队列。 diff --git a/source/c02/c02_11.rst b/source/c02/c02_11.rst old mode 100755 new mode 100644 index f747cd4..27271cf --- a/source/c02/c02_11.rst +++ b/source/c02/c02_11.rst @@ -14,8 +14,8 @@ 在实战中,将会用到以下知识点: - 多线程的基本使用 - Queue消息队列的使用 - Redis的基本使用 - asyncio的使用 -2.11.1 动态添加协程 -------------------- +1. 动态添加协程 +--------------- 在实战之前,我们要先了解下在asyncio中如何将协程态添加到事件循环中的。这是前提。 @@ -119,8 +119,8 @@ 第一个 协程运行完.. Thu May 31 22:23:41 2018 -2.11.2 利用redis实现动态添加任务 --------------------------------- +2. 利用redis实现动态添加任务 +---------------------------- 对于并发任务,通常是用生成消费模型,对队列的处理可以使用类似master-worker的方式,master主要用户获取队列的msg,worker用户处理消息。 diff --git a/source/c03/c03_01.md b/source/c03/c03_01.md index b97b33f..5c3562e 100644 --- a/source/c03/c03_01.md +++ b/source/c03/c03_01.md @@ -18,9 +18,9 @@ 当时带着这两个问题,我就开始系统的学习装饰器的所有内容。这些一直整理在自己的博客中,今天对其进行了大量的补充和勘误,发表在这里分享给大家。希望对刚入门以及进阶的朋友可以提供一些参考。 -![](http://image.python-online.cn/20190811100737.png) +![](http://image.iswbm.com/20190811100737.png) -## 3.1.1 Hello,装饰器 +## 1. Hello,装饰器 装饰器的使用方法很固定 - 先定义一个装饰器(帽子) @@ -47,7 +47,7 @@ def function(): 接下来,我将以实例讲解,如何编写出各种简单及复杂的装饰器。 -## 3.1.2 入门:日志打印器 +## 2. 入门:日志打印器 首先是**日志打印器**。 实现的功能: @@ -83,7 +83,7 @@ add(200, 50) 主人,我执行完啦。 ``` -## 3.1.3 入门:时间计时器 +## 3. 入门:时间计时器 再来看看 **时间计时器** 实现功能:顾名思义,就是计算一个函数的执行时长。 @@ -118,7 +118,7 @@ want_sleep(10) ``` -## 3.1.4 进阶:带参数的函数装饰器 +## 4. 进阶:带参数的函数装饰器 通过上面两个简单的入门示例,你应该能体会到装饰器的工作原理了。 @@ -188,7 +188,7 @@ jack() ------------ hello. ``` -## 3.1.5 高阶:不带参数的类装饰器 +## 5. 高阶:不带参数的类装饰器 以上都是基于函数实现的装饰器,在阅读别人代码时,还可以时常发现还有基于类实现的装饰器。 @@ -220,7 +220,7 @@ say("hello") say hello! ``` -## 3.1.6 高阶:带参数的类装饰器 +## 6. 高阶:带参数的类装饰器 上面不带参数的例子,你发现没有,只能打印`INFO`级别的日志,正常情况下,我们还需要打印`DEBUG` `WARNING`等级别的日志。 这就需要给类装饰器传入参数,给这个函数指定级别了。 @@ -253,7 +253,7 @@ say("hello") say hello! ``` -## 3.1.7 使用偏函数与类实现装饰器 +## 7. 使用偏函数与类实现装饰器 绝大多数装饰器都是基于函数和闭包实现的,但这并非制造装饰器的唯一方式。 @@ -319,7 +319,7 @@ Wait for 2 seconds... ``` -## 3.1.8 如何写能装饰类的装饰器? +## 8. 如何写能装饰类的装饰器? 用 Python 写单例模式的时候,常用的有三种写法。其中一种,是用装饰器来实现的。 @@ -352,9 +352,9 @@ class User: 其实例化的过程,你可以参考我这里的调试过程,加以理解。 -![](http://image.python-online.cn/20190512113917.png) +![](http://image.iswbm.com/20190512113917.png) -## 3.1.9 wraps 装饰器有啥用? +## 9. wraps 装饰器有啥用? 在 functools 标准库中有提供一个 wraps 装饰器,你应该也经常见过,那他有啥用呢? 先来看一个例子 @@ -444,7 +444,7 @@ def wrapped(): print(wrapped.__name__) ``` -## 3.1.10 内置装饰器:property +## 10. 内置装饰器:property 以上,我们介绍的都是自定义的装饰器。 @@ -666,7 +666,7 @@ in __get__ 如对上面代码的运行原理,有疑问的同学,请务必结合上面两点说明加以理解,那两点相当关键。 -## 3.1.11 其他装饰器:装饰器实战 +## 11. 其他装饰器:装饰器实战 读完并理解了上面的内容,你可以说是Python高手了。别怀疑,自信点,因为很多人都不知道装饰器有这么多用法呢。 diff --git a/source/c03/c03_01.rst b/source/c03/c03_01.rst old mode 100755 new mode 100644 index 305fde9..0fcf09e --- a/source/c03/c03_01.rst +++ b/source/c03/c03_01.rst @@ -24,8 +24,8 @@ |image1| -3.1.1 Hello,装饰器 -------------------- +1. Hello,装饰器 +---------------- 装饰器的使用方法很固定 - 先定义一个装饰器(帽子) - 再定义你的业务函数或者类(人) - @@ -51,8 +51,8 @@ 接下来,我将以实例讲解,如何编写出各种简单及复杂的装饰器。 -3.1.2 入门:日志打印器 ----------------------- +2. 入门:日志打印器 +------------------- 首先是\ **日志打印器**\ 。 实现的功能: - 在函数执行前,先打印一行日志告知一下主人,我要执行函数了。 - @@ -93,8 +93,8 @@ 200 + 50 = 250 主人,我执行完啦。 -3.1.3 入门:时间计时器 ----------------------- +3. 入门:时间计时器 +------------------- 再来看看 **时间计时器** 实现功能:顾名思义,就是计算一个函数的执行时长。 @@ -131,8 +131,8 @@ 花费时间:10.0073800086975098秒 -3.1.4 进阶:带参数的函数装饰器 ------------------------------- +4. 进阶:带参数的函数装饰器 +--------------------------- 通过上面两个简单的入门示例,你应该能体会到装饰器的工作原理了。 @@ -209,8 +209,8 @@ periodic_task ------------ hello. -3.1.5 高阶:不带参数的类装饰器 ------------------------------- +5. 高阶:不带参数的类装饰器 +--------------------------- 以上都是基于函数实现的装饰器,在阅读别人代码时,还可以时常发现还有基于类实现的装饰器。 @@ -244,8 +244,8 @@ periodic_task [INFO]: the function say() is running... say hello! -3.1.6 高阶:带参数的类装饰器 ----------------------------- +6. 高阶:带参数的类装饰器 +------------------------- 上面不带参数的例子,你发现没有,只能打印\ ``INFO``\ 级别的日志,正常情况下,我们还需要打印\ ``DEBUG`` ``WARNING``\ 等级别的日志。 @@ -282,8 +282,8 @@ periodic_task [WARNING]: the function say() is running... say hello! -3.1.7 使用偏函数与类实现装饰器 ------------------------------- +7. 使用偏函数与类实现装饰器 +--------------------------- 绝大多数装饰器都是基于函数和闭包实现的,但这并非制造装饰器的唯一方式。 @@ -354,8 +354,8 @@ Python工匠:使用装饰器的小技巧) >>> add.func # 实现实例方法 -3.1.8 如何写能装饰类的装饰器? ------------------------------- +8. 如何写能装饰类的装饰器? +--------------------------- 用 Python 写单例模式的时候,常用的有三种写法。其中一种,是用装饰器来实现的。 @@ -392,8 +392,8 @@ Python工匠:使用装饰器的小技巧) |image2| -3.1.9 wraps 装饰器有啥用? --------------------------- +9. wraps 装饰器有啥用? +----------------------- 在 functools 标准库中有提供一个 wraps 装饰器,你应该也经常见过,那他有啥用呢? @@ -488,8 +488,8 @@ wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码 print(wrapped.__name__) -3.1.10 内置装饰器:property ---------------------------- +10. 内置装饰器:property +------------------------ 以上,我们介绍的都是自定义的装饰器。 @@ -732,8 +732,8 @@ property 如对上面代码的运行原理,有疑问的同学,请务必结合上面两点说明加以理解,那两点相当关键。 -3.1.11 其他装饰器:装饰器实战 ------------------------------ +11. 其他装饰器:装饰器实战 +-------------------------- 读完并理解了上面的内容,你可以说是Python高手了。别怀疑,自信点,因为很多人都不知道装饰器有这么多用法呢。 @@ -778,7 +778,7 @@ property |image3| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190811100737.png -.. |image2| image:: http://image.python-online.cn/20190512113917.png +.. |image1| image:: http://image.iswbm.com/20190811100737.png +.. |image2| image:: http://image.iswbm.com/20190512113917.png .. |image3| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c03/c03_02.md b/source/c03/c03_02.md index 8ee46db..45edc25 100644 --- a/source/c03/c03_02.md +++ b/source/c03/c03_02.md @@ -4,7 +4,7 @@ --- -## 3.2.1 类是如何产生的 +## 1. 类是如何产生的 类是如何产生? @@ -15,7 +15,7 @@ type?这不是判断对象类型的函数吗? 是的,type通常用法就是用来判断对象的类型。但除此之外,他最大的用途是用来动态创建类。当Python扫描到class的语法的时候,就会调用type函数进行类的创建。 -## 3.2.2 如何使用type创建类 +## 2. 如何使用type创建类 首先,type()需要接收三个参数 ``` @@ -39,7 +39,7 @@ def say(self): User = type("User", (BaseClass, ), {"name":"user", "say":say}) ``` -## 3.2.3 理解什么是元类 +## 3. 理解什么是元类 什么是类?可能谁都知道,类就是用来创建对象的「模板」。 @@ -157,7 +157,7 @@ True -## 3.3.4 使用元类的意义 +## 4. 使用元类的意义 正常情况下,我们都不会使用到元类。但是这并不意味着,它不重要。假如某一天,我们需要写一个框架,很有可能就需要你对元类要有进一步的研究。 @@ -173,7 +173,7 @@ True 但是,这样说,你一定不会服气,到底元类用来干什么?其实元类的作用就是`创建API`,一个最典型的应用是 `Django ORM`。 -## 3.3.5 元类实战:ORM +## 5. 元类实战:ORM 使用过Django ORM的人都知道,有了ORM,使得我们操作数据库,变得异常简单。 @@ -307,7 +307,7 @@ class ModelMetaClass(type): return super().__new__(cls, name, bases, attrs) ``` -## 3.2.6 \__new__ 有什么用? +## 6. \__new__ 有什么用? 在没有元类的情况下,每次创建实例,在先进入 `__init__` 之前都会先进入 ` __new__`。 diff --git a/source/c03/c03_02.rst b/source/c03/c03_02.rst old mode 100755 new mode 100644 index a134058..38fa73b --- a/source/c03/c03_02.rst +++ b/source/c03/c03_02.rst @@ -5,8 +5,8 @@ -------------- -3.2.1 类是如何产生的 --------------------- +1. 类是如何产生的 +----------------- 类是如何产生? @@ -16,8 +16,8 @@ type?这不是判断对象类型的函数吗? 是的,type通常用法就是用来判断对象的类型。但除此之外,他最大的用途是用来动态创建类。当Python扫描到class的语法的时候,就会调用type函数进行类的创建。 -3.2.2 如何使用type创建类 ------------------------- +2. 如何使用type创建类 +--------------------- 首先,type()需要接收三个参数 @@ -43,8 +43,8 @@ type?这不是判断对象类型的函数吗? # 使用type来创建User类 User = type("User", (BaseClass, ), {"name":"user", "say":say}) -3.2.3 理解什么是元类 --------------------- +3. 理解什么是元类 +----------------- 什么是类?可能谁都知道,类就是用来创建对象的「模板」。 @@ -170,8 +170,8 @@ type是Python在背后用来创建所有类的元类,我们熟知的类的始 >>> u1 is u2 True -3.3.4 使用元类的意义 --------------------- +4. 使用元类的意义 +----------------- 正常情况下,我们都不会使用到元类。但是这并不意味着,它不重要。假如某一天,我们需要写一个框架,很有可能就需要你对元类要有进一步的研究。 @@ -188,8 +188,8 @@ type是Python在背后用来创建所有类的元类,我们熟知的类的始 但是,这样说,你一定不会服气,到底元类用来干什么?其实元类的作用就是\ ``创建API``\ ,一个最典型的应用是 ``Django ORM``\ 。 -3.3.5 元类实战:ORM -------------------- +5. 元类实战:ORM +---------------- 使用过Django ORM的人都知道,有了ORM,使得我们操作数据库,变得异常简单。 @@ -330,8 +330,8 @@ ORM的一个类(User),就对应数据库中的一张表。id,name,email,passwo attrs["fields"] = fields return super().__new__(cls, name, bases, attrs) -3.2.6 \__new_\_ 有什么用? --------------------------- +6. \__new_\_ 有什么用? +----------------------- 在没有元类的情况下,每次创建实例,在先进入 ``__init__`` 之前都会先进入 ``__new__``\ 。 diff --git a/source/c03/c03_03.md b/source/c03/c03_03.md index bc5dee3..a6347d5 100644 --- a/source/c03/c03_03.md +++ b/source/c03/c03_03.md @@ -4,7 +4,7 @@ --- -## 3.3.1 什么是socket? +## 1. 什么是socket? 说到网络编程,难免要提到socket? @@ -14,7 +14,7 @@ 不管是不同主机,还是同一主机。既然是通信,必定有一个发送方,一个接收方。对应一个客户端,和一个服务端。 -## 3.3.2 创建客户端 +## 2. 创建客户端 - 创建socket,建立连接 ```python @@ -47,7 +47,7 @@ while True: data = ''.join(buffer) ``` -## 3.3.3 创建服务端 +## 3. 创建服务端 - 创建socket ```python import socket @@ -84,11 +84,11 @@ def tcplink(sock, addr): sock.close() ``` -## 3.3.4 socket工作流程 +## 4. socket工作流程 ![](https://i.loli.net/2018/04/30/5ae6c303c870c.png) -## 3.3.5 socket公共函数汇总 +## 5. socket公共函数汇总 - 发送数据 ```python # 发送TCP数据,返回值:发送的字节当量 @@ -145,7 +145,7 @@ sk.setsockopt(level,optname,value) sk.getsockopt(level,optname) ``` -## 3.3.6 搭建在线聊天机器人 +## 6. 搭建在线聊天机器人 通过上面的学习,我们知道,同主机下或不同主机下的两个进程要进行通信(TCP/UDP,不管是消息传输还是文件传输),必定要借助socket这个桥梁。 diff --git a/source/c03/c03_03.rst b/source/c03/c03_03.rst old mode 100755 new mode 100644 index 8c032a5..00502ce --- a/source/c03/c03_03.rst +++ b/source/c03/c03_03.rst @@ -5,8 +5,8 @@ -------------- -3.3.1 什么是socket? --------------------- +1. 什么是socket? +----------------- 说到网络编程,难免要提到socket? @@ -16,8 +16,8 @@ 不管是不同主机,还是同一主机。既然是通信,必定有一个发送方,一个接收方。对应一个客户端,和一个服务端。 -3.3.2 创建客户端 ----------------- +2. 创建客户端 +------------- - 创建socket,建立连接 @@ -55,8 +55,8 @@ break data = ''.join(buffer) -3.3.3 创建服务端 ----------------- +3. 创建服务端 +------------- - 创建socket @@ -103,13 +103,13 @@ sock.send('Hello, %s!' % data) sock.close() -3.3.4 socket工作流程 --------------------- +4. socket工作流程 +----------------- |image1| -3.3.5 socket公共函数汇总 ------------------------- +5. socket公共函数汇总 +--------------------- - 发送数据 @@ -176,8 +176,8 @@ sk.setsockopt(level,optname,value) sk.getsockopt(level,optname) -3.3.6 搭建在线聊天机器人 ------------------------- +6. 搭建在线聊天机器人 +--------------------- 通过上面的学习,我们知道,同主机下或不同主机下的两个进程要进行通信(TCP/UDP,不管是消息传输还是文件传输),必定要借助socket这个桥梁。 diff --git a/source/c03/c03_04.rst b/source/c03/c03_04.rst old mode 100755 new mode 100644 diff --git a/source/c03/c03_05.md b/source/c03/c03_05.md index da3aacc..06afe19 100644 --- a/source/c03/c03_05.md +++ b/source/c03/c03_05.md @@ -31,7 +31,7 @@ 这三个对象都是 `werkzeug` 里提供的,定义的 `local.py` 里,所以它们并不是Flask 中特有的, 这就意味着我们可以直接在自己的项目中使用它们,而不用依托于 Flask 的环境。 -## 3.5.1 Local +## 1. Local 首先是 `Local` ,记得在以前的「并发编程系列」的第五篇线程的 `信息隔离` 的时候,提过了 `threading.local` ,它是专门用来存储当前线程的变量,从而实现对象的线程隔离。 @@ -124,11 +124,11 @@ self.__storage__['1'][k2] = v2 如果要用图来表示,最开始的Local对象就是一个空盒子 -![](http://image.python-online.cn/Fuhww2CZdUv4mGqx-N0YqAuXUWlX) +![](http://image.iswbm.com/Fuhww2CZdUv4mGqx-N0YqAuXUWlX) 当有不同的线程往里写数据时,Local 对象为每个线程分配了一个 micro-box。 -![](http://image.python-online.cn/FgI6y-_Ka-S20VCjyufsCIczKjup) +![](http://image.iswbm.com/FgI6y-_Ka-S20VCjyufsCIczKjup) local 是需要被 `localmanager` 管理的,在请求结束后,会调用 `localmanager.cleanup()` 函数,其实是调用 `local.__release_local__ ` 进行数据清理。是如何做到的呢,看下面这段代码。 @@ -189,7 +189,7 @@ class Local(object): -## 3.5.2 LocalStack +## 2. LocalStack 通过对 Local 的介绍,可以知道 Local 其实是通过封装了字典的,以此实现了线程隔离。 @@ -197,7 +197,7 @@ class Local(object): 同样用一张图来表示 -![](http://image.python-online.cn/FimULzWaeZWS2KJx_EQLAK_yRZ4A) +![](http://image.iswbm.com/FimULzWaeZWS2KJx_EQLAK_yRZ4A) 栈结构的特性,无非就是后进先出。这里就不说了,这里的重点是线程隔离的特性如何体现,还是以上面的例子,稍微做了下修改。 @@ -267,7 +267,7 @@ request = LocalProxy(partial(_lookup_req_object, 'request')) session = LocalProxy(partial(_lookup_req_object, 'session')) ``` -## 3.5.3 LocalProxy +## 3. LocalProxy 通过上面的代码,你可以发现,我们访问LocalStack里的元素的时候,都是通过`LocalProxy` 来进行的有没有? @@ -359,7 +359,7 @@ def _get_current_object(self): 这样就能实现每次对栈顶元素的操作,都是面对最新元素执行的。 -## 3.5.4 经典错误 +## 4. 经典错误 在 Flask 中经常会遇到的一个错误是: diff --git a/source/c03/c03_05.rst b/source/c03/c03_05.rst index a9de523..72c8fca 100644 --- a/source/c03/c03_05.rst +++ b/source/c03/c03_05.rst @@ -40,8 +40,8 @@ request。这是它们之间的对应关系。 这就意味着我们可以直接在自己的项目中使用它们,而不用依托于 Flask 的环境。 -3.5.1 Local ------------ +1. Local +-------- 首先是 ``Local`` ,记得在以前的「并发编程系列」的第五篇线程的 ``信息隔离`` 的时候,提过了 ``threading.local`` @@ -206,8 +206,8 @@ local 是需要被 ``localmanager`` 管理的,在请求结束后,会调用 except KeyError: raise AttributeError(name) -3.5.2 LocalStack ----------------- +2. LocalStack +------------- 通过对 Local 的介绍,可以知道 Local 其实是通过封装了字典的,以此实现了线程隔离。 @@ -295,8 +295,8 @@ local 是需要被 ``localmanager`` 管理的,在请求结束后,会调用 request = LocalProxy(partial(_lookup_req_object, 'request')) session = LocalProxy(partial(_lookup_req_object, 'session')) -3.5.3 LocalProxy ----------------- +3. LocalProxy +------------- 通过上面的代码,你可以发现,我们访问LocalStack里的元素的时候,都是通过\ ``LocalProxy`` 来进行的有没有? @@ -394,8 +394,8 @@ local 是需要被 ``localmanager`` 管理的,在请求结束后,会调用 这样就能实现每次对栈顶元素的操作,都是面对最新元素执行的。 -3.5.4 经典错误 --------------- +4. 经典错误 +----------- 在 Flask 中经常会遇到的一个错误是: @@ -503,8 +503,8 @@ app 的上下文信息是否已经 push 进去了,如果没有的话,就会 |image4| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/Fuhww2CZdUv4mGqx-N0YqAuXUWlX -.. |image2| image:: http://image.python-online.cn/FgI6y-_Ka-S20VCjyufsCIczKjup -.. |image3| image:: http://image.python-online.cn/FimULzWaeZWS2KJx_EQLAK_yRZ4A +.. |image1| image:: http://image.iswbm.com/Fuhww2CZdUv4mGqx-N0YqAuXUWlX +.. |image2| image:: http://image.iswbm.com/FgI6y-_Ka-S20VCjyufsCIczKjup +.. |image3| image:: http://image.iswbm.com/FimULzWaeZWS2KJx_EQLAK_yRZ4A .. |image4| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c03/c03_06.md b/source/c03/c03_06.md index 728a25b..820bbdf 100644 --- a/source/c03/c03_06.md +++ b/source/c03/c03_06.md @@ -20,7 +20,7 @@ 一个HTTP请求的过程可以分为两个阶段,第一阶段是从客户端到WSGI Server,第二阶段是从WSGI Server 到WSGI Application -![](http://image.python-online.cn/20190607131728.png) +![](http://image.iswbm.com/20190607131728.png) 今天主要是讲第二阶段,主要内容有以下几点: @@ -34,7 +34,7 @@ 8. webob.dec.wsgify 装饰器 9. 第二次路由:中间件 routes 路由 -## 3.6.1 WSGI 是什么,因何而生? +## 1. WSGI 是什么,因何而生? WSGI是 Web Server Gateway Interface 的缩写。 @@ -58,7 +58,7 @@ WSGI 对于 application 对象有如下三点要求 -## 3.6.2 为什么要有 WSGI? +## 2. 为什么要有 WSGI? 这是来自我的知乎专栏一个朋友在评论区的一个问题,我觉得问得很好,所以来答一下,更新在这里。 @@ -82,7 +82,7 @@ web 服务器 和 web 框架,分工不同,职责不同(web 服务器专注 最好的情况应该是,由专业的团队去开发专业的web服务器,而开发出来的web服务器需要具备框架通用性,Django可以用,Flask也可以用,开发者也可以自由选择用哪个web 服务器软件,用哪个web 框架,灵活组合。 -## 3.6.3 HTTP请求是如何到应用程序的? +## 3. HTTP请求是如何到应用程序的? 当客户端发出一个 HTTP 请求后,是如何转到我们的应用程序处理并返回的呢? @@ -90,7 +90,7 @@ web 服务器 和 web 框架,分工不同,职责不同(web 服务器专注 我根据其架构组成的不同将这个过程的实现分为两种: -![](http://image.python-online.cn/20190607191954.png) +![](http://image.iswbm.com/20190607191954.png) **1、两级结构** 在这种结构里,uWSGI作为服务器,它用到了HTTP协议以及wsgi协议,flask应用作为application,实现了wsgi协议。当有客户端发来请求,uWSGI接受请求,调用flask app得到相应,之后相应给客户端。 @@ -104,7 +104,7 @@ web 服务器 和 web 框架,分工不同,职责不同(web 服务器专注 nginx可以做负载均衡(前提是有多个服务器),保护了实际的web服务器(客户端是和nginx交互而不是uWSGI) -## 3.6.4 实现一个简单的 WSGI Server +## 4. 实现一个简单的 WSGI Server 在上面的架构图里,不知道你发现没有,有个库叫做 `wsgiref` ,它是 Python 自带的一个 wsgi 服务器模块。 @@ -125,11 +125,11 @@ server.serve_forever() 使用 lsof 命令可以查到确实开启了这个端口 -![](http://image.python-online.cn/20190607134310.png) +![](http://image.iswbm.com/20190607134310.png) 以上使用 wsgiref 写了一个demo,让你对wsgi有个初步的了解。其由于只适合在学习测试使用,在生产环境中应该另寻他道。 -## 3.6.5 实现“高并发”的 WSGI Server +## 5. 实现“高并发”的 WSGI Server 上面我们说不能在生产中使用 wsgiref ,那在生产中应该使用什么呢?选择有挺多的,比如优秀的 uWSGI,Gunicore等。但是今天我并不准备讲这些,一是因为我不怎么熟悉,二是因为我本人从事 OpenStack 的二次开发,对它比较熟悉。 @@ -143,23 +143,23 @@ server.serve_forever() 从 Service 文件可以得知 nova-api 的入口是 `nova.cmd.api:main()` -![](http://image.python-online.cn/20190607140817.png) +![](http://image.iswbm.com/20190607140817.png) -![](http://image.python-online.cn/20190607140922.png) +![](http://image.iswbm.com/20190607140922.png) 打开`nova.cmd.api:main()` ,一起看看是 OpenStack Nova 的代码。 在如下的黄框里,可以看到在这里使用了service.WSGIService 启动了一个 server,就是我们所说的的 wsgi server -![](http://image.python-online.cn/20190530212557.png) +![](http://image.iswbm.com/20190530212557.png) 那这里的 WSGI Server 是依靠什么实现的呢?让我们继续深入源代码。 -![](http://image.python-online.cn/20190530212753.png) +![](http://image.iswbm.com/20190530212753.png) wsgi.py 可以看到这里使用了 eventlet 这个网络并发框架,它先开启了一个绿色线程池,从配置里可以看到这个服务器可以接收的请求并发量是 1000 。 -![](http://image.python-online.cn/20190530212956.png) +![](http://image.iswbm.com/20190530212956.png) 可是我们还没有看到 WSGI Server 的身影,上面使用eventlet 开启了线程池,那线程池里的每个线程应该都是一个服务器吧?它是如何接收请求的? @@ -194,11 +194,11 @@ wsgi_kwargs = { self._server = utils.spawn(**wsgi_kwargs) ``` -![](http://image.python-online.cn/20190530214820.png) +![](http://image.iswbm.com/20190530214820.png) 就这样,nova 开启了一个可以接受1000个绿色协程并发的 WSGI Server。 -## 3.6.6 第一次路由:PasteDeploy +## 6. 第一次路由:PasteDeploy 上面我们提到 WSGI Server 的创建要传入一个 Application,用来处理接收到的请求,对于一个有多个 app 的项目。 @@ -229,7 +229,7 @@ PasteDeploy 到底是做什么的呢? 具体可以,看下nova的实现。 -![](http://image.python-online.cn/20190530221101.png) +![](http://image.iswbm.com/20190530221101.png) 通过打印的 DEBUG 内容得知 config_url 和 app name 的值 @@ -253,7 +253,7 @@ paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory 这是 OpenStack 使用 PasteDeploy 实现的第一层的路由,如果你不感兴趣,可以直接略过本节,进入下一节,下一节是 介绍 PasteDeploy 的使用,教你实现一个简易的web server demo。推荐一定要看。 -## 3.6.7 PasteDeploy 使用说明 +## 7. PasteDeploy 使用说明 到上一步,我已经得到了 application 的有用的线索。考虑到很多人是第一次接触 PasteDeploy,所以这里结合网上博客做了下总结。对你入门会有帮助。 @@ -389,7 +389,7 @@ server.serve_forever() applications 是URLMap 对象。 -![](http://image.python-online.cn/20190607154119.png) +![](http://image.iswbm.com/20190607154119.png) 完善并整合第二步和第三步的内容,写成一个 Python 文件(wsgi_server.py)。内容如下 @@ -449,7 +449,7 @@ if __name__ == "__main__": 一切都准备好后,在终端执行 ` python wsgi_server.py`来启动 web server -![](http://image.python-online.cn/20190607155432.png) +![](http://image.iswbm.com/20190607155432.png) 如果像上图一样一切正常,那么打开浏览器 @@ -460,17 +460,17 @@ if __name__ == "__main__": 到此,你学会了使用 PasteDeploy 的简单使用。 -## 3.6.8 webob.dec.wsgify 装饰器 +## 8. webob.dec.wsgify 装饰器 经过了 PasteDeploy 的路由调度,我们找到了 `nova.api.openstack.compute:APIRouterV21.factory` 这个 application 的入口,看代码知道它其实返回了 APIRouterV21 类的一个实例。 -![](http://image.python-online.cn/20190602173212.png) +![](http://image.iswbm.com/20190602173212.png) WSGI规定 application 必须是一个 callable 的对象,函数、方法、类、实例,若是一个类实例,就要求这个实例所属的类实现 `__call__` 的方法。 APIRouterV21 本身没有实现 `__call__` ,但它的父类 Router实现了 `__call__` -![](http://image.python-online.cn/20190602173956.png) +![](http://image.iswbm.com/20190602173956.png) 我们知道,application 必须遵丛 WSGI 的规范 @@ -479,7 +479,7 @@ APIRouterV21 本身没有实现 `__call__` ,但它的父类 Router实现了 `_ 但从 Router 的 `__call__` 代码来看,它并没有遵从这个规范,它不接收这两个参数,也不返回 response,而只是返回另一个 callable 的对象,就这样我们的视线被一次又一次的转移,但没有关系,这些`__call__`都是外衣,只要扒掉这些外衣,我们就能看到核心app。 -而负责扒掉这层外衣的,就是其头上的装饰器 `@webob.dec.wsgify` ,wsgify 是一个类,其 `__call__` 源码实现如下:![](http://image.python-online.cn/20190605203016.png) +而负责扒掉这层外衣的,就是其头上的装饰器 `@webob.dec.wsgify` ,wsgify 是一个类,其 `__call__` 源码实现如下:![](http://image.iswbm.com/20190605203016.png) 可以看出,wsgify 在这里,会将 req 这个原始请求(dict对象)封装成 Request 对象(就是规范1里提到的 environ)。然后会一层一层地往里地执行被wsgify装饰的函数(self._route), 得到最内部的核心application。 @@ -493,11 +493,11 @@ APIRouterV21 本身没有实现 `__call__` ,但它的父类 Router实现了 `_ 带着这个问题,我们了解下 routes 是如何为我们实现第二次路由。 -## 3.6.9 第二次路由:中间件 routes 路由 +## 9. 第二次路由:中间件 routes 路由 在文章最开始处,我们给大家画了一张图。 -![](http://image.python-online.cn/20190607131728.png) +![](http://image.iswbm.com/20190607131728.png) 这张图把一个 HTTP 请求粗略简单地划分为两个过程。但事实上,整个过程远比这个过程要复杂得多。 @@ -513,7 +513,7 @@ APIRouterV21 本身没有实现 `__call__` ,但它的父类 Router实现了 `_ 在routes模块里有个中间件,叫 `routes.middleware.RoutesMiddleware` ,它将接受到的 url,自动调用 `map.match()`方法,对 url 进行路由匹配,并将匹配的结果存入request请求的环境变量`['wsgiorg.routing_args']`,最后会调用`self._dispatch`(dispatch返回真正的application)返回response,最后会将这个response返回给 WSGI Server。 -![](http://image.python-online.cn/20190608211233.png) +![](http://image.iswbm.com/20190608211233.png) 这个中间件的原理,看起来是挺简单的。并没有很复杂的逻辑。 @@ -521,7 +521,7 @@ APIRouterV21 本身没有实现 `__call__` ,但它的父类 Router实现了 `_ `self._dispatch` (也就上图中的self.app)函数里,我们看到了 app,controller 这几个很重要的字眼,其是否是我苦苦追寻的 application 对象呢? -![](http://image.python-online.cn/20190531211542.png) +![](http://image.iswbm.com/20190531211542.png) 要搞明白这个问题,只要看清 match 到是什么东西? @@ -545,7 +545,7 @@ APIRouterV21 本身没有实现 `__call__` ,但它的父类 Router实现了 `_ 从 Nova 代码中看出每个Resource 对应一个 Controller 对象,因为 Controller 对象本身就是对一种资源的操作集合。 -![](http://image.python-online.cn/20190531225529.png) +![](http://image.iswbm.com/20190531225529.png) 通过日志的打印,可以发现 nova 管理的 Resource 对象有多么的多而杂 @@ -723,7 +723,7 @@ nova show 1c250b15-a346-43c5-9b41-20767ec7c94b 图示地方,会从 environ 里获取中看到获取 action 的具体代码 -![](http://image.python-online.cn/20190602220246.png) +![](http://image.iswbm.com/20190602220246.png) 我将这边的 action_args打印出来 @@ -736,7 +736,7 @@ nova show 1c250b15-a346-43c5-9b41-20767ec7c94b 在 `__call__` 的最后,会 调用 `_process_stack` 方法 -![](http://image.python-online.cn/20190602220511.png) +![](http://image.iswbm.com/20190602220511.png) 在图标处,get_method 会根据 action(函数名) 取得处理函数对象。 @@ -747,11 +747,11 @@ meth :".format( + self.name, self.math, self.chinese, self.english + ) +``` + +看起来一切都很合理 + +```python +>>> std1 = Student('小明', 76, 87, 68) +>>> std1 + +``` + +但是程序并不像人那么智能,不会自动根据使用场景判断数据的合法性,如果老师在录入成绩的时候,不小心录入了将成绩录成了负数,或者超过100,程序是无法感知的。 + +聪明的你,马上在代码中加入了判断逻辑。 + +```python +class Student: + def __init__(self, name, math, chinese, english): + self.name = name + if 0 <= math <= 100: + self.math = math + else: + raise ValueError("Valid value must be in [0, 100]") + + if 0 <= chinese <= 100: + self.chinese = chinese + else: + raise ValueError("Valid value must be in [0, 100]") + + if 0 <= chinese <= 100: + self.english = english + else: + raise ValueError("Valid value must be in [0, 100]") + + + def __repr__(self): + return "".format( + self.name, self.math, self.chinese, self.english + ) +``` + +这下程序稍微有点人工智能了,能够自己明辨是非了。 + +![](http://image.iswbm.com/20190425221322.png) + +程序是智能了,但在`__init__`里有太多的判断逻辑,很影响代码的可读性。巧的是,你刚好学过 Property 特性,可以很好的应用在这里。于是你将代码修改成如下,代码的可读性瞬间提升了不少 + +```python +class Student: + def __init__(self, name, math, chinese, english): + self.name = name + self.math = math + self.chinese = chinese + self.english = english + + @property + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") + + @property + def chinese(self): + return self._chinese + + @chinese.setter + def chinese(self, value): + if 0 <= value <= 100: + self._chinese = value + else: + raise ValueError("Valid value must be in [0, 100]") + + @property + def english(self): + return self._english + + @english.setter + def english(self, value): + if 0 <= value <= 100: + self._english = value + else: + raise ValueError("Valid value must be in [0, 100]") + + def __repr__(self): + return "".format( + self.name, self.math, self.chinese, self.english + ) +``` + +程序还是一样的人工智能,非常好。 + +![](http://image.iswbm.com/20190425221322.png) + +你以为你写的代码,已经非常优秀,无懈可击了。 + + + +没想到,人外有天,你的主管看了你的代码后,深深地叹了口气:类里的三个属性,math、chinese、english,都使用了 Property 对属性的合法性进行了有效控制。功能上,没有问题,但就是太啰嗦了,三个变量的合法性逻辑都是一样的,只要大于0,小于100 就可以,代码重复率太高了,这里三个成绩还好,但假设还有地理、生物、历史、化学等十几门的成绩呢,这代码简直没法忍。去了解一下 Python 的描述符吧。 + +经过主管的指点,你知道了「描述符」这个东西。怀着一颗敬畏之心,你去搜索了下关于 描述符的用法。 + +其实也很简单,一个实现了 `描述符协议` 的类就是一个描述符。 + +什么描述符协议:在类里实现了 `__get__()`、`__set__()`、`__delete__()` 其中至少一个方法。 + +- `__get__`: 用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。 +- `__set__ `:将在属性分配操作中调用。不会返回任何内容。 +- `__delete__ `:控制删除操作。不会返回内容。 + +对描述符有了大概的了解后,你开始重写上面的方法。 + +如前所述,Score 类是一个描述符,当从 Student 的实例访问 math、chinese、english这三个属性的时候,都会经过 Score 类里的三个特殊的方法。这里的 Score 避免了 使用Property 出现大量的代码无法复用的尴尬。 + +```python +class Score: + def __init__(self, default=0): + self._score = default + + def __set__(self, instance, value): + if not isinstance(value, int): + raise TypeError('Score must be integer') + if not 0 <= value <= 100: + raise ValueError('Valid value must be in [0, 100]') + + self._score = value + + def __get__(self, instance, owner): + return self._score + + def __delete__(self): + del self._score + +class Student: + math = Score(0) + chinese = Score(0) + english = Score(0) + + def __init__(self, name, math, chinese, english): + self.name = name + self.math = math + self.chinese = chinese + self.english = english + + + def __repr__(self): + return "".format( + self.name, self.math, self.chinese, self.english + ) +``` + +实现的效果和前面的一样,可以对数据的合法性进行有效控制(字段类型、数值区间等) + +![](http://image.iswbm.com/20190425221233.png) + +以上,我举了下具体的实例,从最原始的编码风格到 Property ,最后引出描述符。由浅入深,一步一步带你感受到描述符的优雅之处。 + +到这里,你需要记住的只有一点,就是描述符给我们带来的编码上的便利,它在实现 `保护属性不受修改`、`属性类型检查` 的基本功能,同时有大大提高代码的复用率。 + + + +### 2. 描述符的访问规则 + +描述符分两种: + +- 数据描述符:实现了`__get__` 和 `__set__` 两种方法的描述符 +- 非数据描述符:只实现了`__get__` 一种方法的描述符 + +你一定会问,他们有什么区别呢?网上的讲解,我看过几个,很多都把一个简单的东西讲得复杂了。 + +其实就一句话,**数据描述器和非数据描述器的区别在于:它们相对于实例的字典的优先级不同**。 + +如果实例字典中有与描述符同名的属性,如果描述符是数据描述符,优先使用数据描述符,如果是非数据描述符,优先使用字典中的属性。 + +这边还是以上节的成绩管理的例子来说明,方便你理解。 + +```python +# 数据描述符 +class DataDes: + def __init__(self, default=0): + self._score = default + + def __set__(self, instance, value): + self._score = value + + def __get__(self, instance, owner): + print("访问数据描述符里的 __get__") + return self._score + +# 非数据描述符 +class NoDataDes: + def __init__(self, default=0): + self._score = default + + def __get__(self, instance, owner): + print("访问非数据描述符里的 __get__") + return self._score + + +class Student: + math = DataDes(0) + chinese = NoDataDes(0) + + def __init__(self, name, math, chinese): + self.name = name + self.math = math + self.chinese = chinese + + def __getattribute__(self, item): + print("调用 __getattribute__") + return super(Student, self).__getattribute__(item) + + def __repr__(self): + return "".format( + self.name, self.math, self.chinese) +``` + +需要注意的是,math 是数据描述符,而 chinese 是非数据描述符。从下面的验证中,可以看出,当实例属性和数据描述符同名时,会优先访问数据描述符(如下面的math),而当实例属性和非数据描述符同名时,会优先访问实例属性(`__getattribute__`) + +```python +>>> std = Student('xm', 88, 99) +>>> +>>> std.math +调用 __getattribute__ +访问数据描述符里的 __get__ +88 +>>> std.chinese +调用 __getattribute__ +99 +``` + +讲完了数据描述符和非数据描述符,我们还需要了解的对象属性的查找规律。 + +当我们对一个实例属性进行访问时,Python 会按 `obj.__dict__` → `type(obj).__dict__` → `type(obj)的父类.__dict__` 顺序进行查找,如果查找到目标属性并发现是一个描述符,Python 会调用描述符协议来改变默认的控制行为。 + +### 3. 基于描述符如何实现property + +经过上面的讲解,我们已经知道如何定义描述符,且明白了描述符是如何工作的。 + +正常人所见过的描述符的用法就是上面提到的那些,我想说的是那只是描述符协议最常见的应用之一,或许你还不知道,其实有很多 Python 的特性的底层实现机制都是基于 `描述符协议` 的,比如我们熟悉的`@property` 、`@classmethod` 、`@staticmethod` 和 `super` 等。 + +先来说说 `property` 吧。 + +有了前面的基础,我们知道了 property 的基本用法。这里我直接切入主题,从第一篇的例子里精简了一下。 + +```python +class Student: + def __init__(self, name): + self.name = name + + @property + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") +``` + +不防再简单回顾一下它的用法,通过property装饰的函数,如例子中的 math 会变成 Student 实例的属性。而对 math 属性赋值会进入 使用 `math.setter` 装饰函数的逻辑代码块。 + +为什么说 property 底层是基于描述符协议的呢?通过 PyCharm 点击进入 property 的源码,很可惜,只是一份类似文档一样的伪源码,并没有其具体的实现逻辑。 + +不过,从这份伪源码的魔法函数结构组成,可以大体知道其实现逻辑。 + +这里我自己通过模仿其函数结构,结合「描述符协议」来自己实现类 `property` 特性。 + +代码如下: + +```python +class TestProperty(object): + + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + self.fget = fget + self.fset = fset + self.fdel = fdel + self.__doc__ = doc + + def __get__(self, obj, objtype=None): + print("in __get__") + if obj is None: + return self + if self.fget is None: + raise AttributeError + return self.fget(obj) + + def __set__(self, obj, value): + print("in __set__") + if self.fset is None: + raise AttributeError + self.fset(obj, value) + + def __delete__(self, obj): + print("in __delete__") + if self.fdel is None: + raise AttributeError + self.fdel(obj) + + + def getter(self, fget): + print("in getter") + return type(self)(fget, self.fset, self.fdel, self.__doc__) + + def setter(self, fset): + print("in setter") + return type(self)(self.fget, fset, self.fdel, self.__doc__) + + def deleter(self, fdel): + print("in deleter") + return type(self)(self.fget, self.fset, fdel, self.__doc__) +``` + +然后 Student 类,我们也相应改成如下 + +```python +class Student: + def __init__(self, name): + self.name = name + + # 其实只有这里改变 + @TestProperty + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") +``` + + + +为了尽量让你少产生一点疑惑,我这里做两点说明: + +1. 使用`TestProperty`装饰后,`math` 不再是一个函数,而是`TestProperty` 类的一个实例。所以第二个math函数可以使用 `math.setter` 来装饰,本质是调用`TestProperty.setter` 来产生一个新的 `TestProperty` 实例赋值给第二个`math`。 + +2. 第一个 `math` 和第二个 `math` 是两个不同 `TestProperty` 实例。但他们都属于同一个描述符类(TestProperty),当对 math 对于赋值时,就会进入 `TestProperty.__set__`,当对math 进行取值里,就会进入 `TestProperty.__get__`。仔细一看,其实最终访问的还是Student实例的 `_math` 属性。 + +说了这么多,还是运行一下,更加直观一点。 + +```python +# 运行后,会直接打印这一行,这是在实例化 TestProperty 并赋值给第二个math +in setter +>>> +>>> s1.math = 90 +in __set__ +>>> s1.math +in __get__ +90 +``` + +对于以上理解 `property` 的运行原理有困难的同学,请务必参照我上面写的两点说明。如有其他疑问,可以加微信与我进行探讨。 + +### 4. 基于描述符如何实现staticmethod + +说完了 `property` ,这里再来讲讲 `@classmethod` 和 `@staticmethod` 的实现原理。 + +我这里定义了一个类,用了两种方式来实现静态方法。 + +```python +class Test: + @staticmethod + def myfunc(): + print("hello") + +# 上下两种写法等价 + +class Test: + def myfunc(): + print("hello") + # 重点:这就是描述符的体现 + myfunc = staticmethod(myfunc) +``` + +这两种写法是等价的,就好像在 `property` 一样,其实以下两种写法也是等价的。 + +```python +@TestProperty +def math(self): + return self._math + +math = TestProperty(fget=math) +``` + +话题还是转回到 `staticmethod` 这边来吧。 + +由上面的注释,可以看出 `staticmethod` 其实就相当于一个描述符类,而`myfunc` 在此刻变成了一个描述符。关于 `staticmethod` 的实现,你可以参照下面这段我自己写的代码,加以理解。 + +![](http://image.iswbm.com/20190519001930.png) + +调用这个方法可以知道,每调用一次,它都会经过描述符类的 `__get__` 。 + +```python +>>> Test.myfunc() +in staticmethod __get__ +hello +>>> Test().myfunc() +in staticmethod __get__ +hello +``` + +### 5. 基于描述符如何实现classmethod + +同样的 ` classmethod` 也是一样。 + +```python +class classmethod(object): + def __init__(self, f): + self.f = f + + def __get__(self, instance, owner=None): + print("in classmethod __get__") + + def newfunc(*args): + return self.f(owner, *args) + return newfunc + +class Test: + def myfunc(cls): + print("hello") + + # 重点:这就是描述符的体现 + myfunc = classmethod(myfunc) +``` + +验证结果如下 + +```python +>>> Test.myfunc() +in classmethod __get__ +hello +>>> Test().myfunc() +in classmethod __get__ +hello +``` + +讲完了 `property`、`staticmethod`和`classmethod` 与 描述符的关系。我想你应该对描述符在 Python 中的应用有了更深的理解。对于 super 的实现原理,就交由你来自己完成。 + +### 6. 所有实例共享描述符 + +通过以上内容的学习,你是不是觉得自己已经对描述符足够了解了呢? + +可在这里,我想说以上的描述符代码都有问题。 + +问题在哪里呢?请看下面这个例子。 + +```python +class Score: + def __init__(self, default=0): + self._value = default + + def __get__(self, instance, owner): + return self._value + + def __set__(self, instance, value): + if 0 <= value <= 100: + self._value = value + else: + raise ValueError + + +class Student: + math = Score(0) + chinese = Score(0) + english = Score(0) + + def __repr__(self): + return "".format(self.math, self.chinese, self.english) +``` + +Student 里没有像前面那样写了构造函数,但是关键不在这儿,没写只是因为没必要写。 + +然后来看一下会出现什么样的问题呢 + +```python +>>> std1 = Student() +>>> std1 + +>>> std1.math = 85 +>>> std1 + +>>> std2 = Student() +>>> std2 # std2 居然共享了std1 的属性值 + +>>> std2.math = 100 +>>> std1 # std2 也会改变std1 的属性值 + +``` + +从结果上来看,std2 居然共享了 std1 的属性值,只要其中一个实例的变量发生改变,另一个实例的变量也会跟着改变。 + +探其根因,是由于此时 math,chinese,english 三个全部是类变量,导致 std2 和 std1 在访问 math,chinese,english 这三个变量时,其实都是访问类变量。 + +问题是不是来了?小明和小强的分数怎么可能是绑定的呢?这很明显与实际业务不符。 + +使用描述符给我们制造了便利,却无形中给我们带来了麻烦,难道这也是描述符的特性吗? + +描述符是个很好用的特性,会出现这个问题,是由于我们之前写的描述符代码都是错误的。 + +描述符的机制,在我看来,只是抢占了访问顺序,而具体的逻辑却要因地制宜,视情况而定。 + +如果要把 math,chinese,english 这三个变量变成实例之间相互隔离的属性,应该这么写。 + +```python +class Score: + def __init__(self, subject): + self.name = subject + + def __get__(self, instance, owner): + return instance.__dict__[self.name] + + def __set__(self, instance, value): + if 0 <= value <= 100: + instance.__dict__[self.name] = value + else: + raise ValueError + + +class Student: + math = Score("math") + chinese = Score("chinese") + english = Score("english") + + def __init__(self, math, chinese, english): + self.math = math + self.chinese = chinese + self.english = english + + def __repr__(self): + return "".format(self.math, self.chinese, self.english) +``` + +引导程序逻辑进入描述符之后,不管你是获取属性,还是设置属性,都是直接作用于 instance 的。 + +![](http://image.iswbm.com/20200812085823.png) + +这段代码,你可以仔细和前面的对比一下。 + +不难看出: + +- 之前的错误代码,更像是把描述符当做了存储节点。 +- 之后的正确代码,则是把描述符直接当做代理,本身不存储值。 + +以上便是我对描述符的全部分享,希望能对你有所帮助。 + + + +## 参考文档 + +- [Python描述器引导(翻译)](https://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html#python) + + + + + +--- + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c03/c03_08.rst b/source/c03/c03_08.rst new file mode 100644 index 0000000..2441ad2 --- /dev/null +++ b/source/c03/c03_08.rst @@ -0,0 +1,655 @@ +3.8 深入理解 Python 中的描述符 +============================== + +|image0| + +学习 Python 这么久了,说起 Python 的优雅之处,能让我脱口而出的, +Descriptor(描述符)特性可以排得上号。 + +描述符 是Python +语言独有的特性,它不仅在应用层使用,在语言语法糖的实现上也有使用到(在下面的文章会一一介绍)。 + +当你点进这篇文章时 + +- 你也许没学过描述符,甚至没听过描述符。 +- 或者你对描述符只是一知半解 + +无论你是哪种,本篇都将带你全面的学习描述符,一起来感受 Python +语言的优雅。 + +1. 为什么要使用描述符? +~~~~~~~~~~~~~~~~~~~~~~~ + +假想你正在给学校写一个成绩管理系统,并没有太多编码经验的你,可能会这样子写。 + +.. code:: python + + class Student: + def __init__(self, name, math, chinese, english): + self.name = name + self.math = math + self.chinese = chinese + self.english = english + + def __repr__(self): + return "".format( + self.name, self.math, self.chinese, self.english + ) + +看起来一切都很合理 + +.. code:: python + + >>> std1 = Student('小明', 76, 87, 68) + >>> std1 + + +但是程序并不像人那么智能,不会自动根据使用场景判断数据的合法性,如果老师在录入成绩的时候,不小心录入了将成绩录成了负数,或者超过100,程序是无法感知的。 + +聪明的你,马上在代码中加入了判断逻辑。 + +.. code:: python + + class Student: + def __init__(self, name, math, chinese, english): + self.name = name + if 0 <= math <= 100: + self.math = math + else: + raise ValueError("Valid value must be in [0, 100]") + + if 0 <= chinese <= 100: + self.chinese = chinese + else: + raise ValueError("Valid value must be in [0, 100]") + + if 0 <= chinese <= 100: + self.english = english + else: + raise ValueError("Valid value must be in [0, 100]") + + + def __repr__(self): + return "".format( + self.name, self.math, self.chinese, self.english + ) + +这下程序稍微有点人工智能了,能够自己明辨是非了。 + +|image1| + +程序是智能了,但在\ ``__init__``\ 里有太多的判断逻辑,很影响代码的可读性。巧的是,你刚好学过 +Property +特性,可以很好的应用在这里。于是你将代码修改成如下,代码的可读性瞬间提升了不少 + +.. code:: python + + class Student: + def __init__(self, name, math, chinese, english): + self.name = name + self.math = math + self.chinese = chinese + self.english = english + + @property + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") + + @property + def chinese(self): + return self._chinese + + @chinese.setter + def chinese(self, value): + if 0 <= value <= 100: + self._chinese = value + else: + raise ValueError("Valid value must be in [0, 100]") + + @property + def english(self): + return self._english + + @english.setter + def english(self, value): + if 0 <= value <= 100: + self._english = value + else: + raise ValueError("Valid value must be in [0, 100]") + + def __repr__(self): + return "".format( + self.name, self.math, self.chinese, self.english + ) + +程序还是一样的人工智能,非常好。 + +|image2| + +你以为你写的代码,已经非常优秀,无懈可击了。 + +没想到,人外有天,你的主管看了你的代码后,深深地叹了口气:类里的三个属性,math、chinese、english,都使用了 +Property +对属性的合法性进行了有效控制。功能上,没有问题,但就是太啰嗦了,三个变量的合法性逻辑都是一样的,只要大于0,小于100 +就可以,代码重复率太高了,这里三个成绩还好,但假设还有地理、生物、历史、化学等十几门的成绩呢,这代码简直没法忍。去了解一下 +Python 的描述符吧。 + +经过主管的指点,你知道了「描述符」这个东西。怀着一颗敬畏之心,你去搜索了下关于 +描述符的用法。 + +其实也很简单,一个实现了 ``描述符协议`` 的类就是一个描述符。 + +什么描述符协议:在类里实现了 +``__get__()``\ 、\ ``__set__()``\ 、\ ``__delete__()`` +其中至少一个方法。 + +- ``__get__``\ : + 用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。 +- ``__set__``\ :将在属性分配操作中调用。不会返回任何内容。 +- ``__delete__``\ :控制删除操作。不会返回内容。 + +对描述符有了大概的了解后,你开始重写上面的方法。 + +如前所述,Score 类是一个描述符,当从 Student 的实例访问 +math、chinese、english这三个属性的时候,都会经过 Score +类里的三个特殊的方法。这里的 Score 避免了 使用Property +出现大量的代码无法复用的尴尬。 + +.. code:: python + + class Score: + def __init__(self, default=0): + self._score = default + + def __set__(self, instance, value): + if not isinstance(value, int): + raise TypeError('Score must be integer') + if not 0 <= value <= 100: + raise ValueError('Valid value must be in [0, 100]') + + self._score = value + + def __get__(self, instance, owner): + return self._score + + def __delete__(self): + del self._score + + class Student: + math = Score(0) + chinese = Score(0) + english = Score(0) + + def __init__(self, name, math, chinese, english): + self.name = name + self.math = math + self.chinese = chinese + self.english = english + + + def __repr__(self): + return "".format( + self.name, self.math, self.chinese, self.english + ) + +实现的效果和前面的一样,可以对数据的合法性进行有效控制(字段类型、数值区间等) + +|image3| + +以上,我举了下具体的实例,从最原始的编码风格到 Property +,最后引出描述符。由浅入深,一步一步带你感受到描述符的优雅之处。 + +到这里,你需要记住的只有一点,就是描述符给我们带来的编码上的便利,它在实现 +``保护属性不受修改``\ 、\ ``属性类型检查`` +的基本功能,同时有大大提高代码的复用率。 + +2. 描述符的访问规则 +~~~~~~~~~~~~~~~~~~~ + +描述符分两种: + +- 数据描述符:实现了\ ``__get__`` 和 ``__set__`` 两种方法的描述符 +- 非数据描述符:只实现了\ ``__get__`` 一种方法的描述符 + +你一定会问,他们有什么区别呢?网上的讲解,我看过几个,很多都把一个简单的东西讲得复杂了。 + +其实就一句话,\ **数据描述器和非数据描述器的区别在于:它们相对于实例的字典的优先级不同**\ 。 + +如果实例字典中有与描述符同名的属性,如果描述符是数据描述符,优先使用数据描述符,如果是非数据描述符,优先使用字典中的属性。 + +这边还是以上节的成绩管理的例子来说明,方便你理解。 + +.. code:: python + + # 数据描述符 + class DataDes: + def __init__(self, default=0): + self._score = default + + def __set__(self, instance, value): + self._score = value + + def __get__(self, instance, owner): + print("访问数据描述符里的 __get__") + return self._score + + # 非数据描述符 + class NoDataDes: + def __init__(self, default=0): + self._score = default + + def __get__(self, instance, owner): + print("访问非数据描述符里的 __get__") + return self._score + + + class Student: + math = DataDes(0) + chinese = NoDataDes(0) + + def __init__(self, name, math, chinese): + self.name = name + self.math = math + self.chinese = chinese + + def __getattribute__(self, item): + print("调用 __getattribute__") + return super(Student, self).__getattribute__(item) + + def __repr__(self): + return "".format( + self.name, self.math, self.chinese) + +需要注意的是,math 是数据描述符,而 chinese +是非数据描述符。从下面的验证中,可以看出,当实例属性和数据描述符同名时,会优先访问数据描述符(如下面的math),而当实例属性和非数据描述符同名时,会优先访问实例属性(\ ``__getattribute__``\ ) + +.. code:: python + + >>> std = Student('xm', 88, 99) + >>> + >>> std.math + 调用 __getattribute__ + 访问数据描述符里的 __get__ + 88 + >>> std.chinese + 调用 __getattribute__ + 99 + +讲完了数据描述符和非数据描述符,我们还需要了解的对象属性的查找规律。 + +当我们对一个实例属性进行访问时,Python 会按 ``obj.__dict__`` → +``type(obj).__dict__`` → ``type(obj)的父类.__dict__`` +顺序进行查找,如果查找到目标属性并发现是一个描述符,Python +会调用描述符协议来改变默认的控制行为。 + +3. 基于描述符如何实现property +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +经过上面的讲解,我们已经知道如何定义描述符,且明白了描述符是如何工作的。 + +正常人所见过的描述符的用法就是上面提到的那些,我想说的是那只是描述符协议最常见的应用之一,或许你还不知道,其实有很多 +Python 的特性的底层实现机制都是基于 ``描述符协议`` +的,比如我们熟悉的\ ``@property`` 、\ ``@classmethod`` +、\ ``@staticmethod`` 和 ``super`` 等。 + +先来说说 ``property`` 吧。 + +有了前面的基础,我们知道了 property +的基本用法。这里我直接切入主题,从第一篇的例子里精简了一下。 + +.. code:: python + + class Student: + def __init__(self, name): + self.name = name + + @property + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") + +不防再简单回顾一下它的用法,通过property装饰的函数,如例子中的 math +会变成 Student 实例的属性。而对 math 属性赋值会进入 使用 ``math.setter`` +装饰函数的逻辑代码块。 + +为什么说 property 底层是基于描述符协议的呢?通过 PyCharm 点击进入 +property +的源码,很可惜,只是一份类似文档一样的伪源码,并没有其具体的实现逻辑。 + +不过,从这份伪源码的魔法函数结构组成,可以大体知道其实现逻辑。 + +这里我自己通过模仿其函数结构,结合「描述符协议」来自己实现类 +``property`` 特性。 + +代码如下: + +.. code:: python + + class TestProperty(object): + + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + self.fget = fget + self.fset = fset + self.fdel = fdel + self.__doc__ = doc + + def __get__(self, obj, objtype=None): + print("in __get__") + if obj is None: + return self + if self.fget is None: + raise AttributeError + return self.fget(obj) + + def __set__(self, obj, value): + print("in __set__") + if self.fset is None: + raise AttributeError + self.fset(obj, value) + + def __delete__(self, obj): + print("in __delete__") + if self.fdel is None: + raise AttributeError + self.fdel(obj) + + + def getter(self, fget): + print("in getter") + return type(self)(fget, self.fset, self.fdel, self.__doc__) + + def setter(self, fset): + print("in setter") + return type(self)(self.fget, fset, self.fdel, self.__doc__) + + def deleter(self, fdel): + print("in deleter") + return type(self)(self.fget, self.fset, fdel, self.__doc__) + +然后 Student 类,我们也相应改成如下 + +.. code:: python + + class Student: + def __init__(self, name): + self.name = name + + # 其实只有这里改变 + @TestProperty + def math(self): + return self._math + + @math.setter + def math(self, value): + if 0 <= value <= 100: + self._math = value + else: + raise ValueError("Valid value must be in [0, 100]") + +为了尽量让你少产生一点疑惑,我这里做两点说明: + +1. 使用\ ``TestProperty``\ 装饰后,\ ``math`` + 不再是一个函数,而是\ ``TestProperty`` + 类的一个实例。所以第二个math函数可以使用 ``math.setter`` + 来装饰,本质是调用\ ``TestProperty.setter`` 来产生一个新的 + ``TestProperty`` 实例赋值给第二个\ ``math``\ 。 + +2. 第一个 ``math`` 和第二个 ``math`` 是两个不同 ``TestProperty`` + 实例。但他们都属于同一个描述符类(TestProperty),当对 math + 对于赋值时,就会进入 ``TestProperty.__set__``\ ,当对math + 进行取值里,就会进入 + ``TestProperty.__get__``\ 。仔细一看,其实最终访问的还是Student实例的 + ``_math`` 属性。 + +说了这么多,还是运行一下,更加直观一点。 + +.. code:: python + + # 运行后,会直接打印这一行,这是在实例化 TestProperty 并赋值给第二个math + in setter + >>> + >>> s1.math = 90 + in __set__ + >>> s1.math + in __get__ + 90 + +对于以上理解 ``property`` +的运行原理有困难的同学,请务必参照我上面写的两点说明。如有其他疑问,可以加微信与我进行探讨。 + +4. 基于描述符如何实现staticmethod +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +说完了 ``property`` ,这里再来讲讲 ``@classmethod`` 和 ``@staticmethod`` +的实现原理。 + +我这里定义了一个类,用了两种方式来实现静态方法。 + +.. code:: python + + class Test: + @staticmethod + def myfunc(): + print("hello") + + # 上下两种写法等价 + + class Test: + def myfunc(): + print("hello") + # 重点:这就是描述符的体现 + myfunc = staticmethod(myfunc) + +这两种写法是等价的,就好像在 ``property`` +一样,其实以下两种写法也是等价的。 + +.. code:: python + + @TestProperty + def math(self): + return self._math + + math = TestProperty(fget=math) + +话题还是转回到 ``staticmethod`` 这边来吧。 + +由上面的注释,可以看出 ``staticmethod`` +其实就相当于一个描述符类,而\ ``myfunc`` 在此刻变成了一个描述符。关于 +``staticmethod`` 的实现,你可以参照下面这段我自己写的代码,加以理解。 + +|image4| + +调用这个方法可以知道,每调用一次,它都会经过描述符类的 ``__get__`` 。 + +.. code:: python + + >>> Test.myfunc() + in staticmethod __get__ + hello + >>> Test().myfunc() + in staticmethod __get__ + hello + +5. 基于描述符如何实现classmethod +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +同样的 ``classmethod`` 也是一样。 + +.. code:: python + + class classmethod(object): + def __init__(self, f): + self.f = f + + def __get__(self, instance, owner=None): + print("in classmethod __get__") + + def newfunc(*args): + return self.f(owner, *args) + return newfunc + + class Test: + def myfunc(cls): + print("hello") + + # 重点:这就是描述符的体现 + myfunc = classmethod(myfunc) + +验证结果如下 + +.. code:: python + + >>> Test.myfunc() + in classmethod __get__ + hello + >>> Test().myfunc() + in classmethod __get__ + hello + +讲完了 ``property``\ 、\ ``staticmethod``\ 和\ ``classmethod`` 与 +描述符的关系。我想你应该对描述符在 Python 中的应用有了更深的理解。对于 +super 的实现原理,就交由你来自己完成。 + +6. 所有实例共享描述符 +~~~~~~~~~~~~~~~~~~~~~ + +通过以上内容的学习,你是不是觉得自己已经对描述符足够了解了呢? + +可在这里,我想说以上的描述符代码都有问题。 + +问题在哪里呢?请看下面这个例子。 + +.. code:: python + + class Score: + def __init__(self, default=0): + self._value = default + + def __get__(self, instance, owner): + return self._value + + def __set__(self, instance, value): + if 0 <= value <= 100: + self._value = value + else: + raise ValueError + + + class Student: + math = Score(0) + chinese = Score(0) + english = Score(0) + + def __repr__(self): + return "".format(self.math, self.chinese, self.english) + +Student +里没有像前面那样写了构造函数,但是关键不在这儿,没写只是因为没必要写。 + +然后来看一下会出现什么样的问题呢 + +.. code:: python + + >>> std1 = Student() + >>> std1 + + >>> std1.math = 85 + >>> std1 + + >>> std2 = Student() + >>> std2 # std2 居然共享了std1 的属性值 + + >>> std2.math = 100 + >>> std1 # std2 也会改变std1 的属性值 + + +从结果上来看,std2 居然共享了 std1 +的属性值,只要其中一个实例的变量发生改变,另一个实例的变量也会跟着改变。 + +探其根因,是由于此时 math,chinese,english 三个全部是类变量,导致 std2 +和 std1 在访问 math,chinese,english 这三个变量时,其实都是访问类变量。 + +问题是不是来了?小明和小强的分数怎么可能是绑定的呢?这很明显与实际业务不符。 + +使用描述符给我们制造了便利,却无形中给我们带来了麻烦,难道这也是描述符的特性吗? + +描述符是个很好用的特性,会出现这个问题,是由于我们之前写的描述符代码都是错误的。 + +描述符的机制,在我看来,只是抢占了访问顺序,而具体的逻辑却要因地制宜,视情况而定。 + +如果要把 math,chinese,english +这三个变量变成实例之间相互隔离的属性,应该这么写。 + +.. code:: python + + class Score: + def __init__(self, subject): + self.name = subject + + def __get__(self, instance, owner): + return instance.__dict__[self.name] + + def __set__(self, instance, value): + if 0 <= value <= 100: + instance.__dict__[self.name] = value + else: + raise ValueError + + + class Student: + math = Score("math") + chinese = Score("chinese") + english = Score("english") + + def __init__(self, math, chinese, english): + self.math = math + self.chinese = chinese + self.english = english + + def __repr__(self): + return "".format(self.math, self.chinese, self.english) + +引导程序逻辑进入描述符之后,不管你是获取属性,还是设置属性,都是直接作用于 +instance 的。 + +|image5| + +这段代码,你可以仔细和前面的对比一下。 + +不难看出: + +- 之前的错误代码,更像是把描述符当做了存储节点。 +- 之后的正确代码,则是把描述符直接当做代理,本身不存储值。 + +以上便是我对描述符的全部分享,希望能对你有所帮助。 + +参考文档 +-------- + +- `Python描述器引导(翻译) `__ + +-------------- + +|image6| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20190425221322.png +.. |image2| image:: http://image.iswbm.com/20190425221322.png +.. |image3| image:: http://image.iswbm.com/20190425221233.png +.. |image4| image:: http://image.iswbm.com/20190519001930.png +.. |image5| image:: http://image.iswbm.com/20200812085823.png +.. |image6| image:: http://image.iswbm.com/20200607174235.png + diff --git a/source/c03/c03_09.md b/source/c03/c03_09.md new file mode 100644 index 0000000..0da8bd3 --- /dev/null +++ b/source/c03/c03_09.md @@ -0,0 +1,725 @@ +# 3.9 深入探讨 Python 的 import 机制 + +![](http://image.iswbm.com/20200602135014.png) + +所谓的模块导入( `import` ),是指在一个模块中使用另一个模块的代码的操作,它有利于代码的复用。 + +在 Python 中使用 import 关键字来实现这个操作,但不是唯一的方法,还有 `importlib.import_module()` 和 `__import__()` 等。 + +也许你看到这个标题,会说我怎么会发这么基础的文章? + +与此相反。恰恰我觉得这篇文章的内容可以算是 Python 的进阶技能,会深入地探讨并以真实案例讲解 Python import Hook 的知识点。 + +当然为了使文章更系统、全面,前面会有小篇幅讲解基础知识点,但请你有耐心的往后读下去,因为后面才是本篇文章的精华所在,希望你不要错过。 + +![](http://image.iswbm.com/20191027192949.png) + +## 1. 导入系统的基础 + +### 1.1 导入单元构成 + +导入单元有多种,可以是模块、包及变量等。 + +对于这些基础的概念,对于新手还是有必要介绍一下它们的区别。 + +**模块**:类似 \*.py,\*.pyc, \*.pyd ,\*.so,\*.dll 这样的文件,是 Python 代码载体的最小单元。 + +**包** 还可以细分为两种: + +- Regular packages:是一个带有 `__init__.py` 文件的文件夹,此文件夹下可包含其他子包,或者模块 +- Namespace packages + +关于 Namespace packages,有的人会比较陌生,我这里摘抄官方文档的一段说明来解释一下。 + +Namespace packages 是由多个 部分 构成的,每个部分为父包增加一个子包。 各个部分可能处于文件系统的不同位置。 部分也可能处于 zip 文件中、网络上,或者 Python 在导入期间可以搜索的其他地方。 命名空间包并不一定会直接对应到文件系统中的对象;它们有可能是无实体表示的虚拟模块。 + +命名空间包的 `__path__ ` 属性不使用普通的列表。 而是使用定制的可迭代类型,如果其父包的路径 (或者最高层级包的 sys.path) 发生改变,这种对象会在该包内的下一次导入尝试时自动执行新的对包部分的搜索。 + +命名空间包没有 `parent/__init__.py` 文件。 实际上,在导入搜索期间可能找到多个 parent 目录,每个都由不同的部分所提供。 因此 parent/one 的物理位置不一定与 parent/two 相邻。 在这种情况下,Python 将为顶级的 parent 包创建一个命名空间包,无论是它本身还是它的某个子包被导入。 + + + +### 1.2 相对/绝对导入 + +当我们 import 导入模块或包时,Python 提供两种导入方式: + +- 相对导入(relative import ):from . import B 或 from ..A import B,其中.表示当前模块,..表示上层模块 +- 绝对导入(absolute import):import foo.bar 或者 from foo import bar + +你可以根据实际需要进行选择,但有必要说明的是,在早期的版本( Python2.6 之前),Python 默认使用的相对导入。而后来的版本中( Python2.6 之后),都以绝对导入为默认使用的导入方式。 + +使用绝对路径和相对路径各有利弊: + +- 当你在开发维护自己的项目时,应当使用相对路径导入,这样可以避免硬编码带来的麻烦。 +- 而使用绝对路径,会让你模块导入结构更加清晰,而且也避免了重名的包冲突而导入错误。 + +### 1.3 导入的标准写法 + +在 PEP8 中对模块的导入提出了要求,遵守 PEP8规范能让你的代码更具有可读性,我这边也列一下: + +- import 语句应当分行书写 + +```python +# bad +import os,sys + +# good +import os +import sys +``` + +- import语句应当使用absolute import + +```python +# bad +from ..bar import Bar + +# good +from foo.bar import test +``` + +- import语句应当放在文件头部,置于模块说明及docstring之后,全局变量之前 + +- import语句应该按照顺序排列,每组之间用一个空格分隔,按照内置模块,第三方模块,自己所写的模块调用顺序,同时每组内部按照字母表顺序排列 + +```python +# 内置模块 +import os +import sys + +# 第三方模块 +import flask + +# 本地模块 +from foo import bar +``` + +### 1.4 几个有用的 sys 变量 + +`sys.path` 可以列出 Python 模块查找的目录列表 + +```python +>>> import sys +>>> from pprint import pprint +>>> pprint(sys.path) +['', + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', + '/Users/MING/Library/Python/3.6/lib/python/site-packages', + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages'] +>>> +``` + +`sys.meta_path` 存放的是所有的查找器。 + +```python +>>> import sys +>>> from pprint import pprint +>>> pprint(sys.meta_path) +[, + , + ] +``` + + + +`sys.path_importer_cache` 比 `sys.path` 会更大点, 因为它会为所有被加载代码的目录记录它们的查找器。 这包括包的子目录,这些通常在 `sys.path` 中是不存在的。 + +```python +>>> import sys +>>> from pprint import pprint +>>> pprint(sys.path_importer_cache) +{'/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6'), + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/collections': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/collections'), + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/encodings': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/encodings'), + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload'), + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages'), + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip': None, + '/Users/MING': FileFinder('/Users/MING'), + '/Users/MING/Library/Python/3.6/lib/python/site-packages': FileFinder('/Users/MING/Library/Python/3.6/lib/python/site-packages')} +``` + + + +## 2. \__import__ 的妙用 + +import 关键字的使用,可以说是基础中的基础。 + +但这不是模块唯一的方法,还有 `importlib.import_module()` 和 `__import__()` 等。 + +和 import 不同的是,`__import__` 是一个函数,也正是因为这个原因,使得 `__import__` 的使用会更加灵活,常常用于框架中,对于插件的动态加载。 + +实际上,当我们调用 import 导入模块时,其内部也是调用了 `__import__` ,请看如下两种导入方法,他们是等价的。 + +```python +# 使用 import +import os + +# 使用 __import__ +os = __import__('os') +``` + +通过举一反三,下面两种方法同样也是等价的。 + +```python +# 使用 import .. as .. +import pandas as pd + +# 使用 __import__ +pd = __import__('pandas') +``` + +上面我说 `__import__` 常常用于插件的动态,事实上也只有它能做到(相对于 import 来说)。 + +`插件`通常会位于某一特定的文件夹下,在使用过程中,可能你并不会用到全部的插件,也可能你会新增插件。 + +如果使用 import 关键字这种硬编码的方式,显然太不优雅了,当你要新增/修改插件的时候,都需要你修改代码。更合适的做法是,将这些插件以配置的方式,写在配置文件中,然后由代码去读取你的配置,动态导入你要使用的插件,即灵活又方便,也不容易出错。 + +假如我的一个项目中,有 `plugin01` 、`plugin02`、`plugin03 ` 、`plugin04` 四个插件,这些插件下都会实现一个核心方法 `run()` 。但有时候我不想使用全部的插件,只想使用 `plugin02`、`plugin04 ` ,那我就在配置文件中写我要使用的两个插件。 + +```shell +# my.conf +custom_plugins=['plugin02', 'plugin04'] +``` + +那我如何使用动态加载,并运行他们呢? + +```python +# main.py + +for plugin in conf.custom_plugins: + __import__(plugin) + sys.modules[plugin].run() +``` + + + +## 3. 理解模块的缓存 + +在一个模块内部重复引用另一个相同模块,实际并不会导入两次,原因是在使用关键字 `import` 导入模块时,它会先检索 `sys.modules` 里是否已经载入这个模块了,如果已经载入,则不会再次导入,如果不存在,才会去检索导入这个模块。 + +来实验一下,在 `my_mod02` 这个模块里,我 import 两次 `my_mod01` 这个模块,按逻辑每一次 import 会一次 `my_mod01` 里的代码(即打印 `in mod01`),但是验证结果是,只打印了一次。 + +```shell +$ cat my_mod01.py +print('in mod01') + +$ cat my_mod02.py +import my_mod01 +import my_mod01 + +$ python my_mod02.py +in mod01 +``` + +该现象的解释是:因为有 `sys.modules` 的存在。 + +`sys.modules` 是一个字典(key:模块名,value:模块对象),它存放着在当前 namespace 所有已经导入的模块对象。 + +```python +# test_module.py + +import sys +print(sys.modules.get('json', 'NotFound')) + +import json +print(sys.modules.get('json', 'NotFound')) +``` + +运行结果如下,可见在 导入后 json 模块后,`sys.modules` 才有了 json 模块的对象。 + +```shell +$ python test_module.py +NotFound + +``` + + 由于有缓存的存在,使得我们无法重新载入一个模块。 + +但若你想反其道行之,可以借助 importlib 这个神奇的库来实现。事实也确实有此场景,比如在代码调试中,在发现代码有异常并修改后,我们通常要重启服务再次载入程序。这时候,若有了模块重载,就无比方便了,修改完代码后也无需服务的重启,就能继续调试。 + +还是以上面的例子来理解,`my_mod02.py` 改写成如下 + +```python +# my_mod02.py + +import importlib +import my_mod01 +importlib.reload(my_mod01) +``` + +使用 python3 来执行这个模块,与上面不同的是,这边执行了两次 `my_mod01.py` + +```shell +$ python3 my_mod02.py +in mod01 +in mod01 +``` + + + +## 4. 查找器与加载器 + +如果指定名称的模块在 `sys.modules` 找不到,则将发起调用 Python 的导入协议以查找和加载该模块。 + +此协议由两个概念性模块构成,即 `查找器` 和 `加载器`。 + +一个 Python 的模块的导入,其实可以再细分为两个过程: + +1. 由查找器实现的模块查找 +2. 由加载器实现的模块加载 + +### 4.1 查找器是什么? + +查找器(finder),简单点说,查找器定义了一个模块查找机制,让程序知道该如何找到对应的模块。 + +其实 Python 内置了多个默认查找器,其存在于 sys.meta_path 中。 + +但这些查找器对应使用者来说,并不是那么重要,因此在 Python 3.3 之前, Python 解释将其隐藏了,我们称之为隐式查找器。 + +```python +# Python 2.7 +>>> import sys +>>> sys.meta_path +[] +>>> +``` + +由于这点不利于开发者深入理解 import 机制,在 Python 3.3 后,所有的模块导入机制都会通过 sys.meta_path 暴露,不会在有任何隐式导入机制。 + +```python +# Python 3.6 +>>> import sys +>>> from pprint import pprint +>>> pprint(sys.meta_path) +[, + , + ] +``` + +观察一下 Python 默认的这几种查找器 (finder),可以分为三种: + +- 一种知道如何导入内置模块 +- 一种知道如何导入冻结模块 +- 一种知道如何导入来自 [import path](https://docs.python.org/zh-cn/3/glossary.html#term-import-path) 的模块 (即 [path based finder](https://docs.python.org/zh-cn/3/glossary.html#term-path-based-finder))。 + +那我们能不能自已定义一个查找器呢?当然可以,你只要 + +- 定义一个实现了 find_module 方法的类(py2和py3均可),或者实现 find_loader 类方法(仅 py3 有效),如果找到模块需要返回一个 loader 对象或者 ModuleSpec 对象(后面会讲),没找到需要返回 None +- 定义完后,要使用这个查找器,必须注册它,将其插入在 sys.meta_path 的首位,这样就能优先使用。 + +```python +import sys + +class MyFinder(object): + @classmethod + def find_module(cls, name, path, target=None): + print("Importing", name, path, target) + # 将在后面定义 + return MyLoader() + +# 由于 finder 是按顺序读取的,所以必须插入在首位 +sys.meta_path.insert(0, MyFinder) +``` + +查找器可以分为两种: + +```shell +object + +-- Finder (deprecated) + +-- MetaPathFinder + +-- PathEntryFinder +``` + +这里需要注意的是,在 3.4 版前,查找器会直接返回 加载器(Loader)对象,而在 3.4 版后,查找器则会返回模块规格说明(ModuleSpec),其中 包含加载器。 + +而关于什么是 加载器 和 模块规格说明, 请继续往后看。 + +### 4.2 加载器是什么? + +查找器只负责查找定位找模,而真正负责加载模块的,是加载器(loader)。 + +一般的 loader 必须定义名为 ` load_module() ` 的方法。 + +为什么这里说一般,因为 loader 还分多种: + +```shell +object + +-- Finder (deprecated) + | +-- MetaPathFinder + | +-- PathEntryFinder + +-- Loader + +-- ResourceLoader --------+ + +-- InspectLoader | + +-- ExecutionLoader --+ + +-- FileLoader + +-- SourceLoader +``` + +通过查看源码可知,不同的加载器的抽象方法各有不同。 + + + +加载器通常由一个 finder 返回。详情参见 PEP 302,对于 abstract base class 可参见 importlib.abc.Loader。 + +那如何自定义我们自己的加载器呢? + +你只要 + +- 定义一个实现了 load_module 方法的类 +- 对与导入有关的属性([点击查看详情](https://docs.python.org/zh-cn/3/reference/import.html#import-related-module-attributes))进行校验 +- 创建模块对象并绑定所有与导入相关的属性变量到该模块上 +- 将此模块保存到 sys.modules 中(顺序很重要,避免递归导入) +- 然后加载模块(这是核心) +- 若加载出错,需要能够处理抛出异常( ImportError) +- 若加载成功,则返回 module 对象 + +若你想看具体的例子,可以接着往后看。 + +### 4.3 模块规格说明 + +导入机制在导入期间会使用有关每个模块的多种信息,特别是加载之前。 大多数信息都是所有模块通用的。 模块规格说明的目的是基于每个模块来封装这些导入相关信息。 + +模块的规格说明会作为模块对象的 `__spec__` 属性对外公开。 有关模块规格的详细内容请参阅 [`ModuleSpec`](https://docs.python.org/zh-cn/3/library/importlib.html#importlib.machinery.ModuleSpec)。 + +在 Python 3.4 后,查找器不再返回加载器,而是返回 ModuleSpec 对象,它储存着更多的信息 + +- 模块名 +- 加载器 +- 模块绝对路径 + +那如何查看一个模块的 ModuleSpec ? + +这边举个例子 + +```shell +$ cat my_mod02.py +import my_mod01 +print(my_mod01.__spec__) + +$ python3 my_mod02.py +in mod01 +ModuleSpec(name='my_mod01', loader=<_frozen_importlib_external.SourceFileLoader object at 0x000000000392DBE0>, origin='/home/MING/my_mod01.py') +``` + +从 ModuleSpec 中可以看到,加载器是包含在内的,那我们如果要重新加载一个模块,是不是又有了另一种思路了? + +来一起验证一下。 + +现在有两个文件: + +一个是 my_info.py + +```python +# my_info.py +name='wangbm' +``` + + 另一个是:main.py + +```python +# main.py +import my_info + +print(my_info.name) + +# 加一个断点 +import pdb;pdb.set_trace() + +# 再加载一次 +my_info.__spec__.loader.load_module() + +print(my_info.name) +``` + +在 `main.py` 处,我加了一个断点,目的是当运行到断点处时,我修改 my_info.py 里的 name 为 `ming` ,以便验证重载是否有效? + +```shell +$ python3 main.py +wangbm +> /home/MING/main.py(9)() +-> my_info.__spec__.loader.load_module() +(Pdb) c +ming +``` + +从结果来看,重载是有效的。 + + + +### 4.4 导入器是什么? + +导入器(importer),也许你在其他文章里会见到它,但其实它并不是个新鲜的东西。 + +它只是同时实现了查找器和加载器两种接口的对象,所以你可以说导入器(importer)是查找器(finder),也可以说它是加载器(loader)。 + + + +## 5. 远程导入模块 + +由于 Python 默认的 查找器和加载器 仅支持本地的模块的导入,并不支持实现远程模块的导入。 + +为了让你更好的理解 Python Import Hook 机制,我下面会通过实例演示,如何自己实现远程导入模块的导入器。 + +### 5.1 动手实现导入器 + +当导入一个包的时候,Python 解释器首先会从 sys.meta_path 中拿到查找器列表。 + +默认顺序是:内建模块查找器 -> 冻结模块查找器 -> 第三方模块路径(本地的 sys.path)查找器 + +若经过这三个查找器,仍然无法查找到所需的模块,则会抛出ImportError异常。 + + + +因此要实现远程导入模块,有两种思路。 + +- 一种是实现自己的元路径导入器; +- 另一种是编写一个钩子,添加到sys.path_hooks里,识别特定的目录命名模式。 + + + +我这里选择第一种方法来做为示例。 + +实现导入器,我们需要分别查找器和加载器。 + +**首先是查找器** + +由源码得知,路径查找器分为两种 + +- MetaPathFinder +- PathEntryFinder + +这里使用 MetaPathFinder 来进行查找器的编写。 + +在 Python 3.4 版本之前,查找器必须实现 `find_module()` 方法,而 Python 3.4+ 版,则推荐使用 `find_spec()` 方法,但这并不意味着你不能使用 `find_module()`,但是在没有 `find_spec()` 方法时,导入协议还是会尝试 `find_module()` 方法。 + +我先举例下使用 `find_module()` 该如何写。 + +```python +from importlib import abc + +class UrlMetaFinder(abc.MetaPathFinder): + def __init__(self, baseurl): + self._baseurl = baseurl + + def find_module(self, fullname, path=None): + if path is None: + baseurl = self._baseurl + else: + # 不是原定义的url就直接返回不存在 + if not path.startswith(self._baseurl): + return None + baseurl = path + + try: + loader = UrlMetaLoader(baseurl) + loader.load_module(fullname) + return loader + except Exception: + return None +``` + +若使用 `find_spec()` ,要注意此方法的调用需要带有两到三个参数。 + +第一个是被导入模块的完整限定名称,例如 `foo.bar.baz`。 第二个参数是供模块搜索使用的路径条目。 对于最高层级模块,第二个参数为 `None`,但对于子模块或子包,第二个参数为父包 `__path__` 属性的值。 如果相应的 `__path__` 属性无法访问,将引发 [`ModuleNotFoundError`](https://docs.python.org/zh-cn/3/library/exceptions.html#ModuleNotFoundError)。 第三个参数是一个将被作为稍后加载目标的现有模块对象。 导入系统仅会在重加载期间传入一个目标模块。 + +```python +from importlib import abc +from importlib.machinery import ModuleSpec + +class UrlMetaFinder(abc.MetaPathFinder): + def __init__(self, baseurl): + self._baseurl = baseurl + def find_spec(self, fullname, path=None, target=None): + if path is None: + baseurl = self._baseurl + else: + # 不是原定义的url就直接返回不存在 + if not path.startswith(self._baseurl): + return None + baseurl = path + + try: + loader = UrlMetaLoader(baseurl) + return ModuleSpec(fullname, loader, is_package=loader.is_package(fullname)) + except Exception: + return None +``` + + + +**接下来是加载器** + +由源码得知,路径查找器分为三种 + +- FileLoader +- SourceLoader + +按理说,两种加载器都可以实现我们想要的功能,我这里选用 SourceLoader 来示范。 + +在 SourceLoader 这个抽象类里,有几个很重要的方法,在你写实现加载器的时候需要注意 + +- get_code:获取源代码,可以根据自己场景实现实现。 +- exec_module:执行源代码,并将变量赋值给 `module.__dict__` +- get_data:抽象方法,必须实现,返回指定路径的字节码。 +- get_filename:抽象方法,必须实现,返回文件名 + +在一些老的博客文章中,你会经常看到 加载器 要实现 `load_module()` ,而这个方法早已在 Python 3.4 的时候就被废弃了,当然为了兼容考虑,你若使用 `load_module()` 也是可以的。 + +```python +from importlib import abc + +class UrlMetaLoader(abc.SourceLoader): + def __init__(self, baseurl): + self.baseurl = baseurl + + def get_code(self, fullname): + f = urllib2.urlopen(self.get_filename(fullname)) + return f.read() + + def load_module(self, fullname): + code = self.get_code(fullname) + mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) + mod.__file__ = self.get_filename(fullname) + mod.__loader__ = self + mod.__package__ = fullname + exec(code, mod.__dict__) + return None + + def get_data(self): + pass + + def execute_module(self, module): + pass + + def get_filename(self, fullname): + return self.baseurl + fullname + '.py' +``` + +当你使用这种旧模式实现自己的加载时,你需要注意两点,很重要: + +- execute_module 必须重载,而且不应该有任何逻辑,即使它并不是抽象方法。 +- load_module,需要你在查找器里手动执行,才能实现模块的加载。。 + +做为替换,你应该使用 `execute_module()` 和 `create_module()` 。由于基类里已经实现了 `execute_module` 和 `create_module()`,并且满足我们的使用场景。我这边可以不用重复实现。和旧模式相比,这里也不需要在设查找器里手动执行 `execute_module()`。 + +```python +import urllib.request as urllib2 + +class UrlMetaLoader(importlib.abc.SourceLoader): + def __init__(self, baseurl): + self.baseurl = baseurl + + def get_code(self, fullname): + f = urllib2.urlopen(self.get_filename(fullname)) + return f.read() + + def get_data(self): + pass + + def get_filename(self, fullname): + return self.baseurl + fullname + '.py' +``` + +查找器和加载器都有了,别忘了往sys.meta_path 注册我们自定义的查找器(UrlMetaFinder)。 + +```python +def install_meta(address): + finder = UrlMetaFinder(address) + sys.meta_path.append(finder) +``` + +所有的代码都解析完毕后,我们将其整理在一个模块(my_importer.py)中 + +```python +# my_importer.py +import sys +import importlib +import urllib.request as urllib2 + +class UrlMetaFinder(importlib.abc.MetaPathFinder): + def __init__(self, baseurl): + self._baseurl = baseurl + + + def find_module(self, fullname, path=None): + if path is None: + baseurl = self._baseurl + else: + # 不是原定义的url就直接返回不存在 + if not path.startswith(self._baseurl): + return None + baseurl = path + + try: + loader = UrlMetaLoader(baseurl) + return loader + except Exception: + return None + +class UrlMetaLoader(importlib.abc.SourceLoader): + def __init__(self, baseurl): + self.baseurl = baseurl + + def get_code(self, fullname): + f = urllib2.urlopen(self.get_filename(fullname)) + return f.read() + + def get_data(self): + pass + + def get_filename(self, fullname): + return self.baseurl + fullname + '.py' + +def install_meta(address): + finder = UrlMetaFinder(address) + sys.meta_path.append(finder) +``` + +### 5.2 搭建远程服务端 + +最开始我说了,要实现一个远程导入模块的方法。 + +我还缺一个在远端的服务器,来存放我的模块,为了方便,我使用python自带的 `http.server` 模块用一条命令即可实现。 + +```shell +$ mkdir httpserver && cd httpserver +$ cat>my_info.py>> from my_importer import install_meta +>>> install_meta('http://localhost:12800/') # 往 sys.meta_path 注册 finder +>>> import my_info # 打印ok,说明导入成功 +ok +>>> my_info.name # 验证可以取得到变量 +'wangbm' +``` + +至此,我实现了一个简易的可以导入远程服务器上的模块的导入器。 + + + +## 参考文档 + +- https://docs.python.org/zh-cn/3/reference/import.html +- https://docs.python.org/zh-cn/3/library/importlib.html#module-importlib.abc +- https://python3-cookbook.readthedocs.io/zh_CN/latest/c10/p11_load_modules_from_remote_machine_by_hooks.html + + + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c03/c03_09.rst b/source/c03/c03_09.rst new file mode 100644 index 0000000..6ab4a1b --- /dev/null +++ b/source/c03/c03_09.rst @@ -0,0 +1,803 @@ +3.9 深入探讨 Python 的 import 机制 +================================== + +|image0| + +所谓的模块导入( ``import`` +),是指在一个模块中使用另一个模块的代码的操作,它有利于代码的复用。 + +在 Python 中使用 import 关键字来实现这个操作,但不是唯一的方法,还有 +``importlib.import_module()`` 和 ``__import__()`` 等。 + +也许你看到这个标题,会说我怎么会发这么基础的文章? + +与此相反。恰恰我觉得这篇文章的内容可以算是 Python +的进阶技能,会深入地探讨并以真实案例讲解 Python import Hook 的知识点。 + +当然为了使文章更系统、全面,前面会有小篇幅讲解基础知识点,但请你有耐心的往后读下去,因为后面才是本篇文章的精华所在,希望你不要错过。 + +|image1| + +1. 导入系统的基础 +----------------- + +1.1 导入单元构成 +~~~~~~~~~~~~~~~~ + +导入单元有多种,可以是模块、包及变量等。 + +对于这些基础的概念,对于新手还是有必要介绍一下它们的区别。 + +**模块**\ :类似 \*.py,*.pyc, \*.pyd ,*.so,*.dll 这样的文件,是 +Python 代码载体的最小单元。 + +**包** 还可以细分为两种: + +- Regular packages:是一个带有 ``__init__.py`` + 文件的文件夹,此文件夹下可包含其他子包,或者模块 +- Namespace packages + +关于 Namespace +packages,有的人会比较陌生,我这里摘抄官方文档的一段说明来解释一下。 + +Namespace packages 是由多个 部分 构成的,每个部分为父包增加一个子包。 +各个部分可能处于文件系统的不同位置。 部分也可能处于 zip +文件中、网络上,或者 Python 在导入期间可以搜索的其他地方。 +命名空间包并不一定会直接对应到文件系统中的对象;它们有可能是无实体表示的虚拟模块。 + +命名空间包的 ``__path__`` 属性不使用普通的列表。 +而是使用定制的可迭代类型,如果其父包的路径 (或者最高层级包的 sys.path) +发生改变,这种对象会在该包内的下一次导入尝试时自动执行新的对包部分的搜索。 + +命名空间包没有 ``parent/__init__.py`` 文件。 +实际上,在导入搜索期间可能找到多个 parent +目录,每个都由不同的部分所提供。 因此 parent/one 的物理位置不一定与 +parent/two 相邻。 在这种情况下,Python 将为顶级的 parent +包创建一个命名空间包,无论是它本身还是它的某个子包被导入。 + +1.2 相对/绝对导入 +~~~~~~~~~~~~~~~~~ + +当我们 import 导入模块或包时,Python 提供两种导入方式: + +- 相对导入(relative import ):from . import B 或 from ..A import + B,其中.表示当前模块,..表示上层模块 +- 绝对导入(absolute import):import foo.bar 或者 from foo import bar + +你可以根据实际需要进行选择,但有必要说明的是,在早期的版本( Python2.6 +之前),Python 默认使用的相对导入。而后来的版本中( Python2.6 +之后),都以绝对导入为默认使用的导入方式。 + +使用绝对路径和相对路径各有利弊: + +- 当你在开发维护自己的项目时,应当使用相对路径导入,这样可以避免硬编码带来的麻烦。 +- 而使用绝对路径,会让你模块导入结构更加清晰,而且也避免了重名的包冲突而导入错误。 + +1.3 导入的标准写法 +~~~~~~~~~~~~~~~~~~ + +在 PEP8 中对模块的导入提出了要求,遵守 +PEP8规范能让你的代码更具有可读性,我这边也列一下: + +- import 语句应当分行书写 + +.. code:: python + + # bad + import os,sys + + # good + import os + import sys + +- import语句应当使用absolute import + +.. code:: python + + # bad + from ..bar import Bar + + # good + from foo.bar import test + +- import语句应当放在文件头部,置于模块说明及docstring之后,全局变量之前 + +- import语句应该按照顺序排列,每组之间用一个空格分隔,按照内置模块,第三方模块,自己所写的模块调用顺序,同时每组内部按照字母表顺序排列 + +.. code:: python + + # 内置模块 + import os + import sys + + # 第三方模块 + import flask + + # 本地模块 + from foo import bar + +1.4 几个有用的 sys 变量 +~~~~~~~~~~~~~~~~~~~~~~~ + +``sys.path`` 可以列出 Python 模块查找的目录列表 + +.. code:: python + + >>> import sys + >>> from pprint import pprint + >>> pprint(sys.path) + ['', + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip', + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6', + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload', + '/Users/MING/Library/Python/3.6/lib/python/site-packages', + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages'] + >>> + +``sys.meta_path`` 存放的是所有的查找器。 + +.. code:: python + + >>> import sys + >>> from pprint import pprint + >>> pprint(sys.meta_path) + [, + , + ] + +``sys.path_importer_cache`` 比 ``sys.path`` 会更大点, +因为它会为所有被加载代码的目录记录它们的查找器。 +这包括包的子目录,这些通常在 ``sys.path`` 中是不存在的。 + +.. code:: python + + >>> import sys + >>> from pprint import pprint + >>> pprint(sys.path_importer_cache) + {'/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6'), + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/collections': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/collections'), + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/encodings': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/encodings'), + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload'), + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages': FileFinder('/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages'), + '/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip': None, + '/Users/MING': FileFinder('/Users/MING'), + '/Users/MING/Library/Python/3.6/lib/python/site-packages': FileFinder('/Users/MING/Library/Python/3.6/lib/python/site-packages')} + +2. \__import_\_ 的妙用 +---------------------- + +import 关键字的使用,可以说是基础中的基础。 + +但这不是模块唯一的方法,还有 ``importlib.import_module()`` 和 +``__import__()`` 等。 + +和 import 不同的是,\ ``__import__`` +是一个函数,也正是因为这个原因,使得 ``__import__`` +的使用会更加灵活,常常用于框架中,对于插件的动态加载。 + +实际上,当我们调用 import 导入模块时,其内部也是调用了 ``__import__`` +,请看如下两种导入方法,他们是等价的。 + +.. code:: python + + # 使用 import + import os + + # 使用 __import__ + os = __import__('os') + +通过举一反三,下面两种方法同样也是等价的。 + +.. code:: python + + # 使用 import .. as .. + import pandas as pd + + # 使用 __import__ + pd = __import__('pandas') + +上面我说 ``__import__`` 常常用于插件的动态,事实上也只有它能做到(相对于 +import 来说)。 + +``插件``\ 通常会位于某一特定的文件夹下,在使用过程中,可能你并不会用到全部的插件,也可能你会新增插件。 + +如果使用 import +关键字这种硬编码的方式,显然太不优雅了,当你要新增/修改插件的时候,都需要你修改代码。更合适的做法是,将这些插件以配置的方式,写在配置文件中,然后由代码去读取你的配置,动态导入你要使用的插件,即灵活又方便,也不容易出错。 + +假如我的一个项目中,有 ``plugin01`` 、\ ``plugin02``\ 、\ ``plugin03`` +、\ ``plugin04`` 四个插件,这些插件下都会实现一个核心方法 ``run()`` +。但有时候我不想使用全部的插件,只想使用 ``plugin02``\ 、\ ``plugin04`` +,那我就在配置文件中写我要使用的两个插件。 + +.. code:: shell + + # my.conf + custom_plugins=['plugin02', 'plugin04'] + +那我如何使用动态加载,并运行他们呢? + +.. code:: python + + # main.py + + for plugin in conf.custom_plugins: + __import__(plugin) + sys.modules[plugin].run() + +3. 理解模块的缓存 +----------------- + +在一个模块内部重复引用另一个相同模块,实际并不会导入两次,原因是在使用关键字 +``import`` 导入模块时,它会先检索 ``sys.modules`` +里是否已经载入这个模块了,如果已经载入,则不会再次导入,如果不存在,才会去检索导入这个模块。 + +来实验一下,在 ``my_mod02`` 这个模块里,我 import 两次 ``my_mod01`` +这个模块,按逻辑每一次 import 会一次 ``my_mod01`` 里的代码(即打印 +``in mod01``\ ),但是验证结果是,只打印了一次。 + +.. code:: shell + + $ cat my_mod01.py + print('in mod01') + + $ cat my_mod02.py + import my_mod01 + import my_mod01 + + $ python my_mod02.py + in mod01 + +该现象的解释是:因为有 ``sys.modules`` 的存在。 + +``sys.modules`` +是一个字典(key:模块名,value:模块对象),它存放着在当前 namespace +所有已经导入的模块对象。 + +.. code:: python + + # test_module.py + + import sys + print(sys.modules.get('json', 'NotFound')) + + import json + print(sys.modules.get('json', 'NotFound')) + +运行结果如下,可见在 导入后 json 模块后,\ ``sys.modules`` 才有了 json +模块的对象。 + +.. code:: shell + + $ python test_module.py + NotFound + + +由于有缓存的存在,使得我们无法重新载入一个模块。 + +但若你想反其道行之,可以借助 importlib +这个神奇的库来实现。事实也确实有此场景,比如在代码调试中,在发现代码有异常并修改后,我们通常要重启服务再次载入程序。这时候,若有了模块重载,就无比方便了,修改完代码后也无需服务的重启,就能继续调试。 + +还是以上面的例子来理解,\ ``my_mod02.py`` 改写成如下 + +.. code:: python + + # my_mod02.py + + import importlib + import my_mod01 + importlib.reload(my_mod01) + +使用 python3 来执行这个模块,与上面不同的是,这边执行了两次 +``my_mod01.py`` + +.. code:: shell + + $ python3 my_mod02.py + in mod01 + in mod01 + +4. 查找器与加载器 +----------------- + +如果指定名称的模块在 ``sys.modules`` 找不到,则将发起调用 Python +的导入协议以查找和加载该模块。 + +此协议由两个概念性模块构成,即 ``查找器`` 和 ``加载器``\ 。 + +一个 Python 的模块的导入,其实可以再细分为两个过程: + +1. 由查找器实现的模块查找 +2. 由加载器实现的模块加载 + +4.1 查找器是什么? +~~~~~~~~~~~~~~~~~~ + +查找器(finder),简单点说,查找器定义了一个模块查找机制,让程序知道该如何找到对应的模块。 + +其实 Python 内置了多个默认查找器,其存在于 sys.meta_path 中。 + +但这些查找器对应使用者来说,并不是那么重要,因此在 Python 3.3 之前, +Python 解释将其隐藏了,我们称之为隐式查找器。 + +.. code:: python + + # Python 2.7 + >>> import sys + >>> sys.meta_path + [] + >>> + +由于这点不利于开发者深入理解 import 机制,在 Python 3.3 +后,所有的模块导入机制都会通过 sys.meta_path +暴露,不会在有任何隐式导入机制。 + +.. code:: python + + # Python 3.6 + >>> import sys + >>> from pprint import pprint + >>> pprint(sys.meta_path) + [, + , + ] + +观察一下 Python 默认的这几种查找器 (finder),可以分为三种: + +- 一种知道如何导入内置模块 +- 一种知道如何导入冻结模块 +- 一种知道如何导入来自 `import + path `__ + 的模块 (即 `path based + finder `__)。 + +那我们能不能自已定义一个查找器呢?当然可以,你只要 + +- 定义一个实现了 find_module 方法的类(py2和py3均可),或者实现 + find_loader 类方法(仅 py3 有效),如果找到模块需要返回一个 loader + 对象或者 ModuleSpec 对象(后面会讲),没找到需要返回 None +- 定义完后,要使用这个查找器,必须注册它,将其插入在 sys.meta_path + 的首位,这样就能优先使用。 + +.. code:: python + + import sys + + class MyFinder(object): + @classmethod + def find_module(cls, name, path, target=None): + print("Importing", name, path, target) + # 将在后面定义 + return MyLoader() + + # 由于 finder 是按顺序读取的,所以必须插入在首位 + sys.meta_path.insert(0, MyFinder) + +查找器可以分为两种: + +.. code:: shell + + object + +-- Finder (deprecated) + +-- MetaPathFinder + +-- PathEntryFinder + +这里需要注意的是,在 3.4 版前,查找器会直接返回 +加载器(Loader)对象,而在 3.4 +版后,查找器则会返回模块规格说明(ModuleSpec),其中 包含加载器。 + +而关于什么是 加载器 和 模块规格说明, 请继续往后看。 + +4.2 加载器是什么? +~~~~~~~~~~~~~~~~~~ + +查找器只负责查找定位找模,而真正负责加载模块的,是加载器(loader)。 + +一般的 loader 必须定义名为 ``load_module()`` 的方法。 + +为什么这里说一般,因为 loader 还分多种: + +.. code:: shell + + object + +-- Finder (deprecated) + | +-- MetaPathFinder + | +-- PathEntryFinder + +-- Loader + +-- ResourceLoader --------+ + +-- InspectLoader | + +-- ExecutionLoader --+ + +-- FileLoader + +-- SourceLoader + +通过查看源码可知,不同的加载器的抽象方法各有不同。 + +加载器通常由一个 finder 返回。详情参见 PEP 302,对于 abstract base class +可参见 importlib.abc.Loader。 + +那如何自定义我们自己的加载器呢? + +你只要 + +- 定义一个实现了 load_module 方法的类 +- 对与导入有关的属性(\ `点击查看详情 `__\ )进行校验 +- 创建模块对象并绑定所有与导入相关的属性变量到该模块上 +- 将此模块保存到 sys.modules 中(顺序很重要,避免递归导入) +- 然后加载模块(这是核心) +- 若加载出错,需要能够处理抛出异常( ImportError) +- 若加载成功,则返回 module 对象 + +若你想看具体的例子,可以接着往后看。 + +4.3 模块规格说明 +~~~~~~~~~~~~~~~~ + +导入机制在导入期间会使用有关每个模块的多种信息,特别是加载之前。 +大多数信息都是所有模块通用的。 +模块规格说明的目的是基于每个模块来封装这些导入相关信息。 + +模块的规格说明会作为模块对象的 ``__spec__`` 属性对外公开。 +有关模块规格的详细内容请参阅 +```ModuleSpec`` `__\ 。 + +在 Python 3.4 后,查找器不再返回加载器,而是返回 ModuleSpec +对象,它储存着更多的信息 + +- 模块名 +- 加载器 +- 模块绝对路径 + +那如何查看一个模块的 ModuleSpec ? + +这边举个例子 + +.. code:: shell + + $ cat my_mod02.py + import my_mod01 + print(my_mod01.__spec__) + + $ python3 my_mod02.py + in mod01 + ModuleSpec(name='my_mod01', loader=<_frozen_importlib_external.SourceFileLoader object at 0x000000000392DBE0>, origin='/home/MING/my_mod01.py') + +从 ModuleSpec +中可以看到,加载器是包含在内的,那我们如果要重新加载一个模块,是不是又有了另一种思路了? + +来一起验证一下。 + +现在有两个文件: + +一个是 my_info.py + +.. code:: python + + # my_info.py + name='wangbm' + +另一个是:main.py + +.. code:: python + + # main.py + import my_info + + print(my_info.name) + + # 加一个断点 + import pdb;pdb.set_trace() + + # 再加载一次 + my_info.__spec__.loader.load_module() + + print(my_info.name) + +在 ``main.py`` 处,我加了一个断点,目的是当运行到断点处时,我修改 +my_info.py 里的 name 为 ``ming`` ,以便验证重载是否有效? + +.. code:: shell + + $ python3 main.py + wangbm + > /home/MING/main.py(9)() + -> my_info.__spec__.loader.load_module() + (Pdb) c + ming + +从结果来看,重载是有效的。 + +4.4 导入器是什么? +~~~~~~~~~~~~~~~~~~ + +导入器(importer),也许你在其他文章里会见到它,但其实它并不是个新鲜的东西。 + +它只是同时实现了查找器和加载器两种接口的对象,所以你可以说导入器(importer)是查找器(finder),也可以说它是加载器(loader)。 + +5. 远程导入模块 +--------------- + +由于 Python 默认的 查找器和加载器 +仅支持本地的模块的导入,并不支持实现远程模块的导入。 + +为了让你更好的理解 Python Import Hook +机制,我下面会通过实例演示,如何自己实现远程导入模块的导入器。 + +5.1 动手实现导入器 +~~~~~~~~~~~~~~~~~~ + +当导入一个包的时候,Python 解释器首先会从 sys.meta_path +中拿到查找器列表。 + +默认顺序是:内建模块查找器 -> 冻结模块查找器 -> 第三方模块路径(本地的 +sys.path)查找器 + +若经过这三个查找器,仍然无法查找到所需的模块,则会抛出ImportError异常。 + +因此要实现远程导入模块,有两种思路。 + +- 一种是实现自己的元路径导入器; +- 另一种是编写一个钩子,添加到sys.path_hooks里,识别特定的目录命名模式。 + +我这里选择第一种方法来做为示例。 + +实现导入器,我们需要分别查找器和加载器。 + +**首先是查找器** + +由源码得知,路径查找器分为两种 + +- MetaPathFinder +- PathEntryFinder + +这里使用 MetaPathFinder 来进行查找器的编写。 + +在 Python 3.4 版本之前,查找器必须实现 ``find_module()`` 方法,而 Python +3.4+ 版,则推荐使用 ``find_spec()`` 方法,但这并不意味着你不能使用 +``find_module()``\ ,但是在没有 ``find_spec()`` +方法时,导入协议还是会尝试 ``find_module()`` 方法。 + +我先举例下使用 ``find_module()`` 该如何写。 + +.. code:: python + + from importlib import abc + + class UrlMetaFinder(abc.MetaPathFinder): + def __init__(self, baseurl): + self._baseurl = baseurl + + def find_module(self, fullname, path=None): + if path is None: + baseurl = self._baseurl + else: + # 不是原定义的url就直接返回不存在 + if not path.startswith(self._baseurl): + return None + baseurl = path + + try: + loader = UrlMetaLoader(baseurl) + loader.load_module(fullname) + return loader + except Exception: + return None + +若使用 ``find_spec()`` ,要注意此方法的调用需要带有两到三个参数。 + +第一个是被导入模块的完整限定名称,例如 ``foo.bar.baz``\ 。 +第二个参数是供模块搜索使用的路径条目。 对于最高层级模块,第二个参数为 +``None``\ ,但对于子模块或子包,第二个参数为父包 ``__path__`` 属性的值。 +如果相应的 ``__path__`` 属性无法访问,将引发 +```ModuleNotFoundError`` `__\ 。 +第三个参数是一个将被作为稍后加载目标的现有模块对象。 +导入系统仅会在重加载期间传入一个目标模块。 + +.. code:: python + + from importlib import abc + from importlib.machinery import ModuleSpec + + class UrlMetaFinder(abc.MetaPathFinder): + def __init__(self, baseurl): + self._baseurl = baseurl + def find_spec(self, fullname, path=None, target=None): + if path is None: + baseurl = self._baseurl + else: + # 不是原定义的url就直接返回不存在 + if not path.startswith(self._baseurl): + return None + baseurl = path + + try: + loader = UrlMetaLoader(baseurl) + return ModuleSpec(fullname, loader, is_package=loader.is_package(fullname)) + except Exception: + return None + +**接下来是加载器** + +由源码得知,路径查找器分为三种 + +- FileLoader +- SourceLoader + +按理说,两种加载器都可以实现我们想要的功能,我这里选用 SourceLoader +来示范。 + +在 SourceLoader +这个抽象类里,有几个很重要的方法,在你写实现加载器的时候需要注意 + +- get_code:获取源代码,可以根据自己场景实现实现。 +- exec_module:执行源代码,并将变量赋值给 ``module.__dict__`` +- get_data:抽象方法,必须实现,返回指定路径的字节码。 +- get_filename:抽象方法,必须实现,返回文件名 + +在一些老的博客文章中,你会经常看到 加载器 要实现 ``load_module()`` +,而这个方法早已在 Python 3.4 +的时候就被废弃了,当然为了兼容考虑,你若使用 ``load_module()`` +也是可以的。 + +.. code:: python + + from importlib import abc + + class UrlMetaLoader(abc.SourceLoader): + def __init__(self, baseurl): + self.baseurl = baseurl + + def get_code(self, fullname): + f = urllib2.urlopen(self.get_filename(fullname)) + return f.read() + + def load_module(self, fullname): + code = self.get_code(fullname) + mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) + mod.__file__ = self.get_filename(fullname) + mod.__loader__ = self + mod.__package__ = fullname + exec(code, mod.__dict__) + return None + + def get_data(self): + pass + + def execute_module(self, module): + pass + + def get_filename(self, fullname): + return self.baseurl + fullname + '.py' + +当你使用这种旧模式实现自己的加载时,你需要注意两点,很重要: + +- execute_module 必须重载,而且不应该有任何逻辑,即使它并不是抽象方法。 +- load_module,需要你在查找器里手动执行,才能实现模块的加载。。 + +做为替换,你应该使用 ``execute_module()`` 和 ``create_module()`` +。由于基类里已经实现了 ``execute_module`` 和 +``create_module()``\ ,并且满足我们的使用场景。我这边可以不用重复实现。和旧模式相比,这里也不需要在设查找器里手动执行 +``execute_module()``\ 。 + +.. code:: python + + import urllib.request as urllib2 + + class UrlMetaLoader(importlib.abc.SourceLoader): + def __init__(self, baseurl): + self.baseurl = baseurl + + def get_code(self, fullname): + f = urllib2.urlopen(self.get_filename(fullname)) + return f.read() + + def get_data(self): + pass + + def get_filename(self, fullname): + return self.baseurl + fullname + '.py' + +查找器和加载器都有了,别忘了往sys.meta_path +注册我们自定义的查找器(UrlMetaFinder)。 + +.. code:: python + + def install_meta(address): + finder = UrlMetaFinder(address) + sys.meta_path.append(finder) + +所有的代码都解析完毕后,我们将其整理在一个模块(my_importer.py)中 + +.. code:: python + + # my_importer.py + import sys + import importlib + import urllib.request as urllib2 + + class UrlMetaFinder(importlib.abc.MetaPathFinder): + def __init__(self, baseurl): + self._baseurl = baseurl + + + def find_module(self, fullname, path=None): + if path is None: + baseurl = self._baseurl + else: + # 不是原定义的url就直接返回不存在 + if not path.startswith(self._baseurl): + return None + baseurl = path + + try: + loader = UrlMetaLoader(baseurl) + return loader + except Exception: + return None + + class UrlMetaLoader(importlib.abc.SourceLoader): + def __init__(self, baseurl): + self.baseurl = baseurl + + def get_code(self, fullname): + f = urllib2.urlopen(self.get_filename(fullname)) + return f.read() + + def get_data(self): + pass + + def get_filename(self, fullname): + return self.baseurl + fullname + '.py' + + def install_meta(address): + finder = UrlMetaFinder(address) + sys.meta_path.append(finder) + +5.2 搭建远程服务端 +~~~~~~~~~~~~~~~~~~ + +最开始我说了,要实现一个远程导入模块的方法。 + +我还缺一个在远端的服务器,来存放我的模块,为了方便,我使用python自带的 +``http.server`` 模块用一条命令即可实现。 + +.. code:: shell + + $ mkdir httpserver && cd httpserver + $ cat>my_info.py>> from my_importer import install_meta + >>> install_meta('http://localhost:12800/') # 往 sys.meta_path 注册 finder + >>> import my_info # 打印ok,说明导入成功 + ok + >>> my_info.name # 验证可以取得到变量 + 'wangbm' + +至此,我实现了一个简易的可以导入远程服务器上的模块的导入器。 + +参考文档 +-------- + +- https://docs.python.org/zh-cn/3/reference/import.html +- https://docs.python.org/zh-cn/3/library/importlib.html#module-importlib.abc +- https://python3-cookbook.readthedocs.io/zh_CN/latest/c10/p11_load_modules_from_remote_machine_by_hooks.html + +|image2| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20191027192949.png +.. |image2| image:: http://image.iswbm.com/20200607174235.png + diff --git a/source/c03/c03_10.md b/source/c03/c03_10.md new file mode 100644 index 0000000..1551bb4 --- /dev/null +++ b/source/c03/c03_10.md @@ -0,0 +1,148 @@ +# 3.10 Python几个高阶函数 + +![](http://image.iswbm.com/20200602135014.png) + +--- + +## 1. lambda 表达式 + +匿名函数(英语:anonymous function)是指一类无需定义标识符(函数名)的函数。通俗来说呢,就是它可以让我们的函数,可以不需要函数名。 + +正常情况下,我们定义一个函数,使用的是 `def` 关键字,而当你学会使用匿名函数后,替代 `def` 的是 `lambda`。 + +这边使用`def` 和 `lambda` 分别举个例子,你很快就能理解。 + +``` +def mySum(x, y): + return x+y +mySum(2, 3) +# 5 + +(lambda x, y: x+y)(2, 4) +# 6 +``` + +从上面的示例,我们可以看到匿名函数直接运行,省下了很多行的代码,有没有? + +接下来,我们的仔细看一下它的用法 + +带 if/else + +``` +>>>( lambda x, y: x if x < y else y )( 1, 2 ) +1 +``` + +嵌套函数 + +``` +>>>( lambda x: ( lambda y: ( lambda z: x + y + z )( 1 ) )( 2 ) )( 3 ) +6 +``` + +递归函数 + +``` +>>> func = lambda n:1 if n == 0 else n * func(n-1) +>>> func(5) +120 +``` + +或者 + +``` +>>> f = lambda func, n: 1 if n == 0 else n * func( func, n - 1 ) +>>> f(f,4) +24 +``` + +从以上示例来看,lambda 表达式和常规的函数相比,写法比较怪异,可读性相对较差。除了可以直接运行之外,好像并没有其他较为突出的功能,为什么在今天我们要介绍它呢? + +首先我们要知道 lambda 是一个表达式,而不是一个语句。正因为这个特点,我们可以在一些特殊的场景中去使用它。具体是什么场景呢?接下来我们会介绍到几个非常好用的内置函数。 + +## 2. map 函数 + +map 函数,它接收两个参数,第一个参数是一个函数对象(当然也可以是一个lambda表达式),第二个参数是一个序列。 + +它可以实现怎样的功能呢,我举个例子你就明白了。 + +``` +>>> map(lambda x: x*2, [1,2,3,4,5]) +[2, 4, 6, 8, 10] +``` + +可以很清楚地看到,它可以将后面序列中的每一个元素做为参数传入lambda中。 + +当我们不使用 map 函数时,你也许会这样子写。 + +``` +mylist=[] +for i in [1,2,3,4,5]: + mylist.append(i*2) +``` + +## 3. filter 函数 + +filter 函数,和 map 函数相似。同样也是接收两个参数,一个lambda 表达式,一个序列。它会遍历后面序列中每一个元素,并将其做为参数传入lambda表达式中,当表达式返回 True,则元素会被保留下来,当表达式返回 False ,则元素会被丢弃。 + +下面这个例子,将过滤出一个列表中小于0的元素。 + +``` +>>>filter(lambda x: x < 0, range(-5, 5)) +[-5, -4, -3, -2, -1] +``` + +## 4. reduce 函数 + +reduce 函数,也是类似的。它的作用是先对序列中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 lambda 函数运算,将其得到的结果再与第四个元素进行运算,以此类推下去直到后面没有元素了。 + +![](http://image.iswbm.com/20200930175131.png) + +这边举个例子你也就明白了。 + +``` +>>>reduce(lambda x,y: x+y, [1,2,3,4,5]) +15 +``` + +它的运算过程分解一下是这样的。 + +``` +1+2=3 +3+3=6 +6+4+10 +10+5=15 +``` + +## 5. 注意点 + +以上几个函数,熟练的掌握它们的写法,可以让我们的代码看起来更加的 Pythonic ,在某一程度上代码看起来更加的简洁。 + +如果你是新手呢,你需要注意的是,以上示例是在 Python2.x 环境下演示的。而在 Python3.x 中,却有所不同,你可以自己尝试一下。 + +这里总结一下: + +第一点,map 和 filter 函数返回的都不再是一个列表,而是一个迭代器对象。这里以map为例 + +``` +>>> map_obj = map(lambda x: x*2, [1,2,3,4,5]) +>>> from collections.abc import Iterator +>>> isinstance(map_obj, Iterator) +True +>>> next(map_obj) +2 +>>> list(map_obj) +[4, 6, 8, 10] +``` + +第二点,reduce 不可以直接调用,而是要先导入才能使用, + +``` +from functools import reduce +``` + + + +--- + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c03/c03_10.rst b/source/c03/c03_10.rst new file mode 100644 index 0000000..b52655c --- /dev/null +++ b/source/c03/c03_10.rst @@ -0,0 +1,169 @@ +3.10 Python几个高阶函数 +======================= + +|image0| + +-------------- + +1. lambda 表达式 +---------------- + +匿名函数(英语:anonymous +function)是指一类无需定义标识符(函数名)的函数。通俗来说呢,就是它可以让我们的函数,可以不需要函数名。 + +正常情况下,我们定义一个函数,使用的是 ``def`` +关键字,而当你学会使用匿名函数后,替代 ``def`` 的是 ``lambda``\ 。 + +这边使用\ ``def`` 和 ``lambda`` 分别举个例子,你很快就能理解。 + +:: + + def mySum(x, y): + return x+y + mySum(2, 3) + # 5 + + (lambda x, y: x+y)(2, 4) + # 6 + +从上面的示例,我们可以看到匿名函数直接运行,省下了很多行的代码,有没有? + +接下来,我们的仔细看一下它的用法 + +带 if/else + +:: + + >>>( lambda x, y: x if x < y else y )( 1, 2 ) + 1 + +嵌套函数 + +:: + + >>>( lambda x: ( lambda y: ( lambda z: x + y + z )( 1 ) )( 2 ) )( 3 ) + 6 + +递归函数 + +:: + + >>> func = lambda n:1 if n == 0 else n * func(n-1) + >>> func(5) + 120 + +或者 + +:: + + >>> f = lambda func, n: 1 if n == 0 else n * func( func, n - 1 ) + >>> f(f,4) + 24 + +从以上示例来看,lambda +表达式和常规的函数相比,写法比较怪异,可读性相对较差。除了可以直接运行之外,好像并没有其他较为突出的功能,为什么在今天我们要介绍它呢? + +首先我们要知道 lambda +是一个表达式,而不是一个语句。正因为这个特点,我们可以在一些特殊的场景中去使用它。具体是什么场景呢?接下来我们会介绍到几个非常好用的内置函数。 + +2. map 函数 +----------- + +map +函数,它接收两个参数,第一个参数是一个函数对象(当然也可以是一个lambda表达式),第二个参数是一个序列。 + +它可以实现怎样的功能呢,我举个例子你就明白了。 + +:: + + >>> map(lambda x: x*2, [1,2,3,4,5]) + [2, 4, 6, 8, 10] + +可以很清楚地看到,它可以将后面序列中的每一个元素做为参数传入lambda中。 + +当我们不使用 map 函数时,你也许会这样子写。 + +:: + + mylist=[] + for i in [1,2,3,4,5]: + mylist.append(i*2) + +3. filter 函数 +-------------- + +filter 函数,和 map 函数相似。同样也是接收两个参数,一个lambda +表达式,一个序列。它会遍历后面序列中每一个元素,并将其做为参数传入lambda表达式中,当表达式返回 +True,则元素会被保留下来,当表达式返回 False ,则元素会被丢弃。 + +下面这个例子,将过滤出一个列表中小于0的元素。 + +:: + + >>>filter(lambda x: x < 0, range(-5, 5)) + [-5, -4, -3, -2, -1] + +4. reduce 函数 +-------------- + +reduce 函数,也是类似的。它的作用是先对序列中的第 1、2 +个元素进行操作,得到的结果再与第三个数据用 lambda +函数运算,将其得到的结果再与第四个元素进行运算,以此类推下去直到后面没有元素了。 + +|image1| + +这边举个例子你也就明白了。 + +:: + + >>>reduce(lambda x,y: x+y, [1,2,3,4,5]) + 15 + +它的运算过程分解一下是这样的。 + +:: + + 1+2=3 + 3+3=6 + 6+4+10 + 10+5=15 + +5. 注意点 +--------- + +以上几个函数,熟练的掌握它们的写法,可以让我们的代码看起来更加的 +Pythonic ,在某一程度上代码看起来更加的简洁。 + +如果你是新手呢,你需要注意的是,以上示例是在 Python2.x +环境下演示的。而在 Python3.x 中,却有所不同,你可以自己尝试一下。 + +这里总结一下: + +第一点,map 和 filter +函数返回的都不再是一个列表,而是一个迭代器对象。这里以map为例 + +:: + + >>> map_obj = map(lambda x: x*2, [1,2,3,4,5]) + >>> from collections.abc import Iterator + >>> isinstance(map_obj, Iterator) + True + >>> next(map_obj) + 2 + >>> list(map_obj) + [4, 6, 8, 10] + +第二点,reduce 不可以直接调用,而是要先导入才能使用, + +:: + + from functools import reduce + +-------------- + +|image2| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200930175131.png +.. |image2| image:: http://image.iswbm.com/20200607174235.png + diff --git a/source/c03/c03_11.md b/source/c03/c03_11.md new file mode 100644 index 0000000..aa4a66c --- /dev/null +++ b/source/c03/c03_11.md @@ -0,0 +1,198 @@ +# 3.11 with 与 上下文管理器 + +![](http://image.iswbm.com/20200602135014.png) + +`with` 这个关键字,对于每一学习Python的人,都不会陌生。 + +操作文本对象的时候,几乎所有的人都会让我们要用 `with open` ,这就是一个上下文管理的例子。你一定已经相当熟悉了,我就不再废话了。 + +```python +with open('test.txt') as f: + print f.readlines() +``` + +## 1. what context manager? + +**基本语法** + +```python +with EXPR as VAR: + BLOCK +``` + +先理清几个概念 + +``` +1. 上下文表达式:with open('test.txt') as f: +2. 上下文管理器:open('test.txt') +3. f 不是上下文管理器,应该是资源对象。 +``` + +## 2. how context manager? + +要自己实现这样一个上下文管理,要先知道上下文管理协议。 + +简单点说,就是在一个类里,实现了`__enter__`和`__exit__`的方法,这个类的实例就是一个上下文管理器。 + +例如这个示例: + +```python +class Resource(): + def __enter__(self): + print('===connect to resource===') + return self + def __exit__(self, exc_type, exc_val, exc_tb): + print('===close resource connection===') + + def operate(self): + print('===in operation===') + +with Resource() as res: + res.operate() +``` + +我们执行一下,通过日志的打印顺序。可以知道其执行过程。 + +``` +===connect to resource=== +===in operation=== +===close resource connection=== +``` + +从这个示例可以很明显的看出,在编写代码时,可以将资源的连接或者获取放在`__enter__`中,而将资源的关闭写在`__exit__` 中。 + +## 3. why context manager? + +学习时多问自己几个为什么,养成对一些细节的思考,有助于加深对知识点的理解。 + +为什么要使用上下文管理器? + +在我看来,这和 Python 崇尚的优雅风格有关。 + +1. 可以以一种更加优雅的方式,操作(创建/获取/释放)资源,如文件操作、数据库连接; +2. 可以以一种更加优雅的方式,处理异常; + +第一种,我们上面已经以资源的连接为例讲过了。 + +而第二种,会被大多数人所忽略。这里会重点讲一下。 + +大家都知道,处理异常,通常都是使用 `try...execept..` 来捕获处理的。这样做一个不好的地方是,在代码的主逻辑里,会有大量的异常处理代理,这会很大的影响我们的可读性。 + +好一点的做法呢,可以使用 `with` 将异常的处理隐藏起来。 + +仍然是以上面的代码为例,我们将`1/0` 这个`一定会抛出异常的代码`写在 `operate` 里 + +```python +class Resource(): + def __enter__(self): + print('===connect to resource===') + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + print('===close resource connection===') + return True + + def operate(self): + 1/0 + +with Resource() as res: + res.operate() +``` + +运行一下,惊奇地发现,居然不会报错。 + +这就是上下文管理协议的一个强大之处,异常可以在`__exit__` 进行捕获并由你自己决定如何处理,是抛出呢还是在这里就解决了。在`__exit__` 里返回 `True`(没有return 就默认为 return False),就相当于告诉 Python解释器,这个异常我们已经捕获了,不需要再往外抛了。 + +在 写`__exit__` 函数时,需要注意的事,它必须要有这三个参数: + +- exc_type:异常类型 +- exc_val:异常值 +- exc_tb:异常的错误栈信息 + +当主逻辑代码没有报异常时,这三个参数将都为None。 + +## 4. how contextlib? + +在上面的例子中,我们只是为了构建一个上下文管理器,却写了一个类。如果只是要实现一个简单的功能,写一个类未免有点过于繁杂。这时候,我们就想,如果只写一个函数就可以实现上下文管理器就好了。 + +这个点Python早就想到了。它给我们提供了一个装饰器,你只要按照它的代码协议来实现函数内容,就可以将这个函数对象变成一个上下文管理器。 + +我们按照 contextlib 的协议来自己实现一个打开文件(with open)的上下文管理器。 + +```python +import contextlib + +@contextlib.contextmanager +def open_func(file_name): + # __enter__方法 + print('open file:', file_name, 'in __enter__') + file_handler = open(file_name, 'r') + + # 【重点】:yield + yield file_handler + + # __exit__方法 + print('close file:', file_name, 'in __exit__') + file_handler.close() + return + +with open_func('/Users/MING/mytest.txt') as file_in: + for line in file_in: + print(line) +``` + +在被装饰函数里,必须是一个生成器(带有yield),而yield之前的代码,就相当于`__enter__`里的内容。yield 之后的代码,就相当于`__exit__` 里的内容。 + +上面这段代码只能实现上下文管理器的第一个目的(管理资源),并不能实现第二个目的(处理异常)。 + +如果要处理异常,可以改成下面这个样子。 + +```python +import contextlib + +@contextlib.contextmanager +def open_func(file_name): + # __enter__方法 + print('open file:', file_name, 'in __enter__') + file_handler = open(file_name, 'r') + + try: + yield file_handler + except Exception as exc: + # deal with exception + print('the exception was thrown') + finally: + print('close file:', file_name, 'in __exit__') + file_handler.close() + + return + +with open_func('/Users/MING/mytest.txt') as file_in: + for line in file_in: + 1/0 + print(line) +``` + +好像只要讲到上下文管理器,大多数人都会谈到打开文件这个经典的例子。 + +但是在实际开发中,可以使用到上下文管理器的例子也不少。我这边举个我自己的例子。 + +在OpenStack中,给一个虚拟机创建快照时,需要先创建一个临时文件夹,来存放这个本地快照镜像,等到本地快照镜像创建完成后,再将这个镜像上传到Glance。然后删除这个临时目录。 + +这段代码的主逻辑是`创建快照`,而`创建临时目录 `,属于前置条件,`删除临时目录`,是收尾工作。 + +虽然代码量很少,逻辑也不复杂,但是“`创建临时目录,使用完后再删除临时目录`”这个功能,在一个项目中很多地方都需要用到,如果可以将这段逻辑处理写成一个工具函数作为一个上下文管理器,那代码的复用率也大大提高。 + +代码是这样的 + +![](http://image.iswbm.com/20190310172800.png) + +总结起来,使用上下文管理器有三个好处: + +1. 提高代码的复用率; +2. 提高代码的优雅度; +3. 提高代码的可读性; + +------ + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c03/c03_11.rst b/source/c03/c03_11.rst new file mode 100644 index 0000000..179f1e0 --- /dev/null +++ b/source/c03/c03_11.rst @@ -0,0 +1,217 @@ +3.11 with 与 上下文管理器 +========================= + +|image0| + +``with`` 这个关键字,对于每一学习Python的人,都不会陌生。 + +操作文本对象的时候,几乎所有的人都会让我们要用 ``with open`` +,这就是一个上下文管理的例子。你一定已经相当熟悉了,我就不再废话了。 + +.. code:: python + + with open('test.txt') as f: + print f.readlines() + +1. what context manager? +------------------------- + +**基本语法** + +.. code:: python + + with EXPR as VAR: + BLOCK + +先理清几个概念 + +:: + + 1. 上下文表达式:with open('test.txt') as f: + 2. 上下文管理器:open('test.txt') + 3. f 不是上下文管理器,应该是资源对象。 + +2. how context manager? +------------------------ + +要自己实现这样一个上下文管理,要先知道上下文管理协议。 + +简单点说,就是在一个类里,实现了\ ``__enter__``\ 和\ ``__exit__``\ 的方法,这个类的实例就是一个上下文管理器。 + +例如这个示例: + +.. code:: python + + class Resource(): + def __enter__(self): + print('===connect to resource===') + return self + def __exit__(self, exc_type, exc_val, exc_tb): + print('===close resource connection===') + + def operate(self): + print('===in operation===') + + with Resource() as res: + res.operate() + +我们执行一下,通过日志的打印顺序。可以知道其执行过程。 + +:: + + ===connect to resource=== + ===in operation=== + ===close resource connection=== + +从这个示例可以很明显的看出,在编写代码时,可以将资源的连接或者获取放在\ ``__enter__``\ 中,而将资源的关闭写在\ ``__exit__`` +中。 + +3. why context manager? +------------------------ + +学习时多问自己几个为什么,养成对一些细节的思考,有助于加深对知识点的理解。 + +为什么要使用上下文管理器? + +在我看来,这和 Python 崇尚的优雅风格有关。 + +1. 可以以一种更加优雅的方式,操作(创建/获取/释放)资源,如文件操作、数据库连接; +2. 可以以一种更加优雅的方式,处理异常; + +第一种,我们上面已经以资源的连接为例讲过了。 + +而第二种,会被大多数人所忽略。这里会重点讲一下。 + +大家都知道,处理异常,通常都是使用 ``try...execept..`` +来捕获处理的。这样做一个不好的地方是,在代码的主逻辑里,会有大量的异常处理代理,这会很大的影响我们的可读性。 + +好一点的做法呢,可以使用 ``with`` 将异常的处理隐藏起来。 + +仍然是以上面的代码为例,我们将\ ``1/0`` +这个\ ``一定会抛出异常的代码``\ 写在 ``operate`` 里 + +.. code:: python + + class Resource(): + def __enter__(self): + print('===connect to resource===') + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + print('===close resource connection===') + return True + + def operate(self): + 1/0 + + with Resource() as res: + res.operate() + +运行一下,惊奇地发现,居然不会报错。 + +这就是上下文管理协议的一个强大之处,异常可以在\ ``__exit__`` +进行捕获并由你自己决定如何处理,是抛出呢还是在这里就解决了。在\ ``__exit__`` +里返回 ``True``\ (没有return 就默认为 return False),就相当于告诉 +Python解释器,这个异常我们已经捕获了,不需要再往外抛了。 + +在 写\ ``__exit__`` 函数时,需要注意的事,它必须要有这三个参数: + +- exc_type:异常类型 +- exc_val:异常值 +- exc_tb:异常的错误栈信息 + +当主逻辑代码没有报异常时,这三个参数将都为None。 + +4. how contextlib? +------------------ + +在上面的例子中,我们只是为了构建一个上下文管理器,却写了一个类。如果只是要实现一个简单的功能,写一个类未免有点过于繁杂。这时候,我们就想,如果只写一个函数就可以实现上下文管理器就好了。 + +这个点Python早就想到了。它给我们提供了一个装饰器,你只要按照它的代码协议来实现函数内容,就可以将这个函数对象变成一个上下文管理器。 + +我们按照 contextlib 的协议来自己实现一个打开文件(with +open)的上下文管理器。 + +.. code:: python + + import contextlib + + @contextlib.contextmanager + def open_func(file_name): + # __enter__方法 + print('open file:', file_name, 'in __enter__') + file_handler = open(file_name, 'r') + + # 【重点】:yield + yield file_handler + + # __exit__方法 + print('close file:', file_name, 'in __exit__') + file_handler.close() + return + + with open_func('/Users/MING/mytest.txt') as file_in: + for line in file_in: + print(line) + +在被装饰函数里,必须是一个生成器(带有yield),而yield之前的代码,就相当于\ ``__enter__``\ 里的内容。yield +之后的代码,就相当于\ ``__exit__`` 里的内容。 + +上面这段代码只能实现上下文管理器的第一个目的(管理资源),并不能实现第二个目的(处理异常)。 + +如果要处理异常,可以改成下面这个样子。 + +.. code:: python + + import contextlib + + @contextlib.contextmanager + def open_func(file_name): + # __enter__方法 + print('open file:', file_name, 'in __enter__') + file_handler = open(file_name, 'r') + + try: + yield file_handler + except Exception as exc: + # deal with exception + print('the exception was thrown') + finally: + print('close file:', file_name, 'in __exit__') + file_handler.close() + + return + + with open_func('/Users/MING/mytest.txt') as file_in: + for line in file_in: + 1/0 + print(line) + +好像只要讲到上下文管理器,大多数人都会谈到打开文件这个经典的例子。 + +但是在实际开发中,可以使用到上下文管理器的例子也不少。我这边举个我自己的例子。 + +在OpenStack中,给一个虚拟机创建快照时,需要先创建一个临时文件夹,来存放这个本地快照镜像,等到本地快照镜像创建完成后,再将这个镜像上传到Glance。然后删除这个临时目录。 + +这段代码的主逻辑是\ ``创建快照``\ ,而\ ``创建临时目录``\ ,属于前置条件,\ ``删除临时目录``\ ,是收尾工作。 + +虽然代码量很少,逻辑也不复杂,但是“``创建临时目录,使用完后再删除临时目录``”这个功能,在一个项目中很多地方都需要用到,如果可以将这段逻辑处理写成一个工具函数作为一个上下文管理器,那代码的复用率也大大提高。 + +代码是这样的 + +|image1| + +总结起来,使用上下文管理器有三个好处: + +1. 提高代码的复用率; +2. 提高代码的优雅度; +3. 提高代码的可读性; + +-------------- + +|image2| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20190310172800.png +.. |image2| image:: http://image.iswbm.com/20200607174235.png + diff --git a/source/c03/c03_12.md b/source/c03/c03_12.md new file mode 100644 index 0000000..4829596 --- /dev/null +++ b/source/c03/c03_12.md @@ -0,0 +1,106 @@ +# 3.12 静态方法其实暗藏玄机 + +![](http://image.iswbm.com/20200602135014.png) + +静态方法,应该没人不知道吧? + +它可以算是一个很基础、简单的知识点。 + +但是就是这样一个知识点,也有不少可以探究的东西。 + +不信我问你三个问题,你看能否答上来。 + +1. Python2.x和3.x中,函数和方法的区分有什么不同? +2. 有了类/实例方法和普通函数,为什么还会有静态方法? +3. Python3.x 中,静态方法有几种写法? + +如果你没能答上来,那你可以尝试在下文中寻找答案。 + +--- + +在 Python 2 中的函数和方法的区别,十分清晰,很好分辨。但在 Python3中,我却发现完全又是另一套准则。 + +首先先来 Python2 的(以下在 Python2.7中测试通过) + +![](http://image.iswbm.com/20190630111243.png) + +可以得出结论: + +1、普通函数(未定位在类里),都是函数。 + +2、静态方法(@staticmethod),都是函数。 + +3、类方法(@classmethod),都是方法。 + +4、实例方法(首参数为self且非静态、非类方法的),都是方法。 + +你一定想说,类方法和实例方法,是方法没错呀,毕竟名字本身就有方法,普通函数是函数,我也理解呀。那静态方法,为什么不是方法而是函数呢? + +名字只是一个外在的表面称呼,你能说「赵铁男」就一定是汉子吗? + +我的理解是:方法是一种和对象(实例或者类)绑定后的函数。 + +类方法的首参是`cls`,调用时,无需显示传入。实例方法首参是self,调用时,也无需显示传入。 + +而静态方法,其实和普通函数没啥区别,唯一的区别就是,他定义的位置被放在了类里,做为类或者实例的一个函数。 + +那你肯定又要问了,既然静态方法和普通函数都是一样的,为什么要刻意将它放在类里呢? + +我上面说了,放在类里定义,就可以让它成为类的一个工具函数,这就像你身上随身携带了一把刀,这把刀与你本人并没有什么直接关系,唯一的联系就是这把刀是你的,而你把它带在身上,无论你去到哪里,只要需要,你就可以直接拿出来用上。对比将函数放在类外面,缺点是什么呢?就是当你出门在外(在别的模块里)发现你要用刀的时候,还要特地跑一趟去商店买一把刀(import 这个函数)。 + +另外,我觉得静态方法在业务和设计上的意义,会更多一些。 + +一般静态方法是做为类或者实例的一个工具函数,比如对变量的做一个合法性的检验,对数据进行序列化反序列化操作等等。 + +说完了 Python2 ,再来说说Python3. + +以前我觉得 Python2 对于方法和函数的界线更加清晰,但接触了 Python3,我反而觉得Python3里方法和函数的区分似乎更加合理。 + +还是刚刚那段代码,我更改了解释器为Python3.6(以下在 Python3.6中测试通过) + +![](http://image.iswbm.com/20190630104956.png) + +和Python2的唯一区别是,`People.jump` 在Python3 中变成了函数。 + +这一下颠覆了你刚刚才建立起来的知识体系有木有? + +先别急,我再做个试验,也许你就知道了。 + +**在 Python2中** + +执行People.jump('hello'),会报错说,jump的首参必须为People的实例对象,这可以理解,毕竟jump定义时,第一个参数为self。 + +![](http://image.iswbm.com/20190630105735.png) + +**在 Python3中** + +你可以发现,这里的jump的首参不再要求是 People 的一个实例,而可以是任意的对象,比如我使用字符串对象,也没有报错。 + +![](http://image.iswbm.com/20190630105600.png) + +也就是说,当你往jump中传入的首参为People的实例时,jump 就是方法,而当你传入的首参不是People的实例对象时,jump就是函数。 + +你看,多么灵活呀。 + +再总结一下,在Python3中: + +1、普通函数(未定位在类里),都是函数。 + +2、静态方法(@staticmethod),都是函数。 + +3、类方法(@classmethod),都是方法。 + +4、方法和函数区分没有那么明确,而是更加灵活了,一个函数有可能是方法也有可能是函数。 + +你肯定又要问了,那这是不是就意味着,Python3 中静态方法,可以不用再使用@staticmethod 装饰了呢,反正Python3都可以识别。 + +这是个好问题,是的,可以不用指定,但是最好指定,如果你不指定,你调用这个方法只能通过People.jump,而不能通过 self.jump了,因为首参不是 self,而如果使用@staticmethod 就可以使用self.jump。 + +所以说这是一个规范,就像类的私有方法,规范要求外部最好不要调用,但这不是强制要求,不是说外部就不能调用。 + +写这篇文章的起源,是前两天有位读者在交流里问到了相关的问题,正好没什么主题可以写,就拿过来做为素材整理一下,也正好没有写过静态方法、类方法的内容,没想到简单的东西,也能写出这么多的内容出来。 + + + +![](http://image.iswbm.com/20200607174235.png) + diff --git a/source/c03/c03_12.rst b/source/c03/c03_12.rst new file mode 100644 index 0000000..99e9dae --- /dev/null +++ b/source/c03/c03_12.rst @@ -0,0 +1,122 @@ +3.12 静态方法其实暗藏玄机 +========================= + +|image0| + +静态方法,应该没人不知道吧? + +它可以算是一个很基础、简单的知识点。 + +但是就是这样一个知识点,也有不少可以探究的东西。 + +不信我问你三个问题,你看能否答上来。 + +1. Python2.x和3.x中,函数和方法的区分有什么不同? +2. 有了类/实例方法和普通函数,为什么还会有静态方法? +3. Python3.x 中,静态方法有几种写法? + +如果你没能答上来,那你可以尝试在下文中寻找答案。 + +-------------- + +在 Python 2 中的函数和方法的区别,十分清晰,很好分辨。但在 +Python3中,我却发现完全又是另一套准则。 + +首先先来 Python2 的(以下在 Python2.7中测试通过) + +|image1| + +可以得出结论: + +1、普通函数(未定位在类里),都是函数。 + +2、静态方法(@staticmethod),都是函数。 + +3、类方法(@classmethod),都是方法。 + +4、实例方法(首参数为self且非静态、非类方法的),都是方法。 + +你一定想说,类方法和实例方法,是方法没错呀,毕竟名字本身就有方法,普通函数是函数,我也理解呀。那静态方法,为什么不是方法而是函数呢? + +名字只是一个外在的表面称呼,你能说「赵铁男」就一定是汉子吗? + +我的理解是:方法是一种和对象(实例或者类)绑定后的函数。 + +类方法的首参是\ ``cls``\ ,调用时,无需显示传入。实例方法首参是self,调用时,也无需显示传入。 + +而静态方法,其实和普通函数没啥区别,唯一的区别就是,他定义的位置被放在了类里,做为类或者实例的一个函数。 + +那你肯定又要问了,既然静态方法和普通函数都是一样的,为什么要刻意将它放在类里呢? + +我上面说了,放在类里定义,就可以让它成为类的一个工具函数,这就像你身上随身携带了一把刀,这把刀与你本人并没有什么直接关系,唯一的联系就是这把刀是你的,而你把它带在身上,无论你去到哪里,只要需要,你就可以直接拿出来用上。对比将函数放在类外面,缺点是什么呢?就是当你出门在外(在别的模块里)发现你要用刀的时候,还要特地跑一趟去商店买一把刀(import +这个函数)。 + +另外,我觉得静态方法在业务和设计上的意义,会更多一些。 + +一般静态方法是做为类或者实例的一个工具函数,比如对变量的做一个合法性的检验,对数据进行序列化反序列化操作等等。 + +说完了 Python2 ,再来说说Python3. + +以前我觉得 Python2 对于方法和函数的界线更加清晰,但接触了 +Python3,我反而觉得Python3里方法和函数的区分似乎更加合理。 + +还是刚刚那段代码,我更改了解释器为Python3.6(以下在 +Python3.6中测试通过) + +|image2| + +和Python2的唯一区别是,\ ``People.jump`` 在Python3 中变成了函数。 + +这一下颠覆了你刚刚才建立起来的知识体系有木有? + +先别急,我再做个试验,也许你就知道了。 + +**在 Python2中** + +执行People.jump(‘hello’),会报错说,jump的首参必须为People的实例对象,这可以理解,毕竟jump定义时,第一个参数为self。 + +|image3| + +**在 Python3中** + +你可以发现,这里的jump的首参不再要求是 People +的一个实例,而可以是任意的对象,比如我使用字符串对象,也没有报错。 + +|image4| + +也就是说,当你往jump中传入的首参为People的实例时,jump +就是方法,而当你传入的首参不是People的实例对象时,jump就是函数。 + +你看,多么灵活呀。 + +再总结一下,在Python3中: + +1、普通函数(未定位在类里),都是函数。 + +2、静态方法(@staticmethod),都是函数。 + +3、类方法(@classmethod),都是方法。 + +4、方法和函数区分没有那么明确,而是更加灵活了,一个函数有可能是方法也有可能是函数。 + +你肯定又要问了,那这是不是就意味着,Python3 +中静态方法,可以不用再使用@staticmethod +装饰了呢,反正Python3都可以识别。 + +这是个好问题,是的,可以不用指定,但是最好指定,如果你不指定,你调用这个方法只能通过People.jump,而不能通过 +self.jump了,因为首参不是 self,而如果使用@staticmethod +就可以使用self.jump。 + +所以说这是一个规范,就像类的私有方法,规范要求外部最好不要调用,但这不是强制要求,不是说外部就不能调用。 + +写这篇文章的起源,是前两天有位读者在交流里问到了相关的问题,正好没什么主题可以写,就拿过来做为素材整理一下,也正好没有写过静态方法、类方法的内容,没想到简单的东西,也能写出这么多的内容出来。 + +|image5| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20190630111243.png +.. |image2| image:: http://image.iswbm.com/20190630104956.png +.. |image3| image:: http://image.iswbm.com/20190630105735.png +.. |image4| image:: http://image.iswbm.com/20190630105600.png +.. |image5| image:: http://image.iswbm.com/20200607174235.png + diff --git a/source/c03/c03_13.md b/source/c03/c03_13.md new file mode 100644 index 0000000..32e911b --- /dev/null +++ b/source/c03/c03_13.md @@ -0,0 +1,138 @@ +# 3.13 包导入的三个冷门知识点 + +![](http://image.iswbm.com/20200602135014.png) + +## 1. 使用 \__all__ 控制可被导入的变量 + +使用 `from module import *` 默认情况下会导入 module 里的所有变量,若你只想从模块中导入其中几个变量,可以在 module 中使用 `__all__` 来控制想要被其他模块导入的变量。 + +```python +# profile.py +name='wangbm' +age=27 +gender='male' + +__all__=['name'] +``` + +打开 python console 验证一下 + +```python +>>> from profile import * +>>> print(name) +wangbm +>>> print(age) +Traceback (most recent call last): + File "", line 1, in +NameError: name 'age' is not defined +>>> print(gender) +Traceback (most recent call last): + File "", line 1, in +NameError: name 'gender' is not defined +``` + +`__all__` 仅对于使用`from module import *` 这种情况适用。 + +它经常在一个包的 `__init__.py` 中出现。 + + + +## 2. 命名空间包的神奇之处 + +命名空间包,一个陌生的名字。 + +与我们熟悉的常规包不同的是,它没有 `__init__.py` 文件。 + +更为特殊的是,它可以跨空间地将两个不相邻的子包,合并成一个虚拟机的包,我们将其称之为 `命名空间包`。 + +例如,一个项目的部分代码布局如下 + +``` +foo-package/ + spam/ + blah.py + +bar-package/ + spam/ + grok.py +``` + +在这2个目录里,都有着共同的命名空间spam。在任何一个目录里都没有\__init__.py文件。 + +让我们看看,如果将foo-package和bar-package都加到python模块路径并尝试导入会发生什么? + +```python +>>> import sys +>>> sys.path.extend(['foo-package', 'bar-package']) +>>> import spam.blah +>>> import spam.grok +>>> +``` + + + +当一个包为命名空间包时,他就不再和常规包一样具有 `__file_` 属性,取而代之的是 `__path__` + +```python +>>> import sys +>>> sys.path.extend(['foo-package', 'bar-package']) +>>> import spam.blah +>>> import spam.grok +>>> spam.__path__ +_NamespacePath(['foo-package/spam', 'bar-package/spam']) +>>> spam.__file__ +Traceback (most recent call last): + File "", line 1, in +AttributeError: 'module' object has no attribute '__file__' +``` + + + +## 3. 模块重载中的一个坑 + +由于有 sys.modules 的存在,当你导入一个已导入的模块时,实际上是没有效果的。 + +为了达到模块的重载,有的人会将已导入的包从 sys.modules 中移除后再导入 + +就像下面这样子 + +``` +>>> import foo.bar +successful to be imported +>>> +>>> import foo.bar +>>> +>>> import sys +>>> sys.modules['foo.bar'] + +>>> del sys.modules['foo.bar'] +>>> +>>> import foo.bar +successful to be imported +``` + +上面的例子里我使用的是`import foo.bar` ,如果你使用的是 `from foo import bar` 这种导入形式,会发现重载是同样是无效的。 + +这应该算是一个小坑,不知道的人,会掉入坑中爬不出来。 + +``` +>>> import foo.bar +successful to be imported +>>> +>>> import foo.bar +>>> +>>> import sys +>>> del sys.modules['foo.bar'] +>>> from foo import bar +>>> +``` + + + +因此,在生产环境中可能需要避免重新加载模块。而在调试模式中,它会提供一定的便利,但你要知道这个重载的弊端,以免掉入坑里。 + + + + + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c03/c03_13.rst b/source/c03/c03_13.rst new file mode 100644 index 0000000..d68db2f --- /dev/null +++ b/source/c03/c03_13.rst @@ -0,0 +1,140 @@ +3.13 包导入的三个冷门知识点 +=========================== + +|image0| + +1. 使用 \__all_\_ 控制可被导入的变量 +------------------------------------ + +使用 ``from module import *`` 默认情况下会导入 module +里的所有变量,若你只想从模块中导入其中几个变量,可以在 module 中使用 +``__all__`` 来控制想要被其他模块导入的变量。 + +.. code:: python + + # profile.py + name='wangbm' + age=27 + gender='male' + + __all__=['name'] + +打开 python console 验证一下 + +.. code:: python + + >>> from profile import * + >>> print(name) + wangbm + >>> print(age) + Traceback (most recent call last): + File "", line 1, in + NameError: name 'age' is not defined + >>> print(gender) + Traceback (most recent call last): + File "", line 1, in + NameError: name 'gender' is not defined + +``__all__`` 仅对于使用\ ``from module import *`` 这种情况适用。 + +它经常在一个包的 ``__init__.py`` 中出现。 + +2. 命名空间包的神奇之处 +----------------------- + +命名空间包,一个陌生的名字。 + +与我们熟悉的常规包不同的是,它没有 ``__init__.py`` 文件。 + +更为特殊的是,它可以跨空间地将两个不相邻的子包,合并成一个虚拟机的包,我们将其称之为 +``命名空间包``\ 。 + +例如,一个项目的部分代码布局如下 + +:: + + foo-package/ + spam/ + blah.py + + bar-package/ + spam/ + grok.py + +在这2个目录里,都有着共同的命名空间spam。在任何一个目录里都没有__init__.py文件。 + +让我们看看,如果将foo-package和bar-package都加到python模块路径并尝试导入会发生什么? + +.. code:: python + + >>> import sys + >>> sys.path.extend(['foo-package', 'bar-package']) + >>> import spam.blah + >>> import spam.grok + >>> + +当一个包为命名空间包时,他就不再和常规包一样具有 ``__file_`` +属性,取而代之的是 ``__path__`` + +.. code:: python + + >>> import sys + >>> sys.path.extend(['foo-package', 'bar-package']) + >>> import spam.blah + >>> import spam.grok + >>> spam.__path__ + _NamespacePath(['foo-package/spam', 'bar-package/spam']) + >>> spam.__file__ + Traceback (most recent call last): + File "", line 1, in + AttributeError: 'module' object has no attribute '__file__' + +3. 模块重载中的一个坑 +--------------------- + +由于有 sys.modules +的存在,当你导入一个已导入的模块时,实际上是没有效果的。 + +为了达到模块的重载,有的人会将已导入的包从 sys.modules 中移除后再导入 + +就像下面这样子 + +:: + + >>> import foo.bar + successful to be imported + >>> + >>> import foo.bar + >>> + >>> import sys + >>> sys.modules['foo.bar'] + + >>> del sys.modules['foo.bar'] + >>> + >>> import foo.bar + successful to be imported + +上面的例子里我使用的是\ ``import foo.bar`` ,如果你使用的是 +``from foo import bar`` 这种导入形式,会发现重载是同样是无效的。 + +这应该算是一个小坑,不知道的人,会掉入坑中爬不出来。 + +:: + + >>> import foo.bar + successful to be imported + >>> + >>> import foo.bar + >>> + >>> import sys + >>> del sys.modules['foo.bar'] + >>> from foo import bar + >>> + +因此,在生产环境中可能需要避免重新加载模块。而在调试模式中,它会提供一定的便利,但你要知道这个重载的弊端,以免掉入坑里。 + +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png + diff --git a/source/c03/c03_14.md b/source/c03/c03_14.md new file mode 100644 index 0000000..33029d6 --- /dev/null +++ b/source/c03/c03_14.md @@ -0,0 +1,651 @@ +# 3.14 全面学习 Python 包:包的构建与分发 + +![](http://image.iswbm.com/20200602135014.png) + +## 1. 为什么需要对项目分发打包? + +平常我们习惯了使用 pip 来安装一些第三方模块,这个安装过程之所以简单,是因为模块开发者为我们默默地为我们做了所有繁杂的工作,而这个过程就是 `打包`。 + +打包,就是将你的源代码进一步封装,并且将所有的项目部署工作都事先安排好,这样使用者拿到后即装即用,不用再操心如何部署的问题(如果你不想对照着一堆部署文档手工操作的话)。 + +不管你是在工作中,还是业余准备自己写一个可以上传到 PyPI 的项目,你都要学会如何打包你的项目。 + +Python 发展了这么些年了,项目打包工具也已经很成熟了。他们都有哪些呢? + +你可能听过 `disutils`、 `distutils` 、`distutils2`、`setuptools`等等,好像很熟悉,却又很陌生,他们都是什么关系呢? + +## 2. 包分发的始祖:distutils + +`distutils` 是 Python 的一个标准库,从命名上很容易看出它是一个分发(distribute)工具(utlis),它是 Python 官方开发的一个分发打包工具,所有后续的打包工具,全部都是基于它进行开发的。 + +`distutils` 的精髓在于编写 setup.py,它是模块分发与安装的指导文件。 + +那么如何编写 setup.py 呢?这里面的内容非常多,我会在后面进行详细的解析,请你耐心往下看。 + +你有可能没写过 setup.py ,但你绝对使用过 setup.py 来做一些事情,比如下面这条命令,我们经常用它来进行模块的安装。 + +```shell +$ python setup.py install +``` + +这样的安装方法是通过源码安装,与之对应的是通过二进制软件包的安装,同样我也会在后面进行介绍。 + +## 3. 分发工具升级:setuptools + +`setuptools` 是 distutils 增强版,不包括在标准库中。其扩展了很多功能,能够帮助开发者更好的创建和分发 Python 包。大部分 Python 用户都会使用更先进的 setuptools 模块。 + + **distribute**,或许你在其他地方也见过它,这里也提一下。 + +distribute 是 setuptools 有一个分支版本,分支的原因可能是有一部分开发者认为 setuptools 开发太慢了。但现在,distribute 又合并回了 setuptools 中。因此,我们可以认为它们是同一个东西。 + +还有一个大包分发工具是 **distutils2**,其试图尝试充分利用distutils,detuptools 和 distribute 并成为 Python 标准库中的标准工具。但该计划并没有达到预期的目的,且已经是一个废弃的项目。 + +因此,setuptools 是一个优秀的,可靠的 Python 包安装与分发工具。 + +那么如何在一个干净的环境中安装 setuptools 呢? + +主要有两种方法: + +- 源码安装:在 https://pypi.org/project/setuptools/#files 中下载 zip 包 解压执行 `python setup.py install` 安装 +- 通过引导程序安装:下载引导程序,它可以用来下载或者更新最新版本的 setuptools + +```shell +$ wget http://peak.telecommunity.com/dist/ez_setup.py + +# 安装 +$ python ez_setup.py + +# 更新,以下两种任选 +$ python ez_setup.py –U setuptools +$ pip install -U setuptools +``` + + +## 4. easy_install 使用指南 + +当你安装完 setuptools 后,就拥有了一个叫做 `easy_install` 的第三方管理工具,这也是它区分于 distutils 的一大改进。 + +这里简单介绍一下它的用法,虽然它已经用得非常少了。 + +先是包的安装 + +```shell +# 通过包名,从PyPI寻找最新版本,自动下载、编译、安装 +$ easy_install pkg_name + +# 通过包名从指定下载页寻找链接来安装或升级包 +$ easy_install -f http://pythonpaste.org/package_index.html + +# 指定线上的包地址安装 +$ easy_install http://example.com/path/to/MyPackage-1.2.3.tgz + +# 从本地的 .egg 文件安装 +$ easy_install xxx.egg + +# 在安装时你可以添加额外的参数 +指定安装目录:--install-dir=DIR, -d DIR +指定用户安装:--user +``` + +再者是包的升级 + +```shell +# 从 pypi 中搜索并升级包 +$ easy_install --upgrade pkg_name + +# 指定版本进行升级 +$ easy_install "SomePackage==2.0" +``` + +最后是包的删除 + +```shell +$ easy_install -m pkg_name +``` + +需要注意的是,这样的删除,仅是在 easy-install.pth 文件中删除,使其不能在 python 中使用 这个模块,但实际的包还在你的电脑中,若要删除彻底,需要你手动删除相关的 .egg 及 其他文件。 + + + +默认情况下,easy_install 只会从 pypi 上下载相关软件包,由于这个源在国外,下载包的速度并不理想,使用过pip的朋友自然会想,easy_install 是否能指定源进行安装呢? + +答案是,可以的。 + +编辑配置文件 `/root/.pydistutils.cfg` + +```ini +[easy_install] +index-url=http://mirrors.aliyun.com/pypi/simple/ +find-links=http://mirrors.aliyun.com/pypi/simple/ +``` + +以上仅介绍了 easy_install 的一些常用的方法,想要了解更多,你可以点击官方文档:https://setuptools.readthedocs.io/en/latest/easy_install.html + + + +总结一句:setuptools 是官方提供的一个专业用于包分发的工具,若只从安装的角度来看,它的功能确实简单。它更大的意义是对包的分发很有用,定制化程序非常高,我们现在也还在用它进行版本包的发布。 + + + +## 5. 源码包与二进制包什么区别? + +Python 包的分发可以分为两种: + +1. 以源码包的方式发布 + +源码包安装的过程,是先解压,再编译,最后才安装,所以它是跨平台的,由于每次安装都要进行编译,相对二进包安装方式来说安装速度较慢。 + +源码包的本质是一个压缩包,其常见的格式有: + +![](http://image.iswbm.com/20191218202833.png) + +2. 以二进制包形式发布 + +二进制包的安装过程省去了编译的过程,直接进行解压安装,所以安装速度较源码包来说更快。 + +由于不同平台的编译出来的包无法通用,所以在发布时,需事先编译好多个平台的包。 + +二进制包的常见格式有: + +![](http://image.iswbm.com/20191218203005.png) + +## 6. eggs 与 wheels 有什么区别? + +Egg 格式是由 setuptools 在 2004 年引入,而 Wheel 格式是由 PEP427 在 2012 年定义。Wheel 的出现是为了替代 Egg,它的本质是一个zip包,其现在被认为是 Python 的二进制包的标准格式。 + +以下是 Wheel 和 Egg 的主要区别: + +- Wheel 有一个官方的 PEP427 来定义,而 Egg 没有 PEP 定义 +- Wheel 是一种分发格式,即打包格式。而 Egg 既是一种分发格式,也是一种运行时安装的格式,并且是可以被直接 import +- Wheel 文件不会包含 .pyc 文件 +- Wheel 使用和 PEP376 兼容的 .dist-info 目录,而 Egg 使用 .egg-info 目录 +- Wheel 有着更丰富的命名规则。 +- Wheel 是有版本的。每个 Wheel 文件都包含 wheel 规范的版本和打包的实现 +- Wheel 在内部被 sysconfig path type 管理,因此转向其他格式也更容易 + +wheel 包可以通过 pip 来安装,只不过需要先安装 wheel 模块,然后再使用 pip 的命令。 + +```shell +$ pip install wheel +$ pip wheel --wheel-dir=/local/wheels pkg +``` + + + +## 7. 超详细讲解 setup.py 的编写? + +打包分发最关键的一步是编写 `setup.py` 文件。 + +以下是一个 setup.py 简单的使用示例 + +```python +from setuptools import setup, find_packages + +setup( + name="mytest", + version="1.0", + author="wangbm", + author_email="wongbingming@163.com", + description="Learn to Pack Python Module -->公众号:Python编程时光", + + # 项目主页 + url="http://iswbm.com/", + + # 你要安装的包,通过 setuptools.find_packages 找到当前目录下有哪些包 + packages=find_packages() +) +``` + +接下来,我将慢慢扩充这个setup函数,增加更多的参数,以便你能理解setup函数能做哪些事情。 + +**程序分类信息** + +`classifiers` 参数说明包的分类信息。所有支持的分类列表见:https://pypi.org/pypi?%3Aaction=list_classifiers + +示例: + +```python +from setuptools import setup, find_packages + +setup( + classifiers = [ + # 发展时期,常见的如下 + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + 'Development Status :: 3 - Alpha', + + # 开发的目标用户 + 'Intended Audience :: Developers', + + # 属于什么类型 + 'Topic :: Software Development :: Build Tools', + + # 许可证信息 + 'License :: OSI Approved :: MIT License', + + # 目标 Python 版本 + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + ] +) +``` + + + +**关于文件的分发** + +```python +from setuptools import setup, find_packages + + +setup( + name="mytest", + version="1.0", + author="wangbm", + author_email="wongbingming@163.com", + description="Learn to Pack Python Module", + url="http://iswbm.com/", + packages=find_packages(), + + # 安装过程中,需要安装的静态文件,如配置文件、service文件、图片等 + data_files=[ + ('', ['conf/*.conf']), + ('/usr/lib/systemd/system/', ['bin/*.service']), + ], + + # 希望被打包的文件 + package_data={ + '':['*.txt'], + 'bandwidth_reporter':['*.txt'] + }, + # 不打包某些文件 + exclude_package_data={ + 'bandwidth_reporter':['*.txt'] + } +) +``` + +除了以上的参数配置之外,还可以使用一个叫做 `MANIFEST.in` 的文件,来控制文件的分发。 + +如下这是一个 `MANIFEST.in` 的样例: + +``` +include *.txt +recursive-include examples *.txt *.py +prune examples/sample?/build +``` + +这些配置,规定了如下几点 + +- 所有根目录下的以 txt 为后缀名的文件,都会分发 +- 根目录下的 examples 目录 和 txt、py文件都会分发 +- 路径匹配上 examples/sample?/build 不会分发 + +`MANIFEST.in` 需要放在和 setup.py 同级的顶级目录下,setuptools 会自动读取该文件。 + + + +**关于依赖包下载安装** + +```python +from setuptools import setup, find_packages + + +setup( + ... + + # 表明当前模块依赖哪些包,若环境中没有,则会从pypi中下载安装 + install_requires=['docutils>=0.3'], + + # setup.py 本身要依赖的包,这通常是为一些setuptools的插件准备的配置 + # 这里列出的包,不会自动安装。 + setup_requires=['pbr'], + + # 仅在测试时需要使用的依赖,在正常发布的代码中是没有用的。 + # 在执行python setup.py test时,可以自动安装这三个库,确保测试的正常运行。 + tests_require=[ + 'pytest>=3.3.1', + 'pytest-cov>=2.5.1', + ], + + # 用于安装setup_requires或tests_require里的软件包 + # 这些信息会写入egg的 metadata 信息中 + dependency_links=[ + "http://example2.com/p/foobar-1.0.tar.gz", + ], + + # install_requires 在安装模块时会自动安装依赖包 + # 而 extras_require 不会,这里仅表示该模块会依赖这些包 + # 但是这些包通常不会使用到,只有当你深度使用模块时,才会用到,这里需要你手动安装 + extras_require={ + 'PDF': ["ReportLab>=1.2", "RXP"], + 'reST': ["docutils>=0.3"], + } +) + +``` + +关于 `install_requires`, 有以下五种常用的表示方法: + +1. `'argparse'`,只包含包名。 这种形式只检查包的存在性,不检查版本。 方便,但不利于控制风险。 +2. `'setuptools==38.2.4'`,指定版本。 这种形式把风险降到了最低,确保了开发、测试与部署的版本一致,不会出现意外。 缺点是不利于更新,每次更新都需要改动代码。 +3. `'docutils >= 0.3'`,这是比较常用的形式。 当对某个库比较信任时,这种形式可以自动保持版本为最新。 +4. `'Django >= 1.11, != 1.11.1, <= 2'`,这是比较复杂的形式。 如这个例子,保证了Django的大版本在1.11和2之间,也即1.11.x;并且,排除了已知有问题的版本1.11.1(仅举例)。 对于一些大型、复杂的库,这种形式是最合适的。 +5. `'requests[security, socks] >= 2.18.4'`,这是包含了额外的可选依赖的形式。 正常安装requests会自动安装它的`install_requires`中指定的依赖,而不会安装`security`和`socks`这两组依赖。 这两组依赖是定义在它的`extras_require`中。 这种形式,用在深度使用某些库时。 + + + +**关于安装环境的限制** + +有些库并不是在所以的 Python 版本中都适用的,若一个库安装在一个未兼容的 Python 环境中,理论上不应该在使用时才报错,而应该在安装过程就使其失败,提示禁止安装。 + +这样的功能,可以使用 `python_requires` 来实现。 + +```python +setup( + ... + python_requires='>=2.7, <=3', +) +``` + + + +**生成可执行文件的分发** + +```python +from setuptools import setup, find_packages + + +setup( + name="mytest", + version="1.0", + author="wangbm", + author_email="wongbingming@163.com", + description="Learn to Pack Python Module", + url="http://iswbm.com/", + packages=find_packages(), + + # 用来支持自动生成脚本,安装后会自动生成 /usr/bin/foo 的可执行文件 + # 该文件入口指向 foo/main.py 的main 函数 + entry_points={ + 'console_scripts': [ + 'foo = foo.main:main' + ] + }, + + # 将 bin/foo.sh 和 bar.py 脚本,生成到系统 PATH中 + # 执行 python setup.py install 后 + # 会生成 如 /usr/bin/foo.sh 和 如 /usr/bin/bar.py + scripts=['bin/foo.sh', 'bar.py'] +) +``` + +上面的 scripts 里有的脚本中有 `sh` 和 `py` 后缀,那么安装后,setuptools 会原封不动的移动到 /usr/bin 中,并添加可执行权限。 + +若你想对这些文件再作一些更改,比如去掉多余的后缀,可以这样做 + +```python +from setuptools.command.install_scripts import install_scripts + +class InstallScripts(install_scripts): + + def run(self): + setuptools.command.install_scripts.install_scripts.run(self) + + # Rename some script files + for script in self.get_outputs(): + if basename.endswith(".py") or basename.endswith(".sh"): + dest = script[:-3] + else: + continue + print("moving %s to %s" % (script, dest)) + shutil.move(script, dest) + +setup( + ... + scripts=['bin/foo.sh', 'bar.py'], + + cmdclass={ + "install_scripts": InstallScripts + } +) +``` + + + +**ext_modules** + +`ext_modules` 参数用于构建 C 和 C++ 扩展扩展包。其是 Extension 实例的列表,每一个 Extension 实例描述了一个独立的扩展模块,扩展模块可以设置扩展包名,头文件、源文件、链接库及其路径、宏定义和编辑参数等。如: + +```python +setup( + # other arguments here... + ext_modules=[ + Extension('foo', + glob(path.join(here, 'src', '*.c')), + libraries = [ 'rt' ], + include_dirs=[numpy.get_include()]) + ] +) +``` + +详细了解可参考:https://docs.python.org/3.6/distutils/setupscript.html#preprocessor-options + + + +**指定release** + +setup.py 里只能指定 version,而不能指定 release,如果你需要变更版本号,可以使用 `--release` 参数进行指定 + +```shell +python setup.py bdist_rpm --release=20200617 +``` + + + +setup.py 的参数非常多,能够不借助文档写好一个setup.py好像没那么简单。为了备忘,我整理了 setup 函数常用的一些参数: + +![](http://image.iswbm.com/20191218203255.png) + +更多参数可见:https://setuptools.readthedocs.io/en/latest/setuptools.html + +## 8. 打包辅助神器PBR 是什么? + +`pbr` 是 setuptools 的辅助工具,最初是为 OpenStack 开发(https://launchpad.net/pbr),基于`d2to1`。 + + + +`pbr` 会读取和过滤setup.cfg中的数据,然后将解析后的数据提供给 `setup.py` 作为参数。包含如下功能: + +1. 从git中获取Version、AUTHORS and ChangeLog信息 +2. Sphinx Autodoc。pbr 会扫描project,找到所有模块,生成stub files +3. Requirements。pbr会读取requirements.txt,生成setup函数需要的`install_requires/tests_require/dependency_links` + +这里需要注意,在 `requirements.txt` 文件的头部可以使用:`--index https://pypi.python.org/simple/`,这一行把一个抽象的依赖声明如 requests==1.2.0 转变为一个具体的依赖声明 requests 1.2.0 from pypi.python.org/simple/ + +4. long_description。从README.rst, README.txt or README file中生成`long_description`参数 + + + +使用pbr很简单: + +``` +from setuptools import setup + +setup( + setup_requires=['pbr'], + pbr=True, +) + +``` + +使用pbr时,setup.cfg中有一些配置。在[files]中,有三个key: +`packages`:指定需要包含的包,行为类似于setuptools.find_packages +`namespace_packages`:指定namespace packages +`data_files`: 指定目的目录和源文件路径,一个示例: + +``` +[files] +data_files = + etc/pbr = etc/pbr/* + etc/neutron = + etc/api-paste.ini + etc/dhcp-agent.ini + etc/init.d = neutron.init + +``` + +`[entry_points]` 段跟 setuptools 的方式相同。 + + + +到此,我讲了三种编写使用 setup.py 的方法 + +- 使用命令行参数指定,一个一个将参数传递进去(极不推荐) +- 在 setup.py 中的setup函数中指定(推荐使用) +- 使用 pbr ,在 setup.cfg 中指定(易于管理,更推荐) + +## 9. 如何使用 setup.py 构建包 + +1、构建源码发布包。 + +用于发布一个 Python 模块或项目,将源码打包成 tar.gz (用于 Linux 环境中)或者 zip 压缩包(用于 Windows 环境中) + +```shell +$ python setup.py sdist +``` + +那这种包如何安装呢? + +答案是,使用下一节即将介绍的 `setuptools` 中提供的 `easy_install` 工具。 + +```shell +$ easy_install xxx.tar.gz +``` + +使用 sdist 将根据当前平台创建默认格式的存档。在类 Unix 平台上,将创建后缀后为 `.tar.gz` 的 gzip 压缩的tar文件分发包,而在Windows上为 ZIP 文件。 + +当然,你也可以通过指定你要的发布包格式来打破这个默认行为 + +```shell +$ python setup.py sdist --formats=gztar,zip +``` + +你可以指定的格式有哪些呢? + +创建一个压缩的tarball和一个zip文件。可用格式为: + +![](http://image.iswbm.com/20191218203517.png) + +对以上的格式,有几点需要注意一下: + +- 在版本3.5中才添加了对 `xztar` 格式的支持 +- zip 格式需要你事先已安装相应的模块:zip程序或zipfile模块(已成为Python的标准库) +- ztar 格式正在弃用,请尽量不要使用 + +另外,如果您希望归档文件的所有文件归root拥有,可以这样指定 + +``` +python setup.py sdist --owner=root --group=root +``` + + + +2、构建二进制分发包。 + +在windows中我们习惯了双击 exe 进行软件的安装,Python 模块的安装也同样支持 打包成 exe 这样的二进制软件包。 + +```shell +$ python setup.py bdist_wininst +``` + +而在 Linux 中,大家也习惯了使用 rpm 来安装包,对此你可以使用这条命令实现 rpm 包的构建 + +```shell +$ python setup.py bdist_rpm +``` + +若你喜欢使用 easy_install 或者 pip 来安装离线包。你可以将其打包成 egg 包 + +```shell +$ python setup.py bdist_egg +``` + +若你的项目,需要安装多个平台下,既有 Windows 也有 Linux,按照上面的方法,多种格式我们要执行多次命令,为了方便,你可以一步到位,执行如下这条命令,即可生成多个格式的进制包 + +```shell +$ python setup.py bdist +``` + + + +## 10. 如何使用 setup.py 安装包 + +正常情况下,我们都是通过以上构建的源码包或者二进制包进行模块的安装。 + +但在编写 setup.py 的过程中,可能不能一步到位,需要多次调试,这时候如何测试自己写的 setup.py 文件是可用的呢? + +这时候你可以使用这条命令,它会将你的模块安装至系统全局环境中 + +```shell +$ python setup.py install +``` + +如若你的项目还处于开发阶段,频繁的安装模块,也是一个麻烦事。 + +这时候你可以使用这条命令安装,该方法不会真正的安装包,而是在系统环境中创建一个软链接指向包实际所在目录。这边在修改包之后不用再安装就能生效,便于调试。 + +```shell +$ python setup.py develop +``` + + + +## 11. 如何发布包到 PyPi? + +通过上面的学习,你一定已经学会了如何打包自己的项目,若你觉得自己开发的模块非常不错,想要 share 给其他人使用,你可以将其上传到 PyPi (Python Package Index)上,它是 Python 官方维护的第三方包仓库,用于统一存储和管理开发者发布的 Python 包。 + + + +如果要发布自己的包,需要先到 pypi 上注册账号。然后创建 `~/.pypirc` 文件,此文件中配置 PyPI 访问地址和账号。如的.pypirc文件内容请根据自己的账号来修改。 + +典型的 .pypirc 文件 + +```ini +[distutils] +index-servers = pypi + +[pypi] +username:xxx +password:xxx +``` + +然后使用这条命令进行信息注册,完成后,你可以在 PyPi 上看到项目信息。 + +```shell +$ python setup.py register +``` + +注册完了后,你还要上传源码包,别人才使用下载安装 + +```shell +$ python setup.py upload +``` + +或者也可以使用 `twine` 工具注册上传,它是一个专门用于与 pypi 进行交互的工具,详情可以参考官网:https://www.ctolib.com/twine.html,这里不详细讲了。 + + + +## 参考文章 + +- http://blog.konghy.cn/2018/04/29/setup-dot-py/ +- https://note.qidong.name/2018/01/python-setup-requires/ + + + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c03/c03_14.rst b/source/c03/c03_14.rst new file mode 100644 index 0000000..e1aec71 --- /dev/null +++ b/source/c03/c03_14.rst @@ -0,0 +1,703 @@ +3.14 全面学习 Python 包:包的构建与分发 +======================================= + +|image0| + +1. 为什么需要对项目分发打包? +----------------------------- + +平常我们习惯了使用 pip +来安装一些第三方模块,这个安装过程之所以简单,是因为模块开发者为我们默默地为我们做了所有繁杂的工作,而这个过程就是 +``打包``\ 。 + +打包,就是将你的源代码进一步封装,并且将所有的项目部署工作都事先安排好,这样使用者拿到后即装即用,不用再操心如何部署的问题(如果你不想对照着一堆部署文档手工操作的话)。 + +不管你是在工作中,还是业余准备自己写一个可以上传到 PyPI +的项目,你都要学会如何打包你的项目。 + +Python 发展了这么些年了,项目打包工具也已经很成熟了。他们都有哪些呢? + +你可能听过 ``disutils``\ 、 ``distutils`` +、\ ``distutils2``\ 、\ ``setuptools``\ 等等,好像很熟悉,却又很陌生,他们都是什么关系呢? + +2. 包分发的始祖:distutils +-------------------------- + +``distutils`` 是 Python +的一个标准库,从命名上很容易看出它是一个分发(distribute)工具(utlis),它是 +Python +官方开发的一个分发打包工具,所有后续的打包工具,全部都是基于它进行开发的。 + +``distutils`` 的精髓在于编写 setup.py,它是模块分发与安装的指导文件。 + +那么如何编写 setup.py +呢?这里面的内容非常多,我会在后面进行详细的解析,请你耐心往下看。 + +你有可能没写过 setup.py ,但你绝对使用过 setup.py +来做一些事情,比如下面这条命令,我们经常用它来进行模块的安装。 + +.. code:: shell + + $ python setup.py install + +这样的安装方法是通过源码安装,与之对应的是通过二进制软件包的安装,同样我也会在后面进行介绍。 + +3. 分发工具升级:setuptools +--------------------------- + +``setuptools`` 是 distutils +增强版,不包括在标准库中。其扩展了很多功能,能够帮助开发者更好的创建和分发 +Python 包。大部分 Python 用户都会使用更先进的 setuptools 模块。 + +**distribute**\ ,或许你在其他地方也见过它,这里也提一下。 + +distribute 是 setuptools +有一个分支版本,分支的原因可能是有一部分开发者认为 setuptools +开发太慢了。但现在,distribute 又合并回了 setuptools +中。因此,我们可以认为它们是同一个东西。 + +还有一个大包分发工具是 +**distutils2**\ ,其试图尝试充分利用distutils,detuptools 和 distribute +并成为 Python +标准库中的标准工具。但该计划并没有达到预期的目的,且已经是一个废弃的项目。 + +因此,setuptools 是一个优秀的,可靠的 Python 包安装与分发工具。 + +那么如何在一个干净的环境中安装 setuptools 呢? + +主要有两种方法: + +- 源码安装:在 https://pypi.org/project/setuptools/#files 中下载 zip 包 + 解压执行 ``python setup.py install`` 安装 +- 通过引导程序安装:下载引导程序,它可以用来下载或者更新最新版本的 + setuptools + +.. code:: shell + + $ wget http://peak.telecommunity.com/dist/ez_setup.py + + # 安装 + $ python ez_setup.py + + # 更新,以下两种任选 + $ python ez_setup.py –U setuptools + $ pip install -U setuptools + +4. easy_install 使用指南 +------------------------ + +当你安装完 setuptools 后,就拥有了一个叫做 ``easy_install`` +的第三方管理工具,这也是它区分于 distutils 的一大改进。 + +这里简单介绍一下它的用法,虽然它已经用得非常少了。 + +先是包的安装 + +.. code:: shell + + # 通过包名,从PyPI寻找最新版本,自动下载、编译、安装 + $ easy_install pkg_name + + # 通过包名从指定下载页寻找链接来安装或升级包 + $ easy_install -f http://pythonpaste.org/package_index.html + + # 指定线上的包地址安装 + $ easy_install http://example.com/path/to/MyPackage-1.2.3.tgz + + # 从本地的 .egg 文件安装 + $ easy_install xxx.egg + + # 在安装时你可以添加额外的参数 + 指定安装目录:--install-dir=DIR, -d DIR + 指定用户安装:--user + +再者是包的升级 + +.. code:: shell + + # 从 pypi 中搜索并升级包 + $ easy_install --upgrade pkg_name + + # 指定版本进行升级 + $ easy_install "SomePackage==2.0" + +最后是包的删除 + +.. code:: shell + + $ easy_install -m pkg_name + +需要注意的是,这样的删除,仅是在 easy-install.pth 文件中删除,使其不能在 +python 中使用 +这个模块,但实际的包还在你的电脑中,若要删除彻底,需要你手动删除相关的 +.egg 及 其他文件。 + +默认情况下,easy_install 只会从 pypi +上下载相关软件包,由于这个源在国外,下载包的速度并不理想,使用过pip的朋友自然会想,easy_install +是否能指定源进行安装呢? + +答案是,可以的。 + +编辑配置文件 ``/root/.pydistutils.cfg`` + +.. code:: ini + + [easy_install] + index-url=http://mirrors.aliyun.com/pypi/simple/ + find-links=http://mirrors.aliyun.com/pypi/simple/ + +以上仅介绍了 easy_install +的一些常用的方法,想要了解更多,你可以点击官方文档:https://setuptools.readthedocs.io/en/latest/easy_install.html + +总结一句:setuptools +是官方提供的一个专业用于包分发的工具,若只从安装的角度来看,它的功能确实简单。它更大的意义是对包的分发很有用,定制化程序非常高,我们现在也还在用它进行版本包的发布。 + +5. 源码包与二进制包什么区别? +----------------------------- + +Python 包的分发可以分为两种: + +1. 以源码包的方式发布 + +源码包安装的过程,是先解压,再编译,最后才安装,所以它是跨平台的,由于每次安装都要进行编译,相对二进包安装方式来说安装速度较慢。 + +源码包的本质是一个压缩包,其常见的格式有: + +|image1| + +2. 以二进制包形式发布 + +二进制包的安装过程省去了编译的过程,直接进行解压安装,所以安装速度较源码包来说更快。 + +由于不同平台的编译出来的包无法通用,所以在发布时,需事先编译好多个平台的包。 + +二进制包的常见格式有: + +|image2| + +6. eggs 与 wheels 有什么区别? +------------------------------ + +Egg 格式是由 setuptools 在 2004 年引入,而 Wheel 格式是由 PEP427 在 2012 +年定义。Wheel 的出现是为了替代 Egg,它的本质是一个zip包,其现在被认为是 +Python 的二进制包的标准格式。 + +以下是 Wheel 和 Egg 的主要区别: + +- Wheel 有一个官方的 PEP427 来定义,而 Egg 没有 PEP 定义 +- Wheel 是一种分发格式,即打包格式。而 Egg + 既是一种分发格式,也是一种运行时安装的格式,并且是可以被直接 import +- Wheel 文件不会包含 .pyc 文件 +- Wheel 使用和 PEP376 兼容的 .dist-info 目录,而 Egg 使用 .egg-info + 目录 +- Wheel 有着更丰富的命名规则。 +- Wheel 是有版本的。每个 Wheel 文件都包含 wheel 规范的版本和打包的实现 +- Wheel 在内部被 sysconfig path type 管理,因此转向其他格式也更容易 + +wheel 包可以通过 pip 来安装,只不过需要先安装 wheel 模块,然后再使用 pip +的命令。 + +.. code:: shell + + $ pip install wheel + $ pip wheel --wheel-dir=/local/wheels pkg + +7. 超详细讲解 setup.py 的编写? +------------------------------- + +打包分发最关键的一步是编写 ``setup.py`` 文件。 + +以下是一个 setup.py 简单的使用示例 + +.. code:: python + + from setuptools import setup, find_packages + + setup( + name="mytest", + version="1.0", + author="wangbm", + author_email="wongbingming@163.com", + description="Learn to Pack Python Module -->公众号:Python编程时光", + + # 项目主页 + url="http://iswbm.com/", + + # 你要安装的包,通过 setuptools.find_packages 找到当前目录下有哪些包 + packages=find_packages() + ) + +接下来,我将慢慢扩充这个setup函数,增加更多的参数,以便你能理解setup函数能做哪些事情。 + +**程序分类信息** + +``classifiers`` +参数说明包的分类信息。所有支持的分类列表见:https://pypi.org/pypi?%3Aaction=list_classifiers + +示例: + +.. code:: python + + from setuptools import setup, find_packages + + setup( + classifiers = [ + # 发展时期,常见的如下 + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + 'Development Status :: 3 - Alpha', + + # 开发的目标用户 + 'Intended Audience :: Developers', + + # 属于什么类型 + 'Topic :: Software Development :: Build Tools', + + # 许可证信息 + 'License :: OSI Approved :: MIT License', + + # 目标 Python 版本 + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + ] + ) + +**关于文件的分发** + +.. code:: python + + from setuptools import setup, find_packages + + + setup( + name="mytest", + version="1.0", + author="wangbm", + author_email="wongbingming@163.com", + description="Learn to Pack Python Module", + url="http://iswbm.com/", + packages=find_packages(), + + # 安装过程中,需要安装的静态文件,如配置文件、service文件、图片等 + data_files=[ + ('', ['conf/*.conf']), + ('/usr/lib/systemd/system/', ['bin/*.service']), + ], + + # 希望被打包的文件 + package_data={ + '':['*.txt'], + 'bandwidth_reporter':['*.txt'] + }, + # 不打包某些文件 + exclude_package_data={ + 'bandwidth_reporter':['*.txt'] + } + ) + +除了以上的参数配置之外,还可以使用一个叫做 ``MANIFEST.in`` +的文件,来控制文件的分发。 + +如下这是一个 ``MANIFEST.in`` 的样例: + +:: + + include *.txt + recursive-include examples *.txt *.py + prune examples/sample?/build + +这些配置,规定了如下几点 + +- 所有根目录下的以 txt 为后缀名的文件,都会分发 +- 根目录下的 examples 目录 和 txt、py文件都会分发 +- 路径匹配上 examples/sample?/build 不会分发 + +``MANIFEST.in`` 需要放在和 setup.py 同级的顶级目录下,setuptools +会自动读取该文件。 + +**关于依赖包下载安装** + +.. code:: python + + from setuptools import setup, find_packages + + + setup( + ... + + # 表明当前模块依赖哪些包,若环境中没有,则会从pypi中下载安装 + install_requires=['docutils>=0.3'], + + # setup.py 本身要依赖的包,这通常是为一些setuptools的插件准备的配置 + # 这里列出的包,不会自动安装。 + setup_requires=['pbr'], + + # 仅在测试时需要使用的依赖,在正常发布的代码中是没有用的。 + # 在执行python setup.py test时,可以自动安装这三个库,确保测试的正常运行。 + tests_require=[ + 'pytest>=3.3.1', + 'pytest-cov>=2.5.1', + ], + + # 用于安装setup_requires或tests_require里的软件包 + # 这些信息会写入egg的 metadata 信息中 + dependency_links=[ + "http://example2.com/p/foobar-1.0.tar.gz", + ], + + # install_requires 在安装模块时会自动安装依赖包 + # 而 extras_require 不会,这里仅表示该模块会依赖这些包 + # 但是这些包通常不会使用到,只有当你深度使用模块时,才会用到,这里需要你手动安装 + extras_require={ + 'PDF': ["ReportLab>=1.2", "RXP"], + 'reST': ["docutils>=0.3"], + } + ) + +关于 ``install_requires``\ , 有以下五种常用的表示方法: + +1. ``'argparse'``\ ,只包含包名。 这种形式只检查包的存在性,不检查版本。 + 方便,但不利于控制风险。 +2. ``'setuptools==38.2.4'``\ ,指定版本。 + 这种形式把风险降到了最低,确保了开发、测试与部署的版本一致,不会出现意外。 + 缺点是不利于更新,每次更新都需要改动代码。 +3. ``'docutils >= 0.3'``\ ,这是比较常用的形式。 + 当对某个库比较信任时,这种形式可以自动保持版本为最新。 +4. ``'Django >= 1.11, != 1.11.1, <= 2'``\ ,这是比较复杂的形式。 + 如这个例子,保证了Django的大版本在1.11和2之间,也即1.11.x;并且,排除了已知有问题的版本1.11.1(仅举例)。 + 对于一些大型、复杂的库,这种形式是最合适的。 +5. ``'requests[security, socks] >= 2.18.4'``\ ,这是包含了额外的可选依赖的形式。 + 正常安装requests会自动安装它的\ ``install_requires``\ 中指定的依赖,而不会安装\ ``security``\ 和\ ``socks``\ 这两组依赖。 + 这两组依赖是定义在它的\ ``extras_require``\ 中。 + 这种形式,用在深度使用某些库时。 + +**关于安装环境的限制** + +有些库并不是在所以的 Python 版本中都适用的,若一个库安装在一个未兼容的 +Python +环境中,理论上不应该在使用时才报错,而应该在安装过程就使其失败,提示禁止安装。 + +这样的功能,可以使用 ``python_requires`` 来实现。 + +.. code:: python + + setup( + ... + python_requires='>=2.7, <=3', + ) + +**生成可执行文件的分发** + +.. code:: python + + from setuptools import setup, find_packages + + + setup( + name="mytest", + version="1.0", + author="wangbm", + author_email="wongbingming@163.com", + description="Learn to Pack Python Module", + url="http://iswbm.com/", + packages=find_packages(), + + # 用来支持自动生成脚本,安装后会自动生成 /usr/bin/foo 的可执行文件 + # 该文件入口指向 foo/main.py 的main 函数 + entry_points={ + 'console_scripts': [ + 'foo = foo.main:main' + ] + }, + + # 将 bin/foo.sh 和 bar.py 脚本,生成到系统 PATH中 + # 执行 python setup.py install 后 + # 会生成 如 /usr/bin/foo.sh 和 如 /usr/bin/bar.py + scripts=['bin/foo.sh', 'bar.py'] + ) + +上面的 scripts 里有的脚本中有 ``sh`` 和 ``py`` +后缀,那么安装后,setuptools 会原封不动的移动到 /usr/bin +中,并添加可执行权限。 + +若你想对这些文件再作一些更改,比如去掉多余的后缀,可以这样做 + +.. code:: python + + from setuptools.command.install_scripts import install_scripts + + class InstallScripts(install_scripts): + + def run(self): + setuptools.command.install_scripts.install_scripts.run(self) + + # Rename some script files + for script in self.get_outputs(): + if basename.endswith(".py") or basename.endswith(".sh"): + dest = script[:-3] + else: + continue + print("moving %s to %s" % (script, dest)) + shutil.move(script, dest) + + setup( + ... + scripts=['bin/foo.sh', 'bar.py'], + + cmdclass={ + "install_scripts": InstallScripts + } + ) + +**ext_modules** + +``ext_modules`` 参数用于构建 C 和 C++ 扩展扩展包。其是 Extension +实例的列表,每一个 Extension +实例描述了一个独立的扩展模块,扩展模块可以设置扩展包名,头文件、源文件、链接库及其路径、宏定义和编辑参数等。如: + +.. code:: python + + setup( + # other arguments here... + ext_modules=[ + Extension('foo', + glob(path.join(here, 'src', '*.c')), + libraries = [ 'rt' ], + include_dirs=[numpy.get_include()]) + ] + ) + +详细了解可参考:https://docs.python.org/3.6/distutils/setupscript.html#preprocessor-options + +**指定release** + +setup.py 里只能指定 version,而不能指定 +release,如果你需要变更版本号,可以使用 ``--release`` 参数进行指定 + +.. code:: shell + + python setup.py bdist_rpm --release=20200617 + +setup.py +的参数非常多,能够不借助文档写好一个setup.py好像没那么简单。为了备忘,我整理了 +setup 函数常用的一些参数: + +|image3| + +更多参数可见:https://setuptools.readthedocs.io/en/latest/setuptools.html + +8. 打包辅助神器PBR 是什么? +--------------------------- + +``pbr`` 是 setuptools 的辅助工具,最初是为 OpenStack +开发(https://launchpad.net/pbr),基于\ ``d2to1``\ 。 + +``pbr`` 会读取和过滤setup.cfg中的数据,然后将解析后的数据提供给 +``setup.py`` 作为参数。包含如下功能: + +1. 从git中获取Version、AUTHORS and ChangeLog信息 +2. Sphinx Autodoc。pbr 会扫描project,找到所有模块,生成stub files +3. Requirements。pbr会读取requirements.txt,生成setup函数需要的\ ``install_requires/tests_require/dependency_links`` + +这里需要注意,在 ``requirements.txt`` +文件的头部可以使用:\ ``--index https://pypi.python.org/simple/``\ ,这一行把一个抽象的依赖声明如 +requests==1.2.0 转变为一个具体的依赖声明 requests 1.2.0 from +pypi.python.org/simple/ + +4. long_description。从README.rst, README.txt or README + file中生成\ ``long_description``\ 参数 + +使用pbr很简单: + +:: + + from setuptools import setup + + setup( + setup_requires=['pbr'], + pbr=True, + ) + +使用pbr时,setup.cfg中有一些配置。在[files]中,有三个key: +``packages``:指定需要包含的包,行为类似于setuptools.find_packages +``namespace_packages``:指定namespace packages ``data_files``: +指定目的目录和源文件路径,一个示例: + +:: + + [files] + data_files = + etc/pbr = etc/pbr/* + etc/neutron = + etc/api-paste.ini + etc/dhcp-agent.ini + etc/init.d = neutron.init + +``[entry_points]`` 段跟 setuptools 的方式相同。 + +到此,我讲了三种编写使用 setup.py 的方法 + +- 使用命令行参数指定,一个一个将参数传递进去(极不推荐) +- 在 setup.py 中的setup函数中指定(推荐使用) +- 使用 pbr ,在 setup.cfg 中指定(易于管理,更推荐) + +9. 如何使用 setup.py 构建包 +--------------------------- + +1、构建源码发布包。 + +用于发布一个 Python 模块或项目,将源码打包成 tar.gz (用于 Linux +环境中)或者 zip 压缩包(用于 Windows 环境中) + +.. code:: shell + + $ python setup.py sdist + +那这种包如何安装呢? + +答案是,使用下一节即将介绍的 ``setuptools`` 中提供的 ``easy_install`` +工具。 + +.. code:: shell + + $ easy_install xxx.tar.gz + +使用 sdist 将根据当前平台创建默认格式的存档。在类 Unix +平台上,将创建后缀后为 ``.tar.gz`` 的 gzip +压缩的tar文件分发包,而在Windows上为 ZIP 文件。 + +当然,你也可以通过指定你要的发布包格式来打破这个默认行为 + +.. code:: shell + + $ python setup.py sdist --formats=gztar,zip + +你可以指定的格式有哪些呢? + +创建一个压缩的tarball和一个zip文件。可用格式为: + +|image4| + +对以上的格式,有几点需要注意一下: + +- 在版本3.5中才添加了对 ``xztar`` 格式的支持 +- zip + 格式需要你事先已安装相应的模块:zip程序或zipfile模块(已成为Python的标准库) +- ztar 格式正在弃用,请尽量不要使用 + +另外,如果您希望归档文件的所有文件归root拥有,可以这样指定 + +:: + + python setup.py sdist --owner=root --group=root + +2、构建二进制分发包。 + +在windows中我们习惯了双击 exe 进行软件的安装,Python +模块的安装也同样支持 打包成 exe 这样的二进制软件包。 + +.. code:: shell + + $ python setup.py bdist_wininst + +而在 Linux 中,大家也习惯了使用 rpm 来安装包,对此你可以使用这条命令实现 +rpm 包的构建 + +.. code:: shell + + $ python setup.py bdist_rpm + +若你喜欢使用 easy_install 或者 pip 来安装离线包。你可以将其打包成 egg 包 + +.. code:: shell + + $ python setup.py bdist_egg + +若你的项目,需要安装多个平台下,既有 Windows 也有 +Linux,按照上面的方法,多种格式我们要执行多次命令,为了方便,你可以一步到位,执行如下这条命令,即可生成多个格式的进制包 + +.. code:: shell + + $ python setup.py bdist + +10. 如何使用 setup.py 安装包 +---------------------------- + +正常情况下,我们都是通过以上构建的源码包或者二进制包进行模块的安装。 + +但在编写 setup.py +的过程中,可能不能一步到位,需要多次调试,这时候如何测试自己写的 +setup.py 文件是可用的呢? + +这时候你可以使用这条命令,它会将你的模块安装至系统全局环境中 + +.. code:: shell + + $ python setup.py install + +如若你的项目还处于开发阶段,频繁的安装模块,也是一个麻烦事。 + +这时候你可以使用这条命令安装,该方法不会真正的安装包,而是在系统环境中创建一个软链接指向包实际所在目录。这边在修改包之后不用再安装就能生效,便于调试。 + +.. code:: shell + + $ python setup.py develop + +11. 如何发布包到 PyPi? +----------------------- + +通过上面的学习,你一定已经学会了如何打包自己的项目,若你觉得自己开发的模块非常不错,想要 +share 给其他人使用,你可以将其上传到 PyPi (Python Package +Index)上,它是 Python +官方维护的第三方包仓库,用于统一存储和管理开发者发布的 Python 包。 + +如果要发布自己的包,需要先到 pypi 上注册账号。然后创建 ``~/.pypirc`` +文件,此文件中配置 PyPI +访问地址和账号。如的.pypirc文件内容请根据自己的账号来修改。 + +典型的 .pypirc 文件 + +.. code:: ini + + [distutils] + index-servers = pypi + + [pypi] + username:xxx + password:xxx + +然后使用这条命令进行信息注册,完成后,你可以在 PyPi 上看到项目信息。 + +.. code:: shell + + $ python setup.py register + +注册完了后,你还要上传源码包,别人才使用下载安装 + +.. code:: shell + + $ python setup.py upload + +或者也可以使用 ``twine`` 工具注册上传,它是一个专门用于与 pypi +进行交互的工具,详情可以参考官网:https://www.ctolib.com/twine.html,这里不详细讲了。 + +参考文章 +-------- + +- http://blog.konghy.cn/2018/04/29/setup-dot-py/ +- https://note.qidong.name/2018/01/python-setup-requires/ + +|image5| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20191218202833.png +.. |image2| image:: http://image.iswbm.com/20191218203005.png +.. |image3| image:: http://image.iswbm.com/20191218203255.png +.. |image4| image:: http://image.iswbm.com/20191218203517.png +.. |image5| image:: http://image.iswbm.com/20200607174235.png + diff --git a/source/c04/c04_01.md b/source/c04/c04_01.md index 0035b70..0bf4881 100644 --- a/source/c04/c04_01.md +++ b/source/c04/c04_01.md @@ -5,7 +5,7 @@ --- -## 1.0 什么是虚拟环境? +## 1. 什么是虚拟环境? 虚拟环境的意义,就如同 虚拟机 一样,它可以实现不同环境中Python依赖包相互独立,互不干扰。 @@ -27,20 +27,11 @@ 工具很多,但个人认为最好用的,当属 `virtualenvwrapper`,推荐大家也使用。 -## 2.0 virtualenv +## 2. virtualenv 由于 virtualenvwrapper 是 virtualenv 的一组扩展,所以如果要使用 virtualenvwrapper,就必须先安装 virtualenv。 - -**安装** -```bash -$ pip install virtualenv - -# 检查版本 -$ virtualenv --version -``` - -**基本使用** +**安装***基本使用** 由于virtualenv创建虚拟环境是在当前环境下创建的。所以我们要准备一个专门存放虚拟环境的目录。(以下操作在Linux在完成,windows相对简单,请自行完成,有不明白的请微信与我联系。) @@ -94,7 +85,7 @@ $ pip freeze > requirements.txt $ pip install -r requirements.txt ``` -## 3.0 virtualenvwrapper +## 3. virtualenvwrapper virtualenv 虽然已经相当好用了,可是功能还是不够完善。 @@ -137,7 +128,7 @@ source /usr/bin/virtualenvwrapper.sh 若是 windows 则新增环境变量:`WORKON_HOME` -![](http://image.python-online.cn/20200209161935.png) +![](http://image.iswbm.com/20200209161935.png) @@ -195,7 +186,7 @@ $ lssitepackages 更多内容,可查看 官方文档 https://virtualenvwrapper.readthedocs.io/en/latest/command_ref.html -## 4.0 实战演示 +## 4. 实战演示 以上内容,是一份使用指南。接下来,一起来看看,如何在项目中使用虚拟环境。 diff --git a/source/c04/c04_01.rst b/source/c04/c04_01.rst old mode 100755 new mode 100644 index c10202e..129c3ac --- a/source/c04/c04_01.rst +++ b/source/c04/c04_01.rst @@ -5,8 +5,8 @@ -------------- -1.0 什么是虚拟环境? --------------------- +1. 什么是虚拟环境? +------------------- 虚拟环境的意义,就如同 虚拟机 一样,它可以实现不同环境中Python依赖包相互独立,互不干扰。 @@ -29,22 +29,13 @@ Python 版本管理工具。 - ``Vex``\ :可以在虚拟环境中执行命令 工具很多,但个人认为最好用的,当属 ``virtualenvwrapper``\ ,推荐大家也使用。 -2.0 virtualenv --------------- +2. virtualenv +------------- 由于 virtualenvwrapper 是 virtualenv 的一组扩展,所以如果要使用 virtualenvwrapper,就必须先安装 virtualenv。 -**安装** - -.. code:: bash - - $ pip install virtualenv - - # 检查版本 - $ virtualenv --version - -**基本使用** +**安装**\ \*基本使用*\* 由于virtualenv创建虚拟环境是在当前环境下创建的。所以我们要准备一个专门存放虚拟环境的目录。(以下操作在Linux在完成,windows相对简单,请自行完成,有不明白的请微信与我联系。) @@ -103,8 +94,8 @@ virtualenvwrapper,就必须先安装 virtualenv。 # 安装依赖包 $ pip install -r requirements.txt -3.0 virtualenvwrapper ---------------------- +3. virtualenvwrapper +-------------------- virtualenv 虽然已经相当好用了,可是功能还是不够完善。 @@ -206,8 +197,8 @@ mkvirtualenv [-a project_path] [-i package] [-r requirements_file] 更多内容,可查看 官方文档 https://virtualenvwrapper.readthedocs.io/en/latest/command_ref.html -4.0 实战演示 ------------- +4. 实战演示 +----------- 以上内容,是一份使用指南。接下来,一起来看看,如何在项目中使用虚拟环境。 @@ -270,7 +261,7 @@ https://virtualenvwrapper.readthedocs.io/en/latest/command_ref.html |image6| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20200209161935.png +.. |image1| image:: http://image.iswbm.com/20200209161935.png .. |image2| image:: https://i.loli.net/2018/06/11/5b1e7d36ce8ad.png .. |image3| image:: https://i.loli.net/2018/06/11/5b1e7f140be6a.png .. |image4| image:: https://i.loli.net/2018/06/11/5b1e805c996c8.png diff --git a/source/c04/c04_02.md b/source/c04/c04_02.md index 5b2c868..607c775 100644 --- a/source/c04/c04_02.md +++ b/source/c04/c04_02.md @@ -1,160 +1,161 @@ -# 4.2 Xshell的高效使用手册 +# 4.2 虚拟环境:Pipenv ![](http://image.iswbm.com/20200602135014.png) ---- +以前一直使用pip+virtualenv+virtualwrapper管理模块和环境, 但是virtualwrapper在windows上使用不太方便,而且包和环境分开管理确实经常不记得哪个是哪个了。 -![](http://image.python-online.cn/20190511162815.png) -做为一名开发人员,我们难免都会与服务器打交道。 +为什么 会推荐 pipenv 呢? -有时候是公司的线上生产环境,你需要上去部署公司的项目。 -有时候是在阿里上买的云主机,想自己搭个博客来写写文章。 +- 它是 `virtualenv` 和 `pip` 的合体,可以合起来使用; +- 使用`Pipfile` 和 `Pipfile.lock`替代`requirements.txt` +- 可以使用 `pipenv graph`很方便的看出包的依赖关系。 +- 通过加载`.env`文件简化开发工作流程 -就像我,是从事云计算相关的,每天远程登陆的服务器都有几十台。 +## 1. 安装pipenv -这时候,掌握一些远程登陆工具的使用技巧是相当有必要的,会大大增加你使用的便利性和愉悦性。 +如果你的电脑上没有安装 pipenv,可以使用如下方法安装 -优秀的远程工具有很多,Xshell,SecureCRT,PuTTY等等。 +```shell +# mac +$ brew install pipenv -由于小明是个很注重工具顔值的人,所以一开始就选择了Xshell。本篇也只会介绍Xshell的使用技巧,但是快捷键在其他工具上部分是通用的。 +# windows +pip install [--user] pipenv +``` -## 快捷入口 +如果你的电脑是 windows 的。 -**快速命令** +![](http://image.iswbm.com/Fk6WZ2xbqg2DM3AvnYCpsiKQ4xOn) -查看 -> 快速命令 +需要将如标示路径,加入到 环境变量 PATH 中。 -双击底部自定义快速命令。 -![](http://image.python-online.cn/20190511162524.png) +![](http://image.iswbm.com/FjuJ8yZsgjkzVuBRZHxK1ZnnzaEX) -**使用收藏栏** +然后需要重启一下,CMD 终端才能够刷新环境变量。 -点击最左侧按按钮添加收藏。 -![](http://image.python-online.cn/20190511162607.png) +## 2. 创建虚拟环境 -**快捷设置** +DjangoWebBlog 是我们的项目目录,进入这个目录下创建虚拟环境 -工具 -> 选项 -> 键盘和鼠标 +```shell +$ mkdir DjangoWebBlog && cd DjangoWebBlog -① 右键粘贴 -② 双击分隔符 -③ 选中即复制 +# 在当前目录下创建一个虚拟环境(默认的Python版本) +$ pipenv install +``` -![](http://image.python-online.cn/20190511162716.png) +你也可以指定版本创建 -**设置Meta键** +```shell +$ pipenv --two # 相当于 pipenv --python /usr/bin/python2 +$ pipenv --three # 相当于 pipenv --python /usr/bin/python3 -文件 -> 属性 -> 键盘 +$ pipenv --python 3.7 # 也可以指定具体的版本 +pipenv install --python 2 +``` -一定要打钩,这是后面诸多快捷使用的前提。 -![](http://image.python-online.cn/20190511162730.png) +这边以安装 python2 版本的虚拟环境为例说明。 -## 移动光标 -``` -Ctrl+f 向后移动一个字符 -Ctrl+b 向前移动一个字符 +![](http://image.iswbm.com/20190612211330.png) -Ctrl+a 将光标移至输入行头,相当于Home键 -Ctrl+e 将光标移至输入行末,相当于End键 +如果你原项目使用的是 requirements.txt 这个管理包的方式,这时候执行 `pipenv --tow` 创建一个虚拟环境后,会找到 requirements.txt ,并根据这里面的依赖包生成 Pipfile文件。 -Alt+f 以单词为单位,向前移动 -Alt+b 以单词为单位,向前移动 +![](http://image.iswbm.com/20190612213015.png) -Shift+PgUp 将终端显示向上滚动 -Shift+PgDn 将终端显示向下滚动 -``` +## 3. 查询虚拟环境 -## 删除元素 -``` -Ctrl+u 删除到行首 -Ctrl+k 删除到行末 +```shell +# 返回项目的路径 +$ pipenv --where -Ctrl+y 粘贴上次Ctrl+u/k的字符 -Ctrl+d 删除当前字符,等同于Del +# 返回虚拟环境路径 +$ pipenv --venv -Alt+Backspace 向前删除一个单词,和 Ctrl+w 一样 +# 返回该虚拟环境的解释器 +$ pipenv --py ``` -## 切换标签 -``` -Alt+n n为1-9数字,快速切换标签页 -Ctrl+Tab 向右切换标签 +演示如下: -Ctrl+Shift+Tab 向右切换标签 -Shift+Tab 两个窗口来回切换 -``` +![](http://image.iswbm.com/20190612213950.png) -## 屏幕模式 -``` -Alt+s 切换到简单版模式 -Alt+Enter 切换至全屏 -``` +## 4. 操作虚拟环境 -## 快速操作 ```shell -Alt+. 取得上次命令的参数,并粘贴 -Ctrl+r 在历史命令中查找,回车执行 -Ctrl+o 放在命令后执行,可重复执行 +# 进入这个虚拟环境 +$ pipenv shell +# 退出这个虚拟环境 +$ exit +$ deactivate -Alt+u 以单词为单位,将光标处到单词结尾的字符转化为大写 -Alt+l 以单词为单位,将光标处到单词结尾的字符转化为小写 - -Alt+Shift+# 注释当前命令,相当于ctrl-a,#,enter +# 移除当前目录的虚拟环境 +$ pipenv --rm ``` -## 辅助命令 +执行 `pipenv shell` 就可以进入这个虚拟环境,在头部会有虚拟环境的标识名称。有这个标识,说明已经进入虚拟环境。 + +![](http://image.iswbm.com/20190612211925.png) + +```python +# 在当前虚拟环境中运行 +$ pipenv run python # 进入交互式,跟直接执行 python 一样 +$ pipenv run python 文件名 # 运行文件 +$ pipenv run pip ... # 运行pip ``` -Ctrl+s 锁住终端,可用来停留在当前屏 -Ctrl+q 解锁终端,恢复刷屏 -Ctrl+d 键盘输入结束或退出终端 +## 5. 虚拟环境包管理 -Ctrl+s 暂停当前程序,暂停后按下任意键恢复运行 -Ctrl+z 将当前程序放到后台运行,恢复到前台为命令fg +```shell +# 安装一个本地包(setup.py)到虚拟环境(Pipfile) +$ pipenv install -e . -Ctrl+Shift+r 重新连接 +# 安装、卸载模块 +$ pipenv install requests +$ pipenv uninstall requests +$ pipenv uninstall --all # 卸载全部包 +$ pipenv install -r path/to/requirements.txt -Ctrl+Insert 复制 -Shift+Insert 粘贴 -``` -以我日常使用到的,暂时就这么些了,以后有用更多的使用技巧和快捷键再来补充。 +# 安装所有依赖 +$ pipenv install --dev -## 配色方案 +# 更新包 +$ pipenv update # 更新所有包 +$ pipenv update --outdated # 打印所有要更新的包 +$ pipenv update <包名> # 更新指定的包 -新建一个文件`ubuntu.xcs` +# 将Pipfile和Pipfile.lock文件里面的包导出为requirements.txt文件 +$ pipenv run pip freeze # 相当于pipenv run pip freeze >requirements.txt +$ pipenv lock -r > requirements.txt +$ pipenv lock -r --dev # 若只想导出开发用的包 ``` -[ubuntu] -text(bold)=ffffff -magenta(bold)=ad7fa8 -text=ffffff -white(bold)=eeeeec -green=4e9a06 -red(bold)=ef2929 -green(bold)=8ae234 -black(bold)=555753 -red=cc0000 -blue=3465a4 -black=000000 -blue(bold)=729fcf -yellow(bold)=fce94f -cyan(bold)=34e2e2 -yellow=c4a000 -magenta=75507b -background=300a24 -white=d3d7cf -cyan=06989a -[Names] -count=1 -name0=ubuntu + +## 6. 其他命令 + +```shell + +# 创建一个包含预发布的锁文件: +$ pipenv lock --pre + +# 打印所有包的依赖关系图 +$ pipenv graph + +# 检查安全漏洞 +$ pipenv check ``` -然后在xshell配色方案中导入即可 +打印该虚拟环境下所有包的依赖关系图 + +![](http://image.iswbm.com/20190614000336.png) + +有的python第三方包旧版本会有安全漏洞,使用 pipenv check 可以检查安全漏洞。 + +![](http://image.iswbm.com/20190612215924.png) -附上一个更全的帖子:[Xshell快捷键汇总](https://www.cnblogs.com/zhoushihui/p/5404392.html) +.env`文件,用来存放一些环境变量。 --- diff --git a/source/c04/c04_02.rst b/source/c04/c04_02.rst old mode 100755 new mode 100644 index 1e9fadc..5508abc --- a/source/c04/c04_02.rst +++ b/source/c04/c04_02.rst @@ -1,180 +1,185 @@ -4.2 Xshell的高效使用手册 -======================== +4.2 虚拟环境:Pipenv +==================== |image0| --------------- +以前一直使用pip+virtualenv+virtualwrapper管理模块和环境, +但是virtualwrapper在windows上使用不太方便,而且包和环境分开管理确实经常不记得哪个是哪个了。 -|image1| +为什么 会推荐 pipenv 呢? + +- 它是 ``virtualenv`` 和 ``pip`` 的合体,可以合起来使用; +- 使用\ ``Pipfile`` 和 ``Pipfile.lock``\ 替代\ ``requirements.txt`` +- 可以使用 ``pipenv graph``\ 很方便的看出包的依赖关系。 +- 通过加载\ ``.env``\ 文件简化开发工作流程 + +1. 安装pipenv +------------- -做为一名开发人员,我们难免都会与服务器打交道。 +如果你的电脑上没有安装 pipenv,可以使用如下方法安装 -有时候是公司的线上生产环境,你需要上去部署公司的项目。 -有时候是在阿里上买的云主机,想自己搭个博客来写写文章。 +.. code:: shell -就像我,是从事云计算相关的,每天远程登陆的服务器都有几十台。 + # mac + $ brew install pipenv -这时候,掌握一些远程登陆工具的使用技巧是相当有必要的,会大大增加你使用的便利性和愉悦性。 + # windows + pip install [--user] pipenv -优秀的远程工具有很多,Xshell,SecureCRT,PuTTY等等。 +如果你的电脑是 windows 的。 -由于小明是个很注重工具顔值的人,所以一开始就选择了Xshell。本篇也只会介绍Xshell的使用技巧,但是快捷键在其他工具上部分是通用的。 +|image1| -快捷入口 --------- +需要将如标示路径,加入到 环境变量 PATH 中。 -**快速命令** +|image2| -查看 -> 快速命令 +然后需要重启一下,CMD 终端才能够刷新环境变量。 -双击底部自定义快速命令。 |image2| +2. 创建虚拟环境 +--------------- -**使用收藏栏** +DjangoWebBlog 是我们的项目目录,进入这个目录下创建虚拟环境 -点击最左侧按按钮添加收藏。 |image3| +.. code:: shell -**快捷设置** + $ mkdir DjangoWebBlog && cd DjangoWebBlog -工具 -> 选项 -> 键盘和鼠标 + # 在当前目录下创建一个虚拟环境(默认的Python版本) + $ pipenv install -① 右键粘贴 ② 双击分隔符 ③ 选中即复制 +你也可以指定版本创建 -|image4| +.. code:: shell + + $ pipenv --two # 相当于 pipenv --python /usr/bin/python2 + $ pipenv --three # 相当于 pipenv --python /usr/bin/python3 -**设置Meta键** + $ pipenv --python 3.7 # 也可以指定具体的版本 + pipenv install --python 2 -文件 -> 属性 -> 键盘 +这边以安装 python2 版本的虚拟环境为例说明。 -一定要打钩,这是后面诸多快捷使用的前提。 |image5| +|image3| -移动光标 --------- +如果你原项目使用的是 requirements.txt 这个管理包的方式,这时候执行 +``pipenv --tow`` 创建一个虚拟环境后,会找到 requirements.txt +,并根据这里面的依赖包生成 Pipfile文件。 -:: +|image4| - Ctrl+f 向后移动一个字符 - Ctrl+b 向前移动一个字符 +3. 查询虚拟环境 +--------------- - Ctrl+a 将光标移至输入行头,相当于Home键 - Ctrl+e 将光标移至输入行末,相当于End键 +.. code:: shell - Alt+f 以单词为单位,向前移动 - Alt+b 以单词为单位,向前移动 + # 返回项目的路径 + $ pipenv --where - Shift+PgUp 将终端显示向上滚动 - Shift+PgDn 将终端显示向下滚动 + # 返回虚拟环境路径 + $ pipenv --venv -删除元素 --------- + # 返回该虚拟环境的解释器 + $ pipenv --py -:: +演示如下: - Ctrl+u 删除到行首 - Ctrl+k 删除到行末 +|image5| - Ctrl+y 粘贴上次Ctrl+u/k的字符 - Ctrl+d 删除当前字符,等同于Del +4. 操作虚拟环境 +--------------- - Alt+Backspace 向前删除一个单词,和 Ctrl+w 一样 +.. code:: shell -切换标签 --------- + # 进入这个虚拟环境 + $ pipenv shell -:: + # 退出这个虚拟环境 + $ exit + $ deactivate - Alt+n n为1-9数字,快速切换标签页 - Ctrl+Tab 向右切换标签 + # 移除当前目录的虚拟环境 + $ pipenv --rm - Ctrl+Shift+Tab 向右切换标签 - Shift+Tab 两个窗口来回切换 +执行 ``pipenv shell`` +就可以进入这个虚拟环境,在头部会有虚拟环境的标识名称。有这个标识,说明已经进入虚拟环境。 -屏幕模式 --------- +|image6| -:: +.. code:: python - Alt+s 切换到简单版模式 - Alt+Enter 切换至全屏 + # 在当前虚拟环境中运行 + $ pipenv run python # 进入交互式,跟直接执行 python 一样 + $ pipenv run python 文件名 # 运行文件 + $ pipenv run pip ... # 运行pip -快速操作 --------- +5. 虚拟环境包管理 +----------------- .. code:: shell - Alt+. 取得上次命令的参数,并粘贴 - Ctrl+r 在历史命令中查找,回车执行 - Ctrl+o 放在命令后执行,可重复执行 + # 安装一个本地包(setup.py)到虚拟环境(Pipfile) + $ pipenv install -e . + # 安装、卸载模块 + $ pipenv install requests + $ pipenv uninstall requests + $ pipenv uninstall --all # 卸载全部包 + $ pipenv install -r path/to/requirements.txt - Alt+u 以单词为单位,将光标处到单词结尾的字符转化为大写 - Alt+l 以单词为单位,将光标处到单词结尾的字符转化为小写 - Alt+Shift+# 注释当前命令,相当于ctrl-a,#,enter + # 安装所有依赖 + $ pipenv install --dev -辅助命令 --------- + # 更新包 + $ pipenv update # 更新所有包 + $ pipenv update --outdated # 打印所有要更新的包 + $ pipenv update <包名> # 更新指定的包 -:: + # 将Pipfile和Pipfile.lock文件里面的包导出为requirements.txt文件 + $ pipenv run pip freeze # 相当于pipenv run pip freeze >requirements.txt - Ctrl+s 锁住终端,可用来停留在当前屏 - Ctrl+q 解锁终端,恢复刷屏 - Ctrl+d 键盘输入结束或退出终端 + $ pipenv lock -r > requirements.txt + $ pipenv lock -r --dev # 若只想导出开发用的包 +6. 其他命令 +----------- - Ctrl+s 暂停当前程序,暂停后按下任意键恢复运行 - Ctrl+z 将当前程序放到后台运行,恢复到前台为命令fg +.. code:: shell - Ctrl+Shift+r 重新连接 - Ctrl+Insert 复制 - Shift+Insert 粘贴 + # 创建一个包含预发布的锁文件: + $ pipenv lock --pre -以我日常使用到的,暂时就这么些了,以后有用更多的使用技巧和快捷键再来补充。 + # 打印所有包的依赖关系图 + $ pipenv graph -配色方案 --------- + # 检查安全漏洞 + $ pipenv check -新建一个文件\ ``ubuntu.xcs`` +打印该虚拟环境下所有包的依赖关系图 -:: +|image7| - [ubuntu] - text(bold)=ffffff - magenta(bold)=ad7fa8 - text=ffffff - white(bold)=eeeeec - green=4e9a06 - red(bold)=ef2929 - green(bold)=8ae234 - black(bold)=555753 - red=cc0000 - blue=3465a4 - black=000000 - blue(bold)=729fcf - yellow(bold)=fce94f - cyan(bold)=34e2e2 - yellow=c4a000 - magenta=75507b - background=300a24 - white=d3d7cf - cyan=06989a - [Names] - count=1 - name0=ubuntu +有的python第三方包旧版本会有安全漏洞,使用 pipenv check +可以检查安全漏洞。 -然后在xshell配色方案中导入即可 +|image8| -附上一个更全的帖子:\ `Xshell快捷键汇总 `__ +.env`文件,用来存放一些环境变量。 -------------- -|image6| +|image9| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190511162815.png -.. |image2| image:: http://image.python-online.cn/20190511162524.png -.. |image3| image:: http://image.python-online.cn/20190511162607.png -.. |image4| image:: http://image.python-online.cn/20190511162716.png -.. |image5| image:: http://image.python-online.cn/20190511162730.png -.. |image6| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/Fk6WZ2xbqg2DM3AvnYCpsiKQ4xOn +.. |image2| image:: http://image.iswbm.com/FjuJ8yZsgjkzVuBRZHxK1ZnnzaEX +.. |image3| image:: http://image.iswbm.com/20190612211330.png +.. |image4| image:: http://image.iswbm.com/20190612213015.png +.. |image5| image:: http://image.iswbm.com/20190612213950.png +.. |image6| image:: http://image.iswbm.com/20190612211925.png +.. |image7| image:: http://image.iswbm.com/20190614000336.png +.. |image8| image:: http://image.iswbm.com/20190612215924.png +.. |image9| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_03.md b/source/c04/c04_03.md index bb9a984..ec5afbd 100644 --- a/source/c04/c04_03.md +++ b/source/c04/c04_03.md @@ -2,8 +2,6 @@ ![](http://image.iswbm.com/20200602135014.png) -《网络是怎样连接的》 豆瓣评分:9.1 - 10个优秀的程序员里,有9个人都有写博客的习惯。这是非常好的习惯,值得每个程序员,投入时间和精力去坚持做下去。 写博客的平台有很多,CSDN,博客园,51CTO,还有人会使用Hexo+GitHub,WordPress,比较会折腾的人还会自己使用Java,Python搭建,我就干过这样的事,不过每年还要支付域名和服务器,比较麻烦而且浪费钱。 @@ -25,30 +23,30 @@ - ReadtheDocs:发布网页 -## 4.3.1 成品展示 +## 1. 成品展示 -以我的博客(`python.iswbm.com`)为例,先给大家展示一下。 +以我的博客(`pythontime.iswbm.com`)为例,先给大家展示一下。 这是首页。显示了你所有的文章索引。 -![](http://image.python-online.cn/20190511160523.png) +![](http://image.iswbm.com/20190511160523.png) 这是我的导航栏。是不是结构很清晰,很方便索引。 -![](http://image.python-online.cn/20190511161056.png) +![](http://image.iswbm.com/20190511161056.png) 点击文章后,还可以很方便查看标题,跳转。 -![](http://image.python-online.cn/20190511161130.png) +![](http://image.iswbm.com/20190511161130.png) 体验下搜索功能,速度很快。 -![](http://image.python-online.cn/20190511161147.png) +![](http://image.iswbm.com/20190511161147.png) 看完这些你是不是也很想拥有这样一个博客呢? 只要你认真往下看,30分钟搭建这样一个博客不在话下。 -## 4.3.2 安装Sphinx +## 2. 安装Sphinx 安装之前,请确认下Python版本。我这里使用的是Python 2.7.14,其他版本请自行尝试噢,Python3.6好像有些坑,你需要踩一下。 @@ -105,7 +103,7 @@ F:. - Makefile:编译文件。完全不用管。 - make.bat:bat脚本。你也不用管。 -## 4.3.3 配置及扩展 +## 3. 配置及扩展 Sphinx 的配置文件是 source\conifg.py @@ -131,7 +129,7 @@ Sphinx 的配置文件是 source\conifg.py pip install -r requirements.txt -i https://pypi.douban.com/simple/ ``` -## 4.3.4 撰写文章 +## 4. 撰写文章 万事俱备,接下来要写文档了。 @@ -210,14 +208,14 @@ The HTML pages are in build\html. 执行完了后,你可以发现原先的build,不再是空文件夹了。 我们点进去 build\html\ 目录,使用浏览器打开index.html文件。 -![](http://image.python-online.cn/20190511161212.png) +![](http://image.iswbm.com/20190511161212.png) 真棒,已经完成了一半了。点击 我们刚写的 暴富指南。 -![](http://image.python-online.cn/20190511161240.png) +![](http://image.iswbm.com/20190511161240.png) -## 4.3.5 托管项目 +## 5. 托管项目 看到网页的那一刻是不是相当激动。 @@ -237,27 +235,27 @@ build/ -## 4.3.6 发布上线 +## 6. 发布上线 托管完成后,我们要发布它,让别人也可以使用公网访问。 你需要先去 Read the Docs 注册下帐号。 关联一下GitHub -![](http://image.python-online.cn/20190511161255.png) +![](http://image.iswbm.com/20190511161255.png) -![](http://image.python-online.cn/20190511161311.png) +![](http://image.iswbm.com/20190511161311.png) 导入代码库。填好与你对应的信息。 -![](http://image.python-online.cn/20190511161334.png) +![](http://image.iswbm.com/20190511161334.png) -![](http://image.python-online.cn/20190511161414.png) +![](http://image.iswbm.com/20190511161414.png) 构建网页后。右下方,你可以看见你的在线地址。 -![](http://image.python-online.cn/20190511161426.png) +![](http://image.iswbm.com/20190511161426.png) 这里要提醒一下的是,Sphinx的文档格式,默认是 rst 格式,如果你习惯了使用Markdown来写文章,可以使用 Pandoc 这个神器转换一下。 @@ -275,9 +273,9 @@ pandoc -V mainfont="SimSun" -f markdown -t rst hello.md -o hello.rst -## 4.3.7 自定义插件 +## 7. 自定义插件 -之前有不少同学看过我的个人博客(http://python.iswbm.com),也根据我写的搭建教程,完成了自己的个人站点。 +之前有不少同学看过我的个人博客(http://pythontime.iswbm.com),也根据我写的搭建教程,完成了自己的个人站点。 使用这个方法搭建的站点,一直有一个痛点,就是无法自定义页面,自由度非常的低(和 WordPress 真的是没法比,因为这两种产品定位本身就不一样。) @@ -291,7 +289,7 @@ pandoc -V mainfont="SimSun" -f markdown -t rst hello.md -o hello.rst 方法就是引入两个 JavaSript 插件实现。 -## 第一个插件:导流工具 +### 7.1 第一个插件:导流工具 **作用**:用于将自己博客上流量引导到自己的公众号上。 @@ -299,7 +297,7 @@ pandoc -V mainfont="SimSun" -f markdown -t rst hello.md -o hello.rst 游客无法阅读博客的全部内容,因为会有一半的内容会被隐藏,就像这样。 -![](http://image.python-online.cn/20191015230346.png) +![](http://image.iswbm.com/20191015230346.png) 如想要浏览完整内容,需要点击 “阅读全文” 进行解锁: @@ -309,7 +307,7 @@ pandoc -V mainfont="SimSun" -f markdown -t rst hello.md -o hello.rst 3. 在如下文本框中输入验证码。 这样就可以永久解锁本博客的所有干货文章。 -![](http://image.python-online.cn/20191015230502.png) +![](http://image.iswbm.com/20191015230502.png) 思路有了,那么如何实现呢? @@ -385,7 +383,7 @@ html_js_files = [ - 改 CPython 2.x 为 CPython 3.x - 指定我们的本地生成的 requirements.txt(使用 pip freeze >requirements.txt) -![](http://image.python-online.cn/20191015234452.png) +![](http://image.iswbm.com/20191015234452.png) 同时你如果之前是看过我写的教程,使用过我的中文检索插件,那你要注意了。 @@ -397,7 +395,7 @@ html_js_files = [ -## 第二个插件:百度统计 +### 3.2 第二个插件:百度统计 **作用**:用于收集个人网站的访问数据。 @@ -415,11 +413,11 @@ html_js_files = [ 然后在网站列表新增一个你的网站,我的信息如下: -![](http://image.python-online.cn/20191016205336.png) +![](http://image.iswbm.com/20191016205336.png) 填写完成,就可以获取一段属于你的网站的专属 js 代码(下面第一步)。 -![](http://image.python-online.cn/20191016205653.png) +![](http://image.iswbm.com/20191016205653.png) 第二步内容,是教你如何安装这段 js 代码。 @@ -448,15 +446,15 @@ html_js_files = [ 构建完成后,去执行第三步,代码安装检查。像我下面这样,就是安装完成了。 -![](http://image.python-online.cn/20191015225652.png) +![](http://image.iswbm.com/20191015225652.png) 这个插件安装完成后,如果你的网站有流量,可以过个一个小时,点击一下查看报告查看你网站的详细访问数据。 -![](http://image.python-online.cn/20191016211012.png) +![](http://image.iswbm.com/20191016211012.png) 数据真的非常全面,你可以知道,访客都是从哪里访问(直接访问,Google等),每篇文章的点击量(你就知道哪篇是爆款?),每天有多少老访问客,多少新访客等等,更多维度的数据你可以自己去体验一下。 -##第三个插件:评论系统 +###3.3 第三个插件:评论系统 先到这个[网站](http://disqus.com/admin/create)去注册一个 disqus 帐号,我使用了 gmail 帐号进行注册 @@ -464,13 +462,13 @@ html_js_files = [ 然后根据指引填写好资料 -![image-20200704154846176](/Users/MING/Library/Application Support/typora-user-images/image-20200704154846176.png) +![](http://image.iswbm.com/image-20200704154846176.png) 选择基础版 -![image-20200704155335679](/Users/MING/Library/Application Support/typora-user-images/image-20200704155335679.png) +![](http://image.iswbm.com/image-20200704155335679.png) -![image-20200704155410411](/Users/MING/Library/Application Support/typora-user-images/image-20200704155410411.png) +![](http://image.iswbm.com/image-20200704155410411.png) ## 附录:参考文档 diff --git a/source/c04/c04_03.rst b/source/c04/c04_03.rst old mode 100755 new mode 100644 index 890f43a..33ddd0d --- a/source/c04/c04_03.rst +++ b/source/c04/c04_03.rst @@ -3,8 +3,6 @@ |image0| -《网络是怎样连接的》 豆瓣评分:9.1 - 10个优秀的程序员里,有9个人都有写博客的习惯。这是非常好的习惯,值得每个程序员,投入时间和精力去坚持做下去。 写博客的平台有很多,CSDN,博客园,51CTO,还有人会使用Hexo+GitHub,WordPress,比较会折腾的人还会自己使用Java,Python搭建,我就干过这样的事,不过每年还要支付域名和服务器,比较麻烦而且浪费钱。 @@ -25,10 +23,10 @@ - GitHub:托管项目 - ReadtheDocs:发布网页 -4.3.1 成品展示 --------------- +1. 成品展示 +----------- -以我的博客(\ ``python.iswbm.com``)为例,先给大家展示一下。 +以我的博客(\ ``pythontime.iswbm.com``)为例,先给大家展示一下。 这是首页。显示了你所有的文章索引。 |image1| @@ -44,8 +42,8 @@ 只要你认真往下看,30分钟搭建这样一个博客不在话下。 -4.3.2 安装Sphinx ----------------- +2. 安装Sphinx +------------- 安装之前,请确认下Python版本。我这里使用的是Python 2.7.14,其他版本请自行尝试噢,Python3.6好像有些坑,你需要踩一下。 @@ -104,8 +102,8 @@ - Makefile:编译文件。完全不用管。 - make.bat:bat脚本。你也不用管。 -4.3.3 配置及扩展 ----------------- +3. 配置及扩展 +------------- Sphinx 的配置文件是 source:raw-latex:`\conifg`.py @@ -133,8 +131,8 @@ exts pip install -r requirements.txt -i https://pypi.douban.com/simple/ -4.3.4 撰写文章 --------------- +4. 撰写文章 +----------- 万事俱备,接下来要写文档了。 @@ -219,8 +217,8 @@ source:raw-latex:`\index`.rst,千万要注意中间的空行不可忽略。 真棒,已经完成了一半了。点击 我们刚写的 暴富指南。 |image6| -4.3.5 托管项目 --------------- +5. 托管项目 +----------- 看到网页的那一刻是不是相当激动。 @@ -238,8 +236,8 @@ source:raw-latex:`\index`.rst,千万要注意中间的空行不可忽略。 接下来,在你的GitHub上新建一个仓库。然后把mkdocs这个目录下的所有文件都提交上去。步骤很简单,这里就不细讲了。 -4.3.6 发布上线 --------------- +6. 发布上线 +----------- 托管完成后,我们要发布它,让别人也可以使用公网访问。 @@ -273,10 +271,10 @@ source:raw-latex:`\index`.rst,千万要注意中间的空行不可忽略。 到这里,属于你的个人博客就搭建好了,快去试一下吧。 最后,整个项目的源码和模块包我都放在公众号(\ ``Python编程时光``\ )后台,请关注后,回复「\ ``Sphinx``\ 」领取。 -4.3.7 自定义插件 ----------------- +7. 自定义插件 +------------- -之前有不少同学看过我的个人博客(http://python.iswbm.com),也根据我写的搭建教程,完成了自己的个人站点。 +之前有不少同学看过我的个人博客(http://pythontime.iswbm.com),也根据我写的搭建教程,完成了自己的个人站点。 使用这个方法搭建的站点,一直有一个痛点,就是无法自定义页面,自由度非常的低(和 WordPress 真的是没法比,因为这两种产品定位本身就不一样。) @@ -291,8 +289,8 @@ WordPress 真的是没法比,因为这两种产品定位本身就不一样。 方法就是引入两个 JavaSript 插件实现。 -第一个插件:导流工具 --------------------- +7.1 第一个插件:导流工具 +~~~~~~~~~~~~~~~~~~~~~~~~ **作用**\ :用于将自己博客上流量引导到自己的公众号上。 @@ -411,8 +409,8 @@ Python 3.x ,所以这里的代码也要对应修改。 一切按照上面的步骤全部设置完成后,提交Github后,再次从 readthedocs 构建就可以看到效果了。 -第二个插件:百度统计 --------------------- +3.2 第二个插件:百度统计 +~~~~~~~~~~~~~~~~~~~~~~~~ **作用**\ :用于收集个人网站的访问数据。 @@ -471,7 +469,7 @@ Python 3.x ,所以这里的代码也要对应修改。 数据真的非常全面,你可以知道,访客都是从哪里访问(直接访问,Google等),每篇文章的点击量(你就知道哪篇是爆款?),每天有多少老访问客,多少新访客等等,更多维度的数据你可以自己去体验一下。 -##第三个插件:评论系统 +###3.3 第三个插件:评论系统 先到这个\ `网站 `__\ 去注册一个 disqus 帐号,我使用了 gmail 帐号进行注册 @@ -480,22 +478,13 @@ Python 3.x ,所以这里的代码也要对应修改。 然后根据指引填写好资料 -.. figure:: /Users/MING/Library/Application%20Support/typora-user-images/image-20200704154846176.png - :alt: image-20200704154846176 - - image-20200704154846176 +|image20| 选择基础版 -.. figure:: /Users/MING/Library/Application%20Support/typora-user-images/image-20200704155335679.png - :alt: image-20200704155335679 - - image-20200704155335679 +|image21| -.. figure:: /Users/MING/Library/Application%20Support/typora-user-images/image-20200704155410411.png - :alt: image-20200704155410411 - - image-20200704155410411 +|image22| 附录:参考文档 -------------- @@ -506,27 +495,30 @@ Python 3.x ,所以这里的代码也要对应修改。 -------------- -|image20| +|image23| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190511160523.png -.. |image2| image:: http://image.python-online.cn/20190511161056.png -.. |image3| image:: http://image.python-online.cn/20190511161130.png -.. |image4| image:: http://image.python-online.cn/20190511161147.png -.. |image5| image:: http://image.python-online.cn/20190511161212.png -.. |image6| image:: http://image.python-online.cn/20190511161240.png -.. |image7| image:: http://image.python-online.cn/20190511161255.png -.. |image8| image:: http://image.python-online.cn/20190511161311.png -.. |image9| image:: http://image.python-online.cn/20190511161334.png -.. |image10| image:: http://image.python-online.cn/20190511161414.png -.. |image11| image:: http://image.python-online.cn/20190511161426.png -.. |image12| image:: http://image.python-online.cn/20191015230346.png -.. |image13| image:: http://image.python-online.cn/20191015230502.png -.. |image14| image:: http://image.python-online.cn/20191015234452.png -.. |image15| image:: http://image.python-online.cn/20191016205336.png -.. |image16| image:: http://image.python-online.cn/20191016205653.png -.. |image17| image:: http://image.python-online.cn/20191015225652.png -.. |image18| image:: http://image.python-online.cn/20191016211012.png +.. |image1| image:: http://image.iswbm.com/20190511160523.png +.. |image2| image:: http://image.iswbm.com/20190511161056.png +.. |image3| image:: http://image.iswbm.com/20190511161130.png +.. |image4| image:: http://image.iswbm.com/20190511161147.png +.. |image5| image:: http://image.iswbm.com/20190511161212.png +.. |image6| image:: http://image.iswbm.com/20190511161240.png +.. |image7| image:: http://image.iswbm.com/20190511161255.png +.. |image8| image:: http://image.iswbm.com/20190511161311.png +.. |image9| image:: http://image.iswbm.com/20190511161334.png +.. |image10| image:: http://image.iswbm.com/20190511161414.png +.. |image11| image:: http://image.iswbm.com/20190511161426.png +.. |image12| image:: http://image.iswbm.com/20191015230346.png +.. |image13| image:: http://image.iswbm.com/20191015230502.png +.. |image14| image:: http://image.iswbm.com/20191015234452.png +.. |image15| image:: http://image.iswbm.com/20191016205336.png +.. |image16| image:: http://image.iswbm.com/20191016205653.png +.. |image17| image:: http://image.iswbm.com/20191015225652.png +.. |image18| image:: http://image.iswbm.com/20191016211012.png .. |image19| image:: http://image.iswbm.com/image-20200704154427375.png -.. |image20| image:: http://image.iswbm.com/20200607174235.png +.. |image20| image:: http://image.iswbm.com/image-20200704154846176.png +.. |image21| image:: http://image.iswbm.com/image-20200704155335679.png +.. |image22| image:: http://image.iswbm.com/image-20200704155410411.png +.. |image23| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_04.md b/source/c04/c04_04.md index 7ecbff2..d6c3a95 100644 --- a/source/c04/c04_04.md +++ b/source/c04/c04_04.md @@ -29,7 +29,7 @@ Anaconda 号称是适用于企业级大数据分析的Python工具。它包含 ## 4.4.1 下载安装 到官网下载 [Anaconda](https://www.anaconda.com/download/) -![](http://image.python-online.cn/20190511163102.png) +![](http://image.iswbm.com/20190511163102.png) 我这里选择下载 Py3.6,下载下来是 exe 文件(600多M)。安装即可。 安装完成后,还会提示你是否安装 VS Code,自行选择。不安装直接 Skip 就好。 @@ -37,20 +37,20 @@ Anaconda 号称是适用于企业级大数据分析的Python工具。它包含 ## 4.4.2 启动程序 点击打开 Anaconda 。会出现这个界面。第一眼,我看到了很多工具,包含 NoteBook,qtconsole,VS Ccode等。 -![](http://image.python-online.cn/20190511163123.png) +![](http://image.iswbm.com/20190511163123.png) 这里就介绍一下,我最经常使用的 NoteBook 就好了。其他的大家自行尝试。 选择 NoteBook,点击 Launch,会启动浏览器打开一个 web 页面。 -![](http://image.python-online.cn/20190511163137.png) +![](http://image.iswbm.com/20190511163137.png) 点击右上角的new,就可以进入 Python3 的交互界面。 -![](http://image.python-online.cn/20190511163145.png) +![](http://image.iswbm.com/20190511163145.png) 来感受一下,如何使用这个工具。 每一行的命令,都在一个可编辑的输入框。即使你的命令输入有误,你也不用从头写代码,直接编辑后重新执行即可。 -![](http://image.python-online.cn/20190511163200.png) +![](http://image.iswbm.com/20190511163200.png) 还有一点,命令的执行是可间断的,某个命令执行错误,不会导致整个程序中断,这将很方便我们调试代码,只要改完代码,再重新执行该行代码即可,而不用重新执行全部代码。 @@ -153,18 +153,18 @@ Anaconda 号称是适用于企业级大数据分析的Python工具。它包含 其实以上快捷键,在非编辑模式下,按 h 就会出现快捷键帮助菜单。 -![](http://image.python-online.cn/20190511163245.png) -![](http://image.python-online.cn/20190511163253.png) +![](http://image.iswbm.com/20190511163245.png) +![](http://image.iswbm.com/20190511163253.png) ## 4.4.4 导出笔记文件 NoteBook 既然支持 Markdown ,你已经也能想到它可以用来记录学习笔记。 它提供多种常用的文件格式,md,rst,pdf等。如果你希望再次编辑,可以保存为ipynb,这是Jupyter的文件格式,可以再次打开进行编辑。 -![](http://image.python-online.cn/20190511163304.png) +![](http://image.iswbm.com/20190511163304.png) 以前我学习 Pandas 的时候,也曾经使用它做过笔记,输出的是PDF文件,可以按目录导航,相当方便。 -![](http://image.python-online.cn/20190511163311.png) +![](http://image.iswbm.com/20190511163311.png) 好了,大概就是这些内容。 diff --git a/source/c04/c04_04.rst b/source/c04/c04_04.rst old mode 100755 new mode 100644 index bbe77dd..a694a1d --- a/source/c04/c04_04.rst +++ b/source/c04/c04_04.rst @@ -242,14 +242,14 @@ NoteBook 既然支持 Markdown ,你已经也能想到它可以用来记录学 |image10| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190511163102.png -.. |image2| image:: http://image.python-online.cn/20190511163123.png -.. |image3| image:: http://image.python-online.cn/20190511163137.png -.. |image4| image:: http://image.python-online.cn/20190511163145.png -.. |image5| image:: http://image.python-online.cn/20190511163200.png -.. |image6| image:: http://image.python-online.cn/20190511163245.png -.. |image7| image:: http://image.python-online.cn/20190511163253.png -.. |image8| image:: http://image.python-online.cn/20190511163304.png -.. |image9| image:: http://image.python-online.cn/20190511163311.png +.. |image1| image:: http://image.iswbm.com/20190511163102.png +.. |image2| image:: http://image.iswbm.com/20190511163123.png +.. |image3| image:: http://image.iswbm.com/20190511163137.png +.. |image4| image:: http://image.iswbm.com/20190511163145.png +.. |image5| image:: http://image.iswbm.com/20190511163200.png +.. |image6| image:: http://image.iswbm.com/20190511163245.png +.. |image7| image:: http://image.iswbm.com/20190511163253.png +.. |image8| image:: http://image.iswbm.com/20190511163304.png +.. |image9| image:: http://image.iswbm.com/20190511163311.png .. |image10| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_05.md b/source/c04/c04_05.md index ccfc64b..0f9ad0e 100644 --- a/source/c04/c04_05.md +++ b/source/c04/c04_05.md @@ -1,132 +1,378 @@ -# 4.5 Win10+Ubuntu 双系统安装教程 +# 4.5 最全的 pip 使用指南 ![](http://image.iswbm.com/20200602135014.png) ---- +所有的 Python 开发者都清楚,Python 之所以如此受欢迎,能够在众多高级语言中,脱颖而出,除了语法简单,上手容易之外,更多还要归功于 Python 生态的完备,有数以万计的 Python 爱好者愿意以 Python 为基础封装出各种有利于开发的第三方工具包。 + +这才使用我们能够以最快的速度开发出一个满足基本需要的项目,而不是每次都重复造轮子。 + +Python 从1991年诞生到现在,已经过去28个年头了,这其间产生了数以万计的第三方包,且每个包都会不断更新,会有越来越多的版本。 + +当你在一个复杂的项目环境中,如果没有一个有效的依赖包管理方案,项目的维护将会是一个大问题。 + +pip 是官方推荐的包管理工具,在大多数开发者眼里,pip 几乎是 Python 的标配。 + +当然也有其他的包管理工具 + +- **distutils**:仅用于打包和安装,严格来讲不算是包管理工具 + +- **setuptools**:distutils的增强版,扩展了distutils,提供更多的功能,引入包依赖的管理,easy_install就是它的一个命令行工具,引入了 egg 的文件格式。 + +- **Pipenv**:一个集依赖包管理(pip)及虚拟环境管理(virtualenv)的工具 + +- 还有其他的,这里不一一列出。 + + + +今天的主角是 pip ,大家肯定不会陌生。但我相信不少人,只是熟悉几个常用的用法,而对于其他几个低频且实用的用法,却知之甚少,这两天,我查阅官方文档,把这些用法整理了一下,应该是网络上比较全的介绍。 + + + +## 1. 查询软件包 + +查询当前环境安装的所有软件包 + +```shell +$ pip list +``` + +查询 pypi 上含有某名字的包 + +```shell +$ pip search pkg +``` + +查询当前环境中可升级的包 -在大多数情况下,对于一个程序开发人员,电脑的操作系统的最佳选择不应该是 Windows,而是 Mac 或者 Linux。 +```shell +$ pip list --outdated +``` -Mac 固然很好,但是价钱实在不亲民,让很多人连体验都体验不了,只能停留在想象中,那是属于“别人家的电脑”。如果你想装个黑苹果,那我劝你还是放弃吧,你可以去尝试一下。 +查询一个包的详细内容 -除了 Mac 外,还有开源免费的 Linux 可以选择,可以让你 远离游戏和一切娱乐项目,而专注于开发。 +```shell +$ pip show pkg +``` -Linux 的发行版本有很多,这里选择 Ubuntu 并没有什么特殊的缘由。只是个人感觉在桌面版本 Ubuntu 会比其他发行版做得更加人性,更加成熟。而你如果想使用 CentOS,Debian等,在本篇教程中,换汤不换药,应该也是同样适用的。 +## 2. 下载软件包 -## 4.5.1 准备工作 +在不安装软件包的情况下下载软件包到本地 -由于本教程,是在双硬盘下安装的。所以你要确保你的电脑上有两块硬盘,如果没有,可以去购买一块,这里强烈推荐 SSD,不要再买 HDD 了。因为在我的对比之下,安装速度差的不是一点半点,HDD 下安装可能需要20-30分钟吧(没有去注意,反正挺久),SSD 下安装,啾的一下,很快就好,真的超级快的。 +```shell +$ pip download --destination-directory /local/wheels -r requirements.txt +``` -另外,你还需要两个软件,这里我会打包准备好,你可以 扫描二维码,关注我公众号后,回复「双系统」直接获取。 +下载完,总归是要安装的,可以指定这个目录中安装软件包,而不从 pypi 上安装。 -最后,如果你怕误操作,将新系统覆盖到你原来的物理盘上,导致你的数据丢失,你最好进行备份,但是我相信你只要跟着文章一步一步来操作,不会出现这种意外。 +```shell +$ pip install --no-index --find-links=/local/wheels -r requirements.txt +``` -总结如下: +当然你也从你下载的包中,自己构建生成 wheel 文件 -- 双硬盘(SSD) -- U盘(4G+) -- 两个软件(UltraISO + DiskGenius) -- iso文件(Ubuntu 16.04) -- 数据无价,注意备份 +```shell +$ pip install wheel +$ pip wheel --wheel-dir=/local/wheels -r requirements.txt +``` -## 4.5.2 安装硬盘 -若你的电脑已经有两块硬盘(都是HDD也无防,没有强制),那你可以直接跳过,进入第二步。 -由于我此前只有一块硬盘,所以为了这次顺利的安装,我特地网购了一块 SSD。其实在此之前,我有尝试在单硬盘上尝试安装,也已经成功安装了,但是在开机重启后,电脑无法得知该从哪个分区引导系统,导致我的电脑直接无法使用,不论是 Windows 还是新装的 Ubuntu 都进入不到系统,好在我不慌,我家里有好几个 U盘,其中一个 U盘,装的是 PE系统,里面有DiskGenius工具,可以自动修复引导。这才得以重新进入 Windows。 +## 3. 安装软件包 -关于单个硬盘引导的问题,我曾尝试使用 EasyBCD 和 NTBOOTautofix 来创建新的引导。但是都没有成功,为了减少折腾的成本,我才直接选择双硬盘安装的。如果有单硬盘安装经验的朋友,欢迎与我交流,我也想学习一下。 +使用 `pip install ` 可以很方便地从 pypi 上搜索下载并安装 python 包。 -这里上一张我安装硬盘的记念图。 +如下所示 -![](http://image.python-online.cn/20190511163441.png) +```shell +$ pip install requests +``` -你没有看错,我的是台式机。说起这台电脑,还是前年我自己搜罗配置单,自己从各大电商平台,有京东,淘宝,还有天猫买了所有的配件,然后自己一件一件组装起来的。还是挺有感情的,虽然也是渣渣配置,但是这个过程还是很愉快的。在有了每一次装体验后,后面我还给别人装过好几台,如果说高三是我知识储备最高的时期,那前年就是我动手能力最强的时期的。组装过将近十台的电脑。这都是题外话了。 +这是安装包的基本格式,我们也可以为其添加更多参数来实现不同的效果。 -第一次装好硬盘后呢,要进入原先的 Windows 系统,检查一下,我们安装的硬盘有没有装成功。由于我装了工具,开始键和你们的可能不一样(不过真的是Win10),你也可以右击桌面 「我的电脑」,再点击「管理」。 -![](http://image.python-online.cn/20190511163457.png) -如果硬盘安装成功,这里会有一块未分配的盘,如图中的 硬盘0。 -第一次使用,需要初始化硬盘,记得选 GPT。 -![](http://image.python-online.cn/20190511163510.png) +**3.1 只从本地安装,而不从 pypi 安装** -这里可以不用急着分区(在后面安装系统时会让你分的),如果你要提前分好(使用DiskGenius),也没有关系。 +```shell +# 前提你得保证你已经下载 pkg 包到 /local/wheels 目录下 +$ pip install --no-index --find-links=/local/wheels pkg +``` -## 4.5.3 制作U盘系统 +**3.2 限定版本进行软件包安装** -有安装过系统的人(Windows),正常都知道,我们使用U盘安装一个 PE 系统,然后通过这个 U盘 过渡,将真正的操作系统写入硬盘中。 +以下三种,对单个 python 包的版本进行了约束 -那么如何安装这个 U盘 系统呢,你首先需要先去官网下载一个对应的系统的 iso文件,Ubuntu 的话,你可以去官网下载:https://www.ubuntu.com/download/desktop,我这里下载的是 16.04 的。 +```shell +# 所安装的包的版本为 2.1.2 +$ pip install pkg==2.1.2 -此外,你还需要一个可以 iso文件 写入到U盘中的工具,这里我使用UltraISO ,你可以关注公众号后,在后台回复关键字「双系统」直接获取下载地址。 +# 所安装的包必须大于等于 2.1.2 +$ pip install pkg>=2.1.2 -iso 和 UltraISO 都准备完成后,就可以安装U盘系统了。 +# 所安装的包必须小于等于 2.1.2 +$ pip install pkg<=2.1.2 +``` -首先,打开软件,点击 文件 - 打开,选择你所下载的 iso 文件。出现如下界面 +以下命令用于管理/控制整个 python 环境的包版本 -![](http://image.python-online.cn/20190511163520.png) +```shell +# 导出依赖包列表 +pip freeze >requirements.txt -再点击 启动 - 写入硬盘映像 - 写入 +# 从依赖包列表中安装 +pip install -r requirements.txt -![](http://image.python-online.cn/20190511163531.png) +# 确保当前环境软件包的版本(并不确保安装) +pip install -c constraints.txt +``` -如一切顺利,U盘就制作完成,一般 99.99% 都不会在这地方出错。 -## 4.5.4 关闭快速启动 -大家都知道,win10的开机速度有多快,具体原理我就不讲了,有兴趣可以搜索引擎查找。 +**3.3 限制不使用二进制包安装** -但在这里,必须关闭 win10的快速启动功能。方法如下:取消勾选「启用快速启动」,点击保存修改。然后就可以正常关机了。 +由于默认情况下,wheel 包的平台是运行 pip download 命令 的平台,所以可能出现平台不适配的情况。 -![](http://image.python-online.cn/20190511163542.png) +比如在 MacOS 系统下得到的 pymongo-2.8-cp27-none-macosx_10_10_intel.whl 就不能在 linux_x86_64 安装。 +使用下面这条命令下载的是 tar.gz 的包,可以直接使用 pip install 安装。 -## 4.5.5 安装Ubuntu +比 wheel 包,这种包在安装时会进行编译,所以花费的时间会长一些。 -在安装之前呢,首先你要根据你的主板,查找选择 启动顺序 的快捷键。由于我的主板是 MSI ,所以我的快捷键是 F11。当然你可以选择,按DEL键进入 Bios,更改顺序。但是我这里不想这么麻烦,因为安装完后又要改回来。 +```shell +# 下载非二进制的包 +$ pip download --no-binary=:all: pkg -查找完后,你可以对电脑开机了,按住你的快捷键,选择启动方式。由于我们要从U盘启动,所以这里选择 +# 安装非二进制的包 +$ pip install pkg --no-binary +``` -这里一定要注意,选择最后一个,自定义选项。 -![](http://image.python-online.cn/20190511163550.png) +**3.4 指定代理服务器安装** +当你身处在一个内网环境中时,无法直接连接公网。这时候你使用`pip install` 安装包,就会失败。 -终于到了分区的这一步了,这是最关键的一步。对于Linux不熟悉的人,到这里可能会懵逼。不用怕,这里给你提供一个最简单的分区配置。具体为什么这么分,我想你并不关心吧? +面对这种情况,可以有两种方法: -- efi:系统分区,我分20G,分太多了。 -- swap:系统交换分区,设置为8G -- /:剩下全部分给根目录,类型选 ext4 +1. 下载离线包拷贝到内网机器中安装 +2. 使用代理服务器转发请求 -对,就是这么简单粗暴。如果你想更加精细一点,你还可以自定义 /home,/usr等。 +第一种方法,虽说可行,但有相当多的弊端 -分区完成后,一定要注意如下这二个红框,选择安装启动引导器的设备为 我们刚刚设置的efi分区。检查无误后,就可以点击「现在安装」。 -![](http://image.python-online.cn/20190511163559.png) +- 步骤繁杂,耗时耗力 +- 无法处理包的依赖问题 -接下来就是 选择时区 - 配置键盘。 -![](http://image.python-online.cn/20190511163612.png) +这里重点来介绍,第二种方法: -![](http://image.python-online.cn/20190511163633.png) +```shell +$ pip install --proxy [user:passwd@]http_server_ip:port pkg +``` +每次安装包就发输入长长的参数,未免有些麻烦,为此你可以将其写入配置文件中:`$HOME/.config/pip/pip.conf` +对于这个路径,说明几点 -到了这里,你应该可以长舒第一口气了。成功了一半了。 -![](http://image.python-online.cn/20190511163700.png) +- 不同的操作系统,路径各不相同 -如果你和我一样使用 SSD ,应该不出5分钟系统就可以安装完毕。弹出如下界面。点击现在重启。 -![](http://image.python-online.cn/20190511163711.png) +```shell +# Linux/Unix: +/etc/pip.conf +~/.pip/pip.conf +~/.config/pip/pip.conf + +# Mac OSX: +~/Library/Application Support/pip/pip.conf +~/.pip/pip.conf +/Library/Application Support/pip/pip.conf + +# Windows: +%APPDATA%\pip\pip.ini +%HOME%\pip\pip.ini +C:\Documents and Settings\All Users\Application Data\PyPA\pip\pip.conf (Windows XP) +C:\ProgramData\PyPA\pip\pip.conf (Windows 7及以后) +``` -重启的过程,记住还是一样按住你的快捷键,我这里仍然是 F11,看到没有,已经有一个叫 ubuntu的启动设备。就它了,选择进入系统。接下来,就是选择要以哪种模式进入ubuntu,你根据需要去选吧。 -![](http://image.python-online.cn/20190511163722.png) +- 若在你的机子上没有此文件,则自行创建即可 -## 4.5.6 效果展示 +如何配置,这边给个样例: + +```ini +[global] +index-url = http://mirrors.aliyun.com/pypi/simple/ + +# 替换出自己的代理地址,格式为[user:passwd@]proxy.server:port +proxy=http://xxx.xxx.xxx.xxx:8080 + +[install] +# 信任阿里云的镜像源,否则会有警告 +trusted-host=mirrors.aliyun.com +``` + +**3.5 安装用户私有软件包** + +很多人可能还不清楚,python 的安装包是可以用户隔离的。 + +如果你拥有管理员权限,你可以将包安装在全局环境中。在全局环境中的这个包可被该机器上的所有拥有管理员权限的用户使用。 + +如果一台机器上的使用者不只一样,自私地将在全局环境中安装或者升级某个包,是不负责任且危险的做法。 + +面对这种情况,我们就想能否安装单独为我所用的包呢? + +庆幸的是,还真有。 + +我能想到的有两种方法: + +1. 使用虚拟环境 +2. 将包安装在用户的环境中 + +虚拟环境,之前写过几篇文章,这里不再展开讲。 + +今天的重点是第二种方法,教你如何安装用户私有的包? + +命令也很简单,只要加上 `--user` 参数,pip 就会将其安装在当前用户的 `~/.local/lib/python3.x/site-packages` 下,而其他用户的 python 则不会受影响。 + +```shell +pip install --user pkg +``` + +来举个例子 + +```shell +# 在全局环境中未安装 requests +[root@localhost ~]# pip list | grep requests +[root@localhost ~]# su - wangbm +[root@localhost ~]# + +# 由于用户环境继承自全局环境,这里也未安装 +[wangbm@localhost ~]# pip list | grep requests +[wangbm@localhost ~]# pip install --user requests +[wangbm@localhost ~]# pip list | grep requests +requests (2.22.0) +[wangbm@localhost ~]# + +# 从 Location 属性可发现 requests 只安装在当前用户环境中 +[wangbm@ws_compute01 ~]$ pip show requests +--- +Metadata-Version: 2.1 +Name: requests +Version: 2.22.0 +Summary: Python HTTP for Humans. +Home-page: http://python-requests.org +Author: Kenneth Reitz +Author-email: me@kennethreitz.org +Installer: pip +License: Apache 2.0 +Location: /home/wangbm/.local/lib/python2.7/site-packages +[wangbm@localhost ~]$ exit +logout -由于默认的Ubuntu主题也是丑得可以,经过一个晚上的美化,它变成如下这般帅气逼人。 +# 退出 wangbm 用户,在 root 用户环境中发现 requests 未安装 +[root@localhost ~]$ pip list | grep requests +[root@localhost ~]$ +``` -![](http://image.python-online.cn/20190511163731.png) +当你身处个人用户环境中,python 导包时会先检索当前用户环境中是否已安装这个包,已安装则优先使用,未安装则使用全局环境中的包。 -![](http://image.python-online.cn/20190511163750.png) +验证如下: + +```python +>>> import sys +>>> from pprint import pprint +>>> pprint(sys.path) +['', + '/usr/lib64/python27.zip', + '/usr/lib64/python2.7', + '/usr/lib64/python2.7/plat-linux2', + '/usr/lib64/python2.7/lib-tk', + '/usr/lib64/python2.7/lib-old', + '/usr/lib64/python2.7/lib-dynload', + '/home/wangbm/.local/lib/python2.7/site-packages', + '/usr/lib64/python2.7/site-packages', + '/usr/lib64/python2.7/site-packages/gtk-2.0', + '/usr/lib/python2.7/site-packages', + '/usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg', + '/usr/lib/python2.7/site-packages/lockfile-0.12.2-py2.7.egg'] +>>> + +``` + +**3.6 延长超时时间** + +若网络情况不是很好,在安装某些包时经常会因为 ReadTimeout 而失败。 + +对于这种情况,一般重试几次就好了。 + +但是这样难免有些麻烦,有没有更好的解决方法呢? + +有的,可以通过延长超时时间。 + +```shell +$ pip install --default-timeout=100 +``` + + + +## 4. 卸载软件包 + +就一条命令,不再赘述 + +```shell +$ pip uninstall pkg +``` + + + +## 5. 升级软件包 + +想要对现有的 python 进行升级,其本质上也是先从 pypi 上下载最新版本的包,再对其进行安装。所以升级也是使用 `pip install`,只不过要加一个参数 `--upgrade`。 + +``` +$ pip install --upgrade pkg +``` + +在升级的时候,其实还有一个不怎么用到的选项 `--upgrade-strategy`,它是用来指定升级策略。 + +它的可选项只有两个: + +- `eager` :升级全部依赖包 +- `only-if-need`:只有当旧版本不能适配新的父依赖包时,才会升级。 + +在 pip 10.0 版本之后,这个选项的默认值是 `only-if-need`,因此如下两种写法是一互致的。 + +```shell +pip install --upgrade pkg1 +pip install --upgrade pkg1 --upgrade-strategy only-if-need +``` + +## 6. 配置文件 + +由于在使用 pip 安装一些包时,默认会使用 pip 的官方源,所以经常会报网络超时失败。 + +常用的解决办法是,在安装包时,使用 `-i` 参数指定一个国内的镜像源。但是每次指定就很麻烦呀,还要打超长的一串字母。 + +这时候,其实可以将这个源写进 pip 的配置文件里。以后安装的时候,就默认从你配置的这个 源里安装了。 + +那怎么配置呢?文件文件在哪? + +使用` win+r` 输入 `%APPDATA%` 进入用户资料文件夹,查看有没有一个 pip 的文件夹,若没有则创建之。 + +然后进入这个 文件夹,新建一个 `pip.ini` 的文件,内容如下 + +```ini +[global] +time-out=60 +index-url=https://pypi.tuna.tsinghua.edu.cn/simple/ +[install] +trusted-host=tsinghua.edu.cn +``` + + + + + +以上几乎包含了 pip 的所有常用使用场景,为了方便,我将其整理成一张表格,如果你需要,可以关注我的公众号(Python编程时光),后台回复“pip”,可获取高清无水印图片。 + +![](http://image.iswbm.com/20191105200041.png) -![](http://image.python-online.cn/20190511163757.png) -![](http://image.python-online.cn/20190511163805.png) ----- ![](http://image.iswbm.com/20200607174235.png) + + + diff --git a/source/c04/c04_05.rst b/source/c04/c04_05.rst old mode 100755 new mode 100644 index e4093c4..7353b73 --- a/source/c04/c04_05.rst +++ b/source/c04/c04_05.rst @@ -1,183 +1,392 @@ -4.5 Win10+Ubuntu 双系统安装教程 -=============================== +4.5 最全的 pip 使用指南 +======================= |image0| --------------- +所有的 Python 开发者都清楚,Python +之所以如此受欢迎,能够在众多高级语言中,脱颖而出,除了语法简单,上手容易之外,更多还要归功于 +Python 生态的完备,有数以万计的 Python 爱好者愿意以 Python +为基础封装出各种有利于开发的第三方工具包。 -在大多数情况下,对于一个程序开发人员,电脑的操作系统的最佳选择不应该是 -Windows,而是 Mac 或者 Linux。 +这才使用我们能够以最快的速度开发出一个满足基本需要的项目,而不是每次都重复造轮子。 -Mac -固然很好,但是价钱实在不亲民,让很多人连体验都体验不了,只能停留在想象中,那是属于“别人家的电脑”。如果你想装个黑苹果,那我劝你还是放弃吧,你可以去尝试一下。 +Python +从1991年诞生到现在,已经过去28个年头了,这其间产生了数以万计的第三方包,且每个包都会不断更新,会有越来越多的版本。 -除了 Mac 外,还有开源免费的 Linux 可以选择,可以让你 -远离游戏和一切娱乐项目,而专注于开发。 +当你在一个复杂的项目环境中,如果没有一个有效的依赖包管理方案,项目的维护将会是一个大问题。 -Linux 的发行版本有很多,这里选择 Ubuntu -并没有什么特殊的缘由。只是个人感觉在桌面版本 Ubuntu -会比其他发行版做得更加人性,更加成熟。而你如果想使用 -CentOS,Debian等,在本篇教程中,换汤不换药,应该也是同样适用的。 +pip 是官方推荐的包管理工具,在大多数开发者眼里,pip 几乎是 Python +的标配。 -4.5.1 准备工作 --------------- +当然也有其他的包管理工具 -由于本教程,是在双硬盘下安装的。所以你要确保你的电脑上有两块硬盘,如果没有,可以去购买一块,这里强烈推荐 -SSD,不要再买 HDD 了。因为在我的对比之下,安装速度差的不是一点半点,HDD -下安装可能需要20-30分钟吧(没有去注意,反正挺久),SSD -下安装,啾的一下,很快就好,真的超级快的。 +- **distutils**\ :仅用于打包和安装,严格来讲不算是包管理工具 -另外,你还需要两个软件,这里我会打包准备好,你可以 -扫描二维码,关注我公众号后,回复「双系统」直接获取。 +- **setuptools**\ :distutils的增强版,扩展了distutils,提供更多的功能,引入包依赖的管理,easy_install就是它的一个命令行工具,引入了 + egg 的文件格式。 -最后,如果你怕误操作,将新系统覆盖到你原来的物理盘上,导致你的数据丢失,你最好进行备份,但是我相信你只要跟着文章一步一步来操作,不会出现这种意外。 +- **Pipenv**\ :一个集依赖包管理(pip)及虚拟环境管理(virtualenv)的工具 -总结如下: +- 还有其他的,这里不一一列出。 -- 双硬盘(SSD) -- U盘(4G+) -- 两个软件(UltraISO + DiskGenius) -- iso文件(Ubuntu 16.04) -- 数据无价,注意备份 +今天的主角是 pip +,大家肯定不会陌生。但我相信不少人,只是熟悉几个常用的用法,而对于其他几个低频且实用的用法,却知之甚少,这两天,我查阅官方文档,把这些用法整理了一下,应该是网络上比较全的介绍。 -4.5.2 安装硬盘 --------------- +1. 查询软件包 +------------- -若你的电脑已经有两块硬盘(都是HDD也无防,没有强制),那你可以直接跳过,进入第二步。 +查询当前环境安装的所有软件包 -由于我此前只有一块硬盘,所以为了这次顺利的安装,我特地网购了一块 -SSD。其实在此之前,我有尝试在单硬盘上尝试安装,也已经成功安装了,但是在开机重启后,电脑无法得知该从哪个分区引导系统,导致我的电脑直接无法使用,不论是 -Windows 还是新装的 Ubuntu 都进入不到系统,好在我不慌,我家里有好几个 -U盘,其中一个 U盘,装的是 -PE系统,里面有DiskGenius工具,可以自动修复引导。这才得以重新进入 -Windows。 +.. code:: shell -关于单个硬盘引导的问题,我曾尝试使用 EasyBCD 和 NTBOOTautofix -来创建新的引导。但是都没有成功,为了减少折腾的成本,我才直接选择双硬盘安装的。如果有单硬盘安装经验的朋友,欢迎与我交流,我也想学习一下。 + $ pip list -这里上一张我安装硬盘的记念图。 +查询 pypi 上含有某名字的包 -|image1| +.. code:: shell + + $ pip search pkg + +查询当前环境中可升级的包 + +.. code:: shell + + $ pip list --outdated + +查询一个包的详细内容 + +.. code:: shell + + $ pip show pkg + +2. 下载软件包 +------------- + +在不安装软件包的情况下下载软件包到本地 + +.. code:: shell + + $ pip download --destination-directory /local/wheels -r requirements.txt + +下载完,总归是要安装的,可以指定这个目录中安装软件包,而不从 pypi +上安装。 + +.. code:: shell + + $ pip install --no-index --find-links=/local/wheels -r requirements.txt + +当然你也从你下载的包中,自己构建生成 wheel 文件 + +.. code:: shell + + $ pip install wheel + $ pip wheel --wheel-dir=/local/wheels -r requirements.txt + +3. 安装软件包 +------------- + +使用 ``pip install `` 可以很方便地从 pypi 上搜索下载并安装 python +包。 + +如下所示 + +.. code:: shell + + $ pip install requests + +这是安装包的基本格式,我们也可以为其添加更多参数来实现不同的效果。 + +**3.1 只从本地安装,而不从 pypi 安装** + +.. code:: shell + + # 前提你得保证你已经下载 pkg 包到 /local/wheels 目录下 + $ pip install --no-index --find-links=/local/wheels pkg + +**3.2 限定版本进行软件包安装** + +以下三种,对单个 python 包的版本进行了约束 + +.. code:: shell + + # 所安装的包的版本为 2.1.2 + $ pip install pkg==2.1.2 + + # 所安装的包必须大于等于 2.1.2 + $ pip install pkg>=2.1.2 + + # 所安装的包必须小于等于 2.1.2 + $ pip install pkg<=2.1.2 + +以下命令用于管理/控制整个 python 环境的包版本 + +.. code:: shell + + # 导出依赖包列表 + pip freeze >requirements.txt + + # 从依赖包列表中安装 + pip install -r requirements.txt + + # 确保当前环境软件包的版本(并不确保安装) + pip install -c constraints.txt + +**3.3 限制不使用二进制包安装** + +由于默认情况下,wheel 包的平台是运行 pip download 命令 +的平台,所以可能出现平台不适配的情况。 + +比如在 MacOS 系统下得到的 pymongo-2.8-cp27-none-macosx_10_10_intel.whl +就不能在 linux_x86_64 安装。 + +使用下面这条命令下载的是 tar.gz 的包,可以直接使用 pip install 安装。 + +比 wheel 包,这种包在安装时会进行编译,所以花费的时间会长一些。 -你没有看错,我的是台式机。说起这台电脑,还是前年我自己搜罗配置单,自己从各大电商平台,有京东,淘宝,还有天猫买了所有的配件,然后自己一件一件组装起来的。还是挺有感情的,虽然也是渣渣配置,但是这个过程还是很愉快的。在有了每一次装体验后,后面我还给别人装过好几台,如果说高三是我知识储备最高的时期,那前年就是我动手能力最强的时期的。组装过将近十台的电脑。这都是题外话了。 +.. code:: shell -第一次装好硬盘后呢,要进入原先的 Windows -系统,检查一下,我们安装的硬盘有没有装成功。由于我装了工具,开始键和你们的可能不一样(不过真的是Win10),你也可以右击桌面 -「我的电脑」,再点击「管理」。 |image2| -如果硬盘安装成功,这里会有一块未分配的盘,如图中的 硬盘0。 -第一次使用,需要初始化硬盘,记得选 GPT。 |image3| + # 下载非二进制的包 + $ pip download --no-binary=:all: pkg -这里可以不用急着分区(在后面安装系统时会让你分的),如果你要提前分好(使用DiskGenius),也没有关系。 + # 安装非二进制的包 + $ pip install pkg --no-binary -4.5.3 制作U盘系统 ------------------ +**3.4 指定代理服务器安装** -有安装过系统的人(Windows),正常都知道,我们使用U盘安装一个 PE -系统,然后通过这个 U盘 过渡,将真正的操作系统写入硬盘中。 +当你身处在一个内网环境中时,无法直接连接公网。这时候你使用\ ``pip install`` +安装包,就会失败。 -那么如何安装这个 U盘 系统呢,你首先需要先去官网下载一个对应的系统的 -iso文件,Ubuntu -的话,你可以去官网下载:https://www.ubuntu.com/download/desktop,我这里下载的是 -16.04 的。 +面对这种情况,可以有两种方法: -此外,你还需要一个可以 iso文件 写入到U盘中的工具,这里我使用UltraISO -,你可以关注公众号后,在后台回复关键字「双系统」直接获取下载地址。 +1. 下载离线包拷贝到内网机器中安装 +2. 使用代理服务器转发请求 -iso 和 UltraISO 都准备完成后,就可以安装U盘系统了。 +第一种方法,虽说可行,但有相当多的弊端 -首先,打开软件,点击 文件 - 打开,选择你所下载的 iso 文件。出现如下界面 +- 步骤繁杂,耗时耗力 +- 无法处理包的依赖问题 -|image4| +这里重点来介绍,第二种方法: -再点击 启动 - 写入硬盘映像 - 写入 +.. code:: shell -|image5| + $ pip install --proxy [user:passwd@]http_server_ip:port pkg -如一切顺利,U盘就制作完成,一般 99.99% 都不会在这地方出错。 +每次安装包就发输入长长的参数,未免有些麻烦,为此你可以将其写入配置文件中:\ ``$HOME/.config/pip/pip.conf`` -4.5.4 关闭快速启动 ------------------- +对于这个路径,说明几点 -大家都知道,win10的开机速度有多快,具体原理我就不讲了,有兴趣可以搜索引擎查找。 +- 不同的操作系统,路径各不相同 -但在这里,必须关闭 -win10的快速启动功能。方法如下:取消勾选「启用快速启动」,点击保存修改。然后就可以正常关机了。 +.. code:: shell -|image6| + # Linux/Unix: + /etc/pip.conf + ~/.pip/pip.conf + ~/.config/pip/pip.conf + + # Mac OSX: + ~/Library/Application Support/pip/pip.conf + ~/.pip/pip.conf + /Library/Application Support/pip/pip.conf + + # Windows: + %APPDATA%\pip\pip.ini + %HOME%\pip\pip.ini + C:\Documents and Settings\All Users\Application Data\PyPA\pip\pip.conf (Windows XP) + C:\ProgramData\PyPA\pip\pip.conf (Windows 7及以后) -4.5.5 安装Ubuntu ----------------- +- 若在你的机子上没有此文件,则自行创建即可 -在安装之前呢,首先你要根据你的主板,查找选择 启动顺序 -的快捷键。由于我的主板是 MSI ,所以我的快捷键是 -F11。当然你可以选择,按DEL键进入 -Bios,更改顺序。但是我这里不想这么麻烦,因为安装完后又要改回来。 +如何配置,这边给个样例: -查找完后,你可以对电脑开机了,按住你的快捷键,选择启动方式。由于我们要从U盘启动,所以这里选择 +.. code:: ini -这里一定要注意,选择最后一个,自定义选项。 |image7| + [global] + index-url = http://mirrors.aliyun.com/pypi/simple/ -终于到了分区的这一步了,这是最关键的一步。对于Linux不熟悉的人,到这里可能会懵逼。不用怕,这里给你提供一个最简单的分区配置。具体为什么这么分,我想你并不关心吧? + # 替换出自己的代理地址,格式为[user:passwd@]proxy.server:port + proxy=http://xxx.xxx.xxx.xxx:8080 -- efi:系统分区,我分20G,分太多了。 -- swap:系统交换分区,设置为8G -- /:剩下全部分给根目录,类型选 ext4 + [install] + # 信任阿里云的镜像源,否则会有警告 + trusted-host=mirrors.aliyun.com -对,就是这么简单粗暴。如果你想更加精细一点,你还可以自定义 -/home,/usr等。 +**3.5 安装用户私有软件包** -分区完成后,一定要注意如下这二个红框,选择安装启动引导器的设备为 -我们刚刚设置的efi分区。检查无误后,就可以点击「现在安装」。 |image8| +很多人可能还不清楚,python 的安装包是可以用户隔离的。 -接下来就是 选择时区 - 配置键盘。 |image9| +如果你拥有管理员权限,你可以将包安装在全局环境中。在全局环境中的这个包可被该机器上的所有拥有管理员权限的用户使用。 -|image10| +如果一台机器上的使用者不只一样,自私地将在全局环境中安装或者升级某个包,是不负责任且危险的做法。 -到了这里,你应该可以长舒第一口气了。成功了一半了。 |image11| +面对这种情况,我们就想能否安装单独为我所用的包呢? -如果你和我一样使用 SSD -,应该不出5分钟系统就可以安装完毕。弹出如下界面。点击现在重启。 -|image12| +庆幸的是,还真有。 -重启的过程,记住还是一样按住你的快捷键,我这里仍然是 -F11,看到没有,已经有一个叫 -ubuntu的启动设备。就它了,选择进入系统。接下来,就是选择要以哪种模式进入ubuntu,你根据需要去选吧。 -|image13| +我能想到的有两种方法: -4.5.6 效果展示 --------------- +1. 使用虚拟环境 +2. 将包安装在用户的环境中 -由于默认的Ubuntu主题也是丑得可以,经过一个晚上的美化,它变成如下这般帅气逼人。 +虚拟环境,之前写过几篇文章,这里不再展开讲。 -|image14| +今天的重点是第二种方法,教你如何安装用户私有的包? -|image15| +命令也很简单,只要加上 ``--user`` 参数,pip 就会将其安装在当前用户的 +``~/.local/lib/python3.x/site-packages`` 下,而其他用户的 python +则不会受影响。 -|image16| +.. code:: shell -|image17| + pip install --user pkg --------------- +来举个例子 + +.. code:: shell + + # 在全局环境中未安装 requests + [root@localhost ~]# pip list | grep requests + [root@localhost ~]# su - wangbm + [root@localhost ~]# + + # 由于用户环境继承自全局环境,这里也未安装 + [wangbm@localhost ~]# pip list | grep requests + [wangbm@localhost ~]# pip install --user requests + [wangbm@localhost ~]# pip list | grep requests + requests (2.22.0) + [wangbm@localhost ~]# + + # 从 Location 属性可发现 requests 只安装在当前用户环境中 + [wangbm@ws_compute01 ~]$ pip show requests + --- + Metadata-Version: 2.1 + Name: requests + Version: 2.22.0 + Summary: Python HTTP for Humans. + Home-page: http://python-requests.org + Author: Kenneth Reitz + Author-email: me@kennethreitz.org + Installer: pip + License: Apache 2.0 + Location: /home/wangbm/.local/lib/python2.7/site-packages + [wangbm@localhost ~]$ exit + logout + + # 退出 wangbm 用户,在 root 用户环境中发现 requests 未安装 + [root@localhost ~]$ pip list | grep requests + [root@localhost ~]$ + +当你身处个人用户环境中,python +导包时会先检索当前用户环境中是否已安装这个包,已安装则优先使用,未安装则使用全局环境中的包。 + +验证如下: + +.. code:: python + + >>> import sys + >>> from pprint import pprint + >>> pprint(sys.path) + ['', + '/usr/lib64/python27.zip', + '/usr/lib64/python2.7', + '/usr/lib64/python2.7/plat-linux2', + '/usr/lib64/python2.7/lib-tk', + '/usr/lib64/python2.7/lib-old', + '/usr/lib64/python2.7/lib-dynload', + '/home/wangbm/.local/lib/python2.7/site-packages', + '/usr/lib64/python2.7/site-packages', + '/usr/lib64/python2.7/site-packages/gtk-2.0', + '/usr/lib/python2.7/site-packages', + '/usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg', + '/usr/lib/python2.7/site-packages/lockfile-0.12.2-py2.7.egg'] + >>> + +**3.6 延长超时时间** + +若网络情况不是很好,在安装某些包时经常会因为 ReadTimeout 而失败。 + +对于这种情况,一般重试几次就好了。 + +但是这样难免有些麻烦,有没有更好的解决方法呢? + +有的,可以通过延长超时时间。 + +.. code:: shell + + $ pip install --default-timeout=100 + +4. 卸载软件包 +------------- + +就一条命令,不再赘述 + +.. code:: shell + + $ pip uninstall pkg + +5. 升级软件包 +------------- + +想要对现有的 python 进行升级,其本质上也是先从 pypi +上下载最新版本的包,再对其进行安装。所以升级也是使用 +``pip install``\ ,只不过要加一个参数 ``--upgrade``\ 。 + +:: + + $ pip install --upgrade pkg + +在升级的时候,其实还有一个不怎么用到的选项 +``--upgrade-strategy``\ ,它是用来指定升级策略。 + +它的可选项只有两个: + +- ``eager`` :升级全部依赖包 +- ``only-if-need``\ :只有当旧版本不能适配新的父依赖包时,才会升级。 + +在 pip 10.0 版本之后,这个选项的默认值是 +``only-if-need``\ ,因此如下两种写法是一互致的。 + +.. code:: shell + + pip install --upgrade pkg1 + pip install --upgrade pkg1 --upgrade-strategy only-if-need + +6. 配置文件 +----------- + +由于在使用 pip 安装一些包时,默认会使用 pip +的官方源,所以经常会报网络超时失败。 + +常用的解决办法是,在安装包时,使用 ``-i`` +参数指定一个国内的镜像源。但是每次指定就很麻烦呀,还要打超长的一串字母。 + +这时候,其实可以将这个源写进 pip +的配置文件里。以后安装的时候,就默认从你配置的这个 源里安装了。 + +那怎么配置呢?文件文件在哪? + +使用\ ``win+r`` 输入 ``%APPDATA%`` 进入用户资料文件夹,查看有没有一个 +pip 的文件夹,若没有则创建之。 + +然后进入这个 文件夹,新建一个 ``pip.ini`` 的文件,内容如下 + +.. code:: ini + + [global] + time-out=60 + index-url=https://pypi.tuna.tsinghua.edu.cn/simple/ + [install] + trusted-host=tsinghua.edu.cn + +以上几乎包含了 pip +的所有常用使用场景,为了方便,我将其整理成一张表格,如果你需要,可以关注我的公众号(Python编程时光),后台回复“pip”,可获取高清无水印图片。 + +|image1| -|image18| +|image2| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190511163441.png -.. |image2| image:: http://image.python-online.cn/20190511163457.png -.. |image3| image:: http://image.python-online.cn/20190511163510.png -.. |image4| image:: http://image.python-online.cn/20190511163520.png -.. |image5| image:: http://image.python-online.cn/20190511163531.png -.. |image6| image:: http://image.python-online.cn/20190511163542.png -.. |image7| image:: http://image.python-online.cn/20190511163550.png -.. |image8| image:: http://image.python-online.cn/20190511163559.png -.. |image9| image:: http://image.python-online.cn/20190511163612.png -.. |image10| image:: http://image.python-online.cn/20190511163633.png -.. |image11| image:: http://image.python-online.cn/20190511163700.png -.. |image12| image:: http://image.python-online.cn/20190511163711.png -.. |image13| image:: http://image.python-online.cn/20190511163722.png -.. |image14| image:: http://image.python-online.cn/20190511163731.png -.. |image15| image:: http://image.python-online.cn/20190511163750.png -.. |image16| image:: http://image.python-online.cn/20190511163757.png -.. |image17| image:: http://image.python-online.cn/20190511163805.png -.. |image18| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20191105200041.png +.. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_06.md b/source/c04/c04_06.md index 9c8085c..feaf6e4 100644 --- a/source/c04/c04_06.md +++ b/source/c04/c04_06.md @@ -192,7 +192,7 @@ $ git log --graph --all --decorate # 以可视化图的形式展示 使用 diff 进行查看 -![](http://image.python-online.cn/20191217150942.png) +![](http://image.iswbm.com/20191217150942.png) 查看两个 commit 之间的修改 @@ -365,11 +365,11 @@ $ git reset --hard HEAD^ 很简单,先使用 `git reflog` 找到你的 reset 的 commit id -![](http://image.python-online.cn/20191231165152.png) +![](http://image.iswbm.com/20191231165152.png) 然后再次使用 `git reset` 指定 commit id 回到一次修改add前的状态 -![](http://image.python-online.cn/20191231165239.png) +![](http://image.iswbm.com/20191231165239.png) ### 3.6 回退远程提交 @@ -529,7 +529,7 @@ ssh-keygen -t rsa -C "wongbingming@163.com" 生成的仅钥 `/c/Users/wangbm/.ssh/id_rsa.pub`,需要在Github上添加。 在github上添加ssh keys方法如下: -![](http://image.python-online.cn/20190511163855.png) +![](http://image.iswbm.com/20190511163855.png) 添加完后,可以使用这个命令测试一下。 ``` @@ -567,7 +567,7 @@ Git 的使用全都是命令行的,由于没有那么直观,所以很容易 需要注意的是,使用它需要你进行一步操作。在 7.2 章节,我在机器上生成了一对私钥和公钥,在这里需要配置一下才能正常访问和提交我们的Github仓库。 -![](http://image.python-online.cn/20190430235625.png) +![](http://image.iswbm.com/20190430235625.png) diff --git a/source/c04/c04_06.rst b/source/c04/c04_06.rst old mode 100755 new mode 100644 index 7bc9b85..a3e83b5 --- a/source/c04/c04_06.rst +++ b/source/c04/c04_06.rst @@ -628,12 +628,12 @@ Desktop真心觉得不好用)。 |image8| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20191217150942.png -.. |image2| image:: http://image.python-online.cn/20191231165152.png -.. |image3| image:: http://image.python-online.cn/20191231165239.png +.. |image1| image:: http://image.iswbm.com/20191217150942.png +.. |image2| image:: http://image.iswbm.com/20191231165152.png +.. |image3| image:: http://image.iswbm.com/20191231165239.png .. |image4| image:: https://i.loli.net/2018/04/15/5ad2c06e8893d.png -.. |image5| image:: http://image.python-online.cn/20190511163855.png +.. |image5| image:: http://image.iswbm.com/20190511163855.png .. |image6| image:: https://i.loli.net/2018/04/15/5ad2c2a9813b9.png -.. |image7| image:: http://image.python-online.cn/20190430235625.png +.. |image7| image:: http://image.iswbm.com/20190430235625.png .. |image8| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_07.md b/source/c04/c04_07.md index ef3fb8a..4d509af 100644 --- a/source/c04/c04_07.md +++ b/source/c04/c04_07.md @@ -14,14 +14,14 @@ - 下载安装git(官网下载安装) - 下载安装hexo。方法:打开终端 运行`npm install -g hexo`(要翻墙) -![](http://image.python-online.cn/image-20200321163152876.png) +![](http://image.iswbm.com/image-20200321163152876.png) ### 1.2 初始化项目 - 新创建一个目录,比如叫做 `iswbm-blog` - 使用终端进入该文件夹内,执行`hexo init` (初始化项目) -![](http://image.python-online.cn/image-20200321163746032.png) +![](http://image.iswbm.com/image-20200321163746032.png) - 运行你的博客项目(用于调试),查看生成的文章是否符合自己的预期。 @@ -40,7 +40,7 @@ $ hexo s # 运行本地web服务器 - 在Github上创建名字为XXX.github.io的项目,XXX为自己的github用户名。然后 clone 到你的本地电脑上。 - ![](http://image.python-online.cn/image-20200321165634287.png) + ![](http://image.iswbm.com/image-20200321165634287.png) - 打开该文件夹内的`_config.yml`配置文件,将其中的type设置为git,其他配置如下 @@ -68,7 +68,7 @@ $ hexo d 在第一次部署后,请到 github 后台,补充你的 网站信息,否则无法访问。 -![](http://image.python-online.cn/image-20200321171008622.png) +![](http://image.iswbm.com/image-20200321171008622.png) 一切完成之后,你就可以通过上面的网址来访问我的博客了。 @@ -79,10 +79,10 @@ $ hexo d 我们可以自己去阿里云购买一个域名(我的是 iswbm.com),然后将其 CNAME 到你的博客地址上。 - 去阿里云 域名云解析 - ![](http://image.python-online.cn/image-20200321171939919.png) + ![](http://image.iswbm.com/image-20200321171939919.png) - 然后到对应的GitHub仓库,绑定我的个性域名 - ![](http://image.python-online.cn/image-20200321171821683.png) + ![](http://image.iswbm.com/image-20200321171821683.png) - 然后再在你本地的项目里的 `source` 目录下新建 `CNAME`文件,内容就是我的域名 @@ -128,7 +128,7 @@ Do not just seek happiness for yourself. Seek happiness for all. Through kindnes {% endblockquote %} ``` 效果如下 -![](http://image.python-online.cn/17-9-10/85269241.jpg) +![](http://image.iswbm.com/17-9-10/85269241.jpg) ### 3.2 一键生成md头格式 @@ -149,7 +149,7 @@ password: 然后使用 `hexo new`就可以一键生成新文章的头格式了,不用手动去搬运或者书写。相当方便。 -![image-20200321201555321](http://image.python-online.cn/image-20200321201555321.png) +![image-20200321201555321](http://image.iswbm.com/image-20200321201555321.png) ## 四、美化博客 @@ -209,7 +209,7 @@ social: GitHub: https://github.com/iswbm || github E-Mail: mailto:wongbingming@163.com || envelope-o 微博: http://weibo.com/942663728 || weibo - WeChat: http://image.python-online.cn/17-9-9/58657236.jpg || weixin + WeChat: http://image.iswbm.com/17-9-9/58657236.jpg || weixin 知乎: https://www.zhihu.com/people/wongbingming/activities || chain-broken CnBlog: http://www.cnblogs.com/wongbingming/ || file-text-o @@ -350,7 +350,7 @@ post_copyright: ``` 接着打开`themes/next/layout/_macro/post.swig`文件,添加如下下代码,注意位置 -![](http://image.python-online.cn/17-9-9/63041495.jpg) +![](http://image.iswbm.com/17-9-9/63041495.jpg) 代码如下: ``` @@ -670,7 +670,7 @@ permalink: Database-MySQL-Basic_usage 在web界面新建分支,命名为`hexo` 在web界面设置 `hexo` 为默认分支,因为我们只会在这个分支上进行操作。 -![](http://image.python-online.cn/image-20200321193444320.png) +![](http://image.iswbm.com/image-20200321193444320.png) ### 6.2 本地PC操作 diff --git a/source/c04/c04_07.rst b/source/c04/c04_07.rst old mode 100755 new mode 100644 index a38ede7..0179476 --- a/source/c04/c04_07.rst +++ b/source/c04/c04_07.rst @@ -168,7 +168,7 @@ 然后使用 ``hexo new``\ 就可以一键生成新文章的头格式了,不用手动去搬运或者书写。相当方便。 -.. figure:: http://image.python-online.cn/image-20200321201555321.png +.. figure:: http://image.iswbm.com/image-20200321201555321.png :alt: image-20200321201555321 image-20200321201555321 @@ -245,7 +245,7 @@ GitHub: https://github.com/iswbm || github E-Mail: mailto:wongbingming@163.com || envelope-o 微博: http://weibo.com/942663728 || weibo - WeChat: http://image.python-online.cn/17-9-9/58657236.jpg || weixin + WeChat: http://image.iswbm.com/17-9-9/58657236.jpg || weixin 知乎: https://www.zhihu.com/people/wongbingming/activities || chain-broken CnBlog: http://www.cnblogs.com/wongbingming/ || file-text-o @@ -894,16 +894,16 @@ bash窗口,不然会提示找不到npm命令) |image12| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/image-20200321163152876.png -.. |image2| image:: http://image.python-online.cn/image-20200321163746032.png -.. |image3| image:: http://image.python-online.cn/image-20200321165634287.png -.. |image4| image:: http://image.python-online.cn/image-20200321171008622.png -.. |image5| image:: http://image.python-online.cn/image-20200321171939919.png -.. |image6| image:: http://image.python-online.cn/image-20200321171821683.png -.. |image7| image:: http://image.python-online.cn/17-9-10/85269241.jpg -.. |image8| image:: http://image.python-online.cn/17-9-9/63041495.jpg +.. |image1| image:: http://image.iswbm.com/image-20200321163152876.png +.. |image2| image:: http://image.iswbm.com/image-20200321163746032.png +.. |image3| image:: http://image.iswbm.com/image-20200321165634287.png +.. |image4| image:: http://image.iswbm.com/image-20200321171008622.png +.. |image5| image:: http://image.iswbm.com/image-20200321171939919.png +.. |image6| image:: http://image.iswbm.com/image-20200321171821683.png +.. |image7| image:: http://image.iswbm.com/17-9-10/85269241.jpg +.. |image8| image:: http://image.iswbm.com/17-9-9/63041495.jpg .. |image9| image:: /Users/MING/Library/Application%20Support/typora-user-images/image-20200321210014963.png -.. |image10| image:: http://image.python-online.cn/image-20200321193444320.png +.. |image10| image:: http://image.iswbm.com/image-20200321193444320.png .. |image11| image:: https://i.loli.net/2018/04/15/5ad31888232e9.png .. |image12| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_08.rst b/source/c04/c04_08.rst old mode 100755 new mode 100644 diff --git a/source/c04/c04_09.rst b/source/c04/c04_09.rst old mode 100755 new mode 100644 diff --git a/source/c04/c04_10.rst b/source/c04/c04_10.rst old mode 100755 new mode 100644 diff --git a/source/c04/c04_11.md b/source/c04/c04_11.md index 9ec0689..49fb61f 100644 --- a/source/c04/c04_11.md +++ b/source/c04/c04_11.md @@ -1,4 +1,4 @@ -# 4.11 不能不会的远程调试技巧 +# 4.11 超详细图文教你如何使用 PyCharm 进行远程调试 ![](http://image.iswbm.com/20200602135014.png) @@ -19,17 +19,17 @@ 区别就在于,本地调试不需要事前配置,只要你的代码准备好了,随时可以开始 Debug 。而远程调试需要不少前置步骤,这些设置过程,也是本文的主要内容。 -### 4.11.1 新建一个项目 +### 1. 新建一个项目 首先,要在Pycharm中新建一个空的项目,后面我们拉服务器上的项目代码就会放置在这个项目目录下。我这边的名字是 NOVA,你可以自己定义。 -![](http://image.python-online.cn/20190113104817.png) +![](http://image.iswbm.com/20190113104817.png) -### 4.11.2 配置连接服务器 +### 2. 配置连接服务器 Tools -> Deployment -> configuration -![](http://image.python-online.cn/20190113105512.png) +![](http://image.iswbm.com/20190113105512.png) 添加一个`Server` @@ -37,7 +37,7 @@ Tools -> Deployment -> configuration - Type:设定为SFTP -![](http://image.python-online.cn/20190113105858.png) +![](http://image.iswbm.com/20190113105858.png) 点击`OK`后,进入如下界面,你可以按我的备注,填写信息: @@ -50,38 +50,38 @@ Tools -> Deployment -> configuration 这里请注意,要确保你的电脑可以ssh连接到你的服务器,不管是密钥登陆还是密码登陆,如果开启了白名单限制要先解除。 -![](http://image.python-online.cn/20190113105931.png) +![](http://image.iswbm.com/20190113105931.png) 填写完成后,切换到`Mappings`选项卡,在箭头位置,填写`\` -![](http://image.python-online.cn/20190113110928.png) +![](http://image.iswbm.com/20190113110928.png) 以上服务器信息配置,全部正确填写完成后,点击`OK` 接下来,我们要连接远程服务器了。 Tools -> Deployment -> Browse Remote Host -![](http://image.python-online.cn/20190113111042.png) +![](http://image.iswbm.com/20190113111042.png) -### 4.11.3 下载项目代码 +### 3. 下载项目代码 如果之前填写的服务器登陆信息准确无误的话,现在就可以看到远程的项目代码。 -![](http://image.python-online.cn/20190113111151.png) +![](http://image.iswbm.com/20190113111151.png) 选择下载远程代码要本地。 -![](http://image.python-online.cn/20190113111217.png) +![](http://image.iswbm.com/20190113111217.png) 下载完成提示。 -![](http://image.python-online.cn/20190113111248.png) +![](http://image.iswbm.com/20190113111248.png) 现在的IDE界面应该是这样子的。 -![](http://image.python-online.cn/20190113111307.png) +![](http://image.iswbm.com/20190113111307.png) -### 4.11.4 下载远程解释器 +### 4. 下载远程解释器 为什么需要这步呢? @@ -90,15 +90,15 @@ Tools -> Deployment -> Browse Remote Host 进入 File -> Settings 按图示,添加远程解释器。 -![](http://image.python-online.cn/20190113111747.png) +![](http://image.iswbm.com/20190113111747.png) 填写远程服务器信息,跟之前的一样,不再赘述。 -![](http://image.python-online.cn/20190113111828.png) +![](http://image.iswbm.com/20190113111828.png) 点击`OK`后,会自动下载远程解释器。如果你的项目比较大,这个时间可能会比较久,请耐心等待。 -### 4.11.5 添加程序入口 +### 5. 添加程序入口 因为我们要在本地DEBUG,所以你一定要知道你的项目的入口程序。如果这个入口程序已经包含在你的项目代码中,那么请略过这一步。 @@ -128,31 +128,31 @@ WantedBy=multi-user.target 看到那个`ExecStart`没有?那个就是我们程序的入口。 我们只要将其拷贝至我们的Pycharm中,并向远程同步该文件。 -![](http://image.python-online.cn/20190113112004.png) +![](http://image.iswbm.com/20190113112004.png) -### 4.11.6 调试前设置 +### 6. 调试前设置 开启代码自动同步,这样,我们对代码的修改Pycharm都能识别,并且为我们提交到远程服务器。 -![](http://image.python-online.cn/20190113112055.png) +![](http://image.iswbm.com/20190113112055.png) 开启 `Gevent compatible`,如果不开启,在调试过程中,很可能出现无法调试,或者无法追踪/查看变量等问题。 -![](http://image.python-online.cn/20190113113211.png) +![](http://image.iswbm.com/20190113113211.png) -### 4.11.7 开始调试代码 +### 7. 开始调试代码 在你的程序入口文件处,点击右键,选择Debug即可。 如果你的程序入口,需要引入参数,这是经常有的事,可以的这里配置。 -![](http://image.python-online.cn/20190113112456.png) +![](http://image.iswbm.com/20190113112456.png) 配置完点击保存即可。 -![](http://image.python-online.cn/20190113112649.png) +![](http://image.iswbm.com/20190113112649.png) -### 4.11.8 友情提醒 +### 8. 友情提醒 按照文章的试调试代码,会自动同步代码至远端,千万不要在生产环境使用,一定要在开发环境中使用,否则后果自负。 diff --git a/source/c04/c04_11.rst b/source/c04/c04_11.rst index 61adafe..a27e659 100644 --- a/source/c04/c04_11.rst +++ b/source/c04/c04_11.rst @@ -1,5 +1,5 @@ -4.11 不能不会的远程调试技巧 -=========================== +4.11 超详细图文教你如何使用 PyCharm 进行远程调试 +================================================ |image0| @@ -25,16 +25,16 @@ Google,这里不涉及。 区别就在于,本地调试不需要事前配置,只要你的代码准备好了,随时可以开始 Debug 。而远程调试需要不少前置步骤,这些设置过程,也是本文的主要内容。 -4.11.1 新建一个项目 -~~~~~~~~~~~~~~~~~~~ +1. 新建一个项目 +~~~~~~~~~~~~~~~ 首先,要在Pycharm中新建一个空的项目,后面我们拉服务器上的项目代码就会放置在这个项目目录下。我这边的名字是 NOVA,你可以自己定义。 |image1| -4.11.2 配置连接服务器 -~~~~~~~~~~~~~~~~~~~~~ +2. 配置连接服务器 +~~~~~~~~~~~~~~~~~ Tools -> Deployment -> configuration @@ -72,8 +72,8 @@ Host |image6| -4.11.3 下载项目代码 -~~~~~~~~~~~~~~~~~~~ +3. 下载项目代码 +~~~~~~~~~~~~~~~ 如果之前填写的服务器登陆信息准确无误的话,现在就可以看到远程的项目代码。 @@ -91,8 +91,8 @@ Host |image10| -4.11.4 下载远程解释器 -~~~~~~~~~~~~~~~~~~~~~ +4. 下载远程解释器 +~~~~~~~~~~~~~~~~~ 为什么需要这步呢? @@ -108,8 +108,8 @@ Host 点击\ ``OK``\ 后,会自动下载远程解释器。如果你的项目比较大,这个时间可能会比较久,请耐心等待。 -4.11.5 添加程序入口 -~~~~~~~~~~~~~~~~~~~ +5. 添加程序入口 +~~~~~~~~~~~~~~~ 因为我们要在本地DEBUG,所以你一定要知道你的项目的入口程序。如果这个入口程序已经包含在你的项目代码中,那么请略过这一步。 @@ -141,8 +141,8 @@ Host |image13| -4.11.6 调试前设置 -~~~~~~~~~~~~~~~~~ +6. 调试前设置 +~~~~~~~~~~~~~ 开启代码自动同步,这样,我们对代码的修改Pycharm都能识别,并且为我们提交到远程服务器。 @@ -153,8 +153,8 @@ Host |image15| -4.11.7 开始调试代码 -~~~~~~~~~~~~~~~~~~~ +7. 开始调试代码 +~~~~~~~~~~~~~~~ 在你的程序入口文件处,点击右键,选择Debug即可。 @@ -166,8 +166,8 @@ Host |image17| -4.11.8 友情提醒 -~~~~~~~~~~~~~~~ +8. 友情提醒 +~~~~~~~~~~~ 按照文章的试调试代码,会自动同步代码至远端,千万不要在生产环境使用,一定要在开发环境中使用,否则后果自负。 @@ -178,22 +178,22 @@ Host |image18| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190113104817.png -.. |image2| image:: http://image.python-online.cn/20190113105512.png -.. |image3| image:: http://image.python-online.cn/20190113105858.png -.. |image4| image:: http://image.python-online.cn/20190113105931.png -.. |image5| image:: http://image.python-online.cn/20190113110928.png -.. |image6| image:: http://image.python-online.cn/20190113111042.png -.. |image7| image:: http://image.python-online.cn/20190113111151.png -.. |image8| image:: http://image.python-online.cn/20190113111217.png -.. |image9| image:: http://image.python-online.cn/20190113111248.png -.. |image10| image:: http://image.python-online.cn/20190113111307.png -.. |image11| image:: http://image.python-online.cn/20190113111747.png -.. |image12| image:: http://image.python-online.cn/20190113111828.png -.. |image13| image:: http://image.python-online.cn/20190113112004.png -.. |image14| image:: http://image.python-online.cn/20190113112055.png -.. |image15| image:: http://image.python-online.cn/20190113113211.png -.. |image16| image:: http://image.python-online.cn/20190113112456.png -.. |image17| image:: http://image.python-online.cn/20190113112649.png +.. |image1| image:: http://image.iswbm.com/20190113104817.png +.. |image2| image:: http://image.iswbm.com/20190113105512.png +.. |image3| image:: http://image.iswbm.com/20190113105858.png +.. |image4| image:: http://image.iswbm.com/20190113105931.png +.. |image5| image:: http://image.iswbm.com/20190113110928.png +.. |image6| image:: http://image.iswbm.com/20190113111042.png +.. |image7| image:: http://image.iswbm.com/20190113111151.png +.. |image8| image:: http://image.iswbm.com/20190113111217.png +.. |image9| image:: http://image.iswbm.com/20190113111248.png +.. |image10| image:: http://image.iswbm.com/20190113111307.png +.. |image11| image:: http://image.iswbm.com/20190113111747.png +.. |image12| image:: http://image.iswbm.com/20190113111828.png +.. |image13| image:: http://image.iswbm.com/20190113112004.png +.. |image14| image:: http://image.iswbm.com/20190113112055.png +.. |image15| image:: http://image.iswbm.com/20190113113211.png +.. |image16| image:: http://image.iswbm.com/20190113112456.png +.. |image17| image:: http://image.iswbm.com/20190113112649.png .. |image18| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_12.md b/source/c04/c04_12.md index a3838d8..c52c148 100644 --- a/source/c04/c04_12.md +++ b/source/c04/c04_12.md @@ -4,11 +4,11 @@ 上一篇文章,讲的是 Pycharm 的远程调试,若你还没学会,可以点击这里进行查看。 -[不能不会的远程调试技巧](http://python-online.cn/zh_CN/latest/c04/c04_11.html) +[不能不会的远程调试技巧](http://iswbm.com/zh_CN/latest/c04/c04_11.html) Pycharm 的图形化界面虽然好用,但是在某些场景中,是无法使用的。而 Python 本身已经给我们提供了一个调试神器 -- pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文章来帮助你轻松的理解它。 -## 4.12.1 准备文件 +## 1. 准备文件 在调试之前先将这两个文件准备好(做为演示用),并放在同级目录中。 @@ -40,7 +40,7 @@ if __name__ == '__main__': -## 4.12.2 进入调试模式 +## 2. 进入调试模式 主要有两种方法 @@ -52,7 +52,7 @@ ptyhon -m pdb pdb_demo.py 使用这个方式进入调试模式,会在脚本的第一行开始单步调试。 -![](http://image.python-online.cn/20190118000111.png) +![](http://image.iswbm.com/20190118000111.png) 对于单文件的脚本并没有什么问题,如果是一个大型的项目,项目里有很多的文件,使用这种方式只能大大降低我们的效率。 @@ -67,13 +67,13 @@ pdb.set_trace() 然后执行时,也不需要再指定`-m pdb`了,直接`python pdb_demo.py ` ,就会直接在这个地方暂停。 -![](http://image.python-online.cn/20190118000234.png) +![](http://image.iswbm.com/20190118000234.png) -![](http://image.python-online.cn/20190118000557.png) +![](http://image.iswbm.com/20190118000557.png) -## 4.12.3 调试指令 +## 3. 调试指令 熟悉 Pycharm 的人都知道,我们执行下一步,执行到下一个断点是 @@ -129,15 +129,15 @@ pdb.set_trace() 其实你大可不必死记这些命令,忘记的时候,只要敲入`help`并回车,就可以看所有的指令了。 -![](http://image.python-online.cn/20190118083809.png) +![](http://image.iswbm.com/20190118083809.png) -## 4.12.4 开始调试 +## 4. 开始调试 这里就几个最常用的指定,来演示一遍。 -![](http://image.python-online.cn/20190118005507.png) +![](http://image.iswbm.com/20190118005507.png) diff --git a/source/c04/c04_12.rst b/source/c04/c04_12.rst index d9e56e9..cfee977 100644 --- a/source/c04/c04_12.rst +++ b/source/c04/c04_12.rst @@ -6,14 +6,14 @@ 上一篇文章,讲的是 Pycharm 的远程调试,若你还没学会,可以点击这里进行查看。 -`不能不会的远程调试技巧 `__ +`不能不会的远程调试技巧 `__ Pycharm 的图形化界面虽然好用,但是在某些场景中,是无法使用的。而 Python 本身已经给我们提供了一个调试神器 – pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文章来帮助你轻松的理解它。 -4.12.1 准备文件 ---------------- +1. 准备文件 +----------- 在调试之前先将这两个文件准备好(做为演示用),并放在同级目录中。 @@ -43,8 +43,8 @@ pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文 myfunc([1,2,3,4]) print("----end-----") -4.12.2 进入调试模式 -------------------- +2. 进入调试模式 +--------------- 主要有两种方法 @@ -76,8 +76,8 @@ pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文 |image3| -4.12.3 调试指令 ---------------- +3. 调试指令 +----------- 熟悉 Pycharm 的人都知道,我们执行下一步,执行到下一个断点是 @@ -161,8 +161,8 @@ pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文 |image4| -4.12.4 开始调试 ---------------- +4. 开始调试 +----------- 这里就几个最常用的指定,来演示一遍。 @@ -179,10 +179,10 @@ pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文 |image6| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190118000111.png -.. |image2| image:: http://image.python-online.cn/20190118000234.png -.. |image3| image:: http://image.python-online.cn/20190118000557.png -.. |image4| image:: http://image.python-online.cn/20190118083809.png -.. |image5| image:: http://image.python-online.cn/20190118005507.png +.. |image1| image:: http://image.iswbm.com/20190118000111.png +.. |image2| image:: http://image.iswbm.com/20190118000234.png +.. |image3| image:: http://image.iswbm.com/20190118000557.png +.. |image4| image:: http://image.iswbm.com/20190118083809.png +.. |image5| image:: http://image.iswbm.com/20190118005507.png .. |image6| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_14.md b/source/c04/c04_14.md index 259e3e2..672b5ce 100644 --- a/source/c04/c04_14.md +++ b/source/c04/c04_14.md @@ -1,161 +1,160 @@ -# 4.14 虚拟环境:Pipenv +# 4.14 Xshell的高效使用手册 ![](http://image.iswbm.com/20200602135014.png) -以前一直使用pip+virtualenv+virtualwrapper管理模块和环境, 但是virtualwrapper在windows上使用不太方便,而且包和环境分开管理确实经常不记得哪个是哪个了。 - +--- -为什么 会推荐 pipenv 呢? +![](http://image.iswbm.com/20190511162815.png) -- 它是 `virtualenv` 和 `pip` 的合体,可以合起来使用; -- 使用`Pipfile` 和 `Pipfile.lock`替代`requirements.txt` -- 可以使用 `pipenv graph`很方便的看出包的依赖关系。 -- 通过加载`.env`文件简化开发工作流程 +做为一名开发人员,我们难免都会与服务器打交道。 -## 4.14.1 安装pipenv +有时候是公司的线上生产环境,你需要上去部署公司的项目。 +有时候是在阿里上买的云主机,想自己搭个博客来写写文章。 -如果你的电脑上没有安装 pipenv,可以使用如下方法安装 +就像我,是从事云计算相关的,每天远程登陆的服务器都有几十台。 -```shell -# mac -$ brew install pipenv +这时候,掌握一些远程登陆工具的使用技巧是相当有必要的,会大大增加你使用的便利性和愉悦性。 -# windows -pip install [--user] pipenv -``` +优秀的远程工具有很多,Xshell,SecureCRT,PuTTY等等。 -如果你的电脑是 windows 的。 +由于小明是个很注重工具顔值的人,所以一开始就选择了Xshell。本篇也只会介绍Xshell的使用技巧,但是快捷键在其他工具上部分是通用的。 -![](http://image.python-online.cn/Fk6WZ2xbqg2DM3AvnYCpsiKQ4xOn) +## 快捷入口 -需要将如标示路径,加入到 环境变量 PATH 中。 +**快速命令** -![](http://image.python-online.cn/FjuJ8yZsgjkzVuBRZHxK1ZnnzaEX) +查看 -> 快速命令 -然后需要重启一下,CMD 终端才能够刷新环境变量。 +双击底部自定义快速命令。 +![](http://image.iswbm.com/20190511162524.png) -## 4.14.2 创建虚拟环境 +**使用收藏栏** -DjangoWebBlog 是我们的项目目录,进入这个目录下创建虚拟环境 +点击最左侧按按钮添加收藏。 +![](http://image.iswbm.com/20190511162607.png) -```shell -$ mkdir DjangoWebBlog && cd DjangoWebBlog +**快捷设置** -# 在当前目录下创建一个虚拟环境(默认的Python版本) -$ pipenv install -``` +工具 -> 选项 -> 键盘和鼠标 -你也可以指定版本创建 +① 右键粘贴 +② 双击分隔符 +③ 选中即复制 -```shell -$ pipenv --two # 相当于 pipenv --python /usr/bin/python2 -$ pipenv --three # 相当于 pipenv --python /usr/bin/python3 +![](http://image.iswbm.com/20190511162716.png) -$ pipenv --python 3.7 # 也可以指定具体的版本 -pipenv install --python 2 -``` +**设置Meta键** -这边以安装 python2 版本的虚拟环境为例说明。 +文件 -> 属性 -> 键盘 -![](http://image.python-online.cn/20190612211330.png) +一定要打钩,这是后面诸多快捷使用的前提。 +![](http://image.iswbm.com/20190511162730.png) -如果你原项目使用的是 requirements.txt 这个管理包的方式,这时候执行 `pipenv --tow` 创建一个虚拟环境后,会找到 requirements.txt ,并根据这里面的依赖包生成 Pipfile文件。 - -![](http://image.python-online.cn/20190612213015.png) - -## 4.14.3 查询虚拟环境 - -```shell -# 返回项目的路径 -$ pipenv --where - -# 返回虚拟环境路径 -$ pipenv --venv - -# 返回该虚拟环境的解释器 -$ pipenv --py +## 移动光标 ``` +Ctrl+f 向后移动一个字符 +Ctrl+b 向前移动一个字符 -演示如下: +Ctrl+a 将光标移至输入行头,相当于Home键 +Ctrl+e 将光标移至输入行末,相当于End键 -![](http://image.python-online.cn/20190612213950.png) +Alt+f 以单词为单位,向前移动 +Alt+b 以单词为单位,向前移动 -## 4.14.4 操作虚拟环境 +Shift+PgUp 将终端显示向上滚动 +Shift+PgDn 将终端显示向下滚动 +``` -```shell -# 进入这个虚拟环境 -$ pipenv shell +## 删除元素 +``` +Ctrl+u 删除到行首 +Ctrl+k 删除到行末 -# 退出这个虚拟环境 -$ exit -$ deactivate +Ctrl+y 粘贴上次Ctrl+u/k的字符 +Ctrl+d 删除当前字符,等同于Del -# 移除当前目录的虚拟环境 -$ pipenv --rm +Alt+Backspace 向前删除一个单词,和 Ctrl+w 一样 ``` -执行 `pipenv shell` 就可以进入这个虚拟环境,在头部会有虚拟环境的标识名称。有这个标识,说明已经进入虚拟环境。 - -![](http://image.python-online.cn/20190612211925.png) +## 切换标签 +``` +Alt+n n为1-9数字,快速切换标签页 +Ctrl+Tab 向右切换标签 -```python -# 在当前虚拟环境中运行 -$ pipenv run python # 进入交互式,跟直接执行 python 一样 -$ pipenv run python 文件名 # 运行文件 -$ pipenv run pip ... # 运行pip +Ctrl+Shift+Tab 向右切换标签 +Shift+Tab 两个窗口来回切换 ``` -## 4.14.5 虚拟环境包管理 +## 屏幕模式 +``` +Alt+s 切换到简单版模式 +Alt+Enter 切换至全屏 +``` +## 快速操作 ```shell -# 安装一个本地包(setup.py)到虚拟环境(Pipfile) -$ pipenv install -e . - -# 安装、卸载模块 -$ pipenv install requests -$ pipenv uninstall requests -$ pipenv uninstall --all # 卸载全部包 -$ pipenv install -r path/to/requirements.txt +Alt+. 取得上次命令的参数,并粘贴 +Ctrl+r 在历史命令中查找,回车执行 +Ctrl+o 放在命令后执行,可重复执行 -# 安装所有依赖 -$ pipenv install --dev +Alt+u 以单词为单位,将光标处到单词结尾的字符转化为大写 +Alt+l 以单词为单位,将光标处到单词结尾的字符转化为小写 -# 更新包 -$ pipenv update # 更新所有包 -$ pipenv update --outdated # 打印所有要更新的包 -$ pipenv update <包名> # 更新指定的包 - -# 将Pipfile和Pipfile.lock文件里面的包导出为requirements.txt文件 -$ pipenv run pip freeze # 相当于pipenv run pip freeze >requirements.txt - -$ pipenv lock -r > requirements.txt -$ pipenv lock -r --dev # 若只想导出开发用的包 +Alt+Shift+# 注释当前命令,相当于ctrl-a,#,enter ``` -## 4.14.5 其他命令 +## 辅助命令 +``` +Ctrl+s 锁住终端,可用来停留在当前屏 +Ctrl+q 解锁终端,恢复刷屏 +Ctrl+d 键盘输入结束或退出终端 -```shell -# 创建一个包含预发布的锁文件: -$ pipenv lock --pre +Ctrl+s 暂停当前程序,暂停后按下任意键恢复运行 +Ctrl+z 将当前程序放到后台运行,恢复到前台为命令fg -# 打印所有包的依赖关系图 -$ pipenv graph +Ctrl+Shift+r 重新连接 -# 检查安全漏洞 -$ pipenv check +Ctrl+Insert 复制 +Shift+Insert 粘贴 ``` -打印该虚拟环境下所有包的依赖关系图 +以我日常使用到的,暂时就这么些了,以后有用更多的使用技巧和快捷键再来补充。 -![](http://image.python-online.cn/20190614000336.png) +## 配色方案 -有的python第三方包旧版本会有安全漏洞,使用 pipenv check 可以检查安全漏洞。 +新建一个文件`ubuntu.xcs` + +``` +[ubuntu] +text(bold)=ffffff +magenta(bold)=ad7fa8 +text=ffffff +white(bold)=eeeeec +green=4e9a06 +red(bold)=ef2929 +green(bold)=8ae234 +black(bold)=555753 +red=cc0000 +blue=3465a4 +black=000000 +blue(bold)=729fcf +yellow(bold)=fce94f +cyan(bold)=34e2e2 +yellow=c4a000 +magenta=75507b +background=300a24 +white=d3d7cf +cyan=06989a +[Names] +count=1 +name0=ubuntu +``` -![](http://image.python-online.cn/20190612215924.png) +然后在xshell配色方案中导入即可 -.env`文件,用来存放一些环境变量。 +附上一个更全的帖子:[Xshell快捷键汇总](https://www.cnblogs.com/zhoushihui/p/5404392.html) --- diff --git a/source/c04/c04_14.rst b/source/c04/c04_14.rst index 7fa8d79..61ecafb 100644 --- a/source/c04/c04_14.rst +++ b/source/c04/c04_14.rst @@ -1,185 +1,180 @@ -4.14 虚拟环境:Pipenv -===================== +4.14 Xshell的高效使用手册 +========================= |image0| -以前一直使用pip+virtualenv+virtualwrapper管理模块和环境, -但是virtualwrapper在windows上使用不太方便,而且包和环境分开管理确实经常不记得哪个是哪个了。 - -为什么 会推荐 pipenv 呢? - -- 它是 ``virtualenv`` 和 ``pip`` 的合体,可以合起来使用; -- 使用\ ``Pipfile`` 和 ``Pipfile.lock``\ 替代\ ``requirements.txt`` -- 可以使用 ``pipenv graph``\ 很方便的看出包的依赖关系。 -- 通过加载\ ``.env``\ 文件简化开发工作流程 - -4.14.1 安装pipenv ------------------ +-------------- -如果你的电脑上没有安装 pipenv,可以使用如下方法安装 +|image1| -.. code:: shell +做为一名开发人员,我们难免都会与服务器打交道。 - # mac - $ brew install pipenv +有时候是公司的线上生产环境,你需要上去部署公司的项目。 +有时候是在阿里上买的云主机,想自己搭个博客来写写文章。 - # windows - pip install [--user] pipenv +就像我,是从事云计算相关的,每天远程登陆的服务器都有几十台。 -如果你的电脑是 windows 的。 +这时候,掌握一些远程登陆工具的使用技巧是相当有必要的,会大大增加你使用的便利性和愉悦性。 -|image1| +优秀的远程工具有很多,Xshell,SecureCRT,PuTTY等等。 -需要将如标示路径,加入到 环境变量 PATH 中。 +由于小明是个很注重工具顔值的人,所以一开始就选择了Xshell。本篇也只会介绍Xshell的使用技巧,但是快捷键在其他工具上部分是通用的。 -|image2| +快捷入口 +-------- -然后需要重启一下,CMD 终端才能够刷新环境变量。 +**快速命令** -4.14.2 创建虚拟环境 -------------------- +查看 -> 快速命令 -DjangoWebBlog 是我们的项目目录,进入这个目录下创建虚拟环境 +双击底部自定义快速命令。 |image2| -.. code:: shell +**使用收藏栏** - $ mkdir DjangoWebBlog && cd DjangoWebBlog +点击最左侧按按钮添加收藏。 |image3| - # 在当前目录下创建一个虚拟环境(默认的Python版本) - $ pipenv install +**快捷设置** -你也可以指定版本创建 +工具 -> 选项 -> 键盘和鼠标 -.. code:: shell +① 右键粘贴 ② 双击分隔符 ③ 选中即复制 - $ pipenv --two # 相当于 pipenv --python /usr/bin/python2 - $ pipenv --three # 相当于 pipenv --python /usr/bin/python3 +|image4| - $ pipenv --python 3.7 # 也可以指定具体的版本 - pipenv install --python 2 +**设置Meta键** -这边以安装 python2 版本的虚拟环境为例说明。 +文件 -> 属性 -> 键盘 -|image3| +一定要打钩,这是后面诸多快捷使用的前提。 |image5| -如果你原项目使用的是 requirements.txt 这个管理包的方式,这时候执行 -``pipenv --tow`` 创建一个虚拟环境后,会找到 requirements.txt -,并根据这里面的依赖包生成 Pipfile文件。 +移动光标 +-------- -|image4| +:: -4.14.3 查询虚拟环境 -------------------- + Ctrl+f 向后移动一个字符 + Ctrl+b 向前移动一个字符 -.. code:: shell + Ctrl+a 将光标移至输入行头,相当于Home键 + Ctrl+e 将光标移至输入行末,相当于End键 - # 返回项目的路径 - $ pipenv --where + Alt+f 以单词为单位,向前移动 + Alt+b 以单词为单位,向前移动 - # 返回虚拟环境路径 - $ pipenv --venv + Shift+PgUp 将终端显示向上滚动 + Shift+PgDn 将终端显示向下滚动 - # 返回该虚拟环境的解释器 - $ pipenv --py +删除元素 +-------- -演示如下: +:: -|image5| + Ctrl+u 删除到行首 + Ctrl+k 删除到行末 -4.14.4 操作虚拟环境 -------------------- + Ctrl+y 粘贴上次Ctrl+u/k的字符 + Ctrl+d 删除当前字符,等同于Del -.. code:: shell + Alt+Backspace 向前删除一个单词,和 Ctrl+w 一样 - # 进入这个虚拟环境 - $ pipenv shell +切换标签 +-------- - # 退出这个虚拟环境 - $ exit - $ deactivate +:: - # 移除当前目录的虚拟环境 - $ pipenv --rm + Alt+n n为1-9数字,快速切换标签页 + Ctrl+Tab 向右切换标签 -执行 ``pipenv shell`` -就可以进入这个虚拟环境,在头部会有虚拟环境的标识名称。有这个标识,说明已经进入虚拟环境。 + Ctrl+Shift+Tab 向右切换标签 + Shift+Tab 两个窗口来回切换 -|image6| +屏幕模式 +-------- -.. code:: python +:: - # 在当前虚拟环境中运行 - $ pipenv run python # 进入交互式,跟直接执行 python 一样 - $ pipenv run python 文件名 # 运行文件 - $ pipenv run pip ... # 运行pip + Alt+s 切换到简单版模式 + Alt+Enter 切换至全屏 -4.14.5 虚拟环境包管理 ---------------------- +快速操作 +-------- .. code:: shell - # 安装一个本地包(setup.py)到虚拟环境(Pipfile) - $ pipenv install -e . + Alt+. 取得上次命令的参数,并粘贴 + Ctrl+r 在历史命令中查找,回车执行 + Ctrl+o 放在命令后执行,可重复执行 - # 安装、卸载模块 - $ pipenv install requests - $ pipenv uninstall requests - $ pipenv uninstall --all # 卸载全部包 - $ pipenv install -r path/to/requirements.txt + Alt+u 以单词为单位,将光标处到单词结尾的字符转化为大写 + Alt+l 以单词为单位,将光标处到单词结尾的字符转化为小写 - # 安装所有依赖 - $ pipenv install --dev + Alt+Shift+# 注释当前命令,相当于ctrl-a,#,enter - # 更新包 - $ pipenv update # 更新所有包 - $ pipenv update --outdated # 打印所有要更新的包 - $ pipenv update <包名> # 更新指定的包 +辅助命令 +-------- - # 将Pipfile和Pipfile.lock文件里面的包导出为requirements.txt文件 - $ pipenv run pip freeze # 相当于pipenv run pip freeze >requirements.txt +:: - $ pipenv lock -r > requirements.txt - $ pipenv lock -r --dev # 若只想导出开发用的包 + Ctrl+s 锁住终端,可用来停留在当前屏 + Ctrl+q 解锁终端,恢复刷屏 + Ctrl+d 键盘输入结束或退出终端 -4.14.5 其他命令 ---------------- -.. code:: shell + Ctrl+s 暂停当前程序,暂停后按下任意键恢复运行 + Ctrl+z 将当前程序放到后台运行,恢复到前台为命令fg + Ctrl+Shift+r 重新连接 - # 创建一个包含预发布的锁文件: - $ pipenv lock --pre + Ctrl+Insert 复制 + Shift+Insert 粘贴 - # 打印所有包的依赖关系图 - $ pipenv graph +以我日常使用到的,暂时就这么些了,以后有用更多的使用技巧和快捷键再来补充。 - # 检查安全漏洞 - $ pipenv check +配色方案 +-------- -打印该虚拟环境下所有包的依赖关系图 +新建一个文件\ ``ubuntu.xcs`` -|image7| +:: -有的python第三方包旧版本会有安全漏洞,使用 pipenv check -可以检查安全漏洞。 + [ubuntu] + text(bold)=ffffff + magenta(bold)=ad7fa8 + text=ffffff + white(bold)=eeeeec + green=4e9a06 + red(bold)=ef2929 + green(bold)=8ae234 + black(bold)=555753 + red=cc0000 + blue=3465a4 + black=000000 + blue(bold)=729fcf + yellow(bold)=fce94f + cyan(bold)=34e2e2 + yellow=c4a000 + magenta=75507b + background=300a24 + white=d3d7cf + cyan=06989a + [Names] + count=1 + name0=ubuntu -|image8| +然后在xshell配色方案中导入即可 -.env`文件,用来存放一些环境变量。 +附上一个更全的帖子:\ `Xshell快捷键汇总 `__ -------------- -|image9| +|image6| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/Fk6WZ2xbqg2DM3AvnYCpsiKQ4xOn -.. |image2| image:: http://image.python-online.cn/FjuJ8yZsgjkzVuBRZHxK1ZnnzaEX -.. |image3| image:: http://image.python-online.cn/20190612211330.png -.. |image4| image:: http://image.python-online.cn/20190612213015.png -.. |image5| image:: http://image.python-online.cn/20190612213950.png -.. |image6| image:: http://image.python-online.cn/20190612211925.png -.. |image7| image:: http://image.python-online.cn/20190614000336.png -.. |image8| image:: http://image.python-online.cn/20190612215924.png -.. |image9| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20190511162815.png +.. |image2| image:: http://image.iswbm.com/20190511162524.png +.. |image3| image:: http://image.iswbm.com/20190511162607.png +.. |image4| image:: http://image.iswbm.com/20190511162716.png +.. |image5| image:: http://image.iswbm.com/20190511162730.png +.. |image6| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_15.md b/source/c04/c04_15.md index 6b53a7b..02718b4 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -29,19 +29,19 @@ Working directory: $ProjectFileDir$ Output filters: $FILE_PATH$\:$LINE$\:$COLUMN$\:.* ``` -![](http://image.python-online.cn/20190323164120.png) +![](http://image.iswbm.com/20190323164120.png) 我随意写了一段不符合 pep8 规范的代码。 -![](http://image.python-online.cn/20190323211635.png) +![](http://image.iswbm.com/20190323211635.png) 点击右键,选择 `External Tools` -> `AutoPep8` -![](http://image.python-online.cn/20190323211301.png) +![](http://image.iswbm.com/20190323211301.png) 看一下效果,还是挺明显的。 -![](http://image.python-online.cn/20190324111603.png) +![](http://image.iswbm.com/20190324111603.png) 你可能会说,Pycharm 本身就自带这个功能了呀,快捷键 `Command`+`Option`+`L` ,就可以实现一键pep8了。你可以对比一下,Pycharm 自带的代码 pep8 化功能 并没有像这个`autopep8` 来得彻底。 我相信你最终的选择肯定是后者。 @@ -61,7 +61,7 @@ Output filters: $FILE_PATH$\:$LINE$\:$COLUMN$\:.* 此时你可以在你的项目目录里,点击右键,有个 `Local History` 的选项,再点击子选项 `Show History`,你可以看到这里有个记录板。如果你想恢复删除的文件,就在删除的记录项点击右键,选择 `Revert` 即可恢复。 -![](http://image.python-online.cn/20190323153643.png) +![](http://image.iswbm.com/20190323153643.png) @@ -73,7 +73,7 @@ Vi 可以满足你对文本操作的所有需求,比可视化界面更加效 安装方法如下,安装完后需要重启 Pycharm 生效。 -![](http://image.python-online.cn/20190323214545.png) +![](http://image.iswbm.com/20190323214545.png) @@ -81,17 +81,17 @@ Vi 可以满足你对文本操作的所有需求,比可视化界面更加效 Pycharm 提供的这个代码模板,可以说是相当实用的一个功能了。它可以在你新建一个文件时,按照你预设的模板给你生成一段内容,比如解释器路径,编码方法,作者详细信息等 -![](http://image.python-online.cn/20190323225704.png) +![](http://image.iswbm.com/20190323225704.png) 按照上图模板,生成的效果如下。 -![](http://image.python-online.cn/20190323225631.png) +![](http://image.iswbm.com/20190323225631.png) 除了新建文件时可以初始化文件,在开发编写代码时,也同样使用 Pycharm 中自带的实用的代码模板,提高你的编码效率。 当你在键盘中敲入 `Command` + `J` 时,就可以调出一个面板,从下图可以看出里面有许多预设的模板。 -![](http://image.python-online.cn/20190323232017.png) +![](http://image.iswbm.com/20190323232017.png) 如果我们想选择最后一个 main ,可以继续键入 main,然后就可以直接生成如下这段平时都要手动敲入的代码。 @@ -124,7 +124,7 @@ Ctrl + Shift + 1 添加“1”序号的标签 在你要打书签的位置,按下 `Command` + `F11` ,你可以给这个位置加个序号,可以是数字也可以是字母,假如在下面这个位置 加了 `1` 这个序号,下次你就可以使用 `Control` + `1` 直接跳转到这个位置。 -![](http://image.python-online.cn/20190324111429.png) +![](http://image.iswbm.com/20190324111429.png) 当然你也可以不加,不加的话就是匿名书签了。你可以使用 `Shift` + `F11` 展示所有的书签,再进行跳转。 @@ -140,25 +140,25 @@ Ctrl + Shift + 1 添加“1”序号的标签 假如我在调试如下几行简单的代码。在第 3 行处打了个断点。然后点击图示位置 `Show Python Prompt` 按钮。 -![](http://image.python-online.cn/Fi3N02x9OeOPatGdaReam_icn9G_) +![](http://image.iswbm.com/Fi3N02x9OeOPatGdaReam_icn9G_) 就进入了 `Python Shell` 的界面,这个Shell 环境和我们当前运行的程序环境是打通的,变量之间可以互相访问,这下你可以轻松地进行调试了。 -![](http://image.python-online.cn/Fj1W53Txj0iFs5eYhFYh_dHlPtIL) +![](http://image.iswbm.com/Fj1W53Txj0iFs5eYhFYh_dHlPtIL) 上面我们打了个断点,是为了方便说明这个效果。并不是说一定要打断点。如果不打断点,在脚本执行完成后,也仍然可以在这个界面查看并操作所有变量。 -![](http://image.python-online.cn/FlMsB7B1x6ET9mLOgydTWuTEXuOe) +![](http://image.iswbm.com/FlMsB7B1x6ET9mLOgydTWuTEXuOe) 现在我们已经可以满足我们的调试的需求,但是每次运行脚本,都要手动点击 `Show Python Prompt` ,有点麻烦。嗯?其实这个有地方可以设置默认打开的。这个开关还比较隐秘,一般人还真发现不了。 你需要点击图示位置 `Edit Configurations` 处。 -![](http://image.python-online.cn/FmfL3r0iWx_srT_xMASBEp1ZaaId) +![](http://image.iswbm.com/FmfL3r0iWx_srT_xMASBEp1ZaaId) 然后在这里打勾选中。 -![](http://image.python-online.cn/FiNCYpVlI93gk1zhOdQn4c0A8FMX) +![](http://image.iswbm.com/FiNCYpVlI93gk1zhOdQn4c0A8FMX) 设置上之后,之后你每次运行后脚本后,都会默认为你存储所有变量的值,并为你打开 console 命令行调试界面。 @@ -166,11 +166,11 @@ Ctrl + Shift + 1 添加“1”序号的标签 使用方法就是,在你打了断点后,在图示位置处,点击右键使用 `Evaluate Expression` -![](http://image.python-online.cn/FrAq1tVRM7Bz948wRqZFzU2PQnI0) +![](http://image.iswbm.com/FrAq1tVRM7Bz948wRqZFzU2PQnI0) 就弹出了一个 `Evaluate Expression` 窗口,这里 可以运行命令表达式,直接操作变量。 -![](http://image.python-online.cn/Fo2aEraqbj_2KqDt44EzJTVe8pEf) +![](http://image.iswbm.com/Fo2aEraqbj_2KqDt44EzJTVe8pEf) @@ -190,15 +190,15 @@ python main.py init --local 对于刚使用 Pycharm 的同学,可能并不知道 Pycharm 也是可以指定参数的。点击下图位置 -![](http://image.python-online.cn/FmfL3r0iWx_srT_xMASBEp1ZaaId) +![](http://image.iswbm.com/FmfL3r0iWx_srT_xMASBEp1ZaaId) 进入设置面板,在 `Script parameters` 中填入参数即可。 -![](http://image.python-online.cn/FujczKwTUPa8l5EEmS0eoh-zL1Nk) +![](http://image.iswbm.com/FujczKwTUPa8l5EEmS0eoh-zL1Nk) 同时在上图的底部,你可以看到,这里可以很方便的切换 解释器,比你跑到这边来要容易得多吧 -![](http://image.python-online.cn/Fq60WOdcRJopqV6MVoRcIuZclYKx) +![](http://image.iswbm.com/Fq60WOdcRJopqV6MVoRcIuZclYKx) @@ -208,17 +208,17 @@ python main.py init --local 我平时会看的框架是 OpenStack ,我不知道其他框架是怎样的,但在 OpenStack 里面带有大量(真的很多)的单元测试文件。这给我在使用 `Find in Path` 时带来了不小的困扰,你可以从下图的搜索结果中感受一下,搜索一个函数,test 文件里的结果比 正常文件要多很多。 -![](http://image.python-online.cn/FlXynbyxh8tTrCpc4tVLqycL7JQm) +![](http://image.iswbm.com/FlXynbyxh8tTrCpc4tVLqycL7JQm) 这些测试文件的搜索结果,对于我们看源代码不仅没有任何帮助的,更重要的是还干扰视线。于是我就研究了一下,从文件名入手,只要在 `File mask` 里填写 `!test*` 可以将这些test文件过滤掉。搜索结果一下子清晰很多。 -![](http://image.python-online.cn/FiD91PR1hUu0Ruc6cmZ7EGNM6Be_) +![](http://image.iswbm.com/FiD91PR1hUu0Ruc6cmZ7EGNM6Be_) ## 4.15.9 关闭烦人的灯泡提示 本来没有想写这个的,但是知乎上有一位朋友有这个需求,那我研究了下。 -![](http://image.python-online.cn/FhkX5Ko3LVZL_p7YfitDsTDxvHmL) +![](http://image.iswbm.com/FhkX5Ko3LVZL_p7YfitDsTDxvHmL) 先来说下这个灯泡提示是什么,有什么用? @@ -230,27 +230,27 @@ python main.py init --local 我研究了下,Pycharm (2018版本)里是有开关按钮的,将下图中的这个选项(`Show intention bulb`)取消勾选,就可以关闭这个功能。 -![](http://image.python-online.cn/FuSSVa-aMqkfCaf62sbUoX2PLaYM) +![](http://image.iswbm.com/FuSSVa-aMqkfCaf62sbUoX2PLaYM) ## 4.15.10 关闭碍眼的波浪线 下面我先给出了一小段代码示例,思考一下,为什么name,my_name 不会有波浪线,而 myname 和 wangbm 会有波浪线呢? -![](http://image.python-online.cn/FtFPI89AOKmPLNpNxf-jdkn1BDLW) +![](http://image.iswbm.com/FtFPI89AOKmPLNpNxf-jdkn1BDLW) Pycharm 本身会实时地对变量名进行检查,如果变量名不是一个已存在的英文单词,就会出现一条波浪线,当一个变量里有多个单词时,Python 推荐的写法是用下划线来分隔(其他语言可能会习惯使用`驼峰式命名法` ,但 Python 是使用下划线),所以在 Pycharm 看来 my_name 是规范的,而 myname 会被当成是一个单词对待,由于它在单词库里并没有它,所以 myname 是不规范的。 每个人的变量命名习惯不一样,如何你在项目里大量使用了 myname 这种风格的变量命名方法,像下面这样(随便找了一段 cloudinit 的代码),是让人挺不舒服的,总有一种代码有 bug 的错觉。 -![](http://image.python-online.cn/FiKyU6tjQauWXfaVfKLhwi3NkXBf) +![](http://image.iswbm.com/FiKyU6tjQauWXfaVfKLhwi3NkXBf) 那么如何关闭这个非语法级别的波浪线呢?很简单,它的开关就在你的右下角那个像 人头像 一样的按钮 -![](http://image.python-online.cn/FsAM-8HyzSrLWZJ_lg3ofw84_ibf) +![](http://image.iswbm.com/FsAM-8HyzSrLWZJ_lg3ofw84_ibf) 然后选择 `Syntax` 级别的即可。同样一段代码,效果如下,干净了很多。 -![](http://image.python-online.cn/FgJCtNYkjPfBaTbRxwb3Z6icHqkf) +![](http://image.iswbm.com/FgJCtNYkjPfBaTbRxwb3Z6icHqkf) ## 4.15.11 一键进行代码性能分析 @@ -289,11 +289,11 @@ fun5() 点击 Run -> Profile '程序' ,即可进行性能分析。 -![](http://image.python-online.cn/20190507222856.png) +![](http://image.iswbm.com/20190507222856.png) 运行完毕后,会自动跳出一个性能统计界面。 -![](http://image.python-online.cn/20190507222119.png) +![](http://image.iswbm.com/20190507222119.png) 性能统计界面由Name、Call Count、Time(ms)、Own Time(ms) ,4列组成一个表格,见下图。 @@ -304,7 +304,7 @@ fun5() 点击 Call Graph(调用关系图)界面直观展示了各函数直接的调用关系、运行时间和时间百分比,见下图。 -![](http://image.python-online.cn/20190507223313.png) +![](http://image.iswbm.com/20190507223313.png) 右上角的4个按钮表示放大、缩小、真实大小、合适大小; @@ -318,25 +318,25 @@ fun5() 按照如下提示点击 Git 仓库配置 -![](http://image.python-online.cn/20190507215525.png) +![](http://image.iswbm.com/20190507215525.png) 接着输入仓库地址 -![](http://image.python-online.cn/20190507220101.png) +![](http://image.iswbm.com/20190507220101.png) 点击 Test,测试连通性,会要求输入密码 -![](http://image.python-online.cn/20190419152120.png) +![](http://image.iswbm.com/20190419152120.png) 若一切顺利,则会看到如下界面 -![](http://image.python-online.cn/20190419152145.png) +![](http://image.iswbm.com/20190419152145.png) 测试连接成功后,点击 Clone 就可以克隆下来了。 对于以前使用 Git 命令来管理的,现在可以直接使用 PyCharm 的菜单栏来操作,这些功能已经可以满足大多数人的日常需求了,应该是够用了。 -![](http://image.python-online.cn/20190507220740.png) +![](http://image.iswbm.com/20190507220740.png) ## 4.15.13 Tab轻松转空格 @@ -346,7 +346,7 @@ fun5() 在图示位置打勾即可开启自动检测。 -![](http://image.python-online.cn/20190423162328.png) +![](http://image.iswbm.com/20190423162328.png) 上面是对一个旧的 Python 模块进行修改时,如何决定当前编辑的缩进方式。 @@ -354,7 +354,7 @@ fun5() 如下图,若在 `Use tab character` 打上勾,则你新建一个 Python 后,就会使用 TAB 进行缩进,反之,则使用四个空格进行缩进。 -![](http://image.python-online.cn/20190423163341.png) +![](http://image.iswbm.com/20190423163341.png) 5. @@ -362,7 +362,7 @@ fun5() PyCharm 有分两个版本,一个是社区版(免费功能有限),一个是专业版(有一些增强功能),详细差异你可以参考这个图,一般来说,社区版用作学习用途是没有问题的。 -![](http://image.python-online.cn/20190506150523.png) +![](http://image.iswbm.com/20190506150523.png) 如果需要使用专业版,网上也有一些注册服务器使用,非常方便,缺点是过一段时间,可能就会失效。这里有一种一劳永逸的方法,但可能仅对早期的 PyCharm 版本有效,可以实现永久激活(到 2099 / 2100年,一定意义上是永久了吧)。 @@ -384,21 +384,21 @@ PyCharm 有分两个版本,一个是社区版(免费功能有限),一个 将第一步下载的 jar 包放入这个目录,并打开如下两个以 `vmoptions` 后缀结尾的文件: -![](http://image.python-online.cn/20190506150010.png) +![](http://image.iswbm.com/20190506150010.png) 添加如下这一行(请根据你的实际安装目录自行调整) -![](http://image.python-online.cn/20190506150100.png) +![](http://image.iswbm.com/20190506150100.png) 若是 Mac OS 系统,请找到并进入你的 Pycharm 安装启动目录(以我的为例) 将第一步下载的 jar 包放入这个目录 -![](http://image.python-online.cn/20190507000850.png) +![](http://image.iswbm.com/20190507000850.png) 并打开如下一个以 `vmoptions` 后缀结尾的文件: -![](http://image.python-online.cn/20190507001025.png) +![](http://image.iswbm.com/20190507001025.png) @@ -418,11 +418,11 @@ BIG3CLIK6F-eyJsaWNlbnNlSWQiOiJCSUczQ0xJSzZGIiwibGljZW5zZWVOYW1lIjoibGFuIHl1IiwiY 若是 Windows 系统,重启 PyCharm 后,查看激活信息:Help -> About -![](http://image.python-online.cn/20190507001422.png) +![](http://image.iswbm.com/20190507001422.png) 如果是 Mac OS 系统,重启 PyCharm 后,查看激活信息:PyCharm -> About PyCharm -![](http://image.python-online.cn/20190507001350.png) +![](http://image.iswbm.com/20190507001350.png) 另外,以上仅做交流和个人学习使用,请勿商用,有能力的朋友还是希望多支持正版! @@ -436,19 +436,19 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 在编写代码时,顺便写好函数的接口文档,是一个很好的编码习惯。它介绍了该函数的参数类型及说明,返回值类型及范例,写得好一点的还会写出 几个简单的 Example Usage 有助于理解使用。在这一点上,Flask 可以说做得相当好。这边随便截一个 Werkzeug 的例子。 -![](http://image.python-online.cn/20190507152911.png) +![](http://image.iswbm.com/20190507152911.png) 假如我们在使用这个类的时候,忘记了这个用法,可以按住 Ctrl + q(Mac暂时未找到对应快捷键),在当前页面就可以快速预览 LocalStack 的接口文档。 -![](http://image.python-online.cn/20190507152840.png) +![](http://image.iswbm.com/20190507152840.png) 同样的,如果你对这个类或者函数的代码逻辑感兴趣,也可以使用快速预览的方式在当前页面展示源代码。快捷键是:Ctrl + shift + i (Mac:Command + shift + i)。效果如下 -![](http://image.python-online.cn/20190507153847.png) +![](http://image.iswbm.com/20190507153847.png) 如果 PyCharm 检测到的同名函数有多个,可以点这里进行选择查看 -![](http://image.python-online.cn/20190507154027.png) +![](http://image.iswbm.com/20190507154027.png) 这两个快捷键比起使用 Ctrl + 鼠标左键 跳进源代码来说,更加方便,,就像微信小程序一样,用完即焚,不会新产生一个标签页,也不需要来回跳转页面。 @@ -456,7 +456,7 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 前几天打开 PyCharm,发现在导航栏这里出现了很多波浪线,有过 PyCharm 使用经验的同学,就会知道,这是代码中出现了错误。 -![](http://image.python-online.cn/20190613154147.png) +![](http://image.iswbm.com/20190613154147.png) 顺着波浪线,我一层一层地展开目录树,终于找到了那个包含错误的文件。由于是手误,我也不知道我改动了哪一行,看了下这个文件,有将近8000行的代码,难道一行一行地去找? @@ -468,11 +468,11 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 我在搜索框输入 Error,就找到了快速定位到错误位置的快捷键 `F2` 和 `Shift+F2` 可以快速的定位到错误行。 -![](http://image.python-online.cn/20190613154401.png) +![](http://image.iswbm.com/20190613154401.png) 使用快捷键 F2 查看了下原来是这里缩进有问题。 -![](http://image.python-online.cn/20190613160905.png) +![](http://image.iswbm.com/20190613160905.png) ## 4.15.17 快速查看最近的修改 @@ -482,7 +482,7 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 太巧的是,今天我打开 PyCharm ,就给我推了这条 tip,(在Mac上)使用 option+shift+C 可以快速查看最近修改的内容(windows 上应该是alt+shift+c吧) -![](http://image.python-online.cn/20190614235120.png) +![](http://image.iswbm.com/20190614235120.png) ## 4.15.18 静态代码分析检查 @@ -500,11 +500,11 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 你只需要像下面这样点击项目文件夹,然后右键,选择 `Inspect Code`,就可以开启静态检查。 -![](http://image.python-online.cn/20190616211359.png) +![](http://image.iswbm.com/20190616211359.png) 我对开源组件 nova 的静态检查发现,其有不规范的地方有数千处。 -![](http://image.python-online.cn/20190616214310.png) +![](http://image.iswbm.com/20190616214310.png) ## 4.15.19 全方位无死角精准定位 @@ -518,25 +518,25 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 - 精准定位到文件:Windows(Ctrl+Shift+N),Mac(Command+ shift +N) -![](http://image.python-online.cn/20190616221620.png) +![](http://image.iswbm.com/20190616221620.png) - 精准定位到类:Windows(Ctrl+N),Mac(Command+N) -![](http://image.python-online.cn/20190616232746.png) +![](http://image.iswbm.com/20190616232746.png) - 精准定位到符号:类的所有成员(函数、变量等)都可以称之为符号,Windows(Ctrl+Alt+Shift+N),Mac(Option+Shift+Command+N) -![](http://image.python-online.cn/20190616233827.png) +![](http://image.iswbm.com/20190616233827.png) - 精准定位到文件结构:文件结构包括类、函数、变量,这说明上面定位到类和定位到符号的方法,你都可以用这个来代替。 Windows:Ctrl+F12,Mac:Command+F12,如果和我一样是Mac是带touchbar的,键盘上是没有F12的,那你应该先按住 Command + fn,这时 touchbar 上会出现 F12,再按F12即可。 -![](http://image.python-online.cn/20190616235007.png) +![](http://image.iswbm.com/20190616235007.png) - 精准定位到某行:Windows(Ctrl+G),Mac(Command+G),如下图定位到第510行第9个字符处。 -![](http://image.python-online.cn/20190616234038.png) +![](http://image.iswbm.com/20190616234038.png) ## 4.15.20 TODO 解救“中年痴呆” @@ -560,13 +560,13 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 使用方法跟注释差不多,只要固定要以 TODO 开头。然后,你要查看全局项目中的所有 TODO 事项的时候,可以使用快捷键调出 TODO 面板。如果你是 Mac, 快捷键 是Command + 6,而 Windows 是 Alt+6。 -![](http://image.python-online.cn/20190616231649.png) +![](http://image.iswbm.com/20190616231649.png) 另外,我还使用这个来记录下个版本要优化的代码逻辑,要添加的功能。 如果是比较紧急的 BUG,可以使用类似 TODO 的标记 — `FIXME` 来区分紧急程度。 -![](http://image.python-online.cn/20190616232527.png) +![](http://image.iswbm.com/20190616232527.png) ## 4.15.21 随处折叠,实现代码自由 @@ -574,7 +574,7 @@ PyCharm 里代码块的折叠功能,相当的显眼,在代码编辑框的左 如果你和我一样是个键盘党,你可以使用快捷(Mac:按住Command键,再按`+`或者`-` ,Windows:按住Ctrl键,再按`+`或者`-` )进行快速反折叠/折叠。 -![](http://image.python-online.cn/20190629183430.png) +![](http://image.iswbm.com/20190629183430.png) 代码块的折叠和反折叠,应该是一个代码编辑器的基本功能。在这一点上, PyCharm 做为一个 IDE,在这一点上势必要做得更出色,事实证明,它做到了。 @@ -602,7 +602,7 @@ PyCharm 里代码块的折叠功能,相当的显眼,在代码编辑框的左 比如下面这段代码,我只想改myfun 里的的test_name,而对于全局下的同名变量是不应该修改的。如果你全局替换,就会有误伤。 -![](http://image.python-online.cn/20190629211910.png) +![](http://image.iswbm.com/20190629211910.png) 这时候,我们如何做呢? @@ -634,13 +634,13 @@ PyCharm 里代码块的折叠功能,相当的显眼,在代码编辑框的左 这样播放宏显得有点繁琐,个人建议你为这个宏定义一个快捷键,这样会更方便播放宏。 -![](http://image.python-online.cn/20190629221224.png) +![](http://image.iswbm.com/20190629221224.png) 设置快捷键时,注意不要和已有的快捷键冲突。 设置好后,查看 Macro,发现PyCharm已经将这个快捷键绑定给这个宏。 -![](http://image.python-online.cn/20190629221547.png) +![](http://image.iswbm.com/20190629221547.png) 之后你就可以使用这个快捷键删除一个函数(其实这只是删除一个代码块,但是这里只讨论设置方法)。 @@ -650,7 +650,7 @@ PyCharm 打开一个文件,就占用一个标签面。 你有没有发现,不知不觉地,打开的文件越来越多,多到一行标签都装不下,装不下的标签页 PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文件。 -![](http://image.python-online.cn/20190629223534.png) +![](http://image.iswbm.com/20190629223534.png) 点击数字5,你才可以查看隐藏了哪些文件。 @@ -660,11 +660,11 @@ PyCharm 打开一个文件,就占用一个标签面。 如下图,将单行显示取消勾选即可。 -![](http://image.python-online.cn/20190629224229.png) +![](http://image.iswbm.com/20190629224229.png) 设置完后,有哪些文件就非常清晰了。 -![](http://image.python-online.cn/20190629224430.png) +![](http://image.iswbm.com/20190629224430.png) ## 4.15.25 应用搜索,阅读源码必备 @@ -682,7 +682,7 @@ PyCharm 打开一个文件,就占用一个标签面。 如下图所示,按下快捷键后可以很轻松地看见调用列表。 -![](http://image.python-online.cn/20190629231322.png) +![](http://image.iswbm.com/20190629231322.png) 如果你嫌这快捷键太长了,可以使用 `鼠标中键` 点击这个类,可以达到同样的效果。 @@ -698,17 +698,17 @@ PyCharm 打开一个文件,就占用一个标签面。 对比示例,可以查看下面这张图,UI做的还是挺好看的。 -![](http://image.python-online.cn/20190721125739.png) +![](http://image.iswbm.com/20190721125739.png) ## 4.15.27 以列为单位的块编辑 先给你出道小题,像下面这段代码,如果在不影响代码的情况下,快速删除后面代码后面的注释呢? -![](http://image.python-online.cn/20190721132238.png) +![](http://image.iswbm.com/20190721132238.png) 我能想到的有两种方法,如果像如上这种有规律的注释,可以使用 `正则匹配` + `替换` 来实现。 -![](http://image.python-online.cn/20190721133403.png) +![](http://image.iswbm.com/20190721133403.png) 对于这个场景我想到了可以用 vim来轻松的解决,vim 支持块编辑,可以以列为单位选择区域然后进行操作,这在vim中是很常用的一个取消注释的操作。 @@ -724,17 +724,17 @@ PyCharm 打开一个文件,就占用一个标签面。 当你的对象是以大写字母开头时,而你使用小写字母编写代码时,是不能查找到该函数的,你必须得先切换成大写再输入一遍。 -![](http://image.python-online.cn/20190721141327.png) +![](http://image.iswbm.com/20190721141327.png) 如何避免这种尴尬的情况? 只要在配置中关闭大小写匹配即可。 -![](http://image.python-online.cn/20190721141653.png) +![](http://image.iswbm.com/20190721141653.png) 效果如下: -![](http://image.python-online.cn/20190721141751.png) +![](http://image.iswbm.com/20190721141751.png) ## 4.15.29 保护眼睛,从PyCharm开始 @@ -746,7 +746,7 @@ PyCharm 打开一个文件,就占用一个标签面。 这里就教大家如何设置 PyCharm 的背景色为护眼色,方法如下: -![](http://image.python-online.cn/20190721143450.png) +![](http://image.iswbm.com/20190721143450.png) 设置护眼色,会降低 PyCharm 的顔值,这需要你从中取一个取舍。 @@ -785,7 +785,7 @@ PyCharm 打开一个文件,就占用一个标签面。 这里我提前准备了几种编程语言的 Hello World ,效果如下: -![](http://image.python-online.cn/20191211210012.png) +![](http://image.iswbm.com/20191211210012.png) @@ -799,17 +799,17 @@ PyCharm 打开一个文件,就占用一个标签面。 以前我经常使用一些在线的网站,比如:https://tool.oschina.net/codeformat/json -![](http://image.python-online.cn/20191211211309.png) +![](http://image.iswbm.com/20191211211309.png) 如果你的电脑无法连网,或者不喜欢多记一个网址,完全可以使用 PyCharnm 来解决这一诉求 没有经过美化是这样的: -![](http://image.python-online.cn/20191211211334.png) +![](http://image.iswbm.com/20191211211334.png) 按住 `Ctrl+Alt+L`经过美化后是这样的 -![](http://image.python-online.cn/20191211211626.png) +![](http://image.iswbm.com/20191211211626.png) @@ -819,11 +819,11 @@ PyCharm 打开一个文件,就占用一个标签面。 对于像我这样熟悉 Linux 的开发者来说,Windows 的 那些 CMD 命令带来的糟糕体验是无法忍受的。 -![](http://image.python-online.cn/20191211212546.png) +![](http://image.iswbm.com/20191211212546.png) 在弹出的 Bash 窗口,你可以敲入你想使用的 Linux 命令,是不是舒服多了。 -![](http://image.python-online.cn/20191222143741.png) +![](http://image.iswbm.com/20191222143741.png) @@ -845,57 +845,57 @@ PyCharm 打开一个文件,就占用一个标签面。 假如,我现在有如下一段代码,红框标出的代码放在主函数中,有些不太合适,况且这段代码不能让人一眼就看出它是在做什么事情。如何将其进行封装,对我们理清整个主程序的逻辑会有帮助。 -![](http://image.python-online.cn/20191222141905.png) +![](http://image.iswbm.com/20191222141905.png) 选中你要封装的代码,然后按住 `Ctrl`+`Alt`+`M` 后,会跳出如下界面,根据自己的需要,修改函数名,选择参数和返回值 -![](http://image.python-online.cn/20191222141955.png) +![](http://image.iswbm.com/20191222141955.png) 一切就绪点击 `OK`,PyCharm 会自动在合适的位置为你定义一个函数名,并将你选中的代码放到里面,其中参数名和返回值也都是按照你的要求,效果如下: -![](http://image.python-online.cn/20191222142223.png) +![](http://image.iswbm.com/20191222142223.png) ## 4.15.35 使用 Git 进行版本管理 点击 `VCS` -> `Git` -> `Clone` -![](http://image.python-online.cn/20191211100048.png) +![](http://image.iswbm.com/20191211100048.png) 填写git仓库相关信息 -![](http://image.python-online.cn/20191211100657.png) +![](http://image.iswbm.com/20191211100657.png) 点击 `Test`,会尝试连接 git 服务器,中间会让你输入登陆的帐号和密码。 -![](http://image.python-online.cn/20191211101706.png) +![](http://image.iswbm.com/20191211101706.png) 点击`OK` 后,若一切正常会提示连接成功。 -![](http://image.python-online.cn/20191211101845.png) +![](http://image.iswbm.com/20191211101845.png) 点击 `OK` 后,PyCharm 需要你选择如何打开这个 Git 仓库目录,是在当前窗口中打开,还是新建一个窗口? 由于我在一个 PyCharm 下会有多个 Git 仓库,为了方便,我选择在当前窗口中打开(注意勾选 `Add to currently opened projects`)。 -![](http://image.python-online.cn/20191211102501.png) +![](http://image.iswbm.com/20191211102501.png) 至此,Git 配置完成。 此时你可以 `VCS` -> `Git` 查看,发现之前这些灰色不可用的按钮都可以使用了。 -![](http://image.python-online.cn/20191211102826.png) +![](http://image.iswbm.com/20191211102826.png) 本篇重在讲解 PyCharm 的配置,关于Git 的操作,不属于本篇重点,就不再展开讲了。 若你想对已配置的Git仓库进行修改,可点击 `File` -> `Setting` -> `Version Control` 调出如下界面。 -![](http://image.python-online.cn/20191211133836.png) +![](http://image.iswbm.com/20191211133836.png) 不得不说 PyCharm 的这 UI 做得可以,随便改了个东西提交一下 -![](http://image.python-online.cn/20191211143510.png) +![](http://image.iswbm.com/20191211143510.png) diff --git a/source/c04/c04_15.rst b/source/c04/c04_15.rst index dab19b3..2c25fb8 100644 --- a/source/c04/c04_15.rst +++ b/source/c04/c04_15.rst @@ -1117,106 +1117,106 @@ Template |image106| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190323164120.png -.. |image2| image:: http://image.python-online.cn/20190323211635.png -.. |image3| image:: http://image.python-online.cn/20190323211301.png -.. |image4| image:: http://image.python-online.cn/20190324111603.png -.. |image5| image:: http://image.python-online.cn/20190323153643.png -.. |image6| image:: http://image.python-online.cn/20190323214545.png -.. |image7| image:: http://image.python-online.cn/20190323225704.png -.. |image8| image:: http://image.python-online.cn/20190323225631.png -.. |image9| image:: http://image.python-online.cn/20190323232017.png +.. |image1| image:: http://image.iswbm.com/20190323164120.png +.. |image2| image:: http://image.iswbm.com/20190323211635.png +.. |image3| image:: http://image.iswbm.com/20190323211301.png +.. |image4| image:: http://image.iswbm.com/20190324111603.png +.. |image5| image:: http://image.iswbm.com/20190323153643.png +.. |image6| image:: http://image.iswbm.com/20190323214545.png +.. |image7| image:: http://image.iswbm.com/20190323225704.png +.. |image8| image:: http://image.iswbm.com/20190323225631.png +.. |image9| image:: http://image.iswbm.com/20190323232017.png .. |image10| image:: https://i.loli.net/2019/03/23/5c965275bf0d7.gif .. |image11| image:: https://i.loli.net/2019/03/23/5c9653e1b757a.gif -.. |image12| image:: http://image.python-online.cn/20190324111429.png -.. |image13| image:: http://image.python-online.cn/Fi3N02x9OeOPatGdaReam_icn9G_ -.. |image14| image:: http://image.python-online.cn/Fj1W53Txj0iFs5eYhFYh_dHlPtIL -.. |image15| image:: http://image.python-online.cn/FlMsB7B1x6ET9mLOgydTWuTEXuOe -.. |image16| image:: http://image.python-online.cn/FmfL3r0iWx_srT_xMASBEp1ZaaId -.. |image17| image:: http://image.python-online.cn/FiNCYpVlI93gk1zhOdQn4c0A8FMX -.. |image18| image:: http://image.python-online.cn/FrAq1tVRM7Bz948wRqZFzU2PQnI0 -.. |image19| image:: http://image.python-online.cn/Fo2aEraqbj_2KqDt44EzJTVe8pEf -.. |image20| image:: http://image.python-online.cn/FmfL3r0iWx_srT_xMASBEp1ZaaId -.. |image21| image:: http://image.python-online.cn/FujczKwTUPa8l5EEmS0eoh-zL1Nk -.. |image22| image:: http://image.python-online.cn/Fq60WOdcRJopqV6MVoRcIuZclYKx -.. |image23| image:: http://image.python-online.cn/FlXynbyxh8tTrCpc4tVLqycL7JQm -.. |image24| image:: http://image.python-online.cn/FiD91PR1hUu0Ruc6cmZ7EGNM6Be_ -.. |image25| image:: http://image.python-online.cn/FhkX5Ko3LVZL_p7YfitDsTDxvHmL -.. |image26| image:: http://image.python-online.cn/FuSSVa-aMqkfCaf62sbUoX2PLaYM -.. |image27| image:: http://image.python-online.cn/FtFPI89AOKmPLNpNxf-jdkn1BDLW -.. |image28| image:: http://image.python-online.cn/FiKyU6tjQauWXfaVfKLhwi3NkXBf -.. |image29| image:: http://image.python-online.cn/FsAM-8HyzSrLWZJ_lg3ofw84_ibf -.. |image30| image:: http://image.python-online.cn/FgJCtNYkjPfBaTbRxwb3Z6icHqkf -.. |image31| image:: http://image.python-online.cn/20190507222856.png -.. |image32| image:: http://image.python-online.cn/20190507222119.png -.. |image33| image:: http://image.python-online.cn/20190507223313.png -.. |image34| image:: http://image.python-online.cn/20190507215525.png -.. |image35| image:: http://image.python-online.cn/20190507220101.png -.. |image36| image:: http://image.python-online.cn/20190419152120.png -.. |image37| image:: http://image.python-online.cn/20190419152145.png -.. |image38| image:: http://image.python-online.cn/20190507220740.png -.. |image39| image:: http://image.python-online.cn/20190423162328.png -.. |image40| image:: http://image.python-online.cn/20190423163341.png -.. |image41| image:: http://image.python-online.cn/20190506150523.png -.. |image42| image:: http://image.python-online.cn/20190506150010.png -.. |image43| image:: http://image.python-online.cn/20190506150100.png -.. |image44| image:: http://image.python-online.cn/20190507000850.png -.. |image45| image:: http://image.python-online.cn/20190507001025.png -.. |image46| image:: http://image.python-online.cn/20190507001422.png -.. |image47| image:: http://image.python-online.cn/20190507001350.png -.. |image48| image:: http://image.python-online.cn/20190507152911.png -.. |image49| image:: http://image.python-online.cn/20190507152840.png -.. |image50| image:: http://image.python-online.cn/20190507153847.png -.. |image51| image:: http://image.python-online.cn/20190507154027.png -.. |image52| image:: http://image.python-online.cn/20190613154147.png -.. |image53| image:: http://image.python-online.cn/20190613154401.png -.. |image54| image:: http://image.python-online.cn/20190613160905.png -.. |image55| image:: http://image.python-online.cn/20190614235120.png -.. |image56| image:: http://image.python-online.cn/20190616211359.png -.. |image57| image:: http://image.python-online.cn/20190616214310.png -.. |image58| image:: http://image.python-online.cn/20190616221620.png -.. |image59| image:: http://image.python-online.cn/20190616232746.png -.. |image60| image:: http://image.python-online.cn/20190616233827.png -.. |image61| image:: http://image.python-online.cn/20190616235007.png -.. |image62| image:: http://image.python-online.cn/20190616234038.png -.. |image63| image:: http://image.python-online.cn/20190616231649.png -.. |image64| image:: http://image.python-online.cn/20190616232527.png -.. |image65| image:: http://image.python-online.cn/20190629183430.png +.. |image12| image:: http://image.iswbm.com/20190324111429.png +.. |image13| image:: http://image.iswbm.com/Fi3N02x9OeOPatGdaReam_icn9G_ +.. |image14| image:: http://image.iswbm.com/Fj1W53Txj0iFs5eYhFYh_dHlPtIL +.. |image15| image:: http://image.iswbm.com/FlMsB7B1x6ET9mLOgydTWuTEXuOe +.. |image16| image:: http://image.iswbm.com/FmfL3r0iWx_srT_xMASBEp1ZaaId +.. |image17| image:: http://image.iswbm.com/FiNCYpVlI93gk1zhOdQn4c0A8FMX +.. |image18| image:: http://image.iswbm.com/FrAq1tVRM7Bz948wRqZFzU2PQnI0 +.. |image19| image:: http://image.iswbm.com/Fo2aEraqbj_2KqDt44EzJTVe8pEf +.. |image20| image:: http://image.iswbm.com/FmfL3r0iWx_srT_xMASBEp1ZaaId +.. |image21| image:: http://image.iswbm.com/FujczKwTUPa8l5EEmS0eoh-zL1Nk +.. |image22| image:: http://image.iswbm.com/Fq60WOdcRJopqV6MVoRcIuZclYKx +.. |image23| image:: http://image.iswbm.com/FlXynbyxh8tTrCpc4tVLqycL7JQm +.. |image24| image:: http://image.iswbm.com/FiD91PR1hUu0Ruc6cmZ7EGNM6Be_ +.. |image25| image:: http://image.iswbm.com/FhkX5Ko3LVZL_p7YfitDsTDxvHmL +.. |image26| image:: http://image.iswbm.com/FuSSVa-aMqkfCaf62sbUoX2PLaYM +.. |image27| image:: http://image.iswbm.com/FtFPI89AOKmPLNpNxf-jdkn1BDLW +.. |image28| image:: http://image.iswbm.com/FiKyU6tjQauWXfaVfKLhwi3NkXBf +.. |image29| image:: http://image.iswbm.com/FsAM-8HyzSrLWZJ_lg3ofw84_ibf +.. |image30| image:: http://image.iswbm.com/FgJCtNYkjPfBaTbRxwb3Z6icHqkf +.. |image31| image:: http://image.iswbm.com/20190507222856.png +.. |image32| image:: http://image.iswbm.com/20190507222119.png +.. |image33| image:: http://image.iswbm.com/20190507223313.png +.. |image34| image:: http://image.iswbm.com/20190507215525.png +.. |image35| image:: http://image.iswbm.com/20190507220101.png +.. |image36| image:: http://image.iswbm.com/20190419152120.png +.. |image37| image:: http://image.iswbm.com/20190419152145.png +.. |image38| image:: http://image.iswbm.com/20190507220740.png +.. |image39| image:: http://image.iswbm.com/20190423162328.png +.. |image40| image:: http://image.iswbm.com/20190423163341.png +.. |image41| image:: http://image.iswbm.com/20190506150523.png +.. |image42| image:: http://image.iswbm.com/20190506150010.png +.. |image43| image:: http://image.iswbm.com/20190506150100.png +.. |image44| image:: http://image.iswbm.com/20190507000850.png +.. |image45| image:: http://image.iswbm.com/20190507001025.png +.. |image46| image:: http://image.iswbm.com/20190507001422.png +.. |image47| image:: http://image.iswbm.com/20190507001350.png +.. |image48| image:: http://image.iswbm.com/20190507152911.png +.. |image49| image:: http://image.iswbm.com/20190507152840.png +.. |image50| image:: http://image.iswbm.com/20190507153847.png +.. |image51| image:: http://image.iswbm.com/20190507154027.png +.. |image52| image:: http://image.iswbm.com/20190613154147.png +.. |image53| image:: http://image.iswbm.com/20190613154401.png +.. |image54| image:: http://image.iswbm.com/20190613160905.png +.. |image55| image:: http://image.iswbm.com/20190614235120.png +.. |image56| image:: http://image.iswbm.com/20190616211359.png +.. |image57| image:: http://image.iswbm.com/20190616214310.png +.. |image58| image:: http://image.iswbm.com/20190616221620.png +.. |image59| image:: http://image.iswbm.com/20190616232746.png +.. |image60| image:: http://image.iswbm.com/20190616233827.png +.. |image61| image:: http://image.iswbm.com/20190616235007.png +.. |image62| image:: http://image.iswbm.com/20190616234038.png +.. |image63| image:: http://image.iswbm.com/20190616231649.png +.. |image64| image:: http://image.iswbm.com/20190616232527.png +.. |image65| image:: http://image.iswbm.com/20190629183430.png .. |image66| image:: https://i.loli.net/2019/06/29/5d17589c1603755790.gif -.. |image67| image:: http://image.python-online.cn/20190629211910.png +.. |image67| image:: http://image.iswbm.com/20190629211910.png .. |image68| image:: https://i.loli.net/2019/06/29/5d1764b94d11128912.gif .. |image69| image:: https://i.loli.net/2019/06/29/5d176e9ba92e916696.gif -.. |image70| image:: http://image.python-online.cn/20190629221224.png -.. |image71| image:: http://image.python-online.cn/20190629221547.png -.. |image72| image:: http://image.python-online.cn/20190629223534.png -.. |image73| image:: http://image.python-online.cn/20190629224229.png -.. |image74| image:: http://image.python-online.cn/20190629224430.png -.. |image75| image:: http://image.python-online.cn/20190629231322.png -.. |image76| image:: http://image.python-online.cn/20190721125739.png -.. |image77| image:: http://image.python-online.cn/20190721132238.png -.. |image78| image:: http://image.python-online.cn/20190721133403.png +.. |image70| image:: http://image.iswbm.com/20190629221224.png +.. |image71| image:: http://image.iswbm.com/20190629221547.png +.. |image72| image:: http://image.iswbm.com/20190629223534.png +.. |image73| image:: http://image.iswbm.com/20190629224229.png +.. |image74| image:: http://image.iswbm.com/20190629224430.png +.. |image75| image:: http://image.iswbm.com/20190629231322.png +.. |image76| image:: http://image.iswbm.com/20190721125739.png +.. |image77| image:: http://image.iswbm.com/20190721132238.png +.. |image78| image:: http://image.iswbm.com/20190721133403.png .. |image79| image:: https://i.loli.net/2019/07/21/5d3401410087b61815.gif -.. |image80| image:: http://image.python-online.cn/20190721141327.png -.. |image81| image:: http://image.python-online.cn/20190721141653.png -.. |image82| image:: http://image.python-online.cn/20190721141751.png -.. |image83| image:: http://image.python-online.cn/20190721143450.png -.. |image84| image:: http://image.python-online.cn/20191211210012.png -.. |image85| image:: http://image.python-online.cn/20191211211309.png -.. |image86| image:: http://image.python-online.cn/20191211211334.png -.. |image87| image:: http://image.python-online.cn/20191211211626.png -.. |image88| image:: http://image.python-online.cn/20191211212546.png -.. |image89| image:: http://image.python-online.cn/20191222143741.png -.. |image90| image:: http://image.python-online.cn/20191222141905.png -.. |image91| image:: http://image.python-online.cn/20191222141955.png -.. |image92| image:: http://image.python-online.cn/20191222142223.png -.. |image93| image:: http://image.python-online.cn/20191211100048.png -.. |image94| image:: http://image.python-online.cn/20191211100657.png -.. |image95| image:: http://image.python-online.cn/20191211101706.png -.. |image96| image:: http://image.python-online.cn/20191211101845.png -.. |image97| image:: http://image.python-online.cn/20191211102501.png -.. |image98| image:: http://image.python-online.cn/20191211102826.png -.. |image99| image:: http://image.python-online.cn/20191211133836.png -.. |image100| image:: http://image.python-online.cn/20191211143510.png +.. |image80| image:: http://image.iswbm.com/20190721141327.png +.. |image81| image:: http://image.iswbm.com/20190721141653.png +.. |image82| image:: http://image.iswbm.com/20190721141751.png +.. |image83| image:: http://image.iswbm.com/20190721143450.png +.. |image84| image:: http://image.iswbm.com/20191211210012.png +.. |image85| image:: http://image.iswbm.com/20191211211309.png +.. |image86| image:: http://image.iswbm.com/20191211211334.png +.. |image87| image:: http://image.iswbm.com/20191211211626.png +.. |image88| image:: http://image.iswbm.com/20191211212546.png +.. |image89| image:: http://image.iswbm.com/20191222143741.png +.. |image90| image:: http://image.iswbm.com/20191222141905.png +.. |image91| image:: http://image.iswbm.com/20191222141955.png +.. |image92| image:: http://image.iswbm.com/20191222142223.png +.. |image93| image:: http://image.iswbm.com/20191211100048.png +.. |image94| image:: http://image.iswbm.com/20191211100657.png +.. |image95| image:: http://image.iswbm.com/20191211101706.png +.. |image96| image:: http://image.iswbm.com/20191211101845.png +.. |image97| image:: http://image.iswbm.com/20191211102501.png +.. |image98| image:: http://image.iswbm.com/20191211102826.png +.. |image99| image:: http://image.iswbm.com/20191211133836.png +.. |image100| image:: http://image.iswbm.com/20191211143510.png .. |image101| image:: http://image.iswbm.com/20200420085524.png .. |image102| image:: http://image.iswbm.com/20200420090117.png .. |image103| image:: http://image.iswbm.com/20200420090428.png diff --git a/source/c04/c04_16.md b/source/c04/c04_16.md index 22f8307..36dd84f 100644 --- a/source/c04/c04_16.md +++ b/source/c04/c04_16.md @@ -1,76 +1,688 @@ -# 4.16 Python 开发技巧集合 +# 4.16 程序员编码必学:Vim ![](http://image.iswbm.com/20200602135014.png) -## 4.16.1 如何在被调用方法中获取调用者的方法名? +我本人是 Vim 的重度使用者,就因为喜欢上这种双手不离键盘就可以操控一切的feel,Vim 可以让我对文本的操作更加精准、高效。 -Python:如何在被调用方法中获取调用者的方法名? +对于未使用过 Vim 的朋友来说,可能还无法体会到这种感觉。由于使用 Vim 有一定的学习成本,只有做到非常熟练的程度才能感受到它带来的快捷。 -假设我有2种方法: +这里我就自己日常有使用过的 Vim 指令做一个总结,总共分成 21 点,建议有想学习 Vim 的同学,可以按照文章**配合搜索引擎**多多尝试,相信你会慢慢喜欢上 Vim。 +本文更倾向于有一定基础的同学,因为有很多点,如果写得太详细的话,一定会变得相当啰嗦。 + +## 1. vim模式 + +```shell +正常模式(按Esc或Ctrl+[进入) 左下角显示文件名或为空 +插入模式(按i进入) 左下角显示--INSERT-- +可视模式(按v进入) 左下角显示--VISUAL-- +替换模式(按r或R开始) 左下角显示 --REPLACE-- +命令行模式(按:或者/或者?开始) +ex模式 没用过,有兴趣的同学可以自行了解 +``` +## 2. 打开文件 + + +```shell +# 打开单个文件 +vim file +# 同时打开多个文件 +vim file1 file2.. + +# 在vim窗口中打开一个新文件 +:open [file] + +【举个例子】 +# 当前打开1.txt,做了一些编辑没保存 +:open! 放弃这些修改,并重新打开未修改的文件 + +# 当前打开1.txt,做了一些编辑并保存 +:open 2.txt 直接退出对1.txt的编辑,直接打开2.txt编辑,省了退出:wq再重新vim 2.txt的步骤 + + +# 打开远程文件,比如ftp或者share folder +:e ftp://192.168.10.76/abc.txt +:e \qadrive\test\1.txt + +# 以只读形式打开文件,但是仍然可以使用 :wq! 写入 +vim -R file + +# 强制性关闭修改功能,无法使用 :wq! 写入 +vim -M file +``` + +## 3. 插入命令 + + +```shell +i 在当前位置生前插入 +I 在当前行首插入 + +a 在当前位置后插入 +A 在当前行尾插入 + +o 在当前行之后插入一行 +O 在当前行之前插入一行 +``` + +## 4. 查找命令 + +最简单的查找 + +```shell +/text  查找text,按n健查找下一个,按N健查找前一个。 +?text  查找text,反向查找,按n健查找下一个,按N健查找前一个。 + +vim中有一些特殊字符在查找时需要转义  .*[]^%/?~$ + +:set ignorecase  忽略大小写的查找 +:set noignorecase  不忽略大小写的查找 +``` + +快速查找,不需要手打字符即可查找 + +``` +* 向后(下)寻找游标所在处的单词 +# 向前(上)寻找游标所在处的单词 + + +以上两种查找,n,N 的继续查找命令依然可以适用 +``` + +精准查找:匹配单词查找 + +如果文本中有 `hello`,` helloworld`,` hellopython` + +那我使用 /hello ,这三个词都会匹配到。 + +有没有办法实现精准查找呢?可以使用 + +```shell +/hello\> +``` + +精准查找:匹配行首、行末 + +```shell +# hello位于行首 +/^hello + +# world位于行末 +/world$ +``` + + +## 5. 替换命令 + +```shell +~ 反转游标字母大小写 + +r<字母> 将当前字符替换为所写字母 +R<字母><字母>... 连续替换字母 + +cc 替换整行(就是删除当前行,并在下一行插入) +cw 替换一个单词(就是删除一个单词,就进入插入模式),前提是游标处于单词第一个字母(可用b定位) +C (大写C)替换至行尾(和D有所区别,D是删除(剪切)至行尾,C是删除至行位并进入插入模式) + +# % 就表示所有行,不加就表求当前行 +# 加g表示对每一行的所有匹配到的进行替换,不加就是第一个 +# 如果不加g,而改成c,就会让你再进行确认,选择a进行全部替换,选择y替换,选择n不替换 + +:s/old/new/ 用old替换new,替换当前行的第一个匹配 +:s/old/new/g 用old替换new,替换当前行的所有匹配 + +:%s/old/new/ 用old替换new,替换所有行的第一个匹配 +:%s/old/new/g 用old替换new,替换整个文件的所有匹配 + + +:10,20 s/^/ /g 在第10行至第20行每行前面加四个空格,用于缩进。 + +ddp 交换光标所在行和其下紧邻的一行。 +``` + +## 6. 撤销与重做 + + +```shell +u 撤销(Undo) + +U 撤销对整行的操作 + +Ctrl + r 重做(Redo),即撤销的撤销。 +``` + +## 7. 删除命令 + +需要说明的是,vim 其实并没有单纯的删除命令,下面你或许理解为剪切更加准确。 + +以字符为单位删除 + +```shell +x 删除当前字符 +3x 删除当前字符3次 + +X 删除当前字符的前一个字符。 +3X 删除当前光标向前三个字符 + +dl 删除当前字符, dl=x +dh 删除前一个字符,X=dh + +D 删除当前字符至行尾。D=d$ +d$ 删除当前字符至行尾 +d^ 删除当前字符之前至行首 +``` + +以单词为单位删除 + +```shell +dw 删除当前字符到单词尾 +daw 删除当前字符所在单词 +``` + +以行为单位删除 + +```shell +dd 删除当前行 +dj 删除下一行 +dk 删除上一行 + +dgg 删除当前行至文档首部 +d1G 删除当前行至文档首部 +dG 删除当前行至文档尾部 + +kdgg 删除当前行之前所有行(不包括当前行) +jdG 删除当前行之后所有行(不包括当前行) + + + +10d 删除当前行开始的10行。 +:1,10d 删除1-10行 +:11,$d 删除11行及以后所有的行 +:1,$d 删除所有行 +J   删除两行之间的空行,实际上是合并两行。 +``` + + + +## 8. 复制粘贴 + +普通模式中使用y复制 +``` +yy 复制游标所在的整行(3yy表示复制3行) + +y^ 复制至行首,或y0。不含光标所在处字符。 +y$ 复制至行尾。含光标所在处字符。 + +yw 复制一个单词。 +y2w 复制两个单词。 + +yG 复制至文本末。 +y1G 复制至文本开头。 ``` -def method1(self): - ... - a = A.method2() -def method2(self): - ... +普通模式中使用p粘贴 +``` +p(小写):代表粘贴至光标后(下边,右边) +P(大写):代表粘贴至光标前(上边,左边) ``` -如果我不想对method1做任何更改,如何在method2中获取调用者的名称(在此示例中,名称为method1)? +## 9. 剪切粘贴 + + +```shell +dd 其实就是剪切命令,剪切当前行 +ddp 剪切当前行并粘贴,可实现当前行和下一行调换位置 -`inspect.getframeinfo` -等相关功能检查可以帮助: +正常模式下按v(逐字)或V(逐行)进入可视模式 +然后用jklh命令移动即可选择某些行或字符,再按d即可剪切 +ndd 剪切当前行之后的n行。利用p命令可以对剪切的内容进行粘贴 + +:1,10d 将1-10行剪切。利用p命令可将剪切后的内容进行粘贴。 + +:1, 10 m 20 将第1-10行移动到第20行之后。 ``` ->>> import inspect ->>> def f1(): f2() -... ->>> def f2(): -... curframe = inspect.currentframe() -... calframe = inspect.getouterframes(curframe, 2) -... print 'caller name:', calframe[1][3] -... ->>> f1() -caller name: f1 ->>> + +## 10. 退出保存 + + +```shell +:wq 保存并退出 + +ZZ 保存并退出 + +:q! 强制退出并忽略所有更改 + +:e! 放弃所有修改,并打开原来文件。 +:open! 放弃所有修改,并打开原来文件。 + +:sav(eas) new.txt 另存为一个新文件,退出原文件的编辑且不会保存 +:f(ile) new.txt 新开一个文件,并不保存,退出原文件的编辑且不会保存 ``` -这种内省旨在帮助调试和开发;为了生产功能的目的,不建议依赖它。 +## 11. 移动命令 + +以字符为单位移动 -## 4.16.2 pprint +```shell +h 左移一个字符 +l 右移一个字符 +k 上移一个字符 +j 下移一个字符 -比如说这段 -```python ->>> a -{'version': 1, 'config': [{'subnets': [{u'routes': [{u'netmask': u'0.0.0.0', u'network': u'0.0.0.0', u'gateway': u'172.20.22.200'}], u'netmask': u'255.255.255.0', u'type': 'static', 'ipv4': True, 'address': u'172.20.22.182'}], u'mtu': 1500, u'type': 'physical', 'name': 'eth0', 'mac_address': u'fa:16:3e:29:27:c6'}, {u'type': 'nameserver', u'address': u'114.114.114.114'}, {u'type': 'nameserver', u'address': u'8.8.8.8'}]} +# 【定位字符】f和F +fx 找到光标后第一个为x的字符 +3fd 找到光标后第三个为d的字符 + +F 同f,反向查找。 ``` -可以使用pprint +以行为单位移动 -```python ->>> import pprint ->>> pprint.pprint(a) -{'config': [{'mac_address': u'fa:16:3e:29:27:c6', - u'mtu': 1500, - 'name': 'eth0', - 'subnets': [{'address': u'172.20.22.182', - 'ipv4': True, - u'netmask': u'255.255.255.0', - u'routes': [{u'gateway': u'172.20.22.200', - u'netmask': u'0.0.0.0', - u'network': u'0.0.0.0'}], - u'type': 'static'}], - u'type': 'physical'}, - {u'address': u'114.114.114.114', u'type': 'nameserver'}, - {u'address': u'8.8.8.8', u'type': 'nameserver'}], - 'version': 1} +```shell +# 10指代所有数字,可任意指定 +10h 左移10个字符 +10l 右移10个字符 +10k 上移10行 +10j 下移10行 +$ 移动到行尾 +3$ 移动到下面3行的行尾 ``` +以单词为单位移动 + +```shell +w 向前移动一个单词(光标停在单词首部) +b 向后移动一个单词 +e,同w,只不过是光标停在单词尾部 +ge 同b,光标停在单词尾部。 +``` + +以句为单位移动 + +```shell +( 移动到句首 +) 移动到句尾 +``` + +跳转到文件的首尾 + +```shell +gg 移动到文件头。 = [[ == `` +G 移动到文件尾。 = ]] +``` + +其他移动方法 + +```shell +^ 移动到本行第一个非空白字符上。 +0 移动到本行第一个字符上(可以是空格) +``` + +使用 `具名标记` 跳转,个人感觉这个很好用,因为可以跨文件。 + + +```shell +使用 ma ,可以将此处标记为 a,使用 'a 进行跳转 +使用 :marks 可以查看所有的标记 +使用 :delm!可以删除所有的标记 +``` + +当在查看错误日志时,正常的步骤是,vim打开文件,然后使用 `shift+g` 再跳转到最后一行,这里有个更简单的操作可以在打开文件时立即跳到最后一行。只要在 vim 和 文件 中间加个 `+` 即可。 + +```shell +vim + you.log +``` + +举一反三,当你想打开文件立即跳转到指定行时,可以这样 + +```shell +# 打开文件并跳转到 20 行 +vim you.log +20 +``` + +当你使用 `/` 搜索定位跳转或者使用 `:行号` 进行精准跳转时,有时我们想返回到上一次的位置,如何实现? + +只要使用 Ctrl+o 即可返回上一次的位置。 + +## 12. 排版功能 + +**缩进** + +``` +:set shiftwidth? 查看缩进值 +:set shiftwidth=4 设置缩进值为4 + +# 缩进相关 最好写到配置文件中 ~/.vimrc +:set tabstop=4 +:set softtabstop=4 +:set shiftwidth=4 +:set expandtab + +>> 向右缩进 +<< 取消缩进 +``` + +如何你要对代码进行缩进,还可以用 `==` 对当前行缩进,如果要对多行对待缩进,则使用 n`==`,这种方式要求你所编辑的文件的扩展名是被vim所识别的,比如`.py`文件。 + +**排版** + +``` +:ce 居中 +:le 靠左 +:ri 靠右 +``` +## 13. 注释命令 + +**多行注释** + +``` +进入命令行模式,按ctrl + v进入 visual block模式,然后按j, 或者k选中多行,把需要注释的行标记起来 + +按大写字母I,再插入注释符,例如// + +按esc键就会全部注释了 + +``` +**取消多行注释** + +``` +进入命令行模式,按ctrl + v进入 visual block模式,按字母l横向选中列的个数,例如 // 需要选中2列 + +按字母j,或者k选中注释符号 + +按d键就可全部取消注释 +``` +**复杂注释** + +```shell +:3,5 s/^/#/g 注释第3-5行 +:3,5 s/^#//g 解除3-5行的注释 + + +:1,$ s/^/#/g 注释整个文档 +:1,$ s/^#//g 取消注释整个文档 + + +:%s/^/#/g 注释整个文档,此法更快 +:%s/^#//g 取消注释整个文档 +``` + +## 14. 调整视野 +``` +"zz":命令会把当前行置为屏幕正中央, +"zt":命令会把当前行置于屏幕顶端 +"zb":则把当前行置于屏幕底端. + +Ctrl + e 向下滚动一行 +Ctrl + y 向上滚动一行 + +Ctrl + d 向下滚动半屏 +Ctrl + u 向上滚动半屏 + +Ctrl + f 向下滚动一屏 +Ctrl + b 向上滚动一屏 + + +【跳到指定行】:两种方法 + +可以先把行号打开 +:set nu 打开行号 + +:20 跳到第20行 +20G 跳到第20行 +``` + +## 15. 区域选择 + +``` +要进行区域选择,要先进入可视模式 + +v 以字符为单位,上下左右选择 +V 以行为单位,上下选择 + +选择后可进行操作 +d 剪切/删除 +y 复制 + +Ctrl+v 如果当前是V(大写)模式,就变成v(小写) + 如果当前是v(小写)模式,就变成普通模式。 + 如果当前是普通模式,就进入v(小写)模式 + +利用这个,可以进行多行缩进。 + +ggVG 选择全文 +``` + + +## 16. 窗口控制 + +**新建窗口** + +```shell +# 打开两个文件分属两个窗口 +vim -o 1.txt 2.txt + + +# 假设现在已经打开了1.txt + +:sp 2.txt 开启一个横向的窗口,编辑2.txt +:vsp 2.txt 开启一个竖向的窗口,编辑2.txt + +:split 将当前窗口再复制一个窗口出来,内容同步,游标可以不同 +:split 2.txt 在新窗口打开2.txt的横向窗口 + +# 需要注意:内容同步,但是游标位置是独立的 + +Ctrl-w s 将当前窗口分成水平窗口 +Ctrl-w v 将当前窗口分成竖直窗口 + +Ctrl-w q 等同:q 结束分割出来的视窗。 +Ctrl-w q! 等同:q! 结束分割出来的视窗。 +Ctrl-w o 打开一个视窗并且隐藏之前的所有视窗 +``` +**窗口切换** + +```shell +# 特别说明:Ctrl w <字母> 不需要同时按 + +Ctrl-w h 切换到左边窗口 +Ctrl-w l 切换到右边窗口 + +Ctrl-w j 切换到下边窗口 +Ctrl-w k 切换到上边窗口 + + +# 特别说明:全屏模式下 +:n 切换下一个窗口 +:N 切换上一个窗口 +:bp 切换上一个窗口 + +# 特别说明:非全屏模式 + +:bn 切换下一个窗口,就当前位置的窗口的内容变了,其他窗口不变 +:bN 切换上一个窗口,就当前位置的窗口的内容变了,其他窗口不变 +``` +**窗口移动** + +```shell +# 特别说明:Ctrl w <字母> 不需要同时按 + +Ctrl-w J 将当前视窗移至最下面 +Ctrl-w K 将当前视窗移最上面 + +Ctrl-w H 将当前视窗移至最左边 +Ctrl-w L 将当前视窗移至最右边 + +Ctrl-ww 按顺序切换窗口 +``` +**调整尺寸** + +```shell +# 友情提示:键盘切记不要处于中文状态 + +Ctrl-w + 增加窗口高度 +Ctrl-w - 减少窗口高度 +``` +**退出窗口** + +```shell +:close 关闭当前窗口 +:close! 强制关闭当前窗口 + +:q 退出,不保存 +:q! 强制退出,不保存 + +:x 保存退出 +:wq 保存退出 +:wq! 强制保存退出 + +:w <[路径/]文件名> 另存为 +:savesa <[路径/]文件名> 另存为 + +ZZ 保存并退出。 + +:only 关闭所有窗口,只保留当前窗口(前提:其他窗口内容有改变的话都要先保存) +:only! 关闭所有窗口,只保留当前窗口 + +:qall 放弃所有操作并退出 +:wall 保存所有, +:wqall 保存所有并退出。 +``` + +## 17. 文档加密 + +``` +vim -x file_name + +然后输入密码: +确认密码: + +如果不修改内容也要保存。:wq,不然密码设定不会生效。 +``` + +## 18. 录制宏 + +按q键加任意字母开始录制,再按q键结束录制(这意味着vim中的宏不可嵌套),使用的时候@加宏名,比如qa。。。q录制名为a的宏,@a使用这个宏。 + +## 19. 执行命令 + + +```shell + +# 重复前一次命令 +. + +# 执行shell命令 +:!command + +# 比如列出当前目录下文件 +:!ls + +# 执行脚本 +:!perl -c script.pl 检查perl脚本语法,可以不用退出vim,非常方便。 +:!perl script.pl 执行perl脚本,可以不用退出vim,非常方便。 + +:suspend或Ctrl - Z 挂起vim,回到shell,按fg可以返回vim。 + +# 执行完当前命令后,自动回到vim环境,但不知为何文件内容变成空了 +:silent !command +``` + +## 20. 帮助命令 + + +```shell +在Unix/Linux系统上 +$ vimtutor + +# 普通模式下 +键盘输入vim或F1 + +# 命令行模式下 + +:help 显示整个帮助 +:help xxx 显示xxx的帮助,比如 :help i, :help CTRL-[(即Ctrl+[的帮助)。 +:help 'number' Vim选项的帮助用单引号括起 + + +在Windows系统上 +:help tutor +``` + +## 21. 配置命令 + +显示当前设定 + +```shell +:set或者:se显示所有修改过的配置 +:set all 显示所有的设定值 +:set option? 显示option的设定值 +:set nooption 取消当期设定值 +:ver 显示vim的所有信息(包括版本和参数等) + +# 需要注意:全屏模式下 +:args 查看当前打开的文件列表,当前正在编辑的文件会用[]括起来 +``` + +更改设定 + +```shell +:set nu 显示行号 + +set autoindent(ai) 设置自动缩进 +set autowrite(aw) 设置自动存档,默认未打开 +set backup(bk) 设置自动备份,默认未打开 + +set background=dark或light,设置背景风格 + +set cindent(cin) 设置C语言风格缩进 + +:set ts=4 设置tab键转换为4个空格 + +:set ff=unix # 修改文件dos文件为unix + +:set shiftwidth? 查看缩进值 +:set shiftwidth=4 设置缩进值为4 + +:set ignorecase  忽略大小写的查找 +:set noignorecase  不忽略大小写的查找 + +:set paste # insert模式下,粘贴格式不会乱掉 + +:set ruler?  查看是否设置了ruler,在.vimrc中,使用set命令设制的选项都可以通过这个命令查看 + +:scriptnames  查看vim脚本文件的位置,比如.vimrc文件,语法文件及plugin等。 + +:set list 显示非打印字符,如tab,空格,行尾等。如果tab无法显示,请确定用set lcs=tab:>-命令设置了.vimrc文件,并确保你的文件中的确有tab,如果开启了expendtab,那么tab将被扩展为空格。 + + +:syntax 列出已经定义的语法项 +:syntax clear 清除已定义的语法规则 + +:syntax case match 大小写敏感,int和Int将视为不同的语法元素 +:syntax case ignore 大小写无关,int和Int将视为相同的语法元素,并使用同样的配色方案 + +``` + + + +以上就是我使用 Vim 的一些使用总结,希望对你能有帮助。 + +------ + +最后,送你一张 Vim 的键盘图,你可以将它设置为你的电脑桌面,对你初学 Vim 可能会有帮助。 + +你可以**关注本公众号「Python编程时光」**,在后台回复“**vim**” ,即可获取高清大图。 + +![图1](http://image.iswbm.com/20190804222221.png) + + +![图2](http://image.iswbm.com/20190804222247.png) --- diff --git a/source/c04/c04_16.rst b/source/c04/c04_16.rst index b26a9e1..7c6fba3 100644 --- a/source/c04/c04_16.rst +++ b/source/c04/c04_16.rst @@ -1,76 +1,723 @@ -4.16 Python 开发技巧集合 +4.16 程序员编码必学:Vim ======================== |image0| -4.16.1 如何在被调用方法中获取调用者的方法名? ---------------------------------------------- +我本人是 Vim +的重度使用者,就因为喜欢上这种双手不离键盘就可以操控一切的feel,Vim +可以让我对文本的操作更加精准、高效。 -Python:如何在被调用方法中获取调用者的方法名? +对于未使用过 Vim 的朋友来说,可能还无法体会到这种感觉。由于使用 Vim +有一定的学习成本,只有做到非常熟练的程度才能感受到它带来的快捷。 -假设我有2种方法: +这里我就自己日常有使用过的 Vim 指令做一个总结,总共分成 21 +点,建议有想学习 Vim +的同学,可以按照文章\ **配合搜索引擎**\ 多多尝试,相信你会慢慢喜欢上 +Vim。 -:: +本文更倾向于有一定基础的同学,因为有很多点,如果写得太详细的话,一定会变得相当啰嗦。 + +1. vim模式 +---------- + +.. code:: shell + + 正常模式(按Esc或Ctrl+[进入) 左下角显示文件名或为空 + 插入模式(按i进入) 左下角显示--INSERT-- + 可视模式(按v进入) 左下角显示--VISUAL-- + 替换模式(按r或R开始) 左下角显示 --REPLACE-- + 命令行模式(按:或者/或者?开始) + ex模式 没用过,有兴趣的同学可以自行了解 + +2. 打开文件 +----------- + +.. code:: shell + + # 打开单个文件 + vim file + # 同时打开多个文件 + vim file1 file2.. + + # 在vim窗口中打开一个新文件 + :open [file] + + 【举个例子】 + # 当前打开1.txt,做了一些编辑没保存 + :open! 放弃这些修改,并重新打开未修改的文件 + + # 当前打开1.txt,做了一些编辑并保存 + :open 2.txt 直接退出对1.txt的编辑,直接打开2.txt编辑,省了退出:wq再重新vim 2.txt的步骤 + + + # 打开远程文件,比如ftp或者share folder + :e ftp://192.168.10.76/abc.txt + :e \qadrive\test\1.txt + + # 以只读形式打开文件,但是仍然可以使用 :wq! 写入 + vim -R file + + # 强制性关闭修改功能,无法使用 :wq! 写入 + vim -M file + +3. 插入命令 +----------- + +.. code:: shell + + i 在当前位置生前插入 + I 在当前行首插入 + + a 在当前位置后插入 + A 在当前行尾插入 - def method1(self): - ... - a = A.method2() + o 在当前行之后插入一行 + O 在当前行之前插入一行 - def method2(self): - ... +4. 查找命令 +----------- -如果我不想对method1做任何更改,如何在method2中获取调用者的名称(在此示例中,名称为method1)? +最简单的查找 -``inspect.getframeinfo`` +.. code:: shell -等相关功能检查可以帮助: + /text  查找text,按n健查找下一个,按N健查找前一个。 + ?text  查找text,反向查找,按n健查找下一个,按N健查找前一个。 + + vim中有一些特殊字符在查找时需要转义  .*[]^%/?~$ + + :set ignorecase  忽略大小写的查找 + :set noignorecase  不忽略大小写的查找 + +快速查找,不需要手打字符即可查找 :: - >>> import inspect - >>> def f1(): f2() - ... - >>> def f2(): - ... curframe = inspect.currentframe() - ... calframe = inspect.getouterframes(curframe, 2) - ... print 'caller name:', calframe[1][3] - ... - >>> f1() - caller name: f1 - >>> - -这种内省旨在帮助调试和开发;为了生产功能的目的,不建议依赖它。 - -4.16.2 pprint + * 向后(下)寻找游标所在处的单词 + # 向前(上)寻找游标所在处的单词 + + + 以上两种查找,n,N 的继续查找命令依然可以适用 + +精准查找:匹配单词查找 + +如果文本中有 ``hello``\ ,\ ``helloworld``\ ,\ ``hellopython`` + +那我使用 /hello ,这三个词都会匹配到。 + +有没有办法实现精准查找呢?可以使用 + +.. code:: shell + + /hello\> + +精准查找:匹配行首、行末 + +.. code:: shell + + # hello位于行首 + /^hello + + # world位于行末 + /world$ + +5. 替换命令 +----------- + +.. code:: shell + + ~ 反转游标字母大小写 + + r<字母> 将当前字符替换为所写字母 + R<字母><字母>... 连续替换字母 + + cc 替换整行(就是删除当前行,并在下一行插入) + cw 替换一个单词(就是删除一个单词,就进入插入模式),前提是游标处于单词第一个字母(可用b定位) + C (大写C)替换至行尾(和D有所区别,D是删除(剪切)至行尾,C是删除至行位并进入插入模式) + + # % 就表示所有行,不加就表求当前行 + # 加g表示对每一行的所有匹配到的进行替换,不加就是第一个 + # 如果不加g,而改成c,就会让你再进行确认,选择a进行全部替换,选择y替换,选择n不替换 + + :s/old/new/ 用old替换new,替换当前行的第一个匹配 + :s/old/new/g 用old替换new,替换当前行的所有匹配 + + :%s/old/new/ 用old替换new,替换所有行的第一个匹配 + :%s/old/new/g 用old替换new,替换整个文件的所有匹配 + + + :10,20 s/^/ /g 在第10行至第20行每行前面加四个空格,用于缩进。 + + ddp 交换光标所在行和其下紧邻的一行。 + +6. 撤销与重做 ------------- -比如说这段 - -.. code:: python - - >>> a - {'version': 1, 'config': [{'subnets': [{u'routes': [{u'netmask': u'0.0.0.0', u'network': u'0.0.0.0', u'gateway': u'172.20.22.200'}], u'netmask': u'255.255.255.0', u'type': 'static', 'ipv4': True, 'address': u'172.20.22.182'}], u'mtu': 1500, u'type': 'physical', 'name': 'eth0', 'mac_address': u'fa:16:3e:29:27:c6'}, {u'type': 'nameserver', u'address': u'114.114.114.114'}, {u'type': 'nameserver', u'address': u'8.8.8.8'}]} - -可以使用pprint - -.. code:: python - - >>> import pprint - >>> pprint.pprint(a) - {'config': [{'mac_address': u'fa:16:3e:29:27:c6', - u'mtu': 1500, - 'name': 'eth0', - 'subnets': [{'address': u'172.20.22.182', - 'ipv4': True, - u'netmask': u'255.255.255.0', - u'routes': [{u'gateway': u'172.20.22.200', - u'netmask': u'0.0.0.0', - u'network': u'0.0.0.0'}], - u'type': 'static'}], - u'type': 'physical'}, - {u'address': u'114.114.114.114', u'type': 'nameserver'}, - {u'address': u'8.8.8.8', u'type': 'nameserver'}], - 'version': 1} +.. code:: shell + + u 撤销(Undo) + + U 撤销对整行的操作 + + Ctrl + r 重做(Redo),即撤销的撤销。 + +7. 删除命令 +----------- + +需要说明的是,vim +其实并没有单纯的删除命令,下面你或许理解为剪切更加准确。 + +以字符为单位删除 + +.. code:: shell + + x 删除当前字符 + 3x 删除当前字符3次 + + X 删除当前字符的前一个字符。 + 3X 删除当前光标向前三个字符 + + dl 删除当前字符, dl=x + dh 删除前一个字符,X=dh + + D 删除当前字符至行尾。D=d$ + d$ 删除当前字符至行尾 + d^ 删除当前字符之前至行首 + +以单词为单位删除 + +.. code:: shell + + dw 删除当前字符到单词尾 + daw 删除当前字符所在单词 + +以行为单位删除 + +.. code:: shell + + dd 删除当前行 + dj 删除下一行 + dk 删除上一行 + + dgg 删除当前行至文档首部 + d1G 删除当前行至文档首部 + dG 删除当前行至文档尾部 + + kdgg 删除当前行之前所有行(不包括当前行) + jdG 删除当前行之后所有行(不包括当前行) + + + + 10d 删除当前行开始的10行。 + :1,10d 删除1-10行 + :11,$d 删除11行及以后所有的行 + :1,$d 删除所有行 + J   删除两行之间的空行,实际上是合并两行。 + +8. 复制粘贴 +----------- + +普通模式中使用y复制 + +:: + + yy 复制游标所在的整行(3yy表示复制3行) + + y^ 复制至行首,或y0。不含光标所在处字符。 + y$ 复制至行尾。含光标所在处字符。 + + yw 复制一个单词。 + y2w 复制两个单词。 + + yG 复制至文本末。 + y1G 复制至文本开头。 + +普通模式中使用p粘贴 + +:: + + p(小写):代表粘贴至光标后(下边,右边) + P(大写):代表粘贴至光标前(上边,左边) + +9. 剪切粘贴 +----------- + +.. code:: shell + + dd 其实就是剪切命令,剪切当前行 + ddp 剪切当前行并粘贴,可实现当前行和下一行调换位置 + + + 正常模式下按v(逐字)或V(逐行)进入可视模式 + 然后用jklh命令移动即可选择某些行或字符,再按d即可剪切 + + ndd 剪切当前行之后的n行。利用p命令可以对剪切的内容进行粘贴 + + :1,10d 将1-10行剪切。利用p命令可将剪切后的内容进行粘贴。 + + :1, 10 m 20 将第1-10行移动到第20行之后。 + +10. 退出保存 +------------ + +.. code:: shell + + :wq 保存并退出 + + ZZ 保存并退出 + + :q! 强制退出并忽略所有更改 + + :e! 放弃所有修改,并打开原来文件。 + :open! 放弃所有修改,并打开原来文件。 + + :sav(eas) new.txt 另存为一个新文件,退出原文件的编辑且不会保存 + :f(ile) new.txt 新开一个文件,并不保存,退出原文件的编辑且不会保存 + +11. 移动命令 +------------ + +以字符为单位移动 + +.. code:: shell + + h 左移一个字符 + l 右移一个字符 + k 上移一个字符 + j 下移一个字符 + + + # 【定位字符】f和F + fx 找到光标后第一个为x的字符 + 3fd 找到光标后第三个为d的字符 + + F 同f,反向查找。 + +以行为单位移动 + +.. code:: shell + + # 10指代所有数字,可任意指定 + 10h 左移10个字符 + 10l 右移10个字符 + 10k 上移10行 + 10j 下移10行 + + $ 移动到行尾 + 3$ 移动到下面3行的行尾 + +以单词为单位移动 + +.. code:: shell + + w 向前移动一个单词(光标停在单词首部) + b 向后移动一个单词 + e,同w,只不过是光标停在单词尾部 + ge 同b,光标停在单词尾部。 + +以句为单位移动 + +.. code:: shell + + ( 移动到句首 + ) 移动到句尾 + +跳转到文件的首尾 + +.. code:: shell + + gg 移动到文件头。 = [[ == `` + G 移动到文件尾。 = ]] + +其他移动方法 + +.. code:: shell + + ^ 移动到本行第一个非空白字符上。 + 0 移动到本行第一个字符上(可以是空格) + +使用 ``具名标记`` 跳转,个人感觉这个很好用,因为可以跨文件。 + +.. code:: shell + + 使用 ma ,可以将此处标记为 a,使用 'a 进行跳转 + 使用 :marks 可以查看所有的标记 + 使用 :delm!可以删除所有的标记 + +当在查看错误日志时,正常的步骤是,vim打开文件,然后使用 ``shift+g`` +再跳转到最后一行,这里有个更简单的操作可以在打开文件时立即跳到最后一行。只要在 +vim 和 文件 中间加个 ``+`` 即可。 + +.. code:: shell + + vim + you.log + +举一反三,当你想打开文件立即跳转到指定行时,可以这样 + +.. code:: shell + + # 打开文件并跳转到 20 行 + vim you.log +20 + +当你使用 ``/`` 搜索定位跳转或者使用 ``:行号`` +进行精准跳转时,有时我们想返回到上一次的位置,如何实现? + +只要使用 Ctrl+o 即可返回上一次的位置。 + +12. 排版功能 +------------ + +**缩进** + +:: + + :set shiftwidth? 查看缩进值 + :set shiftwidth=4 设置缩进值为4 + + # 缩进相关 最好写到配置文件中 ~/.vimrc + :set tabstop=4 + :set softtabstop=4 + :set shiftwidth=4 + :set expandtab + + >> 向右缩进 + << 取消缩进 + +如何你要对代码进行缩进,还可以用 ``==`` +对当前行缩进,如果要对多行对待缩进,则使用 +n\ ``==``\ ,这种方式要求你所编辑的文件的扩展名是被vim所识别的,比如\ ``.py``\ 文件。 + +**排版** + +:: + + :ce 居中 + :le 靠左 + :ri 靠右 + +13. 注释命令 +------------ + +**多行注释** + +:: + + 进入命令行模式,按ctrl + v进入 visual block模式,然后按j, 或者k选中多行,把需要注释的行标记起来 + + 按大写字母I,再插入注释符,例如// + + 按esc键就会全部注释了 + +**取消多行注释** + +:: + + 进入命令行模式,按ctrl + v进入 visual block模式,按字母l横向选中列的个数,例如 // 需要选中2列 + + 按字母j,或者k选中注释符号 + + 按d键就可全部取消注释 + +**复杂注释** + +.. code:: shell + + :3,5 s/^/#/g 注释第3-5行 + :3,5 s/^#//g 解除3-5行的注释 + + + :1,$ s/^/#/g 注释整个文档 + :1,$ s/^#//g 取消注释整个文档 + + + :%s/^/#/g 注释整个文档,此法更快 + :%s/^#//g 取消注释整个文档 + +14. 调整视野 +------------ + +:: + + "zz":命令会把当前行置为屏幕正中央, + "zt":命令会把当前行置于屏幕顶端 + "zb":则把当前行置于屏幕底端. + + Ctrl + e 向下滚动一行 + Ctrl + y 向上滚动一行 + + Ctrl + d 向下滚动半屏 + Ctrl + u 向上滚动半屏 + + Ctrl + f 向下滚动一屏 + Ctrl + b 向上滚动一屏 + + + 【跳到指定行】:两种方法 + + 可以先把行号打开 + :set nu 打开行号 + + :20 跳到第20行 + 20G 跳到第20行 + +15. 区域选择 +------------ + +:: + + 要进行区域选择,要先进入可视模式 + + v 以字符为单位,上下左右选择 + V 以行为单位,上下选择 + + 选择后可进行操作 + d 剪切/删除 + y 复制 + + Ctrl+v 如果当前是V(大写)模式,就变成v(小写) + 如果当前是v(小写)模式,就变成普通模式。 + 如果当前是普通模式,就进入v(小写)模式 + + 利用这个,可以进行多行缩进。 + + ggVG 选择全文 + +16. 窗口控制 +------------ + +**新建窗口** + +.. code:: shell + + # 打开两个文件分属两个窗口 + vim -o 1.txt 2.txt + + + # 假设现在已经打开了1.txt + + :sp 2.txt 开启一个横向的窗口,编辑2.txt + :vsp 2.txt 开启一个竖向的窗口,编辑2.txt + + :split 将当前窗口再复制一个窗口出来,内容同步,游标可以不同 + :split 2.txt 在新窗口打开2.txt的横向窗口 + + # 需要注意:内容同步,但是游标位置是独立的 + + Ctrl-w s 将当前窗口分成水平窗口 + Ctrl-w v 将当前窗口分成竖直窗口 + + Ctrl-w q 等同:q 结束分割出来的视窗。 + Ctrl-w q! 等同:q! 结束分割出来的视窗。 + Ctrl-w o 打开一个视窗并且隐藏之前的所有视窗 + +**窗口切换** + +.. code:: shell + + # 特别说明:Ctrl w <字母> 不需要同时按 + + Ctrl-w h 切换到左边窗口 + Ctrl-w l 切换到右边窗口 + + Ctrl-w j 切换到下边窗口 + Ctrl-w k 切换到上边窗口 + + + # 特别说明:全屏模式下 + :n 切换下一个窗口 + :N 切换上一个窗口 + :bp 切换上一个窗口 + + # 特别说明:非全屏模式 + + :bn 切换下一个窗口,就当前位置的窗口的内容变了,其他窗口不变 + :bN 切换上一个窗口,就当前位置的窗口的内容变了,其他窗口不变 + +**窗口移动** + +.. code:: shell + + # 特别说明:Ctrl w <字母> 不需要同时按 + + Ctrl-w J 将当前视窗移至最下面 + Ctrl-w K 将当前视窗移最上面 + + Ctrl-w H 将当前视窗移至最左边 + Ctrl-w L 将当前视窗移至最右边 + + Ctrl-ww 按顺序切换窗口 + +**调整尺寸** + +.. code:: shell + + # 友情提示:键盘切记不要处于中文状态 + + Ctrl-w + 增加窗口高度 + Ctrl-w - 减少窗口高度 + +**退出窗口** + +.. code:: shell + + :close 关闭当前窗口 + :close! 强制关闭当前窗口 + + :q 退出,不保存 + :q! 强制退出,不保存 + + :x 保存退出 + :wq 保存退出 + :wq! 强制保存退出 + + :w <[路径/]文件名> 另存为 + :savesa <[路径/]文件名> 另存为 + + ZZ 保存并退出。 + + :only 关闭所有窗口,只保留当前窗口(前提:其他窗口内容有改变的话都要先保存) + :only! 关闭所有窗口,只保留当前窗口 + + :qall 放弃所有操作并退出 + :wall 保存所有, + :wqall 保存所有并退出。 + +17. 文档加密 +------------ + +:: + + vim -x file_name + + 然后输入密码: + 确认密码: + + 如果不修改内容也要保存。:wq,不然密码设定不会生效。 + +18. 录制宏 +---------- + +按q键加任意字母开始录制,再按q键结束录制(这意味着vim中的宏不可嵌套),使用的时候@加宏名,比如qa。。。q录制名为a的宏,@a使用这个宏。 + +19. 执行命令 +------------ + +.. code:: shell + + + # 重复前一次命令 + . + + # 执行shell命令 + :!command + + # 比如列出当前目录下文件 + :!ls + + # 执行脚本 + :!perl -c script.pl 检查perl脚本语法,可以不用退出vim,非常方便。 + :!perl script.pl 执行perl脚本,可以不用退出vim,非常方便。 + + :suspend或Ctrl - Z 挂起vim,回到shell,按fg可以返回vim。 + + # 执行完当前命令后,自动回到vim环境,但不知为何文件内容变成空了 + :silent !command + +20. 帮助命令 +------------ + +.. code:: shell + + 在Unix/Linux系统上 + $ vimtutor + + # 普通模式下 + 键盘输入vim或F1 + + # 命令行模式下 + + :help 显示整个帮助 + :help xxx 显示xxx的帮助,比如 :help i, :help CTRL-[(即Ctrl+[的帮助)。 + :help 'number' Vim选项的帮助用单引号括起 + + + 在Windows系统上 + :help tutor + +21. 配置命令 +------------ + +显示当前设定 + +.. code:: shell + + :set或者:se显示所有修改过的配置 + :set all 显示所有的设定值 + :set option? 显示option的设定值 + :set nooption 取消当期设定值 + :ver 显示vim的所有信息(包括版本和参数等) + + # 需要注意:全屏模式下 + :args 查看当前打开的文件列表,当前正在编辑的文件会用[]括起来 + +更改设定 + +.. code:: shell + + :set nu 显示行号 + + set autoindent(ai) 设置自动缩进 + set autowrite(aw) 设置自动存档,默认未打开 + set backup(bk) 设置自动备份,默认未打开 + + set background=dark或light,设置背景风格 + + set cindent(cin) 设置C语言风格缩进 + + :set ts=4 设置tab键转换为4个空格 + + :set ff=unix # 修改文件dos文件为unix + + :set shiftwidth? 查看缩进值 + :set shiftwidth=4 设置缩进值为4 + + :set ignorecase  忽略大小写的查找 + :set noignorecase  不忽略大小写的查找 + + :set paste # insert模式下,粘贴格式不会乱掉 + + :set ruler?  查看是否设置了ruler,在.vimrc中,使用set命令设制的选项都可以通过这个命令查看 + + :scriptnames  查看vim脚本文件的位置,比如.vimrc文件,语法文件及plugin等。 + + :set list 显示非打印字符,如tab,空格,行尾等。如果tab无法显示,请确定用set lcs=tab:>-命令设置了.vimrc文件,并确保你的文件中的确有tab,如果开启了expendtab,那么tab将被扩展为空格。 + + + :syntax 列出已经定义的语法项 + :syntax clear 清除已定义的语法规则 + + :syntax case match 大小写敏感,int和Int将视为不同的语法元素 + :syntax case ignore 大小写无关,int和Int将视为相同的语法元素,并使用同样的配色方案 + +以上就是我使用 Vim 的一些使用总结,希望对你能有帮助。 + +-------------- + +最后,送你一张 Vim 的键盘图,你可以将它设置为你的电脑桌面,对你初学 Vim +可能会有帮助。 + +你可以\ **关注本公众号「Python编程时光」**\ ,在后台回复“**vim**” +,即可获取高清大图。 + +.. figure:: http://image.iswbm.com/20190804222221.png + :alt: 图1 + + 图1 + +.. figure:: http://image.iswbm.com/20190804222247.png + :alt: 图2 + + 图2 -------------- diff --git a/source/c04/c04_17.md b/source/c04/c04_17.md index 7b1963d..8f1ed7c 100644 --- a/source/c04/c04_17.md +++ b/source/c04/c04_17.md @@ -22,7 +22,7 @@ - Stragety:策略基类 - ConcreteStragety:具体策略 -![](http://image.python-online.cn/20190414144511.png) +![](http://image.iswbm.com/20190414144511.png) 以第一个超市做活动的场景来举个例子。 @@ -276,7 +276,7 @@ class User: 验证结果 -![](http://image.python-online.cn/20190512113846.png) +![](http://image.iswbm.com/20190512113846.png) - 使用装饰器 @@ -305,7 +305,7 @@ class User: 验证结果 -![](http://image.python-online.cn/20190512113917.png) +![](http://image.iswbm.com/20190512113917.png) - 使用元类 @@ -328,7 +328,7 @@ class User(metaclass=MetaSingleton): 验证结果 -![](http://image.python-online.cn/20190512114028.png) +![](http://image.iswbm.com/20190512114028.png) 以上的代码,一般情况下没有问题,但在并发场景中,就会出现线程安全的问题。 diff --git a/source/c04/c04_17.rst b/source/c04/c04_17.rst index 9a457f4..8e812cb 100644 --- a/source/c04/c04_17.rst +++ b/source/c04/c04_17.rst @@ -477,9 +477,9 @@ Order |image5| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190414144511.png -.. |image2| image:: http://image.python-online.cn/20190512113846.png -.. |image3| image:: http://image.python-online.cn/20190512113917.png -.. |image4| image:: http://image.python-online.cn/20190512114028.png +.. |image1| image:: http://image.iswbm.com/20190414144511.png +.. |image2| image:: http://image.iswbm.com/20190512113846.png +.. |image3| image:: http://image.iswbm.com/20190512113917.png +.. |image4| image:: http://image.iswbm.com/20190512114028.png .. |image5| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_18.md b/source/c04/c04_18.md index 029ccdc..aedad82 100644 --- a/source/c04/c04_18.md +++ b/source/c04/c04_18.md @@ -1,354 +1,171 @@ -# 4.18 详细的 Mac 使用指南 +# 4.18 如何用好 Python的用户环境? ![](http://image.iswbm.com/20200602135014.png) -## 4.18.1 iTerm2 +在之前写过一篇关于虚拟环境使用的文章 :[Python 虚拟环境使用指南](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485049&idx=1&sn=c16383d6cc91a7ed8254e344d994f101&chksm=e886669bdff1ef8d82aae3a231ef0651f82d5e97cf1e64aceda00e686119900518c202dc9b1b&scene=21#wechat_redirect). -介绍一下快捷键 +但是还没有好好的介绍一下 Python 的用户环境,原因是自己一直没遇到要使用 `用户环境` 的使用场景,所以就一直懒得写。 -``` -# 分窗口操作 -shift + command + d(横切)command + d(竖切) - -2、历史信息查找和粘贴:command + f,呼出查找功能,找到后 tab 键可以选中找到的文本,通过option + 回车粘贴。 - -3、自动完成:command + ; ,呼出自动完成窗口,根据上下文提供内容选择项 -# 粘贴历史 -shift + command + h - -# 回放功能 -option + command + b - -# 光标去哪了? -command + / - -# 在所有的窗口中搜索 -option + command + e -``` - -## 4.18.2 截图工具 - -1. Xnip:取色+丰富的标注功能+滚动截屏; -2. [Snip](https://snip.qq.com/):小巧轻量+滚动截屏(不能从App Store下载,亲测已经失效); -3. QQ:截屏+录屏+OCR文字识别; -4. Snipaste:贴屏功能; -5. ScreenShot PSD:psd 文件,每种元素都有单独的图层。 - -## 4.18.3 快捷操作 - -``` -# 查看桌面,并不是返回桌面,再按一下就还原原来的窗口了 -fn + f11 # 通常情况下 -f11 # 在外接键盘下,请先设置 F1,F2 这些键为标准功能键,偏好设置->键盘 - -# 最大化窗口与取消 -command + ctrl + f - -# 最小化/隐藏窗口 -command + h - -# 最小化除了当前active窗口之外的所有窗口 -comand + option + h - -# 隐藏除当前窗口外的其他所有窗口 -command + opthon + h - -# 关闭除访达外的其他程序 -command + q - -# 在不同的程序间切换 -command + tab -command + shift + tab - -# 在一个程序的不同窗口切换 -command + ~ - -# 强制退出程序 -command + option + esc - -# 删除光标处到行首 -command + backspace(删除键) - -# 新建窗口打开页面 -command + 鼠标左键 -``` - -查询汇率 - -``` -command + space -输入 1港币或者1美元 -``` +恰巧这两天,自己遇到了一个使用用户环境的体验可以完爆虚拟环境的案例,就拿出来分享一下。 -将网页图片保存到本地 +## 1. 我的使用背景 -``` -直接拖动图片 -如果不仍想让访达窗口保持在最前面,就按住 command 键 -``` +公司有数以万计的服务器,为了对实现对访问记录进行集中管理以及出于安全考虑,每台服务器都有访问限制,必须使用公司的跳板机才能登陆。 -显示控制台 +每个公司的员工在跳板机上都有自己的用户、 家目录,对于很多需要 root 权限的操作,是高度受限制的。 -``` -原生:ctrl+shift+L -由于与 MWeb 的插入链接冲突,而Mweb 又没有修改快捷键的入口,所以我将启动台的快捷键改成:ctrl+option+L - -要卸载 app 的话 -1. ctrl+option+L 显示启动台 -2. 再按 ctrl+option(默认,不需要设置) 就会出现抖动效果,点击 x 就可以卸载了。 -``` +比如我现在我要在跳板机上实现远程登陆大批量的机器进行一些维护工作,当然我这里使用的还是 Python 来实现,这个 Python 脚本里有一些依赖库(比如 之前介绍过的 paramiko 这个神器),在跳板机上中并没有安装。 +![](http://image.iswbm.com/20200427180207.png) +做为普通用户的你,是没有权限安装第三方包的。 -设置我自定义的系统偏好设置 +![](http://image.iswbm.com/20200427180042.png) -![](http://image.iswbm.com/image-20200704192441091.png) +问题就来了,我如何才能在跳板机中使用 paramiko 这个包呢? -设置打开访达快捷键 +## 2. 为何不使用虚拟环境? -1. 打开 『自动操作』 -2. 新建文稿 -3. 选择『服务』 或者 『自动操作』(因为不同版本的 macOS名字不同) -4. 进行如下设置 +既然不能对全局的 Python 环境进行更改,那我完全可以自己再创建一个环境,只要这个环境里事先装好 paramiko 这个包不就好了。 -![](http://image.iswbm.com/image-20200704194215498.png) +因此,使用虚拟环境是一种解决方案,但它并不是一个完美的解决方案。 -5. 取消勾选:因为我要设置的快捷键与它冲突 +**原因有以下几点**: -![、](http://image.iswbm.com/image-20200704195011274.png) +1、 创建虚拟环境的过程,步骤较多,比较复杂。这里的复杂是相对于我后面要使用的用户环境而言。 -6. 设置快捷键 +2、 虚拟环境是包含一整个 Python 解释器,存在大量与系统重复的包,size比较大,并不轻便。 -![](http://image.iswbm.com/image-20200704195122336.png) +3、 使用 console 模式调试的话,进入很不方便 -## 4.18.4 系统设置 +![](http://image.iswbm.com/20200427182334.png) -关闭仪表盘 +就算你不使用 console 模式,你调用脚本的方式,也会很奇怪,你得这样 +```python +$ zabbix_env/bin/python demo.py ``` -点击系统偏好设置 -> 调度中心 -> 仪表盘 -> 关闭 -``` - -finder的显示 - -![](http://image.python-online.cn/20190810161513.png) - - - -**[防止电脑温度过高](https://mp.weixin.qq.com/s/qKQO616vxADFp1cVtA62Cw)** -1. 不使用 Adobe Flash 播放器(改用 HTML5播放器),因为其效能极低,会耗费大量的系统资源,导致电脑温度快速上升。 - -2. 不将电脑放在软的地方,如沙发,枕头等,可以买个散热支架。 - - ![来自Mac派](http://image.python-online.cn/20190810162000.png) - - 3. 打开「活动监视器」(Alfred就可以打开),杀掉暂没用且cpu使用率最高的程序 - - ![](http://image.python-online.cn/20190810162315.png) - - 4. MacBook Pro CPU 温度在 5、60℃ 的时候,风扇会转到两三千转每分钟,只有 CPU 温度达到 70 多度或更高时,才会高速运转降温。但这时 Mac 已经很热了。 - - 我们可以借用软件,手动让散热风扇全速运转,这样就能更快的散热。常用的软件有 Macs Fan Control、TG Pro、smcFanControl,三个用下来我比较推荐 Macs Fan Control。 - - Macs Fan Control 可以查看各 CPU 核心的温度、主板、电池、内存温度等。可以分别调节两个风扇的转速,也可以设定条件自动调整转速。安装后就可以在系统状态栏看到电脑目前的温度和转速。 - - 风扇转得快了,散热自然也快了,但是风扇声音也更大了。建议只在非常烫(超过60℃?)的时候开启。 - -## 4.18.5 brew 的使用 - -设置国内源 +如果你不想使用这样,可以给这个脚本加个可执行权限,并在脚本的第一行指定你的解释器,省去了一点点麻烦,可即便如此,我仍然感觉很别扭。 ```shell -git -C "$(brew --repo)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git - -git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git - -git -C "$(brew --repo homebrew/cask)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask.git +[wangbm@35ha02 ~]$ cat demo.py +#!/home/wangbm/zabbix_env/bin/python -brew update +import zabbix_api +[wangbm@35ha02 ~]$ +[wangbm@35ha02 ~]$ +[wangbm@35ha02 ~]$ chmod +x demo.py +[wangbm@35ha02 ~]$ +[wangbm@35ha02 ~]$ ./demo.py # 可以执行,没有报错 +[wangbm@35ha02 ~]$ ``` -如果要还原 -```shell -git -C "$(brew --repo)" remote set-url origin https://github.com/Homebrew/brew.git -git -C "$(brew --repo homebrew/core)" remote set-url origin https://github.com/Homebrew/homebrew-core.git +你可能会问我:为什么不使用 virtualenv + virtualenvwrapper ,这样可以使用 workon 进入虚拟环境。 -git -C "$(brew --repo homebrew/cask)" remote set-url origin https://github.com/Homebrew/homebrew-cask.git +原因是跳板机里的都是很古老的包,你看上面的 Python 还是 2.7.5 呢,所以你所说的那些工具通通没有。 -brew update -``` -安装docker -```shell -brew cask install docker -``` +## 3. 用户环境原理 -## 4.18.6 访达使用技巧 +这里要介绍的这种方案(**用户环境**),可能很多人都没有使用过,甚至没有听过,它算是一个冷门但是非常好用的功能。 -详细请看这篇文章([MacOS实用技巧之Finder(访达)的使用](https://www.jianshu.com/p/3666e6954e8a)),非常好的教程。 - -**跳转技巧** - -```shell -# 快速打开访达:先打开搜索,再打开个人家目录 -打开搜索:command + option(alt) + space -关闭标签页:command + shift + h +操作之前 ,先简单介绍一下它。 -# 返回父级文件夹 -command + ↑ +先提一个问题,Python 在查找导入包时,如果我们多个路径都有这个包,那 Python 如何确定应该从哪个路径进行导入呢? -# 进入文件夹 -command + ↓ +答案是, 搜索导入路径是有优先级的,你可以通过 sys.path 进行查看。 -# 前进 后退 -command + [ -comand + ] - -# 快速跳转至第一个文件或最后一个文件 -option + ↑ -option + ↓ - - -# 打开指定路径(前提访达得是激活状态的窗口) -# 注意在这里,可以使用 tab 补全 -shift + command + g +```python +>>> import sys +>>> from pprint import pprint +>>> pprint(sys.path) +['', + '/usr/lib64/python27.zip', + '/usr/lib64/python2.7', + '/usr/lib64/python2.7/plat-linux2', + '/usr/lib64/python2.7/lib-tk', + '/usr/lib64/python2.7/lib-old', + '/usr/lib64/python2.7/lib-dynload', + '/home/wangbm/.local/lib/python2.7/site-packages', + '/usr/lib64/python2.7/site-packages', + '/usr/lib64/python2.7/site-packages/gtk-2.0', + '/usr/lib/python2.7/site-packages', + '/usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg', + '/usr/lib/python2.7/site-packages/lockfile-0.12.2-py2.7.egg'] +>>> ``` +可以看到路径 `/home/wangbm/.local/lib/python2.7/site-packages` 是优先于 `/usr/lib64/python2.7/site-packages` 路径的。 +这就是 **用户环境** 的原理,只要我们将包装在自己家目录下,就可以优先于全局环境中进行查找。 -**操作文件与文件夹** - -```shell -# enter -重命名文件夹 - -# 选中所有文件,并将这些文件归档入一个新的文件夹 -右键 -> 用所选项目新建的文件夹(Ctrl+Command+n) -> 回车,重命名 +使用起来,可以做到用户无感知,跟使用原生的全局环境并没有区别。 +## 4. 具体操作方法 -# 选择 -点击 -> 拖拽 -如果想要取消选中,就 command + 点击 +创建一个用户环境,并安装上你所需要的包,一条命令就能搞定,这可比虚拟环境简单方便多了。 +那么怎么操作呢? -# 打开最近使用过的文件夹 -comand + shift + f - -# 显示/隐藏文件 -command + shift + . - -# 查看文件/夹 详情 -command + i - -# mac 中拷贝和复制不一样 -command + c 拷贝 -command + d 复制(会多出一个副本),或者使用鼠标拖动,但是记住要按option -command + v 粘贴 -command + option + v 称动 ,或者使用鼠标拖动 - -# 可以设置搜索的范围 -command + f - -# 新建文件夹 -command + shift + n - -# 关闭访达标签页,如果是最后一个标签页,则关闭访达 -command + w -``` - -**定制服务(复制文件路径)** +只要你在使用 pip 安装包时,加上 `--user` 参数,pip 就会将其安装在当前用户的 `~/.local/lib/python2.x/site-packages` 下,而其他用户的 python 则不会受影响。 ```shell -# 复制文件路径,有两种方法 -# 【第一种】:快捷键 -command + option + c -# 若你使用 alfred ,快捷键会冲突,解决方法:先右键,再 option,选择将 xx 拷贝为路径名称 - -# 第二种:使用服务 -参考 https://sspai.com/post/33422 +$ pip install --user pkg ``` -**在 iTerm2中打开访达** +这里要注意的是,不能使用这种方式,亲测它会将包装到全局环境下,具体原因我还没有深究。 ```shell -# 在当前目录打开 -open . - -# 在指定目录打开 -open ~/Code +$ python -m pip install --user pkg ``` -搜索时,优先搜索当前文件夹:访达的偏好设置 - -![](http://image.iswbm.com/image-20200704192031119.png) - -此时如果你想要搜索电脑全局,那么有两种方法 - -1. Command + option + 空格 -2. command + 空格 - -## 4.18.7 使用小鹤双拼 - -2018 款的 MBP 系统是 10.13.6 ,这个系统支持的双拼是自然码,若想使用小鹤双拼,可以使用如下命令 - -```shell -defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 4 -``` - -同样的,还有更多的方案,都可以使用命令来修改 +为了让你理解这个过程,我这里来举个例子,并且验证其是否可以做到用户隔离。 ```shell -全拼:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 0 - -智能 ABC:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 1 - -微软双拼:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 2 - -紫光双拼:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 3 - -小鹤双拼:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 4 - -自然码:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 5 - -拼音加加:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 6 +# 在全局环境中未安装 requests +[root@localhost ~]$ pip list | grep requests +[root@localhost ~]$ su - wangbm + +# 由于用户环境继承自全局环境,这里也未安装 +[wangbm@localhost ~]$ pip list | grep requests +[wangbm@localhost ~]$ pip install --user requests +[wangbm@localhost ~]$ pip list | grep requests +requests (2.22.0) +[wangbm@localhost ~]$ + +# 从 Location 属性可发现 requests 只安装在当前用户环境中 +[wangbm@localhost ~]$ pip show requests +--- +Metadata-Version: 2.1 +Name: requests +Version: 2.22.0 +Summary: Python HTTP for Humans. +Home-page: http://python-requests.org +Author: Kenneth Reitz +Author-email: me@kennethreitz.org +Installer: pip +License: Apache 2.0 +Location: /home/wangbm/.local/lib/python2.7/site-packages +[wangbm@localhost ~]$ exit +logout -搜狗双拼:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 7 +# 退出 wangbm 用户,在 root 用户环境中发现 requests 未安装 +[root@localhost ~]$ pip list | grep requests +[root@localhost ~]$ ``` -练习的话,可以使用这两个网站: - -练习单字:https://api.ihint.me/shuang/ - -练习文章:https://api.ihint.me/zi/ - -对应的 github:https://github.com/BlueSky-07/Shuang - -## 4.18.8 输入法切换 BUG +有了这个思路,我就可以先在其他机器(前提自己必须拥有管理员权限 )上,创建一个用户环境,并且安装上 paramiko 这个包。 -问题是由 `TISwitcher` 引起的。 +然后将这个用户环境,压缩拷贝至跳板机自己的家目录下的 `.local/lib` 目录下并解压。 -当你按住 `control` 键,不停的敲 `空格` 就可了看到这个进程的面貌了。 +然后直接使用 python 进入 console 模式,现在已经可以直接使用 paramiko 这个包了。 -对于使用 `command + space` 切换输入法的, `control` 换成 `command` 即可。 `TISwitcher` 干掉没有任何影响。顶多就是切换输入法时,看不到切换状态而已。 +![](http://image.iswbm.com/20200427185854.png) -解决方法如下: -1. 打开 `Activity Monitor` -2. 找到 `TISwitcher` 这个进程,干掉就 OK 了 -3. 为了防止重启后,这个进程再次启动, 直接删掉 `/System/Library/CoreServices/Menu Extras/TextInput.menu/Contents/SharedSupport/TISwitcher.app` - - - -## 参考文章 - -1. [Mac 上值得推荐的录屏软件](https://mp.weixin.qq.com/s/cvS6BLI53JFQY2P3rvg9Xw) -2. [Mac 连显示器或电视需要买什么线?](https://mp.weixin.qq.com/s/V8A_1GBxtlN2WZrcTsi-YQ) -3. [新手如何快速入门 Mac 的使用?](https://mp.weixin.qq.com/s/55_R1xJ5fv8F8P9Nin93Ww) - ---- ![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c04/c04_18.rst b/source/c04/c04_18.rst index c17bc76..cef8a2c 100644 --- a/source/c04/c04_18.rst +++ b/source/c04/c04_18.rst @@ -1,386 +1,197 @@ -4.18 详细的 Mac 使用指南 -======================== +4.18 如何用好 Python的用户环境? +================================ |image0| -4.18.1 iTerm2 -------------- +在之前写过一篇关于虚拟环境使用的文章 :\ `Python +虚拟环境使用指南 `__. -介绍一下快捷键 +但是还没有好好的介绍一下 Python 的用户环境,原因是自己一直没遇到要使用 +``用户环境`` 的使用场景,所以就一直懒得写。 -:: +恰巧这两天,自己遇到了一个使用用户环境的体验可以完爆虚拟环境的案例,就拿出来分享一下。 - # 分窗口操作 - shift + command + d(横切)command + d(竖切) - - 2、历史信息查找和粘贴:command + f,呼出查找功能,找到后 tab 键可以选中找到的文本,通过option + 回车粘贴。 - - 3、自动完成:command + ; ,呼出自动完成窗口,根据上下文提供内容选择项 - # 粘贴历史 - shift + command + h - - # 回放功能 - option + command + b - - # 光标去哪了? - command + / - - # 在所有的窗口中搜索 - option + command + e - -4.18.2 截图工具 +1. 我的使用背景 --------------- -1. Xnip:取色+丰富的标注功能+滚动截屏; -2. `Snip `__\ :小巧轻量+滚动截屏(不能从App - Store下载,亲测已经失效); -3. QQ:截屏+录屏+OCR文字识别; -4. Snipaste:贴屏功能; -5. ScreenShot PSD:psd 文件,每种元素都有单独的图层。 - -4.18.3 快捷操作 ---------------- - -:: - - # 查看桌面,并不是返回桌面,再按一下就还原原来的窗口了 - fn + f11 # 通常情况下 - f11 # 在外接键盘下,请先设置 F1,F2 这些键为标准功能键,偏好设置->键盘 - - # 最大化窗口与取消 - command + ctrl + f - - # 最小化/隐藏窗口 - command + h - - # 最小化除了当前active窗口之外的所有窗口 - comand + option + h - - # 隐藏除当前窗口外的其他所有窗口 - command + opthon + h - - # 关闭除访达外的其他程序 - command + q - - # 在不同的程序间切换 - command + tab - command + shift + tab - - # 在一个程序的不同窗口切换 - command + ~ - - # 强制退出程序 - command + option + esc - - # 删除光标处到行首 - command + backspace(删除键) - - # 新建窗口打开页面 - command + 鼠标左键 - -查询汇率 - -:: - - command + space - 输入 1港币或者1美元 - -将网页图片保存到本地 - -:: - - 直接拖动图片 - 如果不仍想让访达窗口保持在最前面,就按住 command 键 +公司有数以万计的服务器,为了对实现对访问记录进行集中管理以及出于安全考虑,每台服务器都有访问限制,必须使用公司的跳板机才能登陆。 -显示控制台 +每个公司的员工在跳板机上都有自己的用户、 家目录,对于很多需要 root +权限的操作,是高度受限制的。 -:: - - 原生:ctrl+shift+L - 由于与 MWeb 的插入链接冲突,而Mweb 又没有修改快捷键的入口,所以我将启动台的快捷键改成:ctrl+option+L - - 要卸载 app 的话 - 1. ctrl+option+L 显示启动台 - 2. 再按 ctrl+option(默认,不需要设置) 就会出现抖动效果,点击 x 就可以卸载了。 - -设置我自定义的系统偏好设置 +比如我现在我要在跳板机上实现远程登陆大批量的机器进行一些维护工作,当然我这里使用的还是 +Python 来实现,这个 Python 脚本里有一些依赖库(比如 之前介绍过的 +paramiko 这个神器),在跳板机上中并没有安装。 |image1| -设置打开访达快捷键 - -1. 打开 『自动操作』 -2. 新建文稿 -3. 选择『服务』 或者 『自动操作』(因为不同版本的 macOS名字不同) -4. 进行如下设置 +做为普通用户的你,是没有权限安装第三方包的。 |image2| -5. 取消勾选:因为我要设置的快捷键与它冲突 - -.. figure:: http://image.iswbm.com/image-20200704195011274.png - :alt: 、 - - 、 - -6. 设置快捷键 - -|image3| - -4.18.4 系统设置 ---------------- - -关闭仪表盘 - -:: +问题就来了,我如何才能在跳板机中使用 paramiko 这个包呢? - 点击系统偏好设置 -> 调度中心 -> 仪表盘 -> 关闭 +2. 为何不使用虚拟环境? +----------------------- -finder的显示 +既然不能对全局的 Python +环境进行更改,那我完全可以自己再创建一个环境,只要这个环境里事先装好 +paramiko 这个包不就好了。 -|image4| - -`防止电脑温度过高 `__ - -1. 不使用 Adobe Flash 播放器(改用 - HTML5播放器),因为其效能极低,会耗费大量的系统资源,导致电脑温度快速上升。 - -2. 不将电脑放在软的地方,如沙发,枕头等,可以买个散热支架。 - - .. figure:: http://image.python-online.cn/20190810162000.png - :alt: 来自Mac派 - - 来自Mac派 - - 3. 打开「活动监视器」(Alfred就可以打开),杀掉暂没用且cpu使用率最高的程序 +因此,使用虚拟环境是一种解决方案,但它并不是一个完美的解决方案。 - |image5| +**原因有以下几点**\ : - 4. MacBook Pro CPU 温度在 5、60℃ - 的时候,风扇会转到两三千转每分钟,只有 CPU 温度达到 70 - 多度或更高时,才会高速运转降温。但这时 Mac 已经很热了。 +1、 +创建虚拟环境的过程,步骤较多,比较复杂。这里的复杂是相对于我后面要使用的用户环境而言。 - 我们可以借用软件,手动让散热风扇全速运转,这样就能更快的散热。常用的软件有 - Macs Fan Control、TG Pro、smcFanControl,三个用下来我比较推荐 Macs - Fan Control。 - - Macs Fan Control 可以查看各 CPU - 核心的温度、主板、电池、内存温度等。可以分别调节两个风扇的转速,也可以设定条件自动调整转速。安装后就可以在系统状态栏看到电脑目前的温度和转速。 - - 风扇转得快了,散热自然也快了,但是风扇声音也更大了。建议只在非常烫(超过60℃?)的时候开启。 - -4.18.5 brew 的使用 ------------------- - -设置国内源 - -.. code:: shell +2、 虚拟环境是包含一整个 Python +解释器,存在大量与系统重复的包,size比较大,并不轻便。 - git -C "$(brew --repo)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git +3、 使用 console 模式调试的话,进入很不方便 - git -C "$(brew --repo homebrew/core)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git - - git -C "$(brew --repo homebrew/cask)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask.git - - brew update - -如果要还原 - -.. code:: shell - - git -C "$(brew --repo)" remote set-url origin https://github.com/Homebrew/brew.git - - git -C "$(brew --repo homebrew/core)" remote set-url origin https://github.com/Homebrew/homebrew-core.git - - git -C "$(brew --repo homebrew/cask)" remote set-url origin https://github.com/Homebrew/homebrew-cask.git - - brew update - -安装docker - -.. code:: shell +|image3| - brew cask install docker +就算你不使用 console 模式,你调用脚本的方式,也会很奇怪,你得这样 -4.18.6 访达使用技巧 -------------------- +.. code:: python -详细请看这篇文章(\ `MacOS实用技巧之Finder(访达)的使用 `__\ ),非常好的教程。 + $ zabbix_env/bin/python demo.py -**跳转技巧** +如果你不想使用这样,可以给这个脚本加个可执行权限,并在脚本的第一行指定你的解释器,省去了一点点麻烦,可即便如此,我仍然感觉很别扭。 .. code:: shell - # 快速打开访达:先打开搜索,再打开个人家目录 - 打开搜索:command + option(alt) + space - 关闭标签页:command + shift + h + [wangbm@35ha02 ~]$ cat demo.py + #!/home/wangbm/zabbix_env/bin/python - # 返回父级文件夹 - command + ↑ + import zabbix_api + [wangbm@35ha02 ~]$ + [wangbm@35ha02 ~]$ + [wangbm@35ha02 ~]$ chmod +x demo.py + [wangbm@35ha02 ~]$ + [wangbm@35ha02 ~]$ ./demo.py # 可以执行,没有报错 + [wangbm@35ha02 ~]$ - # 进入文件夹 - command + ↓ +你可能会问我:为什么不使用 virtualenv + virtualenvwrapper ,这样可以使用 +workon 进入虚拟环境。 - # 前进 后退 - command + [ - comand + ] +原因是跳板机里的都是很古老的包,你看上面的 Python 还是 2.7.5 +呢,所以你所说的那些工具通通没有。 - # 快速跳转至第一个文件或最后一个文件 - option + ↑ - option + ↓ - - - # 打开指定路径(前提访达得是激活状态的窗口) - # 注意在这里,可以使用 tab 补全 - shift + command + g - -**操作文件与文件夹** - -.. code:: shell +3. 用户环境原理 +--------------- - # enter - 重命名文件夹 +这里要介绍的这种方案(\ **用户环境**\ ),可能很多人都没有使用过,甚至没有听过,它算是一个冷门但是非常好用的功能。 - # 选中所有文件,并将这些文件归档入一个新的文件夹 - 右键 -> 用所选项目新建的文件夹(Ctrl+Command+n) -> 回车,重命名 +操作之前 ,先简单介绍一下它。 +先提一个问题,Python 在查找导入包时,如果我们多个路径都有这个包,那 +Python 如何确定应该从哪个路径进行导入呢? - # 选择 - 点击 -> 拖拽 - 如果想要取消选中,就 command + 点击 +答案是, 搜索导入路径是有优先级的,你可以通过 sys.path 进行查看。 +.. code:: python - # 打开最近使用过的文件夹 - comand + shift + f + >>> import sys + >>> from pprint import pprint + >>> pprint(sys.path) + ['', + '/usr/lib64/python27.zip', + '/usr/lib64/python2.7', + '/usr/lib64/python2.7/plat-linux2', + '/usr/lib64/python2.7/lib-tk', + '/usr/lib64/python2.7/lib-old', + '/usr/lib64/python2.7/lib-dynload', + '/home/wangbm/.local/lib/python2.7/site-packages', + '/usr/lib64/python2.7/site-packages', + '/usr/lib64/python2.7/site-packages/gtk-2.0', + '/usr/lib/python2.7/site-packages', + '/usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg', + '/usr/lib/python2.7/site-packages/lockfile-0.12.2-py2.7.egg'] + >>> - # 显示/隐藏文件 - command + shift + . +可以看到路径 ``/home/wangbm/.local/lib/python2.7/site-packages`` +是优先于 ``/usr/lib64/python2.7/site-packages`` 路径的。 - # 查看文件/夹 详情 - command + i +这就是 **用户环境** +的原理,只要我们将包装在自己家目录下,就可以优先于全局环境中进行查找。 - # mac 中拷贝和复制不一样 - command + c 拷贝 - command + d 复制(会多出一个副本),或者使用鼠标拖动,但是记住要按option - command + v 粘贴 - command + option + v 称动 ,或者使用鼠标拖动 +使用起来,可以做到用户无感知,跟使用原生的全局环境并没有区别。 - # 可以设置搜索的范围 - command + f +4. 具体操作方法 +--------------- - # 新建文件夹 - command + shift + n +创建一个用户环境,并安装上你所需要的包,一条命令就能搞定,这可比虚拟环境简单方便多了。 - # 关闭访达标签页,如果是最后一个标签页,则关闭访达 - command + w +那么怎么操作呢? -**定制服务(复制文件路径)** +只要你在使用 pip 安装包时,加上 ``--user`` 参数,pip +就会将其安装在当前用户的 ``~/.local/lib/python2.x/site-packages`` +下,而其他用户的 python 则不会受影响。 .. code:: shell - # 复制文件路径,有两种方法 - # 【第一种】:快捷键 - command + option + c - # 若你使用 alfred ,快捷键会冲突,解决方法:先右键,再 option,选择将 xx 拷贝为路径名称 + $ pip install --user pkg - # 第二种:使用服务 - 参考 https://sspai.com/post/33422 - -**在 iTerm2中打开访达** +这里要注意的是,不能使用这种方式,亲测它会将包装到全局环境下,具体原因我还没有深究。 .. code:: shell - # 在当前目录打开 - open . - - # 在指定目录打开 - open ~/Code - -搜索时,优先搜索当前文件夹:访达的偏好设置 - -|image6| - -此时如果你想要搜索电脑全局,那么有两种方法 - -1. Command + option + 空格 -2. command + 空格 + $ python -m pip install --user pkg -4.18.7 使用小鹤双拼 -------------------- - -2018 款的 MBP 系统是 10.13.6 -,这个系统支持的双拼是自然码,若想使用小鹤双拼,可以使用如下命令 +为了让你理解这个过程,我这里来举个例子,并且验证其是否可以做到用户隔离。 .. code:: shell - defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 4 - -同样的,还有更多的方案,都可以使用命令来修改 - -.. code:: shell - - 全拼:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 0 - - 智能 ABC:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 1 - - 微软双拼:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 2 - - 紫光双拼:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 3 - - 小鹤双拼:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 4 - - 自然码:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 5 - - 拼音加加:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 6 - - 搜狗双拼:defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 7 - -练习的话,可以使用这两个网站: + # 在全局环境中未安装 requests + [root@localhost ~]$ pip list | grep requests + [root@localhost ~]$ su - wangbm + + # 由于用户环境继承自全局环境,这里也未安装 + [wangbm@localhost ~]$ pip list | grep requests + [wangbm@localhost ~]$ pip install --user requests + [wangbm@localhost ~]$ pip list | grep requests + requests (2.22.0) + [wangbm@localhost ~]$ + + # 从 Location 属性可发现 requests 只安装在当前用户环境中 + [wangbm@localhost ~]$ pip show requests + --- + Metadata-Version: 2.1 + Name: requests + Version: 2.22.0 + Summary: Python HTTP for Humans. + Home-page: http://python-requests.org + Author: Kenneth Reitz + Author-email: me@kennethreitz.org + Installer: pip + License: Apache 2.0 + Location: /home/wangbm/.local/lib/python2.7/site-packages + [wangbm@localhost ~]$ exit + logout + + # 退出 wangbm 用户,在 root 用户环境中发现 requests 未安装 + [root@localhost ~]$ pip list | grep requests + [root@localhost ~]$ + +有了这个思路,我就可以先在其他机器(前提自己必须拥有管理员权限 +)上,创建一个用户环境,并且安装上 paramiko 这个包。 + +然后将这个用户环境,压缩拷贝至跳板机自己的家目录下的 ``.local/lib`` +目录下并解压。 + +然后直接使用 python 进入 console 模式,现在已经可以直接使用 paramiko +这个包了。 -练习单字:https://api.ihint.me/shuang/ - -练习文章:https://api.ihint.me/zi/ - -对应的 github:https://github.com/BlueSky-07/Shuang - -4.18.8 输入法切换 BUG ---------------------- - -问题是由 ``TISwitcher`` 引起的。 - -当你按住 ``control`` 键,不停的敲 ``空格`` 就可了看到这个进程的面貌了。 - -对于使用 ``command + space`` 切换输入法的, ``control`` 换成 ``command`` -即可。 ``TISwitcher`` -干掉没有任何影响。顶多就是切换输入法时,看不到切换状态而已。 - -解决方法如下: - -1. 打开 ``Activity Monitor`` -2. 找到 ``TISwitcher`` 这个进程,干掉就 OK 了 -3. 为了防止重启后,这个进程再次启动, 直接删掉 - ``/System/Library/CoreServices/Menu Extras/TextInput.menu/Contents/SharedSupport/TISwitcher.app`` - -参考文章 --------- - -1. `Mac - 上值得推荐的录屏软件 `__ -2. `Mac - 连显示器或电视需要买什么线? `__ -3. `新手如何快速入门 Mac - 的使用? `__ - --------------- +|image4| -|image7| +|image5| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/image-20200704192441091.png -.. |image2| image:: http://image.iswbm.com/image-20200704194215498.png -.. |image3| image:: http://image.iswbm.com/image-20200704195122336.png -.. |image4| image:: http://image.python-online.cn/20190810161513.png -.. |image5| image:: http://image.python-online.cn/20190810162315.png -.. |image6| image:: http://image.iswbm.com/image-20200704192031119.png -.. |image7| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20200427180207.png +.. |image2| image:: http://image.iswbm.com/20200427180042.png +.. |image3| image:: http://image.iswbm.com/20200427182334.png +.. |image4| image:: http://image.iswbm.com/20200427185854.png +.. |image5| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_19.md b/source/c04/c04_19.md deleted file mode 100644 index b4f3fb9..0000000 --- a/source/c04/c04_19.md +++ /dev/null @@ -1,689 +0,0 @@ -# 4.19 程序员编码必学:Vim - -![](http://image.iswbm.com/20200602135014.png) - -我本人是 Vim 的重度使用者,就因为喜欢上这种双手不离键盘就可以操控一切的feel,Vim 可以让我对文本的操作更加精准、高效。 - -对于未使用过 Vim 的朋友来说,可能还无法体会到这种感觉。由于使用 Vim 有一定的学习成本,只有做到非常熟练的程度才能感受到它带来的快捷。 - -这里我就自己日常有使用过的 Vim 指令做一个总结,总共分成 21 点,建议有想学习 Vim 的同学,可以按照文章**配合搜索引擎**多多尝试,相信你会慢慢喜欢上 Vim。 - -本文更倾向于有一定基础的同学,因为有很多点,如果写得太详细的话,一定会变得相当啰嗦。 - -## 1. vim模式 - -```shell -正常模式(按Esc或Ctrl+[进入) 左下角显示文件名或为空 -插入模式(按i进入) 左下角显示--INSERT-- -可视模式(按v进入) 左下角显示--VISUAL-- -替换模式(按r或R开始) 左下角显示 --REPLACE-- -命令行模式(按:或者/或者?开始) -ex模式 没用过,有兴趣的同学可以自行了解 -``` -## 2. 打开文件 - - -```shell -# 打开单个文件 -vim file -# 同时打开多个文件 -vim file1 file2.. - -# 在vim窗口中打开一个新文件 -:open [file] - -【举个例子】 -# 当前打开1.txt,做了一些编辑没保存 -:open! 放弃这些修改,并重新打开未修改的文件 - -# 当前打开1.txt,做了一些编辑并保存 -:open 2.txt 直接退出对1.txt的编辑,直接打开2.txt编辑,省了退出:wq再重新vim 2.txt的步骤 - - -# 打开远程文件,比如ftp或者share folder -:e ftp://192.168.10.76/abc.txt -:e \qadrive\test\1.txt - -# 以只读形式打开文件,但是仍然可以使用 :wq! 写入 -vim -R file - -# 强制性关闭修改功能,无法使用 :wq! 写入 -vim -M file -``` - -## 3. 插入命令 - - -```shell -i 在当前位置生前插入 -I 在当前行首插入 - -a 在当前位置后插入 -A 在当前行尾插入 - -o 在当前行之后插入一行 -O 在当前行之前插入一行 -``` - -## 4. 查找命令 - -最简单的查找 - -```shell -/text  查找text,按n健查找下一个,按N健查找前一个。 -?text  查找text,反向查找,按n健查找下一个,按N健查找前一个。 - -vim中有一些特殊字符在查找时需要转义  .*[]^%/?~$ - -:set ignorecase  忽略大小写的查找 -:set noignorecase  不忽略大小写的查找 -``` - -快速查找,不需要手打字符即可查找 - -``` -* 向后(下)寻找游标所在处的单词 -# 向前(上)寻找游标所在处的单词 - - -以上两种查找,n,N 的继续查找命令依然可以适用 -``` - -精准查找:匹配单词查找 - -如果文本中有 `hello`,` helloworld`,` hellopython` - -那我使用 /hello ,这三个词都会匹配到。 - -有没有办法实现精准查找呢?可以使用 - -```shell -/hello\> -``` - -精准查找:匹配行首、行末 - -```shell -# hello位于行首 -/^hello - -# world位于行末 -/world$ -``` - - -## 5. 替换命令 - -```shell -~ 反转游标字母大小写 - -r<字母> 将当前字符替换为所写字母 -R<字母><字母>... 连续替换字母 - -cc 替换整行(就是删除当前行,并在下一行插入) -cw 替换一个单词(就是删除一个单词,就进入插入模式),前提是游标处于单词第一个字母(可用b定位) -C (大写C)替换至行尾(和D有所区别,D是删除(剪切)至行尾,C是删除至行位并进入插入模式) - -# % 就表示所有行,不加就表求当前行 -# 加g表示对每一行的所有匹配到的进行替换,不加就是第一个 -# 如果不加g,而改成c,就会让你再进行确认,选择a进行全部替换,选择y替换,选择n不替换 - -:s/old/new/ 用old替换new,替换当前行的第一个匹配 -:s/old/new/g 用old替换new,替换当前行的所有匹配 - -:%s/old/new/ 用old替换new,替换所有行的第一个匹配 -:%s/old/new/g 用old替换new,替换整个文件的所有匹配 - - -:10,20 s/^/ /g 在第10行至第20行每行前面加四个空格,用于缩进。 - -ddp 交换光标所在行和其下紧邻的一行。 -``` - -## 6. 撤销与重做 - - -```shell -u 撤销(Undo) - -U 撤销对整行的操作 - -Ctrl + r 重做(Redo),即撤销的撤销。 -``` - -## 7. 删除命令 - -需要说明的是,vim 其实并没有单纯的删除命令,下面你或许理解为剪切更加准确。 - -以字符为单位删除 - -```shell -x 删除当前字符 -3x 删除当前字符3次 - -X 删除当前字符的前一个字符。 -3X 删除当前光标向前三个字符 - -dl 删除当前字符, dl=x -dh 删除前一个字符,X=dh - -D 删除当前字符至行尾。D=d$ -d$ 删除当前字符至行尾 -d^ 删除当前字符之前至行首 -``` - -以单词为单位删除 - -```shell -dw 删除当前字符到单词尾 -daw 删除当前字符所在单词 -``` - -以行为单位删除 - -```shell -dd 删除当前行 -dj 删除下一行 -dk 删除上一行 - -dgg 删除当前行至文档首部 -d1G 删除当前行至文档首部 -dG 删除当前行至文档尾部 - -kdgg 删除当前行之前所有行(不包括当前行) -jdG 删除当前行之后所有行(不包括当前行) - - - -10d 删除当前行开始的10行。 -:1,10d 删除1-10行 -:11,$d 删除11行及以后所有的行 -:1,$d 删除所有行 -J   删除两行之间的空行,实际上是合并两行。 -``` - - - -## 8. 复制粘贴 - -普通模式中使用y复制 -``` -yy 复制游标所在的整行(3yy表示复制3行) - -y^ 复制至行首,或y0。不含光标所在处字符。 -y$ 复制至行尾。含光标所在处字符。 - -yw 复制一个单词。 -y2w 复制两个单词。 - -yG 复制至文本末。 -y1G 复制至文本开头。 -``` - -普通模式中使用p粘贴 -``` -p(小写):代表粘贴至光标后(下边,右边) -P(大写):代表粘贴至光标前(上边,左边) -``` - -## 9. 剪切粘贴 - - -```shell -dd 其实就是剪切命令,剪切当前行 -ddp 剪切当前行并粘贴,可实现当前行和下一行调换位置 - - -正常模式下按v(逐字)或V(逐行)进入可视模式 -然后用jklh命令移动即可选择某些行或字符,再按d即可剪切 - -ndd 剪切当前行之后的n行。利用p命令可以对剪切的内容进行粘贴 - -:1,10d 将1-10行剪切。利用p命令可将剪切后的内容进行粘贴。 - -:1, 10 m 20 将第1-10行移动到第20行之后。 -``` - -## 10. 退出保存 - - -```shell -:wq 保存并退出 - -ZZ 保存并退出 - -:q! 强制退出并忽略所有更改 - -:e! 放弃所有修改,并打开原来文件。 -:open! 放弃所有修改,并打开原来文件。 - -:sav(eas) new.txt 另存为一个新文件,退出原文件的编辑且不会保存 -:f(ile) new.txt 新开一个文件,并不保存,退出原文件的编辑且不会保存 -``` - -## 11. 移动命令 - -以字符为单位移动 - -```shell -h 左移一个字符 -l 右移一个字符 -k 上移一个字符 -j 下移一个字符 - - -# 【定位字符】f和F -fx 找到光标后第一个为x的字符 -3fd 找到光标后第三个为d的字符 - -F 同f,反向查找。 -``` - -以行为单位移动 - -```shell -# 10指代所有数字,可任意指定 -10h 左移10个字符 -10l 右移10个字符 -10k 上移10行 -10j 下移10行 - -$ 移动到行尾 -3$ 移动到下面3行的行尾 -``` - -以单词为单位移动 - -```shell -w 向前移动一个单词(光标停在单词首部) -b 向后移动一个单词 -e,同w,只不过是光标停在单词尾部 -ge 同b,光标停在单词尾部。 -``` - -以句为单位移动 - -```shell -( 移动到句首 -) 移动到句尾 -``` - -跳转到文件的首尾 - -```shell -gg 移动到文件头。 = [[ == `` -G 移动到文件尾。 = ]] -``` - -其他移动方法 - -```shell -^ 移动到本行第一个非空白字符上。 -0 移动到本行第一个字符上(可以是空格) -``` - -使用 `具名标记` 跳转,个人感觉这个很好用,因为可以跨文件。 - - -```shell -使用 ma ,可以将此处标记为 a,使用 'a 进行跳转 -使用 :marks 可以查看所有的标记 -使用 :delm!可以删除所有的标记 -``` - -当在查看错误日志时,正常的步骤是,vim打开文件,然后使用 `shift+g` 再跳转到最后一行,这里有个更简单的操作可以在打开文件时立即跳到最后一行。只要在 vim 和 文件 中间加个 `+` 即可。 - -```shell -vim + you.log -``` - -举一反三,当你想打开文件立即跳转到指定行时,可以这样 - -```shell -# 打开文件并跳转到 20 行 -vim you.log +20 -``` - -当你使用 `/` 搜索定位跳转或者使用 `:行号` 进行精准跳转时,有时我们想返回到上一次的位置,如何实现? - -只要使用 Ctrl+o 即可返回上一次的位置。 - -## 12. 排版功能 - -**缩进** - -``` -:set shiftwidth? 查看缩进值 -:set shiftwidth=4 设置缩进值为4 - -# 缩进相关 最好写到配置文件中 ~/.vimrc -:set tabstop=4 -:set softtabstop=4 -:set shiftwidth=4 -:set expandtab - ->> 向右缩进 -<< 取消缩进 -``` - -如何你要对代码进行缩进,还可以用 `==` 对当前行缩进,如果要对多行对待缩进,则使用 n`==`,这种方式要求你所编辑的文件的扩展名是被vim所识别的,比如`.py`文件。 - -**排版** - -``` -:ce 居中 -:le 靠左 -:ri 靠右 -``` -## 13. 注释命令 - -**多行注释** - -``` -进入命令行模式,按ctrl + v进入 visual block模式,然后按j, 或者k选中多行,把需要注释的行标记起来 - -按大写字母I,再插入注释符,例如// - -按esc键就会全部注释了 - -``` -**取消多行注释** - -``` -进入命令行模式,按ctrl + v进入 visual block模式,按字母l横向选中列的个数,例如 // 需要选中2列 - -按字母j,或者k选中注释符号 - -按d键就可全部取消注释 -``` -**复杂注释** - -```shell -:3,5 s/^/#/g 注释第3-5行 -:3,5 s/^#//g 解除3-5行的注释 - - -:1,$ s/^/#/g 注释整个文档 -:1,$ s/^#//g 取消注释整个文档 - - -:%s/^/#/g 注释整个文档,此法更快 -:%s/^#//g 取消注释整个文档 -``` - -## 14. 调整视野 -``` -"zz":命令会把当前行置为屏幕正中央, -"zt":命令会把当前行置于屏幕顶端 -"zb":则把当前行置于屏幕底端. - -Ctrl + e 向下滚动一行 -Ctrl + y 向上滚动一行 - -Ctrl + d 向下滚动半屏 -Ctrl + u 向上滚动半屏 - -Ctrl + f 向下滚动一屏 -Ctrl + b 向上滚动一屏 - - -【跳到指定行】:两种方法 - -可以先把行号打开 -:set nu 打开行号 - -:20 跳到第20行 -20G 跳到第20行 -``` - -## 15. 区域选择 - -``` -要进行区域选择,要先进入可视模式 - -v 以字符为单位,上下左右选择 -V 以行为单位,上下选择 - -选择后可进行操作 -d 剪切/删除 -y 复制 - -Ctrl+v 如果当前是V(大写)模式,就变成v(小写) - 如果当前是v(小写)模式,就变成普通模式。 - 如果当前是普通模式,就进入v(小写)模式 - -利用这个,可以进行多行缩进。 - -ggVG 选择全文 -``` - - -## 16. 窗口控制 - -**新建窗口** - -```shell -# 打开两个文件分属两个窗口 -vim -o 1.txt 2.txt - - -# 假设现在已经打开了1.txt - -:sp 2.txt 开启一个横向的窗口,编辑2.txt -:vsp 2.txt 开启一个竖向的窗口,编辑2.txt - -:split 将当前窗口再复制一个窗口出来,内容同步,游标可以不同 -:split 2.txt 在新窗口打开2.txt的横向窗口 - -# 需要注意:内容同步,但是游标位置是独立的 - -Ctrl-w s 将当前窗口分成水平窗口 -Ctrl-w v 将当前窗口分成竖直窗口 - -Ctrl-w q 等同:q 结束分割出来的视窗。 -Ctrl-w q! 等同:q! 结束分割出来的视窗。 -Ctrl-w o 打开一个视窗并且隐藏之前的所有视窗 -``` -**窗口切换** - -```shell -# 特别说明:Ctrl w <字母> 不需要同时按 - -Ctrl-w h 切换到左边窗口 -Ctrl-w l 切换到右边窗口 - -Ctrl-w j 切换到下边窗口 -Ctrl-w k 切换到上边窗口 - - -# 特别说明:全屏模式下 -:n 切换下一个窗口 -:N 切换上一个窗口 -:bp 切换上一个窗口 - -# 特别说明:非全屏模式 - -:bn 切换下一个窗口,就当前位置的窗口的内容变了,其他窗口不变 -:bN 切换上一个窗口,就当前位置的窗口的内容变了,其他窗口不变 -``` -**窗口移动** - -```shell -# 特别说明:Ctrl w <字母> 不需要同时按 - -Ctrl-w J 将当前视窗移至最下面 -Ctrl-w K 将当前视窗移最上面 - -Ctrl-w H 将当前视窗移至最左边 -Ctrl-w L 将当前视窗移至最右边 - -Ctrl-ww 按顺序切换窗口 -``` -**调整尺寸** - -```shell -# 友情提示:键盘切记不要处于中文状态 - -Ctrl-w + 增加窗口高度 -Ctrl-w - 减少窗口高度 -``` -**退出窗口** - -```shell -:close 关闭当前窗口 -:close! 强制关闭当前窗口 - -:q 退出,不保存 -:q! 强制退出,不保存 - -:x 保存退出 -:wq 保存退出 -:wq! 强制保存退出 - -:w <[路径/]文件名> 另存为 -:savesa <[路径/]文件名> 另存为 - -ZZ 保存并退出。 - -:only 关闭所有窗口,只保留当前窗口(前提:其他窗口内容有改变的话都要先保存) -:only! 关闭所有窗口,只保留当前窗口 - -:qall 放弃所有操作并退出 -:wall 保存所有, -:wqall 保存所有并退出。 -``` - -## 17. 文档加密 - -``` -vim -x file_name - -然后输入密码: -确认密码: - -如果不修改内容也要保存。:wq,不然密码设定不会生效。 -``` - -## 18. 录制宏 - -按q键加任意字母开始录制,再按q键结束录制(这意味着vim中的宏不可嵌套),使用的时候@加宏名,比如qa。。。q录制名为a的宏,@a使用这个宏。 - -## 19. 执行命令 - - -```shell - -# 重复前一次命令 -. - -# 执行shell命令 -:!command - -# 比如列出当前目录下文件 -:!ls - -# 执行脚本 -:!perl -c script.pl 检查perl脚本语法,可以不用退出vim,非常方便。 -:!perl script.pl 执行perl脚本,可以不用退出vim,非常方便。 - -:suspend或Ctrl - Z 挂起vim,回到shell,按fg可以返回vim。 - -# 执行完当前命令后,自动回到vim环境,但不知为何文件内容变成空了 -:silent !command -``` - -## 20. 帮助命令 - - -```shell -在Unix/Linux系统上 -$ vimtutor - -# 普通模式下 -键盘输入vim或F1 - -# 命令行模式下 - -:help 显示整个帮助 -:help xxx 显示xxx的帮助,比如 :help i, :help CTRL-[(即Ctrl+[的帮助)。 -:help 'number' Vim选项的帮助用单引号括起 - - -在Windows系统上 -:help tutor -``` - -## 21. 配置命令 - -显示当前设定 - -```shell -:set或者:se显示所有修改过的配置 -:set all 显示所有的设定值 -:set option? 显示option的设定值 -:set nooption 取消当期设定值 -:ver 显示vim的所有信息(包括版本和参数等) - -# 需要注意:全屏模式下 -:args 查看当前打开的文件列表,当前正在编辑的文件会用[]括起来 -``` - -更改设定 - -```shell -:set nu 显示行号 - -set autoindent(ai) 设置自动缩进 -set autowrite(aw) 设置自动存档,默认未打开 -set backup(bk) 设置自动备份,默认未打开 - -set background=dark或light,设置背景风格 - -set cindent(cin) 设置C语言风格缩进 - -:set ts=4 设置tab键转换为4个空格 - -:set ff=unix # 修改文件dos文件为unix - -:set shiftwidth? 查看缩进值 -:set shiftwidth=4 设置缩进值为4 - -:set ignorecase  忽略大小写的查找 -:set noignorecase  不忽略大小写的查找 - -:set paste # insert模式下,粘贴格式不会乱掉 - -:set ruler?  查看是否设置了ruler,在.vimrc中,使用set命令设制的选项都可以通过这个命令查看 - -:scriptnames  查看vim脚本文件的位置,比如.vimrc文件,语法文件及plugin等。 - -:set list 显示非打印字符,如tab,空格,行尾等。如果tab无法显示,请确定用set lcs=tab:>-命令设置了.vimrc文件,并确保你的文件中的确有tab,如果开启了expendtab,那么tab将被扩展为空格。 - - -:syntax 列出已经定义的语法项 -:syntax clear 清除已定义的语法规则 - -:syntax case match 大小写敏感,int和Int将视为不同的语法元素 -:syntax case ignore 大小写无关,int和Int将视为相同的语法元素,并使用同样的配色方案 - -``` - - - -以上就是我使用 Vim 的一些使用总结,希望对你能有帮助。 - ------- - -最后,送你一张 Vim 的键盘图,你可以将它设置为你的电脑桌面,对你初学 Vim 可能会有帮助。 - -你可以**关注本公众号「Python编程时光」**,在后台回复“**vim**” ,即可获取高清大图。 - -![图1](http://image.python-online.cn/20190804222221.png) - - - -![图2](http://image.python-online.cn/20190804222247.png) - ---- - -![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_19.rst b/source/c04/c04_19.rst deleted file mode 100644 index 19f0945..0000000 --- a/source/c04/c04_19.rst +++ /dev/null @@ -1,728 +0,0 @@ -4.19 程序员编码必学:Vim -======================== - -|image0| - -我本人是 Vim -的重度使用者,就因为喜欢上这种双手不离键盘就可以操控一切的feel,Vim -可以让我对文本的操作更加精准、高效。 - -对于未使用过 Vim 的朋友来说,可能还无法体会到这种感觉。由于使用 Vim -有一定的学习成本,只有做到非常熟练的程度才能感受到它带来的快捷。 - -这里我就自己日常有使用过的 Vim 指令做一个总结,总共分成 21 -点,建议有想学习 Vim -的同学,可以按照文章\ **配合搜索引擎**\ 多多尝试,相信你会慢慢喜欢上 -Vim。 - -本文更倾向于有一定基础的同学,因为有很多点,如果写得太详细的话,一定会变得相当啰嗦。 - -1. vim模式 ----------- - -.. code:: shell - - 正常模式(按Esc或Ctrl+[进入) 左下角显示文件名或为空 - 插入模式(按i进入) 左下角显示--INSERT-- - 可视模式(按v进入) 左下角显示--VISUAL-- - 替换模式(按r或R开始) 左下角显示 --REPLACE-- - 命令行模式(按:或者/或者?开始) - ex模式 没用过,有兴趣的同学可以自行了解 - -2. 打开文件 ------------ - -.. code:: shell - - # 打开单个文件 - vim file - # 同时打开多个文件 - vim file1 file2.. - - # 在vim窗口中打开一个新文件 - :open [file] - - 【举个例子】 - # 当前打开1.txt,做了一些编辑没保存 - :open! 放弃这些修改,并重新打开未修改的文件 - - # 当前打开1.txt,做了一些编辑并保存 - :open 2.txt 直接退出对1.txt的编辑,直接打开2.txt编辑,省了退出:wq再重新vim 2.txt的步骤 - - - # 打开远程文件,比如ftp或者share folder - :e ftp://192.168.10.76/abc.txt - :e \qadrive\test\1.txt - - # 以只读形式打开文件,但是仍然可以使用 :wq! 写入 - vim -R file - - # 强制性关闭修改功能,无法使用 :wq! 写入 - vim -M file - -3. 插入命令 ------------ - -.. code:: shell - - i 在当前位置生前插入 - I 在当前行首插入 - - a 在当前位置后插入 - A 在当前行尾插入 - - o 在当前行之后插入一行 - O 在当前行之前插入一行 - -4. 查找命令 ------------ - -最简单的查找 - -.. code:: shell - - /text  查找text,按n健查找下一个,按N健查找前一个。 - ?text  查找text,反向查找,按n健查找下一个,按N健查找前一个。 - - vim中有一些特殊字符在查找时需要转义  .*[]^%/?~$ - - :set ignorecase  忽略大小写的查找 - :set noignorecase  不忽略大小写的查找 - -快速查找,不需要手打字符即可查找 - -:: - - * 向后(下)寻找游标所在处的单词 - # 向前(上)寻找游标所在处的单词 - - - 以上两种查找,n,N 的继续查找命令依然可以适用 - -精准查找:匹配单词查找 - -如果文本中有 ``hello``\ ,\ ``helloworld``\ ,\ ``hellopython`` - -那我使用 /hello ,这三个词都会匹配到。 - -有没有办法实现精准查找呢?可以使用 - -.. code:: shell - - /hello\> - -精准查找:匹配行首、行末 - -.. code:: shell - - # hello位于行首 - /^hello - - # world位于行末 - /world$ - -5. 替换命令 ------------ - -.. code:: shell - - ~ 反转游标字母大小写 - - r<字母> 将当前字符替换为所写字母 - R<字母><字母>... 连续替换字母 - - cc 替换整行(就是删除当前行,并在下一行插入) - cw 替换一个单词(就是删除一个单词,就进入插入模式),前提是游标处于单词第一个字母(可用b定位) - C (大写C)替换至行尾(和D有所区别,D是删除(剪切)至行尾,C是删除至行位并进入插入模式) - - # % 就表示所有行,不加就表求当前行 - # 加g表示对每一行的所有匹配到的进行替换,不加就是第一个 - # 如果不加g,而改成c,就会让你再进行确认,选择a进行全部替换,选择y替换,选择n不替换 - - :s/old/new/ 用old替换new,替换当前行的第一个匹配 - :s/old/new/g 用old替换new,替换当前行的所有匹配 - - :%s/old/new/ 用old替换new,替换所有行的第一个匹配 - :%s/old/new/g 用old替换new,替换整个文件的所有匹配 - - - :10,20 s/^/ /g 在第10行至第20行每行前面加四个空格,用于缩进。 - - ddp 交换光标所在行和其下紧邻的一行。 - -6. 撤销与重做 -------------- - -.. code:: shell - - u 撤销(Undo) - - U 撤销对整行的操作 - - Ctrl + r 重做(Redo),即撤销的撤销。 - -7. 删除命令 ------------ - -需要说明的是,vim -其实并没有单纯的删除命令,下面你或许理解为剪切更加准确。 - -以字符为单位删除 - -.. code:: shell - - x 删除当前字符 - 3x 删除当前字符3次 - - X 删除当前字符的前一个字符。 - 3X 删除当前光标向前三个字符 - - dl 删除当前字符, dl=x - dh 删除前一个字符,X=dh - - D 删除当前字符至行尾。D=d$ - d$ 删除当前字符至行尾 - d^ 删除当前字符之前至行首 - -以单词为单位删除 - -.. code:: shell - - dw 删除当前字符到单词尾 - daw 删除当前字符所在单词 - -以行为单位删除 - -.. code:: shell - - dd 删除当前行 - dj 删除下一行 - dk 删除上一行 - - dgg 删除当前行至文档首部 - d1G 删除当前行至文档首部 - dG 删除当前行至文档尾部 - - kdgg 删除当前行之前所有行(不包括当前行) - jdG 删除当前行之后所有行(不包括当前行) - - - - 10d 删除当前行开始的10行。 - :1,10d 删除1-10行 - :11,$d 删除11行及以后所有的行 - :1,$d 删除所有行 - J   删除两行之间的空行,实际上是合并两行。 - -8. 复制粘贴 ------------ - -普通模式中使用y复制 - -:: - - yy 复制游标所在的整行(3yy表示复制3行) - - y^ 复制至行首,或y0。不含光标所在处字符。 - y$ 复制至行尾。含光标所在处字符。 - - yw 复制一个单词。 - y2w 复制两个单词。 - - yG 复制至文本末。 - y1G 复制至文本开头。 - -普通模式中使用p粘贴 - -:: - - p(小写):代表粘贴至光标后(下边,右边) - P(大写):代表粘贴至光标前(上边,左边) - -9. 剪切粘贴 ------------ - -.. code:: shell - - dd 其实就是剪切命令,剪切当前行 - ddp 剪切当前行并粘贴,可实现当前行和下一行调换位置 - - - 正常模式下按v(逐字)或V(逐行)进入可视模式 - 然后用jklh命令移动即可选择某些行或字符,再按d即可剪切 - - ndd 剪切当前行之后的n行。利用p命令可以对剪切的内容进行粘贴 - - :1,10d 将1-10行剪切。利用p命令可将剪切后的内容进行粘贴。 - - :1, 10 m 20 将第1-10行移动到第20行之后。 - -10. 退出保存 ------------- - -.. code:: shell - - :wq 保存并退出 - - ZZ 保存并退出 - - :q! 强制退出并忽略所有更改 - - :e! 放弃所有修改,并打开原来文件。 - :open! 放弃所有修改,并打开原来文件。 - - :sav(eas) new.txt 另存为一个新文件,退出原文件的编辑且不会保存 - :f(ile) new.txt 新开一个文件,并不保存,退出原文件的编辑且不会保存 - -11. 移动命令 ------------- - -以字符为单位移动 - -.. code:: shell - - h 左移一个字符 - l 右移一个字符 - k 上移一个字符 - j 下移一个字符 - - - # 【定位字符】f和F - fx 找到光标后第一个为x的字符 - 3fd 找到光标后第三个为d的字符 - - F 同f,反向查找。 - -以行为单位移动 - -.. code:: shell - - # 10指代所有数字,可任意指定 - 10h 左移10个字符 - 10l 右移10个字符 - 10k 上移10行 - 10j 下移10行 - - $ 移动到行尾 - 3$ 移动到下面3行的行尾 - -以单词为单位移动 - -.. code:: shell - - w 向前移动一个单词(光标停在单词首部) - b 向后移动一个单词 - e,同w,只不过是光标停在单词尾部 - ge 同b,光标停在单词尾部。 - -以句为单位移动 - -.. code:: shell - - ( 移动到句首 - ) 移动到句尾 - -跳转到文件的首尾 - -.. code:: shell - - gg 移动到文件头。 = [[ == `` - G 移动到文件尾。 = ]] - -其他移动方法 - -.. code:: shell - - ^ 移动到本行第一个非空白字符上。 - 0 移动到本行第一个字符上(可以是空格) - -使用 ``具名标记`` 跳转,个人感觉这个很好用,因为可以跨文件。 - -.. code:: shell - - 使用 ma ,可以将此处标记为 a,使用 'a 进行跳转 - 使用 :marks 可以查看所有的标记 - 使用 :delm!可以删除所有的标记 - -当在查看错误日志时,正常的步骤是,vim打开文件,然后使用 ``shift+g`` -再跳转到最后一行,这里有个更简单的操作可以在打开文件时立即跳到最后一行。只要在 -vim 和 文件 中间加个 ``+`` 即可。 - -.. code:: shell - - vim + you.log - -举一反三,当你想打开文件立即跳转到指定行时,可以这样 - -.. code:: shell - - # 打开文件并跳转到 20 行 - vim you.log +20 - -当你使用 ``/`` 搜索定位跳转或者使用 ``:行号`` -进行精准跳转时,有时我们想返回到上一次的位置,如何实现? - -只要使用 Ctrl+o 即可返回上一次的位置。 - -12. 排版功能 ------------- - -**缩进** - -:: - - :set shiftwidth? 查看缩进值 - :set shiftwidth=4 设置缩进值为4 - - # 缩进相关 最好写到配置文件中 ~/.vimrc - :set tabstop=4 - :set softtabstop=4 - :set shiftwidth=4 - :set expandtab - - >> 向右缩进 - << 取消缩进 - -如何你要对代码进行缩进,还可以用 ``==`` -对当前行缩进,如果要对多行对待缩进,则使用 -n\ ``==``\ ,这种方式要求你所编辑的文件的扩展名是被vim所识别的,比如\ ``.py``\ 文件。 - -**排版** - -:: - - :ce 居中 - :le 靠左 - :ri 靠右 - -13. 注释命令 ------------- - -**多行注释** - -:: - - 进入命令行模式,按ctrl + v进入 visual block模式,然后按j, 或者k选中多行,把需要注释的行标记起来 - - 按大写字母I,再插入注释符,例如// - - 按esc键就会全部注释了 - -**取消多行注释** - -:: - - 进入命令行模式,按ctrl + v进入 visual block模式,按字母l横向选中列的个数,例如 // 需要选中2列 - - 按字母j,或者k选中注释符号 - - 按d键就可全部取消注释 - -**复杂注释** - -.. code:: shell - - :3,5 s/^/#/g 注释第3-5行 - :3,5 s/^#//g 解除3-5行的注释 - - - :1,$ s/^/#/g 注释整个文档 - :1,$ s/^#//g 取消注释整个文档 - - - :%s/^/#/g 注释整个文档,此法更快 - :%s/^#//g 取消注释整个文档 - -14. 调整视野 ------------- - -:: - - "zz":命令会把当前行置为屏幕正中央, - "zt":命令会把当前行置于屏幕顶端 - "zb":则把当前行置于屏幕底端. - - Ctrl + e 向下滚动一行 - Ctrl + y 向上滚动一行 - - Ctrl + d 向下滚动半屏 - Ctrl + u 向上滚动半屏 - - Ctrl + f 向下滚动一屏 - Ctrl + b 向上滚动一屏 - - - 【跳到指定行】:两种方法 - - 可以先把行号打开 - :set nu 打开行号 - - :20 跳到第20行 - 20G 跳到第20行 - -15. 区域选择 ------------- - -:: - - 要进行区域选择,要先进入可视模式 - - v 以字符为单位,上下左右选择 - V 以行为单位,上下选择 - - 选择后可进行操作 - d 剪切/删除 - y 复制 - - Ctrl+v 如果当前是V(大写)模式,就变成v(小写) - 如果当前是v(小写)模式,就变成普通模式。 - 如果当前是普通模式,就进入v(小写)模式 - - 利用这个,可以进行多行缩进。 - - ggVG 选择全文 - -16. 窗口控制 ------------- - -**新建窗口** - -.. code:: shell - - # 打开两个文件分属两个窗口 - vim -o 1.txt 2.txt - - - # 假设现在已经打开了1.txt - - :sp 2.txt 开启一个横向的窗口,编辑2.txt - :vsp 2.txt 开启一个竖向的窗口,编辑2.txt - - :split 将当前窗口再复制一个窗口出来,内容同步,游标可以不同 - :split 2.txt 在新窗口打开2.txt的横向窗口 - - # 需要注意:内容同步,但是游标位置是独立的 - - Ctrl-w s 将当前窗口分成水平窗口 - Ctrl-w v 将当前窗口分成竖直窗口 - - Ctrl-w q 等同:q 结束分割出来的视窗。 - Ctrl-w q! 等同:q! 结束分割出来的视窗。 - Ctrl-w o 打开一个视窗并且隐藏之前的所有视窗 - -**窗口切换** - -.. code:: shell - - # 特别说明:Ctrl w <字母> 不需要同时按 - - Ctrl-w h 切换到左边窗口 - Ctrl-w l 切换到右边窗口 - - Ctrl-w j 切换到下边窗口 - Ctrl-w k 切换到上边窗口 - - - # 特别说明:全屏模式下 - :n 切换下一个窗口 - :N 切换上一个窗口 - :bp 切换上一个窗口 - - # 特别说明:非全屏模式 - - :bn 切换下一个窗口,就当前位置的窗口的内容变了,其他窗口不变 - :bN 切换上一个窗口,就当前位置的窗口的内容变了,其他窗口不变 - -**窗口移动** - -.. code:: shell - - # 特别说明:Ctrl w <字母> 不需要同时按 - - Ctrl-w J 将当前视窗移至最下面 - Ctrl-w K 将当前视窗移最上面 - - Ctrl-w H 将当前视窗移至最左边 - Ctrl-w L 将当前视窗移至最右边 - - Ctrl-ww 按顺序切换窗口 - -**调整尺寸** - -.. code:: shell - - # 友情提示:键盘切记不要处于中文状态 - - Ctrl-w + 增加窗口高度 - Ctrl-w - 减少窗口高度 - -**退出窗口** - -.. code:: shell - - :close 关闭当前窗口 - :close! 强制关闭当前窗口 - - :q 退出,不保存 - :q! 强制退出,不保存 - - :x 保存退出 - :wq 保存退出 - :wq! 强制保存退出 - - :w <[路径/]文件名> 另存为 - :savesa <[路径/]文件名> 另存为 - - ZZ 保存并退出。 - - :only 关闭所有窗口,只保留当前窗口(前提:其他窗口内容有改变的话都要先保存) - :only! 关闭所有窗口,只保留当前窗口 - - :qall 放弃所有操作并退出 - :wall 保存所有, - :wqall 保存所有并退出。 - -17. 文档加密 ------------- - -:: - - vim -x file_name - - 然后输入密码: - 确认密码: - - 如果不修改内容也要保存。:wq,不然密码设定不会生效。 - -18. 录制宏 ----------- - -按q键加任意字母开始录制,再按q键结束录制(这意味着vim中的宏不可嵌套),使用的时候@加宏名,比如qa。。。q录制名为a的宏,@a使用这个宏。 - -19. 执行命令 ------------- - -.. code:: shell - - - # 重复前一次命令 - . - - # 执行shell命令 - :!command - - # 比如列出当前目录下文件 - :!ls - - # 执行脚本 - :!perl -c script.pl 检查perl脚本语法,可以不用退出vim,非常方便。 - :!perl script.pl 执行perl脚本,可以不用退出vim,非常方便。 - - :suspend或Ctrl - Z 挂起vim,回到shell,按fg可以返回vim。 - - # 执行完当前命令后,自动回到vim环境,但不知为何文件内容变成空了 - :silent !command - -20. 帮助命令 ------------- - -.. code:: shell - - 在Unix/Linux系统上 - $ vimtutor - - # 普通模式下 - 键盘输入vim或F1 - - # 命令行模式下 - - :help 显示整个帮助 - :help xxx 显示xxx的帮助,比如 :help i, :help CTRL-[(即Ctrl+[的帮助)。 - :help 'number' Vim选项的帮助用单引号括起 - - - 在Windows系统上 - :help tutor - -21. 配置命令 ------------- - -显示当前设定 - -.. code:: shell - - :set或者:se显示所有修改过的配置 - :set all 显示所有的设定值 - :set option? 显示option的设定值 - :set nooption 取消当期设定值 - :ver 显示vim的所有信息(包括版本和参数等) - - # 需要注意:全屏模式下 - :args 查看当前打开的文件列表,当前正在编辑的文件会用[]括起来 - -更改设定 - -.. code:: shell - - :set nu 显示行号 - - set autoindent(ai) 设置自动缩进 - set autowrite(aw) 设置自动存档,默认未打开 - set backup(bk) 设置自动备份,默认未打开 - - set background=dark或light,设置背景风格 - - set cindent(cin) 设置C语言风格缩进 - - :set ts=4 设置tab键转换为4个空格 - - :set ff=unix # 修改文件dos文件为unix - - :set shiftwidth? 查看缩进值 - :set shiftwidth=4 设置缩进值为4 - - :set ignorecase  忽略大小写的查找 - :set noignorecase  不忽略大小写的查找 - - :set paste # insert模式下,粘贴格式不会乱掉 - - :set ruler?  查看是否设置了ruler,在.vimrc中,使用set命令设制的选项都可以通过这个命令查看 - - :scriptnames  查看vim脚本文件的位置,比如.vimrc文件,语法文件及plugin等。 - - :set list 显示非打印字符,如tab,空格,行尾等。如果tab无法显示,请确定用set lcs=tab:>-命令设置了.vimrc文件,并确保你的文件中的确有tab,如果开启了expendtab,那么tab将被扩展为空格。 - - - :syntax 列出已经定义的语法项 - :syntax clear 清除已定义的语法规则 - - :syntax case match 大小写敏感,int和Int将视为不同的语法元素 - :syntax case ignore 大小写无关,int和Int将视为相同的语法元素,并使用同样的配色方案 - -以上就是我使用 Vim 的一些使用总结,希望对你能有帮助。 - --------------- - -最后,送你一张 Vim 的键盘图,你可以将它设置为你的电脑桌面,对你初学 Vim -可能会有帮助。 - -你可以\ **关注本公众号「Python编程时光」**\ ,在后台回复“**vim**” -,即可获取高清大图。 - -.. figure:: http://image.python-online.cn/20190804222221.png - :alt: 图1 - - 图1 - -.. figure:: http://image.python-online.cn/20190804222247.png - :alt: 图2 - - 图2 - --------------- - -|image1| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c04/c04_20.md b/source/c04/c04_20.md deleted file mode 100644 index b94eff8..0000000 --- a/source/c04/c04_20.md +++ /dev/null @@ -1,40 +0,0 @@ -# 4.20 学会使用谷歌搜索引擎 - -![](http://image.iswbm.com/20200602135014.png) - -1、按文件类型搜索 加filetype - -``` -流畅的python filetype:pdf -``` - -2、过滤关键字 用减号 - -``` -Python协程 -CSDN -``` - -3、必须包含某关键字 用加号 - -``` -linux常用命令 +centos -``` - -4、搜索指定网站 加site - -``` -Python冷知识 site:python-online.cn -``` - -5、链接中包含字符串 加inurl - -``` -Python冷知识 inurl:python-online -``` - -6、完全匹配搜索结果 加双绰号包裹 - -``` -"装饰器进阶用法详解" -``` - diff --git a/source/c04/c04_20.rst b/source/c04/c04_20.rst deleted file mode 100644 index 72b74f8..0000000 --- a/source/c04/c04_20.rst +++ /dev/null @@ -1,43 +0,0 @@ -4.20 学会使用谷歌搜索引擎 -========================= - -|image0| - -1、按文件类型搜索 加filetype - -:: - - 流畅的python filetype:pdf - -2、过滤关键字 用减号 - -:: - - Python协程 -CSDN - -3、必须包含某关键字 用加号 - -:: - - linux常用命令 +centos - -4、搜索指定网站 加site - -:: - - Python冷知识 site:python-online.cn - -5、链接中包含字符串 加inurl - -:: - - Python冷知识 inurl:python-online - -6、完全匹配搜索结果 加双绰号包裹 - -:: - - "装饰器进阶用法详解" - -.. |image0| image:: http://image.iswbm.com/20200602135014.png - diff --git a/source/c04/c04_21.md b/source/c04/c04_21.md deleted file mode 100644 index 6127de9..0000000 --- a/source/c04/c04_21.md +++ /dev/null @@ -1,364 +0,0 @@ -# 4.21 最全的 pip 使用指南,50% 你可能没用过 - -![](http://image.iswbm.com/20200602135014.png) - -所有的 Python 开发者都清楚,Python 之所以如此受欢迎,能够在众多高级语言中,脱颖而出,除了语法简单,上手容易之外,更多还要归功于 Python 生态的完备,有数以万计的 Python 爱好者愿意以 Python 为基础封装出各种有利于开发的第三方工具包。 - -这才使用我们能够以最快的速度开发出一个满足基本需要的项目,而不是每次都重复造轮子。 - -Python 从1991年诞生到现在,已经过去28个年头了,这其间产生了数以万计的第三方包,且每个包都会不断更新,会有越来越多的版本。 - -当你在一个复杂的项目环境中,如果没有一个有效的依赖包管理方案,项目的维护将会是一个大问题。 - -pip 是官方推荐的包管理工具,在大多数开发者眼里,pip 几乎是 Python 的标配。 - -当然也有其他的包管理工具 - -- **distutils**:仅用于打包和安装,严格来讲不算是包管理工具 - -- **setuptools**:distutils的增强版,扩展了distutils,提供更多的功能,引入包依赖的管理,easy_install就是它的一个命令行工具,引入了 egg 的文件格式。 - -- **Pipenv**:一个集依赖包管理(pip)及虚拟环境管理(virtualenv)的工具 - -- 还有其他的,这里不一一列出。 - - - -今天的主角是 pip ,大家肯定不会陌生。但我相信不少人,只是熟悉几个常用的用法,而对于其他几个低频且实用的用法,却知之甚少,这两天,我查阅官方文档,把这些用法整理了一下,应该是网络上比较全的介绍。 - - - -## 1. 查询软件包 - -查询当前环境安装的所有软件包 - -```shell -$ pip list -``` - -查询 pypi 上含有某名字的包 - -```shell -$ pip search pkg -``` - -查询当前环境中可升级的包 - -```shell -$ pip list --outdated -``` - -查询一个包的详细内容 - -```shell -$ pip show pkg -``` - -## 2. 下载软件包 - -在不安装软件包的情况下下载软件包到本地 - -```shell -$ pip download --destination-directory /local/wheels -r requirements.txt -``` - -下载完,总归是要安装的,可以指定这个目录中安装软件包,而不从 pypi 上安装。 - -```shell -$ pip install --no-index --find-links=/local/wheels -r requirements.txt -``` - -当然你也从你下载的包中,自己构建生成 wheel 文件 - -```shell -$ pip install wheel -$ pip wheel --wheel-dir=/local/wheels -r requirements.txt -``` - - - -## 3. 安装软件包 - -使用 `pip install ` 可以很方便地从 pypi 上搜索下载并安装 python 包。 - -如下所示 - -```shell -$ pip install requests -``` - -这是安装包的基本格式,我们也可以为其添加更多参数来实现不同的效果。 - -**3.1 只从本地安装,而不从 pypi 安装** - -```shell -# 前提你得保证你已经下载 pkg 包到 /local/wheels 目录下 -$ pip install --no-index --find-links=/local/wheels pkg -``` - -**3.2 限定版本进行软件包安装** - -以下三种,对单个 python 包的版本进行了约束 - -```shell -# 所安装的包的版本为 2.1.2 -$ pip install pkg==2.1.2 - -# 所安装的包必须大于等于 2.1.2 -$ pip install pkg>=2.1.2 - -# 所安装的包必须小于等于 2.1.2 -$ pip install pkg<=2.1.2 -``` - -以下命令用于管理/控制整个 python 环境的包版本 - -```shell -# 导出依赖包列表 -pip freeze >requirements.txt - -# 从依赖包列表中安装 -pip install -r requirements.txt - -# 确保当前环境软件包的版本(并不确保安装) -pip install -c constraints.txt -``` - - - -**3.3 限制不使用二进制包安装** - -由于默认情况下,wheel 包的平台是运行 pip download 命令 的平台,所以可能出现平台不适配的情况。 - -比如在 MacOS 系统下得到的 pymongo-2.8-cp27-none-macosx_10_10_intel.whl 就不能在 linux_x86_64 安装。 - -使用下面这条命令下载的是 tar.gz 的包,可以直接使用 pip install 安装。 - -比 wheel 包,这种包在安装时会进行编译,所以花费的时间会长一些。 - -```shell -# 下载非二进制的包 -$ pip download --no-binary=:all: pkg - -# 安装非二进制的包 -$ pip install pkg --no-binary -``` - -**3.4 指定代理服务器安装** - -当你身处在一个内网环境中时,无法直接连接公网。这时候你使用`pip install` 安装包,就会失败。 - -面对这种情况,可以有两种方法: - -1. 下载离线包拷贝到内网机器中安装 -2. 使用代理服务器转发请求 - -第一种方法,虽说可行,但有相当多的弊端 - -- 步骤繁杂,耗时耗力 -- 无法处理包的依赖问题 - -这里重点来介绍,第二种方法: - -```shell -$ pip install --proxy [user:passwd@]http_server_ip:port pkg -``` - -每次安装包就发输入长长的参数,未免有些麻烦,为此你可以将其写入配置文件中:`$HOME/.config/pip/pip.conf` - -对于这个路径,说明几点 - -- 不同的操作系统,路径各不相同 - -```shell -# Linux/Unix: -/etc/pip.conf -~/.pip/pip.conf -~/.config/pip/pip.conf - -# Mac OSX: -~/Library/Application Support/pip/pip.conf -~/.pip/pip.conf -/Library/Application Support/pip/pip.conf - -# Windows: -%APPDATA%\pip\pip.ini -%HOME%\pip\pip.ini -C:\Documents and Settings\All Users\Application Data\PyPA\pip\pip.conf (Windows XP) -C:\ProgramData\PyPA\pip\pip.conf (Windows 7及以后) -``` - -- 若在你的机子上没有此文件,则自行创建即可 - -如何配置,这边给个样例: - -```ini -[global] -index-url = http://mirrors.aliyun.com/pypi/simple/ - -# 替换出自己的代理地址,格式为[user:passwd@]proxy.server:port -proxy=http://xxx.xxx.xxx.xxx:8080 - -[install] -# 信任阿里云的镜像源,否则会有警告 -trusted-host=mirrors.aliyun.com -``` - -**3.5 安装用户私有软件包** - -很多人可能还不清楚,python 的安装包是可以用户隔离的。 - -如果你拥有管理员权限,你可以将包安装在全局环境中。在全局环境中的这个包可被该机器上的所有拥有管理员权限的用户使用。 - -如果一台机器上的使用者不只一样,自私地将在全局环境中安装或者升级某个包,是不负责任且危险的做法。 - -面对这种情况,我们就想能否安装单独为我所用的包呢? - -庆幸的是,还真有。 - -我能想到的有两种方法: - -1. 使用虚拟环境 -2. 将包安装在用户的环境中 - -虚拟环境,之前写过几篇文章,这里不再展开讲。 - -今天的重点是第二种方法,教你如何安装用户私有的包? - -命令也很简单,只要加上 `--user` 参数,pip 就会将其安装在当前用户的 `~/.local/lib/python3.x/site-packages` 下,而其他用户的 python 则不会受影响。 - -```shell -pip install --user pkg -``` - -来举个例子 - -```shell -# 在全局环境中未安装 requests -[root@localhost ~]# pip list | grep requests -[root@localhost ~]# su - wangbm -[root@localhost ~]# - -# 由于用户环境继承自全局环境,这里也未安装 -[wangbm@localhost ~]# pip list | grep requests -[wangbm@localhost ~]# pip install --user requests -[wangbm@localhost ~]# pip list | grep requests -requests (2.22.0) -[wangbm@localhost ~]# - -# 从 Location 属性可发现 requests 只安装在当前用户环境中 -[wangbm@ws_compute01 ~]$ pip show requests ---- -Metadata-Version: 2.1 -Name: requests -Version: 2.22.0 -Summary: Python HTTP for Humans. -Home-page: http://python-requests.org -Author: Kenneth Reitz -Author-email: me@kennethreitz.org -Installer: pip -License: Apache 2.0 -Location: /home/wangbm/.local/lib/python2.7/site-packages -[wangbm@localhost ~]$ exit -logout - -# 退出 wangbm 用户,在 root 用户环境中发现 requests 未安装 -[root@localhost ~]$ pip list | grep requests -[root@localhost ~]$ -``` - -当你身处个人用户环境中,python 导包时会先检索当前用户环境中是否已安装这个包,已安装则优先使用,未安装则使用全局环境中的包。 - -验证如下: - -```python ->>> import sys ->>> from pprint import pprint ->>> pprint(sys.path) -['', - '/usr/lib64/python27.zip', - '/usr/lib64/python2.7', - '/usr/lib64/python2.7/plat-linux2', - '/usr/lib64/python2.7/lib-tk', - '/usr/lib64/python2.7/lib-old', - '/usr/lib64/python2.7/lib-dynload', - '/home/wangbm/.local/lib/python2.7/site-packages', - '/usr/lib64/python2.7/site-packages', - '/usr/lib64/python2.7/site-packages/gtk-2.0', - '/usr/lib/python2.7/site-packages', - '/usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg', - '/usr/lib/python2.7/site-packages/lockfile-0.12.2-py2.7.egg'] ->>> - -``` - - - -## 4. 卸载软件包 - -就一条命令,不再赘述 - -```shell -$ pip uninstall pkg -``` - - - -## 5. 升级软件包 - -想要对现有的 python 进行升级,其本质上也是先从 pypi 上下载最新版本的包,再对其进行安装。所以升级也是使用 `pip install`,只不过要加一个参数 `--upgrade`。 - -``` -$ pip install --upgrade pkg -``` - -在升级的时候,其实还有一个不怎么用到的选项 `--upgrade-strategy`,它是用来指定升级策略。 - -它的可选项只有两个: - -- `eager` :升级全部依赖包 -- `only-if-need`:只有当旧版本不能适配新的父依赖包时,才会升级。 - -在 pip 10.0 版本之后,这个选项的默认值是 `only-if-need`,因此如下两种写法是一互致的。 - -```shell -pip install --upgrade pkg1 -pip install --upgrade pkg1 --upgrade-strategy only-if-need -``` - -## 6. 配置文件 - -由于在使用 pip 安装一些包时,默认会使用 pip 的官方源,所以经常会报网络超时失败。 - -常用的解决办法是,在安装包时,使用 `-i` 参数指定一个国内的镜像源。但是每次指定就很麻烦呀,还要打超长的一串字母。 - -这时候,其实可以将这个源写进 pip 的配置文件里。以后安装的时候,就默认从你配置的这个 源里安装了。 - -那怎么配置呢?文件文件在哪? - -使用` win+r` 输入 `%APPDATA%` 进入用户资料文件夹,查看有没有一个 pip 的文件夹,若没有则创建之。 - -然后进入这个 文件夹,新建一个 `pip.ini` 的文件,内容如下 - -```ini -[global] -time-out=60 -index-url=https://pypi.tuna.tsinghua.edu.cn/simple/ -[install] -trusted-host=tsinghua.edu.cn -``` - - - - - -以上几乎包含了 pip 的所有常用使用场景,为了方便,我将其整理成一张表格,如果你需要,可以关注我的公众号(Python编程时光),后台回复“pip”,可获取高清无水印图片。 - -![](http://image.python-online.cn/20191105200041.png) - - - -![](http://image.iswbm.com/20200607174235.png) - - - diff --git a/source/c04/c04_21.rst b/source/c04/c04_21.rst deleted file mode 100644 index 36aeff6..0000000 --- a/source/c04/c04_21.rst +++ /dev/null @@ -1,378 +0,0 @@ -4.21 最全的 pip 使用指南,50% 你可能没用过 -========================================== - -|image0| - -所有的 Python 开发者都清楚,Python -之所以如此受欢迎,能够在众多高级语言中,脱颖而出,除了语法简单,上手容易之外,更多还要归功于 -Python 生态的完备,有数以万计的 Python 爱好者愿意以 Python -为基础封装出各种有利于开发的第三方工具包。 - -这才使用我们能够以最快的速度开发出一个满足基本需要的项目,而不是每次都重复造轮子。 - -Python -从1991年诞生到现在,已经过去28个年头了,这其间产生了数以万计的第三方包,且每个包都会不断更新,会有越来越多的版本。 - -当你在一个复杂的项目环境中,如果没有一个有效的依赖包管理方案,项目的维护将会是一个大问题。 - -pip 是官方推荐的包管理工具,在大多数开发者眼里,pip 几乎是 Python -的标配。 - -当然也有其他的包管理工具 - -- **distutils**\ :仅用于打包和安装,严格来讲不算是包管理工具 - -- **setuptools**\ :distutils的增强版,扩展了distutils,提供更多的功能,引入包依赖的管理,easy_install就是它的一个命令行工具,引入了 - egg 的文件格式。 - -- **Pipenv**\ :一个集依赖包管理(pip)及虚拟环境管理(virtualenv)的工具 - -- 还有其他的,这里不一一列出。 - -今天的主角是 pip -,大家肯定不会陌生。但我相信不少人,只是熟悉几个常用的用法,而对于其他几个低频且实用的用法,却知之甚少,这两天,我查阅官方文档,把这些用法整理了一下,应该是网络上比较全的介绍。 - -1. 查询软件包 -------------- - -查询当前环境安装的所有软件包 - -.. code:: shell - - $ pip list - -查询 pypi 上含有某名字的包 - -.. code:: shell - - $ pip search pkg - -查询当前环境中可升级的包 - -.. code:: shell - - $ pip list --outdated - -查询一个包的详细内容 - -.. code:: shell - - $ pip show pkg - -2. 下载软件包 -------------- - -在不安装软件包的情况下下载软件包到本地 - -.. code:: shell - - $ pip download --destination-directory /local/wheels -r requirements.txt - -下载完,总归是要安装的,可以指定这个目录中安装软件包,而不从 pypi -上安装。 - -.. code:: shell - - $ pip install --no-index --find-links=/local/wheels -r requirements.txt - -当然你也从你下载的包中,自己构建生成 wheel 文件 - -.. code:: shell - - $ pip install wheel - $ pip wheel --wheel-dir=/local/wheels -r requirements.txt - -3. 安装软件包 -------------- - -使用 ``pip install `` 可以很方便地从 pypi 上搜索下载并安装 python -包。 - -如下所示 - -.. code:: shell - - $ pip install requests - -这是安装包的基本格式,我们也可以为其添加更多参数来实现不同的效果。 - -**3.1 只从本地安装,而不从 pypi 安装** - -.. code:: shell - - # 前提你得保证你已经下载 pkg 包到 /local/wheels 目录下 - $ pip install --no-index --find-links=/local/wheels pkg - -**3.2 限定版本进行软件包安装** - -以下三种,对单个 python 包的版本进行了约束 - -.. code:: shell - - # 所安装的包的版本为 2.1.2 - $ pip install pkg==2.1.2 - - # 所安装的包必须大于等于 2.1.2 - $ pip install pkg>=2.1.2 - - # 所安装的包必须小于等于 2.1.2 - $ pip install pkg<=2.1.2 - -以下命令用于管理/控制整个 python 环境的包版本 - -.. code:: shell - - # 导出依赖包列表 - pip freeze >requirements.txt - - # 从依赖包列表中安装 - pip install -r requirements.txt - - # 确保当前环境软件包的版本(并不确保安装) - pip install -c constraints.txt - -**3.3 限制不使用二进制包安装** - -由于默认情况下,wheel 包的平台是运行 pip download 命令 -的平台,所以可能出现平台不适配的情况。 - -比如在 MacOS 系统下得到的 pymongo-2.8-cp27-none-macosx_10_10_intel.whl -就不能在 linux_x86_64 安装。 - -使用下面这条命令下载的是 tar.gz 的包,可以直接使用 pip install 安装。 - -比 wheel 包,这种包在安装时会进行编译,所以花费的时间会长一些。 - -.. code:: shell - - # 下载非二进制的包 - $ pip download --no-binary=:all: pkg - - # 安装非二进制的包 - $ pip install pkg --no-binary - -**3.4 指定代理服务器安装** - -当你身处在一个内网环境中时,无法直接连接公网。这时候你使用\ ``pip install`` -安装包,就会失败。 - -面对这种情况,可以有两种方法: - -1. 下载离线包拷贝到内网机器中安装 -2. 使用代理服务器转发请求 - -第一种方法,虽说可行,但有相当多的弊端 - -- 步骤繁杂,耗时耗力 -- 无法处理包的依赖问题 - -这里重点来介绍,第二种方法: - -.. code:: shell - - $ pip install --proxy [user:passwd@]http_server_ip:port pkg - -每次安装包就发输入长长的参数,未免有些麻烦,为此你可以将其写入配置文件中:\ ``$HOME/.config/pip/pip.conf`` - -对于这个路径,说明几点 - -- 不同的操作系统,路径各不相同 - -.. code:: shell - - # Linux/Unix: - /etc/pip.conf - ~/.pip/pip.conf - ~/.config/pip/pip.conf - - # Mac OSX: - ~/Library/Application Support/pip/pip.conf - ~/.pip/pip.conf - /Library/Application Support/pip/pip.conf - - # Windows: - %APPDATA%\pip\pip.ini - %HOME%\pip\pip.ini - C:\Documents and Settings\All Users\Application Data\PyPA\pip\pip.conf (Windows XP) - C:\ProgramData\PyPA\pip\pip.conf (Windows 7及以后) - -- 若在你的机子上没有此文件,则自行创建即可 - -如何配置,这边给个样例: - -.. code:: ini - - [global] - index-url = http://mirrors.aliyun.com/pypi/simple/ - - # 替换出自己的代理地址,格式为[user:passwd@]proxy.server:port - proxy=http://xxx.xxx.xxx.xxx:8080 - - [install] - # 信任阿里云的镜像源,否则会有警告 - trusted-host=mirrors.aliyun.com - -**3.5 安装用户私有软件包** - -很多人可能还不清楚,python 的安装包是可以用户隔离的。 - -如果你拥有管理员权限,你可以将包安装在全局环境中。在全局环境中的这个包可被该机器上的所有拥有管理员权限的用户使用。 - -如果一台机器上的使用者不只一样,自私地将在全局环境中安装或者升级某个包,是不负责任且危险的做法。 - -面对这种情况,我们就想能否安装单独为我所用的包呢? - -庆幸的是,还真有。 - -我能想到的有两种方法: - -1. 使用虚拟环境 -2. 将包安装在用户的环境中 - -虚拟环境,之前写过几篇文章,这里不再展开讲。 - -今天的重点是第二种方法,教你如何安装用户私有的包? - -命令也很简单,只要加上 ``--user`` 参数,pip 就会将其安装在当前用户的 -``~/.local/lib/python3.x/site-packages`` 下,而其他用户的 python -则不会受影响。 - -.. code:: shell - - pip install --user pkg - -来举个例子 - -.. code:: shell - - # 在全局环境中未安装 requests - [root@localhost ~]# pip list | grep requests - [root@localhost ~]# su - wangbm - [root@localhost ~]# - - # 由于用户环境继承自全局环境,这里也未安装 - [wangbm@localhost ~]# pip list | grep requests - [wangbm@localhost ~]# pip install --user requests - [wangbm@localhost ~]# pip list | grep requests - requests (2.22.0) - [wangbm@localhost ~]# - - # 从 Location 属性可发现 requests 只安装在当前用户环境中 - [wangbm@ws_compute01 ~]$ pip show requests - --- - Metadata-Version: 2.1 - Name: requests - Version: 2.22.0 - Summary: Python HTTP for Humans. - Home-page: http://python-requests.org - Author: Kenneth Reitz - Author-email: me@kennethreitz.org - Installer: pip - License: Apache 2.0 - Location: /home/wangbm/.local/lib/python2.7/site-packages - [wangbm@localhost ~]$ exit - logout - - # 退出 wangbm 用户,在 root 用户环境中发现 requests 未安装 - [root@localhost ~]$ pip list | grep requests - [root@localhost ~]$ - -当你身处个人用户环境中,python -导包时会先检索当前用户环境中是否已安装这个包,已安装则优先使用,未安装则使用全局环境中的包。 - -验证如下: - -.. code:: python - - >>> import sys - >>> from pprint import pprint - >>> pprint(sys.path) - ['', - '/usr/lib64/python27.zip', - '/usr/lib64/python2.7', - '/usr/lib64/python2.7/plat-linux2', - '/usr/lib64/python2.7/lib-tk', - '/usr/lib64/python2.7/lib-old', - '/usr/lib64/python2.7/lib-dynload', - '/home/wangbm/.local/lib/python2.7/site-packages', - '/usr/lib64/python2.7/site-packages', - '/usr/lib64/python2.7/site-packages/gtk-2.0', - '/usr/lib/python2.7/site-packages', - '/usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg', - '/usr/lib/python2.7/site-packages/lockfile-0.12.2-py2.7.egg'] - >>> - -4. 卸载软件包 -------------- - -就一条命令,不再赘述 - -.. code:: shell - - $ pip uninstall pkg - -5. 升级软件包 -------------- - -想要对现有的 python 进行升级,其本质上也是先从 pypi -上下载最新版本的包,再对其进行安装。所以升级也是使用 -``pip install``\ ,只不过要加一个参数 ``--upgrade``\ 。 - -:: - - $ pip install --upgrade pkg - -在升级的时候,其实还有一个不怎么用到的选项 -``--upgrade-strategy``\ ,它是用来指定升级策略。 - -它的可选项只有两个: - -- ``eager`` :升级全部依赖包 -- ``only-if-need``\ :只有当旧版本不能适配新的父依赖包时,才会升级。 - -在 pip 10.0 版本之后,这个选项的默认值是 -``only-if-need``\ ,因此如下两种写法是一互致的。 - -.. code:: shell - - pip install --upgrade pkg1 - pip install --upgrade pkg1 --upgrade-strategy only-if-need - -6. 配置文件 ------------ - -由于在使用 pip 安装一些包时,默认会使用 pip -的官方源,所以经常会报网络超时失败。 - -常用的解决办法是,在安装包时,使用 ``-i`` -参数指定一个国内的镜像源。但是每次指定就很麻烦呀,还要打超长的一串字母。 - -这时候,其实可以将这个源写进 pip -的配置文件里。以后安装的时候,就默认从你配置的这个 源里安装了。 - -那怎么配置呢?文件文件在哪? - -使用\ ``win+r`` 输入 ``%APPDATA%`` 进入用户资料文件夹,查看有没有一个 -pip 的文件夹,若没有则创建之。 - -然后进入这个 文件夹,新建一个 ``pip.ini`` 的文件,内容如下 - -.. code:: ini - - [global] - time-out=60 - index-url=https://pypi.tuna.tsinghua.edu.cn/simple/ - [install] - trusted-host=tsinghua.edu.cn - -以上几乎包含了 pip -的所有常用使用场景,为了方便,我将其整理成一张表格,如果你需要,可以关注我的公众号(Python编程时光),后台回复“pip”,可获取高清无水印图片。 - -|image1| - -|image2| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20191105200041.png -.. |image2| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c04/c04_22.md b/source/c04/c04_22.md deleted file mode 100644 index a9d1f88..0000000 --- a/source/c04/c04_22.md +++ /dev/null @@ -1,14 +0,0 @@ -# 4.22 用好 Chrome 必看 - -![](http://image.iswbm.com/20200602135014.png) - -## 开启阅读模式 - -开启阅读模式:chrome://flags/#enable-reader-mode - -![](http://image.python-online.cn/20191201103653.png) - -开启完成后,需要重启浏览器,你可以随便打开一篇博客,然后在地址栏右边会有一个阅读模式的按钮。 - - - diff --git a/source/c04/c04_22.rst b/source/c04/c04_22.rst deleted file mode 100644 index a179e21..0000000 --- a/source/c04/c04_22.rst +++ /dev/null @@ -1,17 +0,0 @@ -4.22 用好 Chrome 必看 -===================== - -|image0| - -开启阅读模式 ------------- - -开启阅读模式:chrome://flags/#enable-reader-mode - -|image1| - -开启完成后,需要重启浏览器,你可以随便打开一篇博客,然后在地址栏右边会有一个阅读模式的按钮。 - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20191201103653.png - diff --git a/source/c04/c04_23.md b/source/c04/c04_23.md deleted file mode 100644 index 7c4195e..0000000 --- a/source/c04/c04_23.md +++ /dev/null @@ -1,56 +0,0 @@ -# 4.23 电脑使用技巧 - -![](http://image.iswbm.com/20200602135014.png) - -## 1. 添加右键菜单 - -参考 Sublime Text3的添加方法 - -在sublime的安装目录下新增一个文件:`sublime_addright.inf`,内容如下,然后右键选择安装。 - -``` -[Version] -Signature="$Windows NT$" - -[DefaultInstall] -AddReg=SublimeText3 - -[SublimeText3] -hkcr,"*\\shell\\SublimeText3",,,"Edit with SublimeText3" -hkcr,"*\\shell\\SublimeText3\\command",,,"""%1%\sublime_text.exe"" ""%%1"" %%*" -hkcr,"Directory\shell\SublimeText3",,,"Edit with SublimeText3" -hkcr,"*\\shell\\SublimeText3","Icon",0x20000,"%1%\sublime_text.exe, 0" -hkcr,"Directory\shell\SublimeText3\command",,,"""%1%\sublime_text.exe"" ""%%1""" -``` - -## 2. Rime五筆安裝方法 - -下載地址:https://rime.im/download/ - -五筆配置:https://github.com/rime/rime-wubi - -下載下來是一個zip包,裏面都是五筆的配置文件。 - -``` -wubi_pinyin.schema.yaml -wubi_trad.schema.yaml -wubi86.dict.yaml -wubi86.schema.yaml -``` - -將這些文件複製到你的rime安裝目錄下的data文件夾下 - -然後在 data 目錄下編輯 default.yaml 添加,兩種五筆輸入方案,你任選一種 - -``` -schema_list: - - schema: wubi_pinyin - - schema: wubi6 -``` - -臨時修改的配置,無法立即生效,需要重新部署。 - -在你的安裝目錄下,找到`WeaselDeployer.exe` 點擊,選擇你想要添加的輸入方案,比如這裏選擇 五筆-拼音,再點中,然後選擇皮膚,最後就會觸發重新部署,配置生效。 - -![](http://image.python-online.cn/20200119143952.png) - diff --git a/source/c04/c04_23.rst b/source/c04/c04_23.rst deleted file mode 100644 index 08803c5..0000000 --- a/source/c04/c04_23.rst +++ /dev/null @@ -1,64 +0,0 @@ -4.23 电脑使用技巧 -================= - -|image0| - -1. 添加右键菜单 ---------------- - -参考 Sublime Text3的添加方法 - -在sublime的安装目录下新增一个文件:\ ``sublime_addright.inf``\ ,内容如下,然后右键选择安装。 - -:: - - [Version] - Signature="$Windows NT$" - - [DefaultInstall] - AddReg=SublimeText3 - - [SublimeText3] - hkcr,"*\\shell\\SublimeText3",,,"Edit with SublimeText3" - hkcr,"*\\shell\\SublimeText3\\command",,,"""%1%\sublime_text.exe"" ""%%1"" %%*" - hkcr,"Directory\shell\SublimeText3",,,"Edit with SublimeText3" - hkcr,"*\\shell\\SublimeText3","Icon",0x20000,"%1%\sublime_text.exe, 0" - hkcr,"Directory\shell\SublimeText3\command",,,"""%1%\sublime_text.exe"" ""%%1""" - -2. Rime五筆安裝方法 -------------------- - -下載地址:https://rime.im/download/ - -五筆配置:https://github.com/rime/rime-wubi - -下載下來是一個zip包,裏面都是五筆的配置文件。 - -:: - - wubi_pinyin.schema.yaml - wubi_trad.schema.yaml - wubi86.dict.yaml - wubi86.schema.yaml - -將這些文件複製到你的rime安裝目錄下的data文件夾下 - -然後在 data 目錄下編輯 default.yaml 添加,兩種五筆輸入方案,你任選一種 - -:: - - schema_list: - - schema: wubi_pinyin - - schema: wubi6 - -臨時修改的配置,無法立即生效,需要重新部署。 - -在你的安裝目錄下,找到\ ``WeaselDeployer.exe`` -點擊,選擇你想要添加的輸入方案,比如這裏選擇 -五筆-拼音,再點中,然後選擇皮膚,最後就會觸發重新部署,配置生效。 - -|image1| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20200119143952.png - diff --git a/source/c04/c04_24.md b/source/c04/c04_24.md deleted file mode 100644 index c85c759..0000000 --- a/source/c04/c04_24.md +++ /dev/null @@ -1,171 +0,0 @@ -# 4.24 Python “用户环境”的一次完美应用 - -![](http://image.iswbm.com/20200602135014.png) - -在之前写过一篇关于虚拟环境使用的文章 :[Python 虚拟环境使用指南](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485049&idx=1&sn=c16383d6cc91a7ed8254e344d994f101&chksm=e886669bdff1ef8d82aae3a231ef0651f82d5e97cf1e64aceda00e686119900518c202dc9b1b&scene=21#wechat_redirect). - -但是还没有好好的介绍一下 Python 的用户环境,原因是自己一直没遇到要使用 `用户环境` 的使用场景,所以就一直懒得写。 - -恰巧这两天,自己遇到了一个使用用户环境的体验可以完爆虚拟环境的案例,就拿出来分享一下。 - -## 1. 我的使用背景 - -公司有数以万计的服务器,为了对实现对访问记录进行集中管理以及出于安全考虑,每台服务器都有访问限制,必须使用公司的跳板机才能登陆。 - -每个公司的员工在跳板机上都有自己的用户、 家目录,对于很多需要 root 权限的操作,是高度受限制的。 - -比如我现在我要在跳板机上实现远程登陆大批量的机器进行一些维护工作,当然我这里使用的还是 Python 来实现,这个 Python 脚本里有一些依赖库(比如 之前介绍过的 paramiko 这个神器),在跳板机上中并没有安装。 - -![](http://image.iswbm.com/20200427180207.png) - -做为普通用户的你,是没有权限安装第三方包的。 - -![](http://image.iswbm.com/20200427180042.png) - -问题就来了,我如何才能在跳板机中使用 paramiko 这个包呢? - -## 2. 为何不使用虚拟环境? - -既然不能对全局的 Python 环境进行更改,那我完全可以自己再创建一个环境,只要这个环境里事先装好 paramiko 这个包不就好了。 - -因此,使用虚拟环境是一种解决方案,但它并不是一个完美的解决方案。 - -**原因有以下几点**: - -1、 创建虚拟环境的过程,步骤较多,比较复杂。这里的复杂是相对于我后面要使用的用户环境而言。 - -2、 虚拟环境是包含一整个 Python 解释器,存在大量与系统重复的包,size比较大,并不轻便。 - -3、 使用 console 模式调试的话,进入很不方便 - -![](http://image.iswbm.com/20200427182334.png) - -就算你不使用 console 模式,你调用脚本的方式,也会很奇怪,你得这样 - -```python -$ zabbix_env/bin/python demo.py -``` - -如果你不想使用这样,可以给这个脚本加个可执行权限,并在脚本的第一行指定你的解释器,省去了一点点麻烦,可即便如此,我仍然感觉很别扭。 - -```shell -[wangbm@35ha02 ~]$ cat demo.py -#!/home/wangbm/zabbix_env/bin/python - -import zabbix_api -[wangbm@35ha02 ~]$ -[wangbm@35ha02 ~]$ -[wangbm@35ha02 ~]$ chmod +x demo.py -[wangbm@35ha02 ~]$ -[wangbm@35ha02 ~]$ ./demo.py # 可以执行,没有报错 -[wangbm@35ha02 ~]$ -``` - - - -你可能会问我:为什么不使用 virtualenv + virtualenvwrapper ,这样可以使用 workon 进入虚拟环境。 - -原因是跳板机里的都是很古老的包,你看上面的 Python 还是 2.7.5 呢,所以你所说的那些工具通通没有。 - - - -## 3. 用户环境原理 - -这里要介绍的这种方案(**用户环境**),可能很多人都没有使用过,甚至没有听过,它算是一个冷门但是非常好用的功能。 - -操作之前 ,先简单介绍一下它。 - -先提一个问题,Python 在查找导入包时,如果我们多个路径都有这个包,那 Python 如何确定应该从哪个路径进行导入呢? - -答案是, 搜索导入路径是有优先级的,你可以通过 sys.path 进行查看。 - -```python ->>> import sys ->>> from pprint import pprint ->>> pprint(sys.path) -['', - '/usr/lib64/python27.zip', - '/usr/lib64/python2.7', - '/usr/lib64/python2.7/plat-linux2', - '/usr/lib64/python2.7/lib-tk', - '/usr/lib64/python2.7/lib-old', - '/usr/lib64/python2.7/lib-dynload', - '/home/wangbm/.local/lib/python2.7/site-packages', - '/usr/lib64/python2.7/site-packages', - '/usr/lib64/python2.7/site-packages/gtk-2.0', - '/usr/lib/python2.7/site-packages', - '/usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg', - '/usr/lib/python2.7/site-packages/lockfile-0.12.2-py2.7.egg'] ->>> -``` - -可以看到路径 `/home/wangbm/.local/lib/python2.7/site-packages` 是优先于 `/usr/lib64/python2.7/site-packages` 路径的。 - -这就是 **用户环境** 的原理,只要我们将包装在自己家目录下,就可以优先于全局环境中进行查找。 - -使用起来,可以做到用户无感知,跟使用原生的全局环境并没有区别。 - -## 4. 具体操作方法 - -创建一个用户环境,并安装上你所需要的包,一条命令就能搞定,这可比虚拟环境简单方便多了。 - -那么怎么操作呢? - -只要你在使用 pip 安装包时,加上 `--user` 参数,pip 就会将其安装在当前用户的 `~/.local/lib/python2.x/site-packages` 下,而其他用户的 python 则不会受影响。 - -```shell -$ pip install --user pkg -``` - -这里要注意的是,不能使用这种方式,亲测它会将包装到全局环境下,具体原因我还没有深究。 - -```shell -$ python -m pip install --user pkg -``` - -为了让你理解这个过程,我这里来举个例子,并且验证其是否可以做到用户隔离。 - -```shell -# 在全局环境中未安装 requests -[root@localhost ~]$ pip list | grep requests -[root@localhost ~]$ su - wangbm - -# 由于用户环境继承自全局环境,这里也未安装 -[wangbm@localhost ~]$ pip list | grep requests -[wangbm@localhost ~]$ pip install --user requests -[wangbm@localhost ~]$ pip list | grep requests -requests (2.22.0) -[wangbm@localhost ~]$ - -# 从 Location 属性可发现 requests 只安装在当前用户环境中 -[wangbm@localhost ~]$ pip show requests ---- -Metadata-Version: 2.1 -Name: requests -Version: 2.22.0 -Summary: Python HTTP for Humans. -Home-page: http://python-requests.org -Author: Kenneth Reitz -Author-email: me@kennethreitz.org -Installer: pip -License: Apache 2.0 -Location: /home/wangbm/.local/lib/python2.7/site-packages -[wangbm@localhost ~]$ exit -logout - -# 退出 wangbm 用户,在 root 用户环境中发现 requests 未安装 -[root@localhost ~]$ pip list | grep requests -[root@localhost ~]$ -``` - -有了这个思路,我就可以先在其他机器(前提自己必须拥有管理员权限 )上,创建一个用户环境,并且安装上 paramiko 这个包。 - -然后将这个用户环境,压缩拷贝至跳板机自己的家目录下的 `.local/lib` 目录下并解压。 - -然后直接使用 python 进入 console 模式,现在已经可以直接使用 paramiko 这个包了。 - -![](http://image.iswbm.com/20200427185854.png) - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c04/c04_24.rst b/source/c04/c04_24.rst deleted file mode 100644 index 9b8949a..0000000 --- a/source/c04/c04_24.rst +++ /dev/null @@ -1,197 +0,0 @@ -4.24 Python “用户环境”的一次完美应用 -==================================== - -|image0| - -在之前写过一篇关于虚拟环境使用的文章 :\ `Python -虚拟环境使用指南 `__. - -但是还没有好好的介绍一下 Python 的用户环境,原因是自己一直没遇到要使用 -``用户环境`` 的使用场景,所以就一直懒得写。 - -恰巧这两天,自己遇到了一个使用用户环境的体验可以完爆虚拟环境的案例,就拿出来分享一下。 - -1. 我的使用背景 ---------------- - -公司有数以万计的服务器,为了对实现对访问记录进行集中管理以及出于安全考虑,每台服务器都有访问限制,必须使用公司的跳板机才能登陆。 - -每个公司的员工在跳板机上都有自己的用户、 家目录,对于很多需要 root -权限的操作,是高度受限制的。 - -比如我现在我要在跳板机上实现远程登陆大批量的机器进行一些维护工作,当然我这里使用的还是 -Python 来实现,这个 Python 脚本里有一些依赖库(比如 之前介绍过的 -paramiko 这个神器),在跳板机上中并没有安装。 - -|image1| - -做为普通用户的你,是没有权限安装第三方包的。 - -|image2| - -问题就来了,我如何才能在跳板机中使用 paramiko 这个包呢? - -2. 为何不使用虚拟环境? ------------------------ - -既然不能对全局的 Python -环境进行更改,那我完全可以自己再创建一个环境,只要这个环境里事先装好 -paramiko 这个包不就好了。 - -因此,使用虚拟环境是一种解决方案,但它并不是一个完美的解决方案。 - -**原因有以下几点**\ : - -1、 -创建虚拟环境的过程,步骤较多,比较复杂。这里的复杂是相对于我后面要使用的用户环境而言。 - -2、 虚拟环境是包含一整个 Python -解释器,存在大量与系统重复的包,size比较大,并不轻便。 - -3、 使用 console 模式调试的话,进入很不方便 - -|image3| - -就算你不使用 console 模式,你调用脚本的方式,也会很奇怪,你得这样 - -.. code:: python - - $ zabbix_env/bin/python demo.py - -如果你不想使用这样,可以给这个脚本加个可执行权限,并在脚本的第一行指定你的解释器,省去了一点点麻烦,可即便如此,我仍然感觉很别扭。 - -.. code:: shell - - [wangbm@35ha02 ~]$ cat demo.py - #!/home/wangbm/zabbix_env/bin/python - - import zabbix_api - [wangbm@35ha02 ~]$ - [wangbm@35ha02 ~]$ - [wangbm@35ha02 ~]$ chmod +x demo.py - [wangbm@35ha02 ~]$ - [wangbm@35ha02 ~]$ ./demo.py # 可以执行,没有报错 - [wangbm@35ha02 ~]$ - -你可能会问我:为什么不使用 virtualenv + virtualenvwrapper ,这样可以使用 -workon 进入虚拟环境。 - -原因是跳板机里的都是很古老的包,你看上面的 Python 还是 2.7.5 -呢,所以你所说的那些工具通通没有。 - -3. 用户环境原理 ---------------- - -这里要介绍的这种方案(\ **用户环境**\ ),可能很多人都没有使用过,甚至没有听过,它算是一个冷门但是非常好用的功能。 - -操作之前 ,先简单介绍一下它。 - -先提一个问题,Python 在查找导入包时,如果我们多个路径都有这个包,那 -Python 如何确定应该从哪个路径进行导入呢? - -答案是, 搜索导入路径是有优先级的,你可以通过 sys.path 进行查看。 - -.. code:: python - - >>> import sys - >>> from pprint import pprint - >>> pprint(sys.path) - ['', - '/usr/lib64/python27.zip', - '/usr/lib64/python2.7', - '/usr/lib64/python2.7/plat-linux2', - '/usr/lib64/python2.7/lib-tk', - '/usr/lib64/python2.7/lib-old', - '/usr/lib64/python2.7/lib-dynload', - '/home/wangbm/.local/lib/python2.7/site-packages', - '/usr/lib64/python2.7/site-packages', - '/usr/lib64/python2.7/site-packages/gtk-2.0', - '/usr/lib/python2.7/site-packages', - '/usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg', - '/usr/lib/python2.7/site-packages/lockfile-0.12.2-py2.7.egg'] - >>> - -可以看到路径 ``/home/wangbm/.local/lib/python2.7/site-packages`` -是优先于 ``/usr/lib64/python2.7/site-packages`` 路径的。 - -这就是 **用户环境** -的原理,只要我们将包装在自己家目录下,就可以优先于全局环境中进行查找。 - -使用起来,可以做到用户无感知,跟使用原生的全局环境并没有区别。 - -4. 具体操作方法 ---------------- - -创建一个用户环境,并安装上你所需要的包,一条命令就能搞定,这可比虚拟环境简单方便多了。 - -那么怎么操作呢? - -只要你在使用 pip 安装包时,加上 ``--user`` 参数,pip -就会将其安装在当前用户的 ``~/.local/lib/python2.x/site-packages`` -下,而其他用户的 python 则不会受影响。 - -.. code:: shell - - $ pip install --user pkg - -这里要注意的是,不能使用这种方式,亲测它会将包装到全局环境下,具体原因我还没有深究。 - -.. code:: shell - - $ python -m pip install --user pkg - -为了让你理解这个过程,我这里来举个例子,并且验证其是否可以做到用户隔离。 - -.. code:: shell - - # 在全局环境中未安装 requests - [root@localhost ~]$ pip list | grep requests - [root@localhost ~]$ su - wangbm - - # 由于用户环境继承自全局环境,这里也未安装 - [wangbm@localhost ~]$ pip list | grep requests - [wangbm@localhost ~]$ pip install --user requests - [wangbm@localhost ~]$ pip list | grep requests - requests (2.22.0) - [wangbm@localhost ~]$ - - # 从 Location 属性可发现 requests 只安装在当前用户环境中 - [wangbm@localhost ~]$ pip show requests - --- - Metadata-Version: 2.1 - Name: requests - Version: 2.22.0 - Summary: Python HTTP for Humans. - Home-page: http://python-requests.org - Author: Kenneth Reitz - Author-email: me@kennethreitz.org - Installer: pip - License: Apache 2.0 - Location: /home/wangbm/.local/lib/python2.7/site-packages - [wangbm@localhost ~]$ exit - logout - - # 退出 wangbm 用户,在 root 用户环境中发现 requests 未安装 - [root@localhost ~]$ pip list | grep requests - [root@localhost ~]$ - -有了这个思路,我就可以先在其他机器(前提自己必须拥有管理员权限 -)上,创建一个用户环境,并且安装上 paramiko 这个包。 - -然后将这个用户环境,压缩拷贝至跳板机自己的家目录下的 ``.local/lib`` -目录下并解压。 - -然后直接使用 python 进入 console 模式,现在已经可以直接使用 paramiko -这个包了。 - -|image4| - -|image5| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200427180207.png -.. |image2| image:: http://image.iswbm.com/20200427180042.png -.. |image3| image:: http://image.iswbm.com/20200427182334.png -.. |image4| image:: http://image.iswbm.com/20200427185854.png -.. |image5| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c05/c05_01.md b/source/c05/c05_01.md index 0abc12c..30bed32 100644 --- a/source/c05/c05_01.md +++ b/source/c05/c05_01.md @@ -19,7 +19,7 @@ **排序原理** :选取一个数组中的某个数,将整个数组分为两个子数组(小于此数的为一组,大于此数的为一组)。然后对分出的子数组,重复以上步骤。 **原理图解**: -![快速排序](http://image.python-online.cn/Fpj4DFN_YCtfmJwb_85QnsuIVLqk) +![快速排序](http://image.iswbm.com/Fpj4DFN_YCtfmJwb_85QnsuIVLqk) 代码实现: @@ -50,7 +50,7 @@ def quick_sort(array, reverse=False): **排序原理** :对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素“浮”到顶端,最终达到完全有序。 **原理图解** : -![|冒泡排序|](http://image.python-online.cn/FvbrVECeq58hY8TptG4ilkL5Owcc) +![|冒泡排序|](http://image.iswbm.com/FvbrVECeq58hY8TptG4ilkL5Owcc) 代码实现: @@ -81,7 +81,7 @@ def bubble_sort(array, reverse=False): **排序原理** :遍历n趟(n为数组的长度),每一趟都从「待排序」的元素中排出最小(或最大)的元素。 **原理图解** : -![](http://image.python-online.cn/FmZ_24t62gF32Dg3AgtZe-U5OuLY) +![](http://image.iswbm.com/FmZ_24t62gF32Dg3AgtZe-U5OuLY) 代码实现: ```python @@ -110,7 +110,7 @@ def select_sort(array, reverse=False): **排序原理** :每次将一个待排序的元素与已排序的元素进行逐一比较,直到找到合适的位置按大小插入。通俗地说,就类似我们打牌的时候,给牌进行排序。 **原理图解** : -![](http://image.python-online.cn/FmLrNuhfNcnYnLGoYJv-YbpBPV7n) +![](http://image.iswbm.com/FmLrNuhfNcnYnLGoYJv-YbpBPV7n) 代码实现: ```python @@ -154,8 +154,8 @@ def insert_sort(array): **原理图解**: -![](http://image.python-online.cn/Fm44FD0KE9Y4RM7MF3knlGVCJba4) -![希尔排序](http://image.python-online.cn/FqTP6YjNoM52fA-bA8pSPdbLgcZh) +![](http://image.iswbm.com/Fm44FD0KE9Y4RM7MF3knlGVCJba4) +![希尔排序](http://image.iswbm.com/FqTP6YjNoM52fA-bA8pSPdbLgcZh) **代码实现**: @@ -203,7 +203,7 @@ def shell_sort(array, reverse=False): 其实难点,在于如何在剩余堆里,找到最大数(或者最小数)。 **原理图解**: -![堆排序](http://image.python-online.cn/FgRFOfPhrL0yeUuGzly5309APCnD) +![堆排序](http://image.iswbm.com/FgRFOfPhrL0yeUuGzly5309APCnD) **代码实现**: @@ -283,11 +283,11 @@ def build_min_heap(arr, start, end): 第一次接触到这个分治思想,是在《算法图解》这本书里,里面举的一个「分割土地」的例子非常生动形象。精髓就是,不断将问题的规模缩小化,然后逐步往上解决问题。 **原理图解**: -![分而治之思想](http://image.python-online.cn/FjQebwFfa2tDYS78_CUxy1rXmufj) +![分而治之思想](http://image.iswbm.com/FjQebwFfa2tDYS78_CUxy1rXmufj) 重点其实是这个“治”的过程,如何实现将两个有序数组合并起来?思路大概是这样的。 -![](http://image.python-online.cn/FjP-_a66OUAZtTpU1ytqZ66My80C) -![合并两个有序数组](http://image.python-online.cn/FnCZ-3Pj39T_ELROSsGRDN31yWtY) +![](http://image.iswbm.com/FjP-_a66OUAZtTpU1ytqZ66My80C) +![合并两个有序数组](http://image.iswbm.com/FnCZ-3Pj39T_ELROSsGRDN31yWtY) **代码实现**: @@ -329,7 +329,7 @@ def merge(left, right): 桶排序是有局限性的,一般情况下,他并不能对有负数或者有小数的数组进行排序。另一方面,在无法预知数组的真实情况下,其实排序性能是非常不稳定的。比如,你可能遇到这样一个数组[1,4,5,1000000],按照桶算法以下面的代码运行,你需要1000000个桶,非常慢,而实际上,这个数组很小,使用任意比较排序算法很快就能结果。 **原理图解**: -![桶排序](http://image.python-online.cn/FljGa3F3wM_YGACETeuRHCiERXKb) +![桶排序](http://image.iswbm.com/FljGa3F3wM_YGACETeuRHCiERXKb) **代码实现**: ```python @@ -379,7 +379,7 @@ def bucket_sort(array, reverse=False): 这里图解下,正序的过程。 原始数组:22, 33, 43, 55, 14, 28, 65, 39, 81, 33, 100 -![基数排序](http://image.python-online.cn/FhwWVp4LVABIMPHqNo_cpjJA9kHV) +![基数排序](http://image.iswbm.com/FhwWVp4LVABIMPHqNo_cpjJA9kHV) **代码实现**: ```python diff --git a/source/c05/c05_01.rst b/source/c05/c05_01.rst old mode 100755 new mode 100644 index 2f1233f..210f381 --- a/source/c05/c05_01.rst +++ b/source/c05/c05_01.rst @@ -424,17 +424,17 @@ sgnificant digital),LSD 的排序方式由键值的最右边开始,而 MSD |image13| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |快速排序| image:: http://image.python-online.cn/Fpj4DFN_YCtfmJwb_85QnsuIVLqk -.. |\|冒泡排序\|| image:: http://image.python-online.cn/FvbrVECeq58hY8TptG4ilkL5Owcc -.. |image3| image:: http://image.python-online.cn/FmZ_24t62gF32Dg3AgtZe-U5OuLY -.. |image4| image:: http://image.python-online.cn/FmLrNuhfNcnYnLGoYJv-YbpBPV7n -.. |image5| image:: http://image.python-online.cn/Fm44FD0KE9Y4RM7MF3knlGVCJba4 -.. |希尔排序| image:: http://image.python-online.cn/FqTP6YjNoM52fA-bA8pSPdbLgcZh -.. |堆排序| image:: http://image.python-online.cn/FgRFOfPhrL0yeUuGzly5309APCnD -.. |分而治之思想| image:: http://image.python-online.cn/FjQebwFfa2tDYS78_CUxy1rXmufj -.. |image9| image:: http://image.python-online.cn/FjP-_a66OUAZtTpU1ytqZ66My80C -.. |合并两个有序数组| image:: http://image.python-online.cn/FnCZ-3Pj39T_ELROSsGRDN31yWtY -.. |桶排序| image:: http://image.python-online.cn/FljGa3F3wM_YGACETeuRHCiERXKb -.. |基数排序| image:: http://image.python-online.cn/FhwWVp4LVABIMPHqNo_cpjJA9kHV +.. |快速排序| image:: http://image.iswbm.com/Fpj4DFN_YCtfmJwb_85QnsuIVLqk +.. |\|冒泡排序\|| image:: http://image.iswbm.com/FvbrVECeq58hY8TptG4ilkL5Owcc +.. |image3| image:: http://image.iswbm.com/FmZ_24t62gF32Dg3AgtZe-U5OuLY +.. |image4| image:: http://image.iswbm.com/FmLrNuhfNcnYnLGoYJv-YbpBPV7n +.. |image5| image:: http://image.iswbm.com/Fm44FD0KE9Y4RM7MF3knlGVCJba4 +.. |希尔排序| image:: http://image.iswbm.com/FqTP6YjNoM52fA-bA8pSPdbLgcZh +.. |堆排序| image:: http://image.iswbm.com/FgRFOfPhrL0yeUuGzly5309APCnD +.. |分而治之思想| image:: http://image.iswbm.com/FjQebwFfa2tDYS78_CUxy1rXmufj +.. |image9| image:: http://image.iswbm.com/FjP-_a66OUAZtTpU1ytqZ66My80C +.. |合并两个有序数组| image:: http://image.iswbm.com/FnCZ-3Pj39T_ELROSsGRDN31yWtY +.. |桶排序| image:: http://image.iswbm.com/FljGa3F3wM_YGACETeuRHCiERXKb +.. |基数排序| image:: http://image.iswbm.com/FhwWVp4LVABIMPHqNo_cpjJA9kHV .. |image13| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c05/c05_02.rst b/source/c05/c05_02.rst old mode 100755 new mode 100644 diff --git a/source/c05/c05_03.md b/source/c05/c05_03.md index 12445d6..798dad7 100644 --- a/source/c05/c05_03.md +++ b/source/c05/c05_03.md @@ -104,7 +104,7 @@ echo -n hello | md5sum 彩虹表确实像它的名字一样美好,至少黑客眼里是这样。下表是7位以内密码在不同字符集下构造出的彩虹表的情况,彩虹表中哈希链的长度和个数随着字符集的增长而增长,彩虹表的大小和生成时间也随之成倍增加。7位数字组合在彩虹表面前简直就是秒破,即使最复杂的7位密码不到一个小时就能破解,如果采用普通的暴力攻击,破解时间可能需要三周。 -![](http://image.python-online.cn/20190112181126.png) +![](http://image.iswbm.com/20190112181126.png) diff --git a/source/c05/c05_03.rst b/source/c05/c05_03.rst old mode 100755 new mode 100644 index 2de0e8d..6b47675 --- a/source/c05/c05_03.rst +++ b/source/c05/c05_03.rst @@ -164,6 +164,6 @@ B 可以读取和更改用户 A 的信息,这无疑带来了很大的安全隐 |image2| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190112181126.png +.. |image1| image:: http://image.iswbm.com/20190112181126.png .. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c06/c06_01.md b/source/c06/c06_01.md index a76e1b3..6e403b1 100644 --- a/source/c06/c06_01.md +++ b/source/c06/c06_01.md @@ -110,7 +110,7 @@ plt.show() 以上的注释,可以说是很直白啦。一张图表该有的东西都有了,不花哨,但实用。 看看我们的代码输出的图表是啥样的。 -![](http://image.python-online.cn/20190511164650.png) +![](http://image.iswbm.com/20190511164650.png) ---- ![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c06/c06_01.rst b/source/c06/c06_01.rst old mode 100755 new mode 100644 index 9fd8245..121250c --- a/source/c06/c06_01.rst +++ b/source/c06/c06_01.rst @@ -137,6 +137,6 @@ ticks(由Locator对象定义),还有ticklabel(由Formatter对象定义 .. |image0| image:: http://image.iswbm.com/20200602135014.png .. |image1| image:: https://i.loli.net/2018/08/12/5b6ff3716fdc0.png -.. |image2| image:: http://image.python-online.cn/20190511164650.png +.. |image2| image:: http://image.iswbm.com/20190511164650.png .. |image3| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c06/c06_02.md b/source/c06/c06_02.md index be1ee80..864efae 100644 --- a/source/c06/c06_02.md +++ b/source/c06/c06_02.md @@ -35,7 +35,7 @@ plt.show() ``` show image -![](http://image.python-online.cn/20190511164738.png) +![](http://image.iswbm.com/20190511164738.png) ## 02. 散点图 @@ -54,7 +54,7 @@ plt.plot(x, x, 'r--', x, x**2, 'bs', x, x**3, 'g^') plt.show() ``` show image -![](http://image.python-online.cn/20190511164753.png) +![](http://image.iswbm.com/20190511164753.png) ## 03. 直方图 @@ -95,7 +95,7 @@ plt.show() show image -![](http://image.python-online.cn/20190511164802.png) +![](http://image.iswbm.com/20190511164802.png) ## 04. 柱状图 @@ -128,7 +128,7 @@ plt.show() ``` show image -![](http://image.python-online.cn/20190511164814.png) +![](http://image.iswbm.com/20190511164814.png) ### 4.2 叠加柱状图 ```python @@ -154,7 +154,7 @@ plt.show() ``` show image -![](http://image.python-online.cn/20190511164825.png) +![](http://image.iswbm.com/20190511164825.png) ## 05. 饼图 @@ -180,7 +180,7 @@ plt.show() ``` show image -![](http://image.python-online.cn/20190511164835.png) +![](http://image.iswbm.com/20190511164835.png) ### 5.2 嵌套饼图 @@ -214,7 +214,7 @@ plt.axis('equal') plt.show() ``` show image -![](http://image.python-online.cn/20190511164843.png) +![](http://image.iswbm.com/20190511164843.png) ### 5.3 极轴饼图 @@ -245,7 +245,7 @@ plt.show() ``` show image -![](http://image.python-online.cn/20190511164852.png) +![](http://image.iswbm.com/20190511164852.png) ## 06. 三维图 @@ -272,7 +272,7 @@ plt.show() ``` show image -![](http://image.python-online.cn/20190511164900.png) +![](http://image.iswbm.com/20190511164900.png) ### 6.2 绘制三维平面图 @@ -295,7 +295,7 @@ ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='rainbow') plt.show() ``` show image -![](http://image.python-online.cn/20190511164915.png) +![](http://image.iswbm.com/20190511164915.png) --- diff --git a/source/c06/c06_02.rst b/source/c06/c06_02.rst old mode 100755 new mode 100644 index d7e5895..11dbcf0 --- a/source/c06/c06_02.rst +++ b/source/c06/c06_02.rst @@ -314,15 +314,15 @@ show image |image10| |image11| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190511164738.png -.. |image2| image:: http://image.python-online.cn/20190511164753.png -.. |image3| image:: http://image.python-online.cn/20190511164802.png -.. |image4| image:: http://image.python-online.cn/20190511164814.png -.. |image5| image:: http://image.python-online.cn/20190511164825.png -.. |image6| image:: http://image.python-online.cn/20190511164835.png -.. |image7| image:: http://image.python-online.cn/20190511164843.png -.. |image8| image:: http://image.python-online.cn/20190511164852.png -.. |image9| image:: http://image.python-online.cn/20190511164900.png -.. |image10| image:: http://image.python-online.cn/20190511164915.png +.. |image1| image:: http://image.iswbm.com/20190511164738.png +.. |image2| image:: http://image.iswbm.com/20190511164753.png +.. |image3| image:: http://image.iswbm.com/20190511164802.png +.. |image4| image:: http://image.iswbm.com/20190511164814.png +.. |image5| image:: http://image.iswbm.com/20190511164825.png +.. |image6| image:: http://image.iswbm.com/20190511164835.png +.. |image7| image:: http://image.iswbm.com/20190511164843.png +.. |image8| image:: http://image.iswbm.com/20190511164852.png +.. |image9| image:: http://image.iswbm.com/20190511164900.png +.. |image10| image:: http://image.iswbm.com/20190511164915.png .. |image11| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c06/c06_03.md b/source/c06/c06_03.md index 6072c64..bd7e68c 100644 --- a/source/c06/c06_03.md +++ b/source/c06/c06_03.md @@ -25,7 +25,7 @@ plot(x,S) show() ``` show image -![](http://image.python-online.cn/20190511164936.png) +![](http://image.iswbm.com/20190511164936.png) ## 6.3.2 设置基本元素 @@ -65,7 +65,7 @@ plt.legend() plt.show() ``` show image -![](http://image.python-online.cn/20190511164949.png) +![](http://image.iswbm.com/20190511164949.png) ## 6.3.3 移动轴线 还记得我们在初高中学习的三角函数图象,可不是这样,它应该是有四个象限的。而这里却是一个四四方方的图表。 @@ -89,7 +89,7 @@ ax.spines['bottom'].set_position(('data',0)) ax.spines['left'].set_position(('data',0)) ``` 关于`set_position()`这个函数中的data是啥意思?我查了下官网。解释如下 -![](http://image.python-online.cn/20190511165003.png) +![](http://image.iswbm.com/20190511165003.png) 然后最后发现,上面的写法可以用一定更简洁的方式设置,是等价的。 ```python ax.spines['bottom'].set_position('zero') @@ -97,7 +97,7 @@ ax.spines['left'].set_position('zero') ``` show image -![](http://image.python-online.cn/20190511165013.png) +![](http://image.iswbm.com/20190511165013.png) ## 6.3.4 添加注释 现在的图形部分已经成型,接下让我们现在使用annotate命令注解一些我们感兴趣的点。 @@ -137,7 +137,7 @@ plt.annotate(r'$cos(\frac{2\pi}{3})=-\frac{1}{2}$', 第七个参数,`arrowprops`,对箭头的类型的一些设置。 show image -![](http://image.python-online.cn/20190511165020.png) +![](http://image.iswbm.com/20190511165020.png) ## 6.3.5 完整代码 diff --git a/source/c06/c06_03.rst b/source/c06/c06_03.rst old mode 100755 new mode 100644 index c5bd3db..09ce0dc --- a/source/c06/c06_03.rst +++ b/source/c06/c06_03.rst @@ -210,10 +210,10 @@ show image |image5| |image6| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190511164936.png -.. |image2| image:: http://image.python-online.cn/20190511164949.png -.. |image3| image:: http://image.python-online.cn/20190511165003.png -.. |image4| image:: http://image.python-online.cn/20190511165013.png -.. |image5| image:: http://image.python-online.cn/20190511165020.png +.. |image1| image:: http://image.iswbm.com/20190511164936.png +.. |image2| image:: http://image.iswbm.com/20190511164949.png +.. |image3| image:: http://image.iswbm.com/20190511165003.png +.. |image4| image:: http://image.iswbm.com/20190511165013.png +.. |image5| image:: http://image.iswbm.com/20190511165020.png .. |image6| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c06/c06_04.md b/source/c06/c06_04.md index 133413c..90c027a 100644 --- a/source/c06/c06_04.md +++ b/source/c06/c06_04.md @@ -19,7 +19,7 @@ plt.subplot(2, 2, 1) ``` 是将当前图像(figure)按 2 行 2 列的布局进行分割,然后取索引为 1 的子图。注意 matlibplot 是完全借鉴了 MATLAB 的思想,所以的起始索引为 1,不像 Python 的起始索引为 0。 -![](http://image.python-online.cn/20190511165103.png) +![](http://image.iswbm.com/20190511165103.png) 他有好几种写法,这里写我在官网学到的几个方法。 @@ -59,7 +59,7 @@ plt.subplot(212) plt.plot(t2, np.cos(2*np.pi*t2), 'r--') plt.show() ``` -![](http://image.python-online.cn/20190511165132.png) +![](http://image.iswbm.com/20190511165132.png) @@ -68,7 +68,7 @@ plt.show() 子图(axes),和子区(subplot)非常相似,一个子图可能是由一个或多个子区域构成的。它比子区更加灵活。 它可以是这样 -![](http://image.python-online.cn/20190511165152.png) +![](http://image.iswbm.com/20190511165152.png) 要实现如上这个效果。常用的有两种方法。 @@ -95,7 +95,7 @@ ax5 = plt.subplot(gs[-1, -2]) 这个比较规则的划分我们举个例子看看。 -![](http://image.python-online.cn/20190511165159.png) +![](http://image.iswbm.com/20190511165159.png) 代码如下: ```python @@ -128,10 +128,10 @@ plt.show() 为什么说,子图的灵活性更高呢,因为它允许把图片放置到图像(figure)中的任何地方(如下图)。所以如果我们想要在一个大图片中嵌套一个小点的图片,我们通过子图(axes)来完成它。 -![](http://image.python-online.cn/20190511165211.png) +![](http://image.iswbm.com/20190511165211.png) 图中的 axes 是如何实现的,刚开始我也有点懵逼,在查阅了官方文档后,我才明白。 -![](http://image.python-online.cn/20190511165221.png) +![](http://image.iswbm.com/20190511165221.png) `left` 是指,离左边界的距离。 `bottom` 是指,离底边的距离。 @@ -145,7 +145,7 @@ plt.show() 同样地,这个我们也来看一个例子。 这个图的亮点,在于中间,多了两个子图,就像往图中贴上了两个插画一样。 -![](http://image.python-online.cn/20190511165229.png) +![](http://image.iswbm.com/20190511165229.png) 那么这个如何实现呢? ```python diff --git a/source/c06/c06_04.rst b/source/c06/c06_04.rst old mode 100755 new mode 100644 index 7e2b655..3b08843 --- a/source/c06/c06_04.rst +++ b/source/c06/c06_04.rst @@ -204,12 +204,12 @@ subplot,一个是axes。这两个概念将贯穿整个 matplotlib |image8| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190511165103.png -.. |image2| image:: http://image.python-online.cn/20190511165132.png -.. |image3| image:: http://image.python-online.cn/20190511165152.png -.. |image4| image:: http://image.python-online.cn/20190511165159.png -.. |image5| image:: http://image.python-online.cn/20190511165211.png -.. |image6| image:: http://image.python-online.cn/20190511165221.png -.. |image7| image:: http://image.python-online.cn/20190511165229.png +.. |image1| image:: http://image.iswbm.com/20190511165103.png +.. |image2| image:: http://image.iswbm.com/20190511165132.png +.. |image3| image:: http://image.iswbm.com/20190511165152.png +.. |image4| image:: http://image.iswbm.com/20190511165159.png +.. |image5| image:: http://image.iswbm.com/20190511165211.png +.. |image6| image:: http://image.iswbm.com/20190511165221.png +.. |image7| image:: http://image.iswbm.com/20190511165229.png .. |image8| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c06/c06_05.rst b/source/c06/c06_05.rst old mode 100755 new mode 100644 diff --git a/source/c06/c06_06.md b/source/c06/c06_06.md index b5e274d..cbba0e1 100644 --- a/source/c06/c06_06.md +++ b/source/c06/c06_06.md @@ -10,7 +10,7 @@ 由于我使用的是 Anaconda ,我需要将其安装到我的环境中。首先打开我们的命令行(注意不是一般的CMD),使用windows 的查找入口: -![](http://image.python-online.cn/20190511165315.png) +![](http://image.iswbm.com/20190511165315.png) 然后执行如下命令安装 ``` diff --git a/source/c06/c06_06.rst b/source/c06/c06_06.rst old mode 100755 new mode 100644 index 6a76a83..2e97d4a --- a/source/c06/c06_06.rst +++ b/source/c06/c06_06.rst @@ -142,6 +142,6 @@ Jupyter NoteBook 里观察整个变化的过程。 |image2| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190511165315.png +.. |image1| image:: http://image.iswbm.com/20190511165315.png .. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/c07_01.md b/source/c07/c07_01.md index ae97523..8abe598 100644 --- a/source/c07/c07_01.md +++ b/source/c07/c07_01.md @@ -479,7 +479,7 @@ nl -b a -n rz -w 3 text 搜索并筛选显示结果。 该命令经常配合管道命令来控制输出。 以下 是常用的选项: -![](http://image.python-online.cn/17-9-20/47469030.jpg) +![](http://image.iswbm.com/17-9-20/47469030.jpg) 【非常好用:不打开文件,直接搜索指定目录下文件内的内容】 ``` @@ -671,7 +671,7 @@ $ ftp ftp> help ``` -![](http://image.python-online.cn/20190705182629.png) +![](http://image.iswbm.com/20190705182629.png) @@ -799,7 +799,7 @@ Linux的分区的过程经历以下几个步骤 3. 挂载:将分区挂载到目录上,才能访问数据 ``` 关于硬件对应的设备文件名,可以参照下图 -![](http://image.python-online.cn/17-10-15/97911325.jpg) +![](http://image.iswbm.com/17-10-15/97911325.jpg) 其中以硬盘为例来说明 ``` diff --git a/source/c07/c07_01.rst b/source/c07/c07_01.rst old mode 100755 new mode 100644 index dd8dcd4..dab1083 --- a/source/c07/c07_01.rst +++ b/source/c07/c07_01.rst @@ -1940,8 +1940,8 @@ url** 即可。 |image4| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/17-9-20/47469030.jpg -.. |image2| image:: http://image.python-online.cn/20190705182629.png -.. |image3| image:: http://image.python-online.cn/17-10-15/97911325.jpg +.. |image1| image:: http://image.iswbm.com/17-9-20/47469030.jpg +.. |image2| image:: http://image.iswbm.com/20190705182629.png +.. |image3| image:: http://image.iswbm.com/17-10-15/97911325.jpg .. |image4| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/c07_02.md b/source/c07/c07_02.md index 9475501..93fd94a 100644 --- a/source/c07/c07_02.md +++ b/source/c07/c07_02.md @@ -8,11 +8,11 @@ zabbix 的使用架构主要有两种,如果你的监控环境比较单一,可以使用简单的` Server` -> `Agent `,由zabbix-agent 直接上报给 zabbix-server。 -![](http://image.python-online.cn/20190404193811.png) +![](http://image.iswbm.com/20190404193811.png) 如果监控的环境比较复杂,就比如我们的线上的生产环境是分布式集群,最好使用的 ` Server`-> `Proxy` -> `Agent ` -![](http://image.python-online.cn/20190404194416.png) +![](http://image.iswbm.com/20190404194416.png) Zabbix Proxy 可以代替 Zabbix Server 检索客户端的数据,然后把数据汇报给 Zabbix Server, 并且在一定程度上分担了 Zabbix Server 的压力。Zabbix Proxy 可以非常简便的实现了集中式、分布式监控。 @@ -207,7 +207,7 @@ $ systemctl start zabbix-proxy 在使用 Proxy 的时候,需要在 Server 端的web 界面上,先注册一下。 -![](http://image.python-online.cn/20190404201313.png) +![](http://image.iswbm.com/20190404201313.png) @@ -256,9 +256,9 @@ ServerActive=172.20.20.200 # proxy 的ip,用内网和公网vip都可以 按照如下图点击,Event source注意选择`Auto registration` -![](http://image.python-online.cn/20190404205221.png) +![](http://image.iswbm.com/20190404205221.png) -![](http://image.python-online.cn/20190404205617.png) +![](http://image.iswbm.com/20190404205617.png) @@ -266,15 +266,15 @@ ServerActive=172.20.20.200 # proxy 的ip,用内网和公网vip都可以 一般情况下,我们的监控项,不会独立存在,而是依托于模板而存在。所以我们在创建监控项前,要首先创建一个模板。 -![](http://image.python-online.cn/20190404202122.png) +![](http://image.iswbm.com/20190404202122.png) 创建完模板后,点进模板的 items 按钮,正常情况下,这里会有很多监控项,但由于我们还没有创建,所以现在一个也没有。现在你可以自己点右上角(`Create Item`)自己创建一个,创建界面是这样的 -![](http://image.python-online.cn/20190404202353.png) +![](http://image.iswbm.com/20190404202353.png) 这里说一下,zabbix 自带有许多的模板,其实对于一些党规的监控项(说白了,就是zabbix给我们造好了轮子),这些模板已经足够了。这边只截了一小部分。 -![](http://image.python-online.cn/20190404210213.png) +![](http://image.iswbm.com/20190404210213.png) 如果以上这些不能满足你的需要,也没有关系,你也可以自己写脚本获取监控数据。 @@ -288,7 +288,7 @@ UserParameter=openstack.service.status[*],sh /usr/lib/zabbix/externalscripts/isA 那我在 web 界面上配置 监控项,就可以这样写 -![](http://image.python-online.cn/20190404213125.png) +![](http://image.iswbm.com/20190404213125.png) openstack-nova-api 是一个服务名,它将作为一个参数,传递给`isActive.sh` 这个脚本。 @@ -303,11 +303,11 @@ openstack-nova-api 是一个服务名,它将作为一个参数,传递给`isA 你可以通过点击下图的操作,查看最近上报的数据(我这里选择value直接查看值,你可以选择Graph,按图表的形式查看) -![](http://image.python-online.cn/20190404202855.png) +![](http://image.iswbm.com/20190404202855.png) 数据是这样的。 -![](http://image.python-online.cn/20190404202937.png) +![](http://image.iswbm.com/20190404202937.png) @@ -315,11 +315,11 @@ openstack-nova-api 是一个服务名,它将作为一个参数,传递给`isA 假如,我们要监控当 CPU 使用率超过90% 就发个通知邮件,那我们就要新增一个发邮件的动作。 -![](http://image.python-online.cn/20190404203425.png) +![](http://image.iswbm.com/20190404203425.png) 然后点击 `Operations`, 添加触发的动作类型,比如发邮件 -![](http://image.python-online.cn/20190404203805.png) +![](http://image.iswbm.com/20190404203805.png) 其中的HTML样式,也是我网上找来的,我觉得还挺不错,这里也贴出来 @@ -355,7 +355,7 @@ openstack-nova-api 是一个服务名,它将作为一个参数,传递给`isA 你有没有看到,旁边有个`Recovery operation` 按钮,它是说当我们的问题解决后,要让zabbix做些什么,比如我想让 当CPU的使用率降下来后,发一个邮件通知一下。 -![](http://image.python-online.cn/20190404204212.png) +![](http://image.iswbm.com/20190404204212.png) 邮件的 HTML 样式 @@ -391,7 +391,7 @@ openstack-nova-api 是一个服务名,它将作为一个参数,传递给`isA 上面邮件中,有设置了一些颜色的自定义宏,我是在这里设置的。 -![](http://image.python-online.cn/20190404205837.png) +![](http://image.iswbm.com/20190404205837.png) 上面的动作,都是写的发邮件,也可以远程执行脚本,比如,我们监控服务,当服务被人为关闭了,我们可以让Zabbix Agent执行重启服务的命令。 @@ -420,7 +420,7 @@ zabbix ALL=NOPASSWD: ALL 那在ACTION 里如何配置呢,看下图。你可以选择在 agent或者proxy,或者server执行都可以,非常灵活。 -![](http://image.python-online.cn/20190404212423.png) +![](http://image.iswbm.com/20190404212423.png) ### 7.2.3.5 配置发件人 @@ -428,7 +428,7 @@ Zabbix 要能发送邮件,需要配置邮箱发送方。这里以163邮箱为 vim /etc/mail.rc -![](http://image.python-online.cn/20190411205822.png) +![](http://image.iswbm.com/20190411205822.png) 配置完成后,可用以下命令测试一下配置是否有效,若 yyy@163.com 能收到一条来自xxx@163.com 的邮件,则说明配置成功。 @@ -459,7 +459,7 @@ rm -rf /tmp/mailtmp.txt 先添加媒介:Administration - Media Types - Create media type -![](http://image.python-online.cn/20190417202834.png) +![](http://image.iswbm.com/20190417202834.png) 上面用到 sendmail.sh 是一个脚本,需要我们来自己写,内容就是如何将我们的告警内容发送出去。 @@ -477,11 +477,11 @@ chmod +x sendmail.sh 然后再创建用户,并添加邮箱:Administration - Users - Admin - Media -![](http://image.python-online.cn/20190404204534.png) +![](http://image.iswbm.com/20190404204534.png) 在创建用户的时候,要指定用户组,要注意这个用户组的权限一定要有相应主机组的权限,否则无法发送告警邮件。 -![](http://image.python-online.cn/20190605173956.png) +![](http://image.iswbm.com/20190605173956.png) 配置好收件人后 ,发件人呢? @@ -503,7 +503,7 @@ service postfix stop zabbix 自带 mysql 的监控模板,监控项不多,只有 14 项。 -![](http://image.python-online.cn/20190409103417.png) +![](http://image.iswbm.com/20190409103417.png) 这个模板在使用前,需要进行两个配置。 @@ -529,7 +529,7 @@ port= 其实这个`.my.cnf`文件 可以放在任何地方,只要你在 `userparameter_mysql.conf` 能够及时将配置文件路径修改过来就行。如下图所示,你只要修改下方 `HOME` 变量值。 -![](http://image.python-online.cn/20190409104026.png) +![](http://image.iswbm.com/20190409104026.png) 然后记得去 web界面在 该host主机上link到 `Template DB MySQL` 这个模板上。 diff --git a/source/c07/c07_02.rst b/source/c07/c07_02.rst old mode 100755 new mode 100644 index 07c75fb..513c842 --- a/source/c07/c07_02.rst +++ b/source/c07/c07_02.rst @@ -698,27 +698,27 @@ float ,log, text 等,所以计算存在一定的误差,需留有冗余 |image23| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190404193811.png -.. |image2| image:: http://image.python-online.cn/20190404194416.png -.. |image3| image:: http://image.python-online.cn/20190404201313.png -.. |image4| image:: http://image.python-online.cn/20190404205221.png -.. |image5| image:: http://image.python-online.cn/20190404205617.png -.. |image6| image:: http://image.python-online.cn/20190404202122.png -.. |image7| image:: http://image.python-online.cn/20190404202353.png -.. |image8| image:: http://image.python-online.cn/20190404210213.png -.. |image9| image:: http://image.python-online.cn/20190404213125.png -.. |image10| image:: http://image.python-online.cn/20190404202855.png -.. |image11| image:: http://image.python-online.cn/20190404202937.png -.. |image12| image:: http://image.python-online.cn/20190404203425.png -.. |image13| image:: http://image.python-online.cn/20190404203805.png -.. |image14| image:: http://image.python-online.cn/20190404204212.png -.. |image15| image:: http://image.python-online.cn/20190404205837.png -.. |image16| image:: http://image.python-online.cn/20190404212423.png -.. |image17| image:: http://image.python-online.cn/20190411205822.png -.. |image18| image:: http://image.python-online.cn/20190417202834.png -.. |image19| image:: http://image.python-online.cn/20190404204534.png -.. |image20| image:: http://image.python-online.cn/20190605173956.png -.. |image21| image:: http://image.python-online.cn/20190409103417.png -.. |image22| image:: http://image.python-online.cn/20190409104026.png +.. |image1| image:: http://image.iswbm.com/20190404193811.png +.. |image2| image:: http://image.iswbm.com/20190404194416.png +.. |image3| image:: http://image.iswbm.com/20190404201313.png +.. |image4| image:: http://image.iswbm.com/20190404205221.png +.. |image5| image:: http://image.iswbm.com/20190404205617.png +.. |image6| image:: http://image.iswbm.com/20190404202122.png +.. |image7| image:: http://image.iswbm.com/20190404202353.png +.. |image8| image:: http://image.iswbm.com/20190404210213.png +.. |image9| image:: http://image.iswbm.com/20190404213125.png +.. |image10| image:: http://image.iswbm.com/20190404202855.png +.. |image11| image:: http://image.iswbm.com/20190404202937.png +.. |image12| image:: http://image.iswbm.com/20190404203425.png +.. |image13| image:: http://image.iswbm.com/20190404203805.png +.. |image14| image:: http://image.iswbm.com/20190404204212.png +.. |image15| image:: http://image.iswbm.com/20190404205837.png +.. |image16| image:: http://image.iswbm.com/20190404212423.png +.. |image17| image:: http://image.iswbm.com/20190411205822.png +.. |image18| image:: http://image.iswbm.com/20190417202834.png +.. |image19| image:: http://image.iswbm.com/20190404204534.png +.. |image20| image:: http://image.iswbm.com/20190605173956.png +.. |image21| image:: http://image.iswbm.com/20190409103417.png +.. |image22| image:: http://image.iswbm.com/20190409104026.png .. |image23| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/c07_03.md b/source/c07/c07_03.md index dc75f2c..aeab306 100644 --- a/source/c07/c07_03.md +++ b/source/c07/c07_03.md @@ -245,9 +245,9 @@ namespace 有下面六种 ``` 正在运行的容器 -![](http://image.python-online.cn/17-12-23/44035514.jpg) +![](http://image.iswbm.com/17-12-23/44035514.jpg) 文件夹内容 -![](http://image.python-online.cn/17-12-23/20133481.jpg) +![](http://image.iswbm.com/17-12-23/20133481.jpg) diff --git a/source/c07/c07_03.rst b/source/c07/c07_03.rst old mode 100755 new mode 100644 index bda175a..edce98a --- a/source/c07/c07_03.rst +++ b/source/c07/c07_03.rst @@ -292,7 +292,7 @@ namespace 有下面六种 |image3| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/17-12-23/44035514.jpg -.. |image2| image:: http://image.python-online.cn/17-12-23/20133481.jpg +.. |image1| image:: http://image.iswbm.com/17-12-23/44035514.jpg +.. |image2| image:: http://image.iswbm.com/17-12-23/20133481.jpg .. |image3| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/c07_04.md b/source/c07/c07_04.md index f1b7a16..7337b1f 100644 --- a/source/c07/c07_04.md +++ b/source/c07/c07_04.md @@ -33,7 +33,7 @@ CMD ["/hello"] # 当容器启动后执行hello文件内容 ``` 而hello的内容,也很简单就是打印这样一段内容 -![](http://image.python-online.cn/17-12-23/49304868.jpg) +![](http://image.iswbm.com/17-12-23/49304868.jpg) ## 三、理解base镜像 ``` @@ -43,7 +43,7 @@ CMD ["/hello"] # 当容器启动后执行hello文件内容 这个怎么理解呢?很重要。 比如说,我们下载的CentOS镜像 -![](http://image.python-online.cn/17-12-23/36753853.jpg) +![](http://image.iswbm.com/17-12-23/36753853.jpg) 你一定很纳闷。怎么才200M不到?我们平时下载的都几个G的。 @@ -66,7 +66,7 @@ ADD centos-7.2.1511-docker.tar.xz / CMD ["/bin/bash"] ``` 对于容器来说,他底层使用的Host的kernel,所以假如我们的容器是CentOS的版本(内核是3.x),当我们把容器放到Ubuntu(内核是4.x)上使用是,他实际上使用的是Ubuntu的内核。 -![](http://image.python-online.cn/17-12-23/70731434.jpg) +![](http://image.iswbm.com/17-12-23/70731434.jpg) 容器只能使用 Host 的 kernel,并且不能修改。 所有容器都共用 host 的 kernel,在容器中没办法对 kernel 升级。如果容器对 kernel 版本有要求(比如应用只能在某个 kernel 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。 @@ -110,7 +110,7 @@ RUN yum install -y tree ``` $ docker build -t centos-tree . ``` -![](http://image.python-online.cn/17-12-24/39646473.jpg) +![](http://image.iswbm.com/17-12-24/39646473.jpg) 第四步,在原始的Dockerfile上添加内容 ``` FROM centos @@ -121,7 +121,7 @@ COPY testfile / ``` docker build -t centos-tree-testfile . ``` -![](http://image.python-online.cn/17-12-24/85625734.jpg) +![](http://image.iswbm.com/17-12-24/85625734.jpg) 注意点,镜像的缓存要求构建的命令顺序要严格一致,只要有一行命令不同,缓存就会失效,即使最后的容器内容完全一致,也无法使用缓存。 比如,我们把第二次的Dockerfile改成如下,也是需要重新构建 @@ -134,13 +134,13 @@ RUN yum install -y tree 当我们build一个新的镜像,也许会有很多的很复杂的步骤,如果一次性build很有可能遇到各种意外情况,导致build失败,这时候,我们就需要进行调试,找出失败原因,修改Dockerfile,最终成功。 那么如何调试?在我们build镜像的时候,每一步都会有一个镜像id,通过这个id我们就可以进入该层镜像。 -![](http://image.python-online.cn/17-12-24/21127582.jpg) +![](http://image.iswbm.com/17-12-24/21127582.jpg) 先来看看第二步的结果,`/`目录下没有testfile文件 ``` $ docker run -it e316b390cf2a ``` 再来看看第三步,`/`目录下已经有testfile文件 -![](http://image.python-online.cn/17-12-24/42825662.jpg) +![](http://image.iswbm.com/17-12-24/42825662.jpg) 友情提示 ``` @@ -215,13 +215,13 @@ CMD:两个功能 exec:CMD ["/bin/echo", "hello world"] bash:CMD echo "hello world" ``` -![](http://image.python-online.cn/17-12-24/80077038.jpg) +![](http://image.iswbm.com/17-12-24/80077038.jpg) ``` 2. 给ENTRYPOINT传递参数 - 若run时,指定了参数,CMD传递的参数同样被覆盖,而ENTRYPOINT永远不会被覆盖。 - 注意:如果要传递参数,必须使用exec格式,诸如CMD [""] ``` -![](http://image.python-online.cn/17-12-24/98318652.jpg) +![](http://image.iswbm.com/17-12-24/98318652.jpg) **推荐用法** ``` diff --git a/source/c07/c07_04.rst b/source/c07/c07_04.rst old mode 100755 new mode 100644 index 4d5c5e7..fce8b33 --- a/source/c07/c07_04.rst +++ b/source/c07/c07_04.rst @@ -363,14 +363,14 @@ CMD:两个功能 |image10| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/17-12-23/49304868.jpg -.. |image2| image:: http://image.python-online.cn/17-12-23/36753853.jpg -.. |image3| image:: http://image.python-online.cn/17-12-23/70731434.jpg -.. |image4| image:: http://image.python-online.cn/17-12-24/39646473.jpg -.. |image5| image:: http://image.python-online.cn/17-12-24/85625734.jpg -.. |image6| image:: http://image.python-online.cn/17-12-24/21127582.jpg -.. |image7| image:: http://image.python-online.cn/17-12-24/42825662.jpg -.. |image8| image:: http://image.python-online.cn/17-12-24/80077038.jpg -.. |image9| image:: http://image.python-online.cn/17-12-24/98318652.jpg +.. |image1| image:: http://image.iswbm.com/17-12-23/49304868.jpg +.. |image2| image:: http://image.iswbm.com/17-12-23/36753853.jpg +.. |image3| image:: http://image.iswbm.com/17-12-23/70731434.jpg +.. |image4| image:: http://image.iswbm.com/17-12-24/39646473.jpg +.. |image5| image:: http://image.iswbm.com/17-12-24/85625734.jpg +.. |image6| image:: http://image.iswbm.com/17-12-24/21127582.jpg +.. |image7| image:: http://image.iswbm.com/17-12-24/42825662.jpg +.. |image8| image:: http://image.iswbm.com/17-12-24/80077038.jpg +.. |image9| image:: http://image.iswbm.com/17-12-24/98318652.jpg .. |image10| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/c07_05.md b/source/c07/c07_05.md index 1440b0d..b31e72b 100644 --- a/source/c07/c07_05.md +++ b/source/c07/c07_05.md @@ -118,7 +118,7 @@ $ docker run -d -p 8500:8500 -h consul --name consul progrium/consul -server -bo # 这里的 eth1 是可以和 192.168.2.55 通信的网卡 --cluster-store=consul://192.168.2.55:8500 --cluster-advertise=eth1:2376 ``` -![](http://image.python-online.cn/18-1-28/92519416.jpg) +![](http://image.iswbm.com/18-1-28/92519416.jpg) 然后重启 ``` @@ -127,7 +127,7 @@ systemctl restart docker.service ``` 在浏览器上输入地址 -![](http://image.python-online.cn/18-1-28/37395940.jpg) +![](http://image.iswbm.com/18-1-28/37395940.jpg) 至此,`Consul` 安装成功。 diff --git a/source/c07/c07_05.rst b/source/c07/c07_05.rst old mode 100755 new mode 100644 index 4ca28c1..23440b7 --- a/source/c07/c07_05.rst +++ b/source/c07/c07_05.rst @@ -322,8 +322,8 @@ Socket发些“奇怪”的数据。 |image5| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/18-1-28/92519416.jpg -.. |image2| image:: http://image.python-online.cn/18-1-28/37395940.jpg +.. |image1| image:: http://image.iswbm.com/18-1-28/92519416.jpg +.. |image2| image:: http://image.iswbm.com/18-1-28/37395940.jpg .. |image3| image:: https://i.loli.net/2018/01/28/5a6de8702428c.png .. |image4| image:: https://i.loli.net/2018/01/28/5a6de73776390.png .. |image5| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/c07_06.rst b/source/c07/c07_06.rst old mode 100755 new mode 100644 diff --git a/source/c07/c07_07.rst b/source/c07/c07_07.rst old mode 100755 new mode 100644 diff --git a/source/c07/c07_10.md b/source/c07/c07_10.md index ff003a0..64bee83 100644 --- a/source/c07/c07_10.md +++ b/source/c07/c07_10.md @@ -12,7 +12,7 @@ master 与 节点间使用ssh通信,也不需要像salt那样需要在客户 无意中使用 pip search 进行搜索,还真的有 ansible-api -![](http://image.python-online.cn/20190716111523.png) +![](http://image.iswbm.com/20190716111523.png) 这是 2018 年10月份才开始的项目,目前来说成熟度还不够。 @@ -120,7 +120,7 @@ ansible-api 会调用 ansible 库的命令,这个过程不能指定 ansible.cf 然后由于原生的 ansible-api 的bug,需要修改代码,在如下函数位置(`/usr/local/python3/lib/python3.7/site-packages/ansible_api/callback.py`)添加一个参数 -![](http://image.python-online.cn/20190716112113.png) +![](http://image.iswbm.com/20190716112113.png) 通过执行命令,即可开启 ansible server @@ -163,13 +163,13 @@ shell :echo -n 'wangbmlocalhostbackup_info.ymlwangbm'|md5sum |cut -d ' ' -f1 发送了请求后,返回的结果如下 -![](http://image.python-online.cn/20190716112824.png) +![](http://image.iswbm.com/20190716112824.png) rc 为0,表示所有节点都没有出现 fatal 致命错误(有设置 ignore_errors 的错误也会返回0). rc 为非0,表示有 fatal 致命错误,说明有部分节点部署/升级失败。 -![](http://image.python-online.cn/20190716112838.png) +![](http://image.iswbm.com/20190716112838.png) --- diff --git a/source/c07/c07_10.rst b/source/c07/c07_10.rst index 1620ef6..5d1f203 100644 --- a/source/c07/c07_10.rst +++ b/source/c07/c07_10.rst @@ -189,9 +189,9 @@ rc 为非0,表示有 fatal 致命错误,说明有部分节点部署/升级 |image5| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190716111523.png -.. |image2| image:: http://image.python-online.cn/20190716112113.png -.. |image3| image:: http://image.python-online.cn/20190716112824.png -.. |image4| image:: http://image.python-online.cn/20190716112838.png +.. |image1| image:: http://image.iswbm.com/20190716111523.png +.. |image2| image:: http://image.iswbm.com/20190716112113.png +.. |image3| image:: http://image.iswbm.com/20190716112824.png +.. |image4| image:: http://image.iswbm.com/20190716112838.png .. |image5| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/c07_11.md b/source/c07/c07_11.md index d971a32..ac60ad7 100644 --- a/source/c07/c07_11.md +++ b/source/c07/c07_11.md @@ -66,7 +66,7 @@ kubectl get namespace K8s 角色详解 -![](http://image.python-online.cn/20190907162015.png) +![](http://image.iswbm.com/20190907162015.png) 其中 Controller 还分为几种: diff --git a/source/c07/c07_11.rst b/source/c07/c07_11.rst index 68c4150..ffe862f 100644 --- a/source/c07/c07_11.rst +++ b/source/c07/c07_11.rst @@ -80,6 +80,6 @@ K8s 角色详解 |image2| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190907162015.png +.. |image1| image:: http://image.iswbm.com/20190907162015.png .. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/c07_12.md b/source/c07/c07_12.md index 099d935..577fcd2 100644 --- a/source/c07/c07_12.md +++ b/source/c07/c07_12.md @@ -360,7 +360,7 @@ $ rpm -e glibc-common-2.17-196.el7_4.2.x86_64 经常在安装一个包的时候,会报如下的错误,找不到某 so 文件 -![](http://image.python-online.cn/20191219152328.png) +![](http://image.iswbm.com/20191219152328.png) 如果是缺一个包,那我们安装它就行了,缺 so 文件,那咋弄? @@ -389,7 +389,7 @@ $ yum history list python-nova-tests $ rpm -qa --last | grep python-nova-tests ``` -![](http://image.python-online.cn/20191225173340.png) +![](http://image.iswbm.com/20191225173340.png) @@ -399,7 +399,7 @@ $ rpm -qa --last | grep python-nova-tests $ yumdb info python-nova-tests ``` -![](http://image.python-online.cn/20191225175350.png) +![](http://image.iswbm.com/20191225175350.png) diff --git a/source/c07/c07_12.rst b/source/c07/c07_12.rst index 0a34867..d489a16 100644 --- a/source/c07/c07_12.rst +++ b/source/c07/c07_12.rst @@ -392,8 +392,8 @@ yum-utils 使用 |image4| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20191219152328.png -.. |image2| image:: http://image.python-online.cn/20191225173340.png -.. |image3| image:: http://image.python-online.cn/20191225175350.png +.. |image1| image:: http://image.iswbm.com/20191219152328.png +.. |image2| image:: http://image.iswbm.com/20191225173340.png +.. |image3| image:: http://image.iswbm.com/20191225175350.png .. |image4| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/c07_15.md b/source/c07/c07_15.md index 0efaa7a..9860a01 100644 --- a/source/c07/c07_15.md +++ b/source/c07/c07_15.md @@ -19,7 +19,7 @@ Galera 是一个MySQL(也支持MariaDB,Percona) 的同步多主集群软件, 5. 用户连接集群后,使用跟 MySQL 基本一致。 6. 不会写binlog -关于Galera是如何做到多主的,可以借助这张图来看看![](http://image.python-online.cn/20191213162259.png) +关于Galera是如何做到多主的,可以借助这张图来看看![](http://image.iswbm.com/20191213162259.png) 来源:https://blog.csdn.net/weixin_42867972/article/details/84198696 diff --git a/source/c07/c07_15.rst b/source/c07/c07_15.rst index 8037826..82889cc 100644 --- a/source/c07/c07_15.rst +++ b/source/c07/c07_15.rst @@ -222,6 +222,6 @@ wsrep_flow_control_sent 和 wsrep_local_recv_queue_avg |image2| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20191213162259.png +.. |image1| image:: http://image.iswbm.com/20191213162259.png .. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/c07_16.md b/source/c07/c07_16.md index b63f093..2778025 100644 --- a/source/c07/c07_16.md +++ b/source/c07/c07_16.md @@ -4,7 +4,7 @@ ## 7.16.1 如何查看并计算 CPU 使用率 -有很多的工具可以查看CPU使用率,使用Linux 自带的 top 是最常用的方式。![](http://image.python-online.cn/20191220202103.png) +有很多的工具可以查看CPU使用率,使用Linux 自带的 top 是最常用的方式。![](http://image.iswbm.com/20191220202103.png) 图中标注解释 @@ -20,13 +20,13 @@ - si,software interrupt: 处理软件中断的CPU时间 - st:这个虚拟机被hypervisor偷去的CPU时间(译注:如果当前处于一个hypervisor下的vm,实际上hypervisor也是要消耗一部分CPU处理时间的)。 -若你觉得这样不够直观,可以按 `t` ,切换显示![](http://image.python-online.cn/20191220203403.png) +若你觉得这样不够直观,可以按 `t` ,切换显示![](http://image.iswbm.com/20191220203403.png) -若这是多核机器,输入 `1` ,可以显示单核CPU使用情况。![](http://image.python-online.cn/20191220202408.png) +若这是多核机器,输入 `1` ,可以显示单核CPU使用情况。![](http://image.iswbm.com/20191220202408.png) -若你觉得这样不够直观,可以按 `t`切换成直方图显示![](http://image.python-online.cn/20191220203205.png) +若你觉得这样不够直观,可以按 `t`切换成直方图显示![](http://image.iswbm.com/20191220203205.png) 标为 `2` 的 栏目,表示各个进程的 cpu 使用率,比如第一个进程 348% 就是占满了3.48个核。 diff --git a/source/c07/c07_16.rst b/source/c07/c07_16.rst index b11e5ac..7eb96f7 100644 --- a/source/c07/c07_16.rst +++ b/source/c07/c07_16.rst @@ -86,9 +86,9 @@ |image5| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20191220202103.png -.. |image2| image:: http://image.python-online.cn/20191220203403.png -.. |image3| image:: http://image.python-online.cn/20191220202408.png -.. |image4| image:: http://image.python-online.cn/20191220203205.png +.. |image1| image:: http://image.iswbm.com/20191220202103.png +.. |image2| image:: http://image.iswbm.com/20191220203403.png +.. |image3| image:: http://image.iswbm.com/20191220202408.png +.. |image4| image:: http://image.iswbm.com/20191220203205.png .. |image5| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/c07_22.md b/source/c07/c07_22.md index a4ad156..5e753d4 100644 --- a/source/c07/c07_22.md +++ b/source/c07/c07_22.md @@ -166,7 +166,7 @@ $ rpm -Vf /etc/path/file rm -f /var/lib/rpm/__db.00* # 重建rpm数据文件 -rpm –rebuilddb +rpm -rebuilddb ``` diff --git a/source/c07/c07_22.rst b/source/c07/c07_22.rst index bcaffe2..0563e93 100644 --- a/source/c07/c07_22.rst +++ b/source/c07/c07_22.rst @@ -165,7 +165,7 @@ rpmdb rm -f /var/lib/rpm/__db.00* # 重建rpm数据文件 - rpm –rebuilddb + rpm -rebuilddb 可选参数 -------- diff --git a/source/c08/c08_01.md b/source/c08/c08_01.md index 33f3ea1..3123f05 100644 --- a/source/c08/c08_01.md +++ b/source/c08/c08_01.md @@ -349,15 +349,7 @@ $ vgreduce --removemissing --force hdd-volumes # 添加 pv 到 vg 中 $ vgextend hdd-volumes /dev/sda ``` -### 1.3 QEMU命令 -```shell -# 查看img镜像信息 -$ qemu-img info - -# 创建qcow2文件 -$ qemu-img create -f qcow2 openstack-name.qcow2 100G -``` ## 三、集群相关 diff --git a/source/c08/c08_01.rst b/source/c08/c08_01.rst old mode 100755 new mode 100644 index 170c046..b67a69f --- a/source/c08/c08_01.rst +++ b/source/c08/c08_01.rst @@ -361,17 +361,6 @@ aggregate管理 # 添加 pv 到 vg 中 $ vgextend hdd-volumes /dev/sda -1.3 QEMU命令 -~~~~~~~~~~~~ - -.. code:: shell - - # 查看img镜像信息 - $ qemu-img info - - # 创建qcow2文件 - $ qemu-img create -f qcow2 openstack-name.qcow2 100G - 三、集群相关 ------------ diff --git a/source/c08/c08_02.md b/source/c08/c08_02.md index 30a0018..ed1b4a2 100644 --- a/source/c08/c08_02.md +++ b/source/c08/c08_02.md @@ -227,7 +227,7 @@ Sriov虚拟机在openstack原生是不支持挂卸网卡操作的,即nova inte 1、使用neutron port-show ,查看并记录原 port 的 `binding:profile` 信息,如果有多个port,把每个port的信息都记录下来。 -![](http://image.python-online.cn/20190529202132.png) +![](http://image.iswbm.com/20190529202132.png) 2、nova interface-detach卸载原来的port。 @@ -252,7 +252,7 @@ nova interface-attach 5a1c1828-4190-43fa-8e05-ae51b0196656 --port-id 3f0668f4-4b select * from pci_devices where instance_uuid='5a1c1828-4190-43fa-8e05-ae51b0196656'; ``` -![](http://image.python-online.cn/20190529202440.png) +![](http://image.iswbm.com/20190529202440.png) ## 附录:参考文档 diff --git a/source/c08/c08_02.rst b/source/c08/c08_02.rst old mode 100755 new mode 100644 index 14b0539..18e56ee --- a/source/c08/c08_02.rst +++ b/source/c08/c08_02.rst @@ -314,7 +314,7 @@ port-update命令不支持,只能使用curl,需要修改port-id及binding:pr .. |image4| image:: https://i.loli.net/2018/01/19/5a61c1cf51b58.png .. |image5| image:: https://i.loli.net/2018/01/19/5a61c1faac447.png .. |image6| image:: https://i.loli.net/2018/01/19/5a61c246451e7.png -.. |image7| image:: http://image.python-online.cn/20190529202132.png -.. |image8| image:: http://image.python-online.cn/20190529202440.png +.. |image7| image:: http://image.iswbm.com/20190529202132.png +.. |image8| image:: http://image.iswbm.com/20190529202440.png .. |image9| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c08/c08_03.md b/source/c08/c08_03.md index 23e8cb0..ef1e329 100644 --- a/source/c08/c08_03.md +++ b/source/c08/c08_03.md @@ -87,7 +87,8 @@ virt-install --name ubuntu-16.04 \ - listen='0.0.0.0'> + + @@ -457,7 +458,7 @@ shutdown -h now 通过 guestfish 工具可以实现不用创建虚拟机就可以修改镜像里的文件内容。 -![](http://image.python-online.cn/20190827200522.png) +![](http://image.iswbm.com/20190827200522.png) ## 8.3.3 KVM 镜像快照 @@ -470,9 +471,9 @@ $ virsh blockcommit ws_controller01 hda --active --verbose --pivot -![](http://image.python-online.cn/20191211174659.png) +![](http://image.iswbm.com/20191211174659.png) -![](http://image.python-online.cn/20191211174956.png) +![](http://image.iswbm.com/20191211174956.png) ## 附录:参考文档 diff --git a/source/c08/c08_03.rst b/source/c08/c08_03.rst old mode 100755 new mode 100644 index 4899e3f..f456eb5 --- a/source/c08/c08_03.rst +++ b/source/c08/c08_03.rst @@ -99,7 +99,8 @@ - listen='0.0.0.0'> + + @@ -535,8 +536,8 @@ CentOS6 创建快照前需要先删除\ ``75-persistent-net-generator.rules`` .. |image0| image:: http://image.iswbm.com/20200602135014.png .. |image1| image:: https://i.loli.net/2018/01/27/5a6c34714685d.png .. |image2| image:: https://i.loli.net/2018/01/27/5a6c34b14c6ec.png -.. |image3| image:: http://image.python-online.cn/20190827200522.png -.. |image4| image:: http://image.python-online.cn/20191211174659.png -.. |image5| image:: http://image.python-online.cn/20191211174956.png +.. |image3| image:: http://image.iswbm.com/20190827200522.png +.. |image4| image:: http://image.iswbm.com/20191211174659.png +.. |image5| image:: http://image.iswbm.com/20191211174956.png .. |image6| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c08/c08_04.md b/source/c08/c08_04.md index 12ade3c..5aaac02 100644 --- a/source/c08/c08_04.md +++ b/source/c08/c08_04.md @@ -12,7 +12,7 @@ 为了让你多了解一些云计算的内容,我想着写这么一篇文章,介绍一下我从事的领域,同时也对云计算和虚拟化这块入门级知识做一个梳理,如果刚好你也想进入这个领域,这份入门通识指南,应该挺适合你的。 -![](http://image.python-online.cn/20190714161353.png) +![](http://image.iswbm.com/20190714161353.png) ## 8.4.1 云计算是什么? @@ -24,7 +24,7 @@ 云,就是将这些零散实体资源变成一个巨大无比的资源池子,有了这个池子,做为个人用户,你不再需要自己你买一个电脑放在家里,做为小型公司,你不需要自己整一个机房,花很多的人力和设备成本去运营这些基础设施。一旦你需要,你就向池子拥有者申请即可。这极大的提高了资源的利用率,以及分配的灵活性。 -![](http://image.python-online.cn/20190716004341.png) +![](http://image.iswbm.com/20190716004341.png) 还有人说,云就像是天上的云一样,聚焦的水汽多了就会下雨,落到地面的雨水又会蒸发到天上,继续等待下一次下雨。云计算里的云,正如大自然里的云一样,可以实现资源的循环利用。你在公有云提供商那里,购买了一年的云主机,一年后资源被回收,可以再分配给其他人使用。 @@ -109,7 +109,7 @@ QEMU是一套由Fabrice Bellard所编写的模拟处理器的自由软件。Qemu 说到了QEMU,其实它也是一个虚拟化软件。作用是什么呢,它相当于一个路由器,当Guest OS的内核想要操作物理硬件时,必须先经由Qemu转发,将操作指令转给真实的硬件。由于所有的指令都要从Qemu里面过一手,因而性能比较差。 -![](http://image.python-online.cn/FjlPaQLTiYCde92WhurWsRx6z8CK) +![](http://image.iswbm.com/FjlPaQLTiYCde92WhurWsRx6z8CK) **总结** @@ -131,7 +131,7 @@ QEMU是一套由Fabrice Bellard所编写的模拟处理器的自由软件。Qemu 目前,libvirt 已经成为使用最为广泛的对各种虚拟机进行管理的工具和应用程序接口(API),而且一些常用的虚拟机管理工具(如virsh、virt-install、virt-manager等)和云计算框架平台(如OpenStack、OpenNebula、Eucalyptus等)都在底层使用libvirt的应用程序接口。 -![](http://image.python-online.cn/20190716005951.png) +![](http://image.iswbm.com/20190716005951.png) ## 8.4.5 虚拟化分类 @@ -167,7 +167,7 @@ QEMU是一套由Fabrice Bellard所编写的模拟处理器的自由软件。Qemu 根据虚拟化层是直接位于硬件之上还是位于操作系统之上,可以分为 Type 1 虚拟化和 Type 2 虚拟化。 -![](http://image.python-online.cn/20190714141644.png) +![](http://image.iswbm.com/20190714141644.png) Type 1:Xen,VMWare ESX @@ -408,7 +408,7 @@ virsh start guest_vm 有了OpenStack,你可以使用 Horizon提供的界面进行虚拟机的管理 -![来源网络,侵删](http://image.python-online.cn/20190714151716.png) +![来源网络,侵删](http://image.iswbm.com/20190714151716.png) 也可以使用nova 的 cli 命令进行创建。 diff --git a/source/c08/c08_04.rst b/source/c08/c08_04.rst index e57be47..ca07413 100644 --- a/source/c08/c08_04.rst +++ b/source/c08/c08_04.rst @@ -453,7 +453,7 @@ OpenStack 有了OpenStack,你可以使用 Horizon提供的界面进行虚拟机的管理 -.. figure:: http://image.python-online.cn/20190714151716.png +.. figure:: http://image.iswbm.com/20190714151716.png :alt: 来源网络,侵删 来源网络,侵删 @@ -488,11 +488,11 @@ OpenStck,你可能不太明白它是做什么的。这里引用我昨天看到 |image7| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190714161353.png -.. |image2| image:: http://image.python-online.cn/20190716004341.png -.. |image3| image:: http://image.python-online.cn/FjlPaQLTiYCde92WhurWsRx6z8CK -.. |image4| image:: http://image.python-online.cn/20190716005951.png -.. |image5| image:: http://image.python-online.cn/20190714141644.png +.. |image1| image:: http://image.iswbm.com/20190714161353.png +.. |image2| image:: http://image.iswbm.com/20190716004341.png +.. |image3| image:: http://image.iswbm.com/FjlPaQLTiYCde92WhurWsRx6z8CK +.. |image4| image:: http://image.iswbm.com/20190716005951.png +.. |image5| image:: http://image.iswbm.com/20190714141644.png .. |image6| image:: https://i.loli.net/2019/02/25/5c73e6160764a.png .. |image7| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c08/c08_05.md b/source/c08/c08_05.md index 050988a..7a057ea 100644 --- a/source/c08/c08_05.md +++ b/source/c08/c08_05.md @@ -8,13 +8,13 @@ 生成xml,准备网络(plug_vif)创建domain 是在 `_create_domain_and_network` 这个函数中 -![](http://image.python-online.cn/20190526144846.png) +![](http://image.iswbm.com/20190526144846.png) 这个函数,只有在 `spawn` 、`_hard_reboot` 和 `finish_migration` 才会执行。 通过libvirt的接口创建虚拟机 -![](http://image.python-online.cn/20190529135942.png) +![](http://image.iswbm.com/20190529135942.png) ## 8.5.2 同步虚机电源状态 @@ -35,11 +35,11 @@ class ComputeManager(manager.Manager): 默认是 600s,就是10分钟同步一次数据库的状态。 -![](http://image.python-online.cn/20190530204839.png) +![](http://image.iswbm.com/20190530204839.png) 具体的函数是在这个 `_sync_instance_power_state` -![](http://image.python-online.cn/20190530204505.png) +![](http://image.iswbm.com/20190530204505.png) 注意这个函数只有两个地方会调用 @@ -47,7 +47,7 @@ class ComputeManager(manager.Manager): 还有一个是由 libvirt 触发的`lifecycle event` 事件,这个只要在虚拟机在hypervisor层发生状态变动时,就会调用。 -![](http://image.python-online.cn/20190530210912.png) +![](http://image.iswbm.com/20190530210912.png) ## 8.5.3 创建快照代码解读? @@ -103,19 +103,19 @@ nova 提供了一个配置项:notify_on_state_change,本意是想,如果 nova-api 的入口如下 -![](http://image.python-online.cn/20190508110723.png) +![](http://image.iswbm.com/20190508110723.png) 接着会调用 nova/compute/api.py -![](http://image.python-online.cn/20190508111109.png) +![](http://image.iswbm.com/20190508111109.png) 在nova-compute 层面:nova/compute/manager.py:_snapshot_instance() -![](http://image.python-online.cn/20190508095028.png) +![](http://image.iswbm.com/20190508095028.png) 接下来会调用 `nova/virt/libvirt/driver.py:snapshot()` -![](http://image.python-online.cn/20190508111527.png) +![](http://image.iswbm.com/20190508111527.png) 先获取imagebackend的类型,然后找到对应的backend @@ -130,11 +130,11 @@ snapshot_backend = self.image_backend.snapshot(instance, 接下来,会调用对应的imagebackend的`snapshot_extract` 方法。 -![](http://image.python-online.cn/FhRPy4B1xEI9SfoD2RcunJl15ZE3) +![](http://image.iswbm.com/FhRPy4B1xEI9SfoD2RcunJl15ZE3) `snapshot_extract` 方法最终会调用`nova/virt/images.py:_convert_image()` ,可以看出其实底层调用的是 `qemu-img` 提供的`convert` 接口。 -![](http://image.python-online.cn/FuyMWZS6HF4g3rPwTlLcereZxg4L) +![](http://image.iswbm.com/FuyMWZS6HF4g3rPwTlLcereZxg4L) 如果是qcow2的backend,不是调用这边,而是调用 `nova/virt/libvirt/utils.py:extract_snapshot()` @@ -152,11 +152,11 @@ grep -E "Start executing commands|End executing commands" /var/log/nova/nova-com 在`libvirt_utils.get_disk_type_from_path` 里没有相应的修改,导致返回的是lvm。 -![](http://image.python-online.cn/FnJA8RNIvJN2lAEXbKtJDpOLg1vg) +![](http://image.iswbm.com/FnJA8RNIvJN2lAEXbKtJDpOLg1vg) 后面的imagebackend也相应的变成 lvm的 -![](http://image.python-online.cn/FnGyI8jCQFLCGi0pGVmI3SV6pDrv) +![](http://image.iswbm.com/FnGyI8jCQFLCGi0pGVmI3SV6pDrv) 然后会进入 lvm这个backend的init函数。由于`path` 是`/dev/sdb` 并不是一个lv,所以这边会报错。 @@ -170,7 +170,7 @@ compute的资源上报,是在 `nova/compute/resource_tracker.py:_init_compute_ 从宿主机上获取数据:`update_available_resource` 函数下的 `resources = self.driver.get_available_resource(self.nodename)` 其调用的函数是`virt/libvirt/driver.py` 里的 `get_available_resource` 函数 -![](http://image.python-online.cn/FrbE6oEZ3vtTWwDfMNQ16MGi6SWr) +![](http://image.iswbm.com/FrbE6oEZ3vtTWwDfMNQ16MGi6SWr) 从数据库获取旧数据 `self.compute_node = self._get_compute_node(context)` @@ -194,31 +194,31 @@ compute的资源上报,是在 `nova/compute/resource_tracker.py:_init_compute_ 虚拟机创建资源的代码是在 `nova/compute/manager.py` 里的 `_build_resource()` 里 -![](http://image.python-online.cn/20190906093751.png) +![](http://image.iswbm.com/20190906093751.png) 这里只讲一下 `network_info`,其他的我都忽略了。 -![](http://image.python-online.cn/20190906094703.png) +![](http://image.iswbm.com/20190906094703.png) 在这个函数`_allocate_network()` ,主要做如下两件事。 -![](http://image.python-online.cn/20190906214536.png) +![](http://image.iswbm.com/20190906214536.png) 继续进入这个函数,如果不指定重试次数,默认是一次。 -![](http://image.python-online.cn/20190906100119.png) +![](http://image.iswbm.com/20190906100119.png) 接下来就是调用我们非常熟悉的 `_create_port_minimal` 函数去创建port -![](http://image.python-online.cn/20190906213038.png) +![](http://image.iswbm.com/20190906213038.png) 如果你全局搜索,你会发现,在 network/rpc.py 下也有这个函数,这个是通过 nova interface-attach 为虚拟机添加网卡,从 nova-api 那边发起的 rpc 请求,才会走到这里 -![](http://image.python-online.cn/20190906210825.png) +![](http://image.iswbm.com/20190906210825.png) 上面创建完port了,后面最后一个函数,我已经标出来了,开始组装获取 network_info 对象。 -![](http://image.python-online.cn/20190906213823.png) +![](http://image.iswbm.com/20190906213823.png) @@ -238,11 +238,11 @@ network_info = compute_utils.get_nw_info_for_instance(instance) 1. 如果有请求req(在nova-api里),可以使用这种 -![](http://image.python-online.cn/20190426153322.png) +![](http://image.iswbm.com/20190426153322.png) 2. 其他地方可以使用这种 -![](http://image.python-online.cn/20190426152148.png) +![](http://image.iswbm.com/20190426152148.png) ## 8.5.9 指定ip时检查allocation_pools @@ -250,17 +250,17 @@ network_info = compute_utils.get_nw_info_for_instance(instance) 先来看看,port 是如何创建的 -![](http://image.python-online.cn/20190526141815.png) +![](http://image.iswbm.com/20190526141815.png) 若要解决这个问题,可以参考原生代码中,在为子网添加allocation_pool时,验证是否合法的的逻辑,代码如下 -![](http://image.python-online.cn/20190526142453.png) +![](http://image.iswbm.com/20190526142453.png) 然后在 `neutron\neutron\db\ipam_pluggable_backend.py` 文件中添加我们检查 ip是否在 allocation_pools 中的逻辑代码。 -![](http://image.python-online.cn/20190526134519.png) +![](http://image.iswbm.com/20190526134519.png) ```python # 代码如下:方便复制 @@ -283,15 +283,15 @@ network_info = compute_utils.get_nw_info_for_instance(instance) 然后还要定义一个异常类型 -![](http://image.python-online.cn/20190526141226.png) +![](http://image.iswbm.com/20190526141226.png) 若指定的ip在allocation pool 里,则正常创建,若不在allocation 里,就会在 nova-compute 日志中报错。 -![](http://image.python-online.cn/20190526134543.png) +![](http://image.iswbm.com/20190526134543.png) 可以发现我们的ip 172.20.22.64 并不在子网的allocation pool,理所当然在nova的日志中可以看到相应的报错。 -![](http://image.python-online.cn/20190526134618.png) +![](http://image.iswbm.com/20190526134618.png) ## 8.5.10 attach port时ip占用提示 @@ -305,7 +305,7 @@ nova-api 返回的结果令人无法理解: 究其原因,是 nova 在调用neutron的api 创建port时,如果ip已被占用,必须neutron会抛出 IpAddressAlreadyAllocated,而在 neutronclient 只有 IpAddressInUseClient 的异常,并不匹配,在neutronclient 端与neutron 对应的异常应该为 IpAddressAlreadyAllocatedClient 。 -![](http://image.python-online.cn/20190526140213.png) +![](http://image.iswbm.com/20190526140213.png) 如何让nova-api能够返回具体的错误信息呢? @@ -315,39 +315,39 @@ nova-api 返回的结果令人无法理解: 并且在nova 创建port的代码处,捕获这个异常 -![](http://image.python-online.cn/20190526140301.png) +![](http://image.iswbm.com/20190526140301.png) 这种要改两个组件,而且要将neutronclient 的代码也管理起来,较为麻烦 一种是,只改neutron,在neutron/ipam/exceptions.py 添加一个与 neutronclient 相对应的异常。 -![](http://image.python-online.cn/20190526140315.png) +![](http://image.iswbm.com/20190526140315.png) 然后修改 neutron/ipam/drivers/neutrondb_ipam/drivers.py 修改异常类型 -![](http://image.python-online.cn/20190526140336.png) +![](http://image.iswbm.com/20190526140336.png) 通过 postman 进行模拟,已经可以返回具体的信息 -![](http://image.python-online.cn/20190526140410.png) +![](http://image.iswbm.com/20190526140410.png) 另附:neutron 是如何判断ip是否已经占用?代码如下 -![](http://image.python-online.cn/20190526143235.png) +![](http://image.iswbm.com/20190526143235.png) ## 8.5.11 nova-compute 如何启动的? 从 /usr/bin/nova-compute 这个文件可以了解到nova-compute的入口是 `nova.cmd.compute:main()` -![](http://image.python-online.cn/20190526205152.png) +![](http://image.iswbm.com/20190526205152.png) 从这个入口进去,会开启一个 `nova-compute` 的服务。 -![](http://image.python-online.cn/20190526165007.png) +![](http://image.iswbm.com/20190526165007.png) 当调用 service.Service.create 时(create 是一个工厂函数),实际是返回实例化的 service.Service 对象。当没有传入 manager 时,就会以binary 里的为准。比如binary 是` nova-compute`,那manager_cls 就是 `compute_manager`,对应的manager 导入路径,会从配置里读取。 -![](http://image.python-online.cn/20190526204328.png) +![](http://image.iswbm.com/20190526204328.png) ## 8.5.12 往 spec_obj 里添加对象 @@ -356,7 +356,7 @@ nova-api 返回的结果令人无法理解: - host_state:包含每台 host 的所有信息 - spec_obj:包含创建虚拟机请求的所有信息 -![](http://image.python-online.cn/20191008173211.png) +![](http://image.iswbm.com/20191008173211.png) 有时候,spec_obj 里并没有我们想要的信息(比如虚拟机的 metadata),这时候,我们就要手动添加。 @@ -366,11 +366,11 @@ nova-api 返回的结果令人无法理解: 要往这个对象加属性,第一步是要定义这个字段。 -![](http://image.python-online.cn/20191008174046.png) +![](http://image.iswbm.com/20191008174046.png) 往 instance_fields 追加属性名,完了后,这个属性会出现在 `request_spec['instance_properties']` 里 -![](http://image.python-online.cn/20191008210127.png) +![](http://image.iswbm.com/20191008210127.png) 最后在 `from_primitives` 这个函数里,把这个新属性赋值给 request_spec 对象。 @@ -386,13 +386,13 @@ spec.metadata = request_spec['instance_properties'].get('metadata', {}) 在 nova-api 接收请求处。 -![](http://image.python-online.cn/20190529203441.png) +![](http://image.iswbm.com/20190529203441.png) -![](http://image.python-online.cn/20190529215953.png) +![](http://image.iswbm.com/20190529215953.png) 对 network_info 进行解析,然后塞给 request 对象返回。 -![](http://image.python-online.cn/20190529215825.png) +![](http://image.iswbm.com/20190529215825.png) ## 8.5.14 HTTP 状态码 @@ -512,7 +512,7 @@ isolate 然后为这个LV,创建一个软连接,通过 `df -Th` 可以看到 这个LV 挂载到 一个目录下。这个目录下有一个名为 disk 的qcow2文件,而这个qcow2 文件的backing file 是指向一个 base 镜像文件(raw格式)。 -![](http://image.python-online.cn/20190627213044.png) +![](http://image.iswbm.com/20190627213044.png) ## 8.5.16 独立磁盘与LVM @@ -522,7 +522,7 @@ isolate 缺点:无法像 LVM 存储池那样,做到精准而灵活的资源分配,有可能造成资源浪费或资源不足。 -![](http://image.python-online.cn/20190627213609.png) +![](http://image.iswbm.com/20190627213609.png) **LVM** @@ -538,17 +538,17 @@ isolate 只要在主机组上设置的metadata 的 key-value 和 extra_spec 的key-value一样就可以实现宿主机的过滤。 -![](http://image.python-online.cn/20190627215038.png) +![](http://image.iswbm.com/20190627215038.png) ## 8.5.18 生成config drive -![](http://image.python-online.cn/20190708100902.png) +![](http://image.iswbm.com/20190708100902.png) -![](http://image.python-online.cn/20190708103119.png) +![](http://image.iswbm.com/20190708103119.png) ``` network_metadata @@ -563,7 +563,7 @@ inst_md.ip_info nova api 的接口参数是在 nova/api/openstack/compute/schemas/ 目录下,如创建虚拟机的接口如下: -![](http://image.python-online.cn/20190719170825.png) +![](http://image.iswbm.com/20190719170825.png) @@ -571,7 +571,7 @@ nova api 的接口参数是在 nova/api/openstack/compute/schemas/ 目录下, 在application 函数的头部,可以发现有如下这几种装饰器, -![](http://image.python-online.cn/20190730140551.png) +![](http://image.iswbm.com/20190730140551.png) 装饰器里会传入一个参数,就是 schema 的内容,通过它可以约束一个请求内的参数合法性。 @@ -581,11 +581,11 @@ schemas 的文件都统一放在 `nova\api\openstack\compute\schemas` 目录下 但并不是说,创建虚拟机的所有参数校验规则只在这里,nova 这边引入了 stevedore,它是 oslo 项目中为OpenStack其他项目提供动态加载功能的公共组件库(详细可以看这篇文章:https://blog.csdn.net/bill_xiang_/article/details/78852717)。通过它可以用添加扩展的方式,给 servers 更新 schemas。 -![](http://image.python-online.cn/20190826210813.png) +![](http://image.iswbm.com/20190826210813.png) 关键在于这个 map 函数,它会循环所有的扩展() -![](http://image.python-online.cn/20190826211542.png) +![](http://image.iswbm.com/20190826211542.png) 将调用传进去的 `self._create_extension_schema` 将已加载到的扩展schemas 更新到主schemas里去。 @@ -596,15 +596,15 @@ schemas 的文件都统一放在 `nova\api\openstack\compute\schemas` 目录下 -![](http://image.python-online.cn/20190826211737.png) +![](http://image.iswbm.com/20190826211737.png) 装饰器 schemas 的定义如下: -![](http://image.python-online.cn/20190730142527.png) +![](http://image.iswbm.com/20190730142527.png) 比如我们使用的 novaclient 发出的请求,是 v2.3.7的,所以 create() 顶部的五个schema 装饰器,上面四个都会空跑,不会进行校验,只有最后一个才会进入检验逻辑。 -![](http://image.python-online.cn/20190730143003.png) +![](http://image.iswbm.com/20190730143003.png) @@ -616,17 +616,17 @@ schemas 的文件都统一放在 `nova\api\openstack\compute\schemas` 目录下 就像下面指明了 server 的 create 接口会去加载这9个扩展资源。 -![](http://image.python-online.cn/20190830091540.png) +![](http://image.iswbm.com/20190830091540.png) 搜索一下 server_create 方法 还真的只有这 9 个资源里才会定义。 -![](http://image.python-online.cn/20190830092203.png) +![](http://image.iswbm.com/20190830092203.png) 这就神奇了,servers 这个核心资源是如何加载到这些资源的,看了下代码是使用 stevedore 这个模块去动态加载,然后还会校验这些资源是否都有 `server_create` ,只有有这个函数,才会被正常加载进来。 -![](http://image.python-online.cn/20190830093613.png) +![](http://image.iswbm.com/20190830093613.png) ## 8.5.20 往数据库中加字段 @@ -664,7 +664,7 @@ self._record_action_start(context, instance, instance_actions.REBOOT) ## 8.5.22 如何生成ConfigDrive -![](http://image.python-online.cn/20190912135302.png) +![](http://image.iswbm.com/20190912135302.png) diff --git a/source/c08/c08_05.rst b/source/c08/c08_05.rst index 0ca7934..b8f1503 100644 --- a/source/c08/c08_05.rst +++ b/source/c08/c08_05.rst @@ -743,65 +743,65 @@ stevedore 这个模块去动态加载,然后还会校验这些资源是否都 |image61| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190526144846.png -.. |image2| image:: http://image.python-online.cn/20190529135942.png -.. |image3| image:: http://image.python-online.cn/20190530204839.png -.. |image4| image:: http://image.python-online.cn/20190530204505.png -.. |image5| image:: http://image.python-online.cn/20190530210912.png -.. |image6| image:: http://image.python-online.cn/20190508110723.png -.. |image7| image:: http://image.python-online.cn/20190508111109.png -.. |image8| image:: http://image.python-online.cn/20190508095028.png -.. |image9| image:: http://image.python-online.cn/20190508111527.png -.. |image10| image:: http://image.python-online.cn/FhRPy4B1xEI9SfoD2RcunJl15ZE3 -.. |image11| image:: http://image.python-online.cn/FuyMWZS6HF4g3rPwTlLcereZxg4L -.. |image12| image:: http://image.python-online.cn/FnJA8RNIvJN2lAEXbKtJDpOLg1vg -.. |image13| image:: http://image.python-online.cn/FnGyI8jCQFLCGi0pGVmI3SV6pDrv -.. |image14| image:: http://image.python-online.cn/FrbE6oEZ3vtTWwDfMNQ16MGi6SWr -.. |image15| image:: http://image.python-online.cn/20190906093751.png -.. |image16| image:: http://image.python-online.cn/20190906094703.png -.. |image17| image:: http://image.python-online.cn/20190906214536.png -.. |image18| image:: http://image.python-online.cn/20190906100119.png -.. |image19| image:: http://image.python-online.cn/20190906213038.png -.. |image20| image:: http://image.python-online.cn/20190906210825.png -.. |image21| image:: http://image.python-online.cn/20190906213823.png -.. |image22| image:: http://image.python-online.cn/20190426153322.png -.. |image23| image:: http://image.python-online.cn/20190426152148.png -.. |image24| image:: http://image.python-online.cn/20190526141815.png -.. |image25| image:: http://image.python-online.cn/20190526142453.png -.. |image26| image:: http://image.python-online.cn/20190526134519.png -.. |image27| image:: http://image.python-online.cn/20190526141226.png -.. |image28| image:: http://image.python-online.cn/20190526134543.png -.. |image29| image:: http://image.python-online.cn/20190526134618.png -.. |image30| image:: http://image.python-online.cn/20190526140213.png -.. |image31| image:: http://image.python-online.cn/20190526140301.png -.. |image32| image:: http://image.python-online.cn/20190526140315.png -.. |image33| image:: http://image.python-online.cn/20190526140336.png -.. |image34| image:: http://image.python-online.cn/20190526140410.png -.. |image35| image:: http://image.python-online.cn/20190526143235.png -.. |image36| image:: http://image.python-online.cn/20190526205152.png -.. |image37| image:: http://image.python-online.cn/20190526165007.png -.. |image38| image:: http://image.python-online.cn/20190526204328.png -.. |image39| image:: http://image.python-online.cn/20191008173211.png -.. |image40| image:: http://image.python-online.cn/20191008174046.png -.. |image41| image:: http://image.python-online.cn/20191008210127.png -.. |image42| image:: http://image.python-online.cn/20190529203441.png -.. |image43| image:: http://image.python-online.cn/20190529215953.png -.. |image44| image:: http://image.python-online.cn/20190529215825.png -.. |image45| image:: http://image.python-online.cn/20190627213044.png -.. |image46| image:: http://image.python-online.cn/20190627213609.png -.. |image47| image:: http://image.python-online.cn/20190627215038.png -.. |image48| image:: http://image.python-online.cn/20190708100902.png -.. |image49| image:: http://image.python-online.cn/20190708103119.png -.. |image50| image:: http://image.python-online.cn/20190719170825.png -.. |image51| image:: http://image.python-online.cn/20190730140551.png -.. |image52| image:: http://image.python-online.cn/20190826210813.png -.. |image53| image:: http://image.python-online.cn/20190826211542.png -.. |image54| image:: http://image.python-online.cn/20190826211737.png -.. |image55| image:: http://image.python-online.cn/20190730142527.png -.. |image56| image:: http://image.python-online.cn/20190730143003.png -.. |image57| image:: http://image.python-online.cn/20190830091540.png -.. |image58| image:: http://image.python-online.cn/20190830092203.png -.. |image59| image:: http://image.python-online.cn/20190830093613.png -.. |image60| image:: http://image.python-online.cn/20190912135302.png +.. |image1| image:: http://image.iswbm.com/20190526144846.png +.. |image2| image:: http://image.iswbm.com/20190529135942.png +.. |image3| image:: http://image.iswbm.com/20190530204839.png +.. |image4| image:: http://image.iswbm.com/20190530204505.png +.. |image5| image:: http://image.iswbm.com/20190530210912.png +.. |image6| image:: http://image.iswbm.com/20190508110723.png +.. |image7| image:: http://image.iswbm.com/20190508111109.png +.. |image8| image:: http://image.iswbm.com/20190508095028.png +.. |image9| image:: http://image.iswbm.com/20190508111527.png +.. |image10| image:: http://image.iswbm.com/FhRPy4B1xEI9SfoD2RcunJl15ZE3 +.. |image11| image:: http://image.iswbm.com/FuyMWZS6HF4g3rPwTlLcereZxg4L +.. |image12| image:: http://image.iswbm.com/FnJA8RNIvJN2lAEXbKtJDpOLg1vg +.. |image13| image:: http://image.iswbm.com/FnGyI8jCQFLCGi0pGVmI3SV6pDrv +.. |image14| image:: http://image.iswbm.com/FrbE6oEZ3vtTWwDfMNQ16MGi6SWr +.. |image15| image:: http://image.iswbm.com/20190906093751.png +.. |image16| image:: http://image.iswbm.com/20190906094703.png +.. |image17| image:: http://image.iswbm.com/20190906214536.png +.. |image18| image:: http://image.iswbm.com/20190906100119.png +.. |image19| image:: http://image.iswbm.com/20190906213038.png +.. |image20| image:: http://image.iswbm.com/20190906210825.png +.. |image21| image:: http://image.iswbm.com/20190906213823.png +.. |image22| image:: http://image.iswbm.com/20190426153322.png +.. |image23| image:: http://image.iswbm.com/20190426152148.png +.. |image24| image:: http://image.iswbm.com/20190526141815.png +.. |image25| image:: http://image.iswbm.com/20190526142453.png +.. |image26| image:: http://image.iswbm.com/20190526134519.png +.. |image27| image:: http://image.iswbm.com/20190526141226.png +.. |image28| image:: http://image.iswbm.com/20190526134543.png +.. |image29| image:: http://image.iswbm.com/20190526134618.png +.. |image30| image:: http://image.iswbm.com/20190526140213.png +.. |image31| image:: http://image.iswbm.com/20190526140301.png +.. |image32| image:: http://image.iswbm.com/20190526140315.png +.. |image33| image:: http://image.iswbm.com/20190526140336.png +.. |image34| image:: http://image.iswbm.com/20190526140410.png +.. |image35| image:: http://image.iswbm.com/20190526143235.png +.. |image36| image:: http://image.iswbm.com/20190526205152.png +.. |image37| image:: http://image.iswbm.com/20190526165007.png +.. |image38| image:: http://image.iswbm.com/20190526204328.png +.. |image39| image:: http://image.iswbm.com/20191008173211.png +.. |image40| image:: http://image.iswbm.com/20191008174046.png +.. |image41| image:: http://image.iswbm.com/20191008210127.png +.. |image42| image:: http://image.iswbm.com/20190529203441.png +.. |image43| image:: http://image.iswbm.com/20190529215953.png +.. |image44| image:: http://image.iswbm.com/20190529215825.png +.. |image45| image:: http://image.iswbm.com/20190627213044.png +.. |image46| image:: http://image.iswbm.com/20190627213609.png +.. |image47| image:: http://image.iswbm.com/20190627215038.png +.. |image48| image:: http://image.iswbm.com/20190708100902.png +.. |image49| image:: http://image.iswbm.com/20190708103119.png +.. |image50| image:: http://image.iswbm.com/20190719170825.png +.. |image51| image:: http://image.iswbm.com/20190730140551.png +.. |image52| image:: http://image.iswbm.com/20190826210813.png +.. |image53| image:: http://image.iswbm.com/20190826211542.png +.. |image54| image:: http://image.iswbm.com/20190826211737.png +.. |image55| image:: http://image.iswbm.com/20190730142527.png +.. |image56| image:: http://image.iswbm.com/20190730143003.png +.. |image57| image:: http://image.iswbm.com/20190830091540.png +.. |image58| image:: http://image.iswbm.com/20190830092203.png +.. |image59| image:: http://image.iswbm.com/20190830093613.png +.. |image60| image:: http://image.iswbm.com/20190912135302.png .. |image61| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c08/c08_06.md b/source/c08/c08_06.md index 39463be..ebed7a7 100644 --- a/source/c08/c08_06.md +++ b/source/c08/c08_06.md @@ -15,7 +15,7 @@ cloud-init 是 linux 的一个工具,当系统启动时,cloud-init 可从 no 这些任务都是以服务存在,并且安装完成后自动加入了开机自启。 -![CentOS 7.x](http://image.python-online.cn/20190430203920.png) +![CentOS 7.x](http://image.iswbm.com/20190430203920.png) 那么你一定会奇怪,这些任务都是分离的,如何控制其启动顺序呢? @@ -23,15 +23,15 @@ cloud-init 是 linux 的一个工具,当系统启动时,cloud-init 可从 no 比如 local 阶段作为第一个执行的。 -![](http://image.python-online.cn/20190430204707.png) +![](http://image.iswbm.com/20190430204707.png) 而后的几个服务(如init阶段),都需要保证 后于 local 已经启动过才能运行。 -![](http://image.python-online.cn/20190430204933.png) +![](http://image.iswbm.com/20190430204933.png) 在 **CentOS 6.x** 中,由于系统过于古老并没有 systemd ,它的服务启动顺序是由 `/etc/rc.d/rc3.d` 目录进行管理的,按照编号进行从小到大执行。 -![](http://image.python-online.cn/20190430205449.png) +![](http://image.iswbm.com/20190430205449.png) 这个启动顺序相当重要,在排查并修复虚拟机创建时 ip 的配置问题的时候会有帮助,主要是 CentOS6.x 上的问题(我曾经碰到过的),这种也顺便讲讲。 @@ -41,7 +41,7 @@ cloud-init 是 linux 的一个工具,当系统启动时,cloud-init 可从 no 从以信息可知,如果创建静态ip的虚拟机,NetworkManager 这个服务必须在 cloudinit-local 之后启动才可正常从配置文件中读取 ip 并配置。而当你在镜像里安装 NetworkManager后,默认情况下它的启动顺序是会在 cloudinit-local 之前的。 -![](http://image.python-online.cn/20190430211900.png) +![](http://image.iswbm.com/20190430211900.png) 解决方法也很简单,将 `S23NetworkManager` 重命名为 `S54NetworkManager` 即可。 @@ -68,19 +68,19 @@ cloud-init 是 linux 的一个工具,当系统启动时,cloud-init 可从 no 这里再顺便说一点,在使用 pdb 进行调试时,你会发现一旦执行就会报错或者无法调试。 -![](http://image.python-online.cn/20190430213012.png) +![](http://image.iswbm.com/20190430213012.png) 这里就需要一个小技巧。你只需修改cloudinit 的入口文件(CentOS6.x 是在/usr/bin/cloud-init,CentOS7.x 是在 /usr/lib/python2.7/site-packages/cloudinit/cmd/main.py)将红框这行注释。 -![](http://image.python-online.cn/20190430213337.png) +![](http://image.iswbm.com/20190430213337.png) 再尝试即可正常调试。 -![](http://image.python-online.cn/20190430213429.png) +![](http://image.iswbm.com/20190430213429.png) 从这里也学到了一点,如何要关闭调试功能,只要关闭标准输入就行了。 -![](http://image.python-online.cn/20190430213729.png) +![](http://image.iswbm.com/20190430213729.png) 如果你在调试 single,而且你把 /var/lib/cloud 目录删除过,请务必要执行一下 cloud-init init ,不然会取不到 user_data @@ -92,29 +92,29 @@ centos 6.5 的cloudinit 使用的是 python2.6,没有 format 语法,请注 数据源的读取入口是在 入口文件(CentOS6.x 是在 `/usr/bin/cloud-init`,CentOS7.x 是在 `/usr/lib/python2.7/site-packages/cloudinit/cmd/main.py`)的 main_init 函数中,会调用 `stages.py:fetch()` 函数。 -![](http://image.python-online.cn/20190430225605.png) +![](http://image.iswbm.com/20190430225605.png) 支持哪些源:是在代码里(settings.py)定义写死的 -![](http://image.python-online.cn/20190430225726.png) +![](http://image.iswbm.com/20190430225726.png) 如果你的虚拟机固定只使用其中一种,那可以只留一种,加快cloudinit 的执行速度。 find_source 是通过一个一个去执行 对应source模块的 get_data(),如果获取到了数据就直接返回。 -![](http://image.python-online.cn/20190430230214.png) +![](http://image.iswbm.com/20190430230214.png) 最经常使用的是 ConfigDrive,来看一下它是如何获取数据的。 -![](http://image.python-online.cn/FpqcyL4hWwpaAGzsdreQwXvH4Rx8) +![](http://image.iswbm.com/FpqcyL4hWwpaAGzsdreQwXvH4Rx8) 它会先创建一个临时目录,尝试将 ` /dev/sr0` 挂载到这个目录下。 -![](http://image.python-online.cn/20190430230839.png) +![](http://image.iswbm.com/20190430230839.png) 然后将读取的数据存入 `/var/lib/cloud/instance/obj.pkl`, 而后续执行都将从这里反序列化,提高速度。 -![](http://image.python-online.cn/20190430231108.png) +![](http://image.iswbm.com/20190430231108.png) @@ -283,7 +283,7 @@ myjob.cfg:text/upstart-job 这就是cloudinit搞的鬼,在cloudinit的local阶段,好像会记录之前的mac地址,如果发现不一致,就会触发rename_interface。 -![](http://image.python-online.cn/20190623091911.png) +![](http://image.iswbm.com/20190623091911.png) @@ -291,7 +291,7 @@ myjob.cfg:text/upstart-job 当创建一个有数据盘的虚拟机,nova会在configdrive里的ec2目录下生成 meta-data.json ,其中有一个字段是block-device-mapping,包含磁盘信息。在虚拟机创建后,如果 /etc/cloud/cloud.cfg里配置了 mounts,cloudinit会根据这个这下面文件中的 ephemeral0 拿到对应的 /dev/vdb,并将其写入 /etc/fstab 中。在下次重启时,会根据 fstab 挂载磁盘,如果挂载不上,就会导致虚拟机启动卡住。 -![](http://image.python-online.cn/20190708175813.png) +![](http://image.iswbm.com/20190708175813.png) @@ -386,20 +386,20 @@ def run(self, name, functor, args, freq=None, clear_on_fail=False): 对于 `PER_INSTANCE` 和 `PER_ONCE` 和区别,从名字和代码实现上看,似乎没有区别,继续看 cloudinit 里的代码,只有一个模块使用了 PER_ONCE ,那就是 `cc_scripts_per_once.py`。 -![](http://image.python-online.cn/20190910160035.png) +![](http://image.iswbm.com/20190910160035.png) 其实他们还是有区别的,由下面的代码来看 - `PER_ONCE` 获取的 sem 文件是从 `/var/lib/cloud/` 下的文件获取的,这个目录一个镜像只会生成一次(若你不删除的话)。 - `PER_INSTANCE` 获取 sem 文件是从 `/var/lib/cloud/instances/` 下的文件获取的,这个目录每个虚拟机一个。 -![](http://image.python-online.cn/20190910150305.png) +![](http://image.iswbm.com/20190910150305.png) 如果你的模块里已经配置了频率,则以此为准。若没有配置,则默认为 `PER_INSTANCE`。 具体函数:`_run_modules` -![](http://image.python-online.cn/20190910142222.png) +![](http://image.iswbm.com/20190910142222.png) @@ -407,15 +407,15 @@ def run(self, name, functor, args, freq=None, clear_on_fail=False): 通过代码可以发现,其在运行时,会先调用 `has_run` 函数,在这里会去取对应目录下(上面的 sem_path)的sem文件,如果有存在 sem 文件,说明已经执行过(返回 False ),如果不存在 sem 文件(返回 True) 说明未执行过。 -![](http://image.python-online.cn/20190910153637.png) +![](http://image.iswbm.com/20190910153637.png) sem 文件是怎样的? 这是 `PER_ONCE` 的 sem 文件: -![](http://image.python-online.cn/20190910171359.png) +![](http://image.iswbm.com/20190910171359.png) -这是 `PER_INSTANCE` 的 sem 文件![](http://image.python-online.cn/20190910171538.png) +这是 `PER_INSTANCE` 的 sem 文件![](http://image.iswbm.com/20190910171538.png) @@ -430,7 +430,7 @@ from cloudinit.net import eni config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') ``` -![](http://image.python-online.cn/20190906091102.png) +![](http://image.iswbm.com/20190906091102.png) ## 8.6.11 低版本的日志输出 @@ -438,7 +438,7 @@ config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') 经过排查,你可以在 `cloudinit/log.py` 中按下面的改法修改解决 -![](http://image.python-online.cn/20190909172153.png) +![](http://image.iswbm.com/20190909172153.png) ## 8.6.12 旧版本网络配置的三个巨坑 @@ -450,11 +450,11 @@ config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') 在 cloud-init 0.7.5中,网络信息的读取与配置都是在且仅能在 local 阶段进行的,代码如下,只在 dsmode 为 local 时才会执行 on_first_boot。 -![](http://image.python-online.cn/20190429104357.png) +![](http://image.iswbm.com/20190429104357.png) 而如果是虚拟机重启的话,是在stages.py 这边判断是否为新虚拟机的,这也和旧版本有所区别。 -![](http://image.python-online.cn/20190829141059.png) +![](http://image.iswbm.com/20190829141059.png) 为了让你能更加清晰的了解这个网络配置过程,我阅读了这块的源代码。 @@ -469,11 +469,13 @@ config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') **坑一** -如果是按照旧虚拟机创建新的快照镜像,然后使用这个镜像创建新的虚拟机,有可能会在同一块网卡上出现新旧两个ip,这是因为虚拟机在启动过程中,会先读取原网络配置配置ip,然后才会运行 cloud-init 进行新ip的配置,而新ip的配置是使用 `ifup` 这个命令![](http://image.python-online.cn/Fp1TeHSiIMIQoZygbW9VSfAagB_d) +如果是按照旧虚拟机创建新的快照镜像,然后使用这个镜像创建新的虚拟机,有可能会在同一块网卡上出现新旧两个ip,这是因为虚拟机在启动过程中,会先读取原网络配置配置ip,然后才会运行 cloud-init 进行新ip的配置,而新ip的配置是使用 `ifup` 这个命令![](http://image.iswbm.com/Fp1TeHSiIMIQoZygbW9VSfAagB_d) -使用这种方式并不会将第一次配置的旧ip给清除掉。![](http://image.python-online.cn/Fh-5SQ8qYjhJEKovI6LmIpabSy2c) +使用这种方式并不会将第一次配置的旧ip给清除掉。 -这个问题,目前我只在CentOS6 中遇到过。可以通过修改代码让其先 `ifdown` 再 `ifup` 就可以解决这个问题。![](http://image.python-online.cn/20190430231812.png) +![](http://image.iswbm.com/Fh-5SQ8qYjhJEKovI6LmIpabSy2c) + +这个问题,目前我只在CentOS6 中遇到过。可以通过修改代码让其先 `ifdown` 再 `ifup` 就可以解决这个问题。![](http://image.iswbm.com/20190430231812.png) **坑二** @@ -481,7 +483,7 @@ config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') 具体的创建逻辑是在这 -![](http://image.python-online.cn/20190430232309.png) +![](http://image.iswbm.com/20190430232309.png) **坑三** @@ -502,11 +504,11 @@ Apr 29 11:13:29 localhost NetworkManager[1365]: exiting (error) 一个是会自动DHCP获取到一个以ip命名的hostname,并将原来的覆盖掉。 -![](http://image.python-online.cn/20190429205735.png) +![](http://image.iswbm.com/20190429205735.png) 为了避免出现这些情况,请务必保证这些包都安装完整(左为 CentOS 7.2,右为 CentOS 6.5)。 -![](http://image.python-online.cn/20190430232911.png) +![](http://image.iswbm.com/20190430232911.png) ## 8.6.13 网络是如何启动的?(新版本) @@ -519,15 +521,15 @@ Apr 29 11:13:29 localhost NetworkManager[1365]: exiting (error) 在 local 阶段的时候,`existing` 是 `check` ,这是合理的。 -![](http://image.python-online.cn/20190911175423.png) +![](http://image.iswbm.com/20190911175423.png) -![](http://image.python-online.cn/20190911174648.png) +![](http://image.iswbm.com/20190911174648.png) 在local阶段,on_first_boot 的函数 network 是 False ,所以这里也不会写网卡配置文件,自然也不会配置。 -![](http://image.python-online.cn/20190911173615.png) +![](http://image.iswbm.com/20190911173615.png) -![](http://image.python-online.cn/20190911195024.png) +![](http://image.iswbm.com/20190911195024.png) @@ -551,11 +553,11 @@ init.apply_network_config(bring_up=bool(mode != sources.DSMODE_LOCAL)) 2. 校正网卡名,以 ConfigDrive 的配置为准 3. 将ip信息写入配置文件,并启动网卡 -![](http://image.python-online.cn/20190911202425.png) +![](http://image.iswbm.com/20190911202425.png) 那它是如何对网卡进行重命令的呢?看了代码,其实是用ip命令实现的。 -![](http://image.python-online.cn/20190911202551.png) +![](http://image.iswbm.com/20190911202551.png) 提取出来其实就三条命令,要注意的是,这三条命令执行是有顺序的 @@ -567,11 +569,11 @@ init.apply_network_config(bring_up=bool(mode != sources.DSMODE_LOCAL)) 还有一点要说的是,cloudinit 是如何取到本机真实的网卡信息的呢?他是从 `/sys/class/net/` 目录下获取的。每个网卡一个目录,每个目录下都有相应的文件记录相应的信息,比如 `/sys/class/net/ens3/address` 记录的是网卡的 mac 地址。 -![](http://image.python-online.cn/20190911203953.png) +![](http://image.iswbm.com/20190911203953.png) 接下来就要开始配置网络了,先写网络配置文件,再根据参数选择是否启用网络。 -![](http://image.python-online.cn/20190911204805.png) +![](http://image.iswbm.com/20190911204805.png) 如果是重启虚拟机或者 init 阶段进入这里呢,会不会又重复配置网络了呢? @@ -579,7 +581,7 @@ init.apply_network_config(bring_up=bool(mode != sources.DSMODE_LOCAL)) cloudinit 会根据缓存中的虚拟机的uuid来与ConfigDrive 的对比,如果不一样,则认为这台虚拟机是新创建的虚拟机,只有新的虚拟机才会走入这里去配置网络。 -![](http://image.python-online.cn/20190911205518.png) +![](http://image.iswbm.com/20190911205518.png) 那问题又来了,虽然上面有个 bring_up 的参数,实际上,通过代码可以发现,在 local 阶段,bring_up 为 False 不会去启用网卡,而在 init 阶段呢,虽然 bring_up 为 True,但是此时,经过 local 阶段后,代码逻辑会认为这是台旧虚拟机,不会再走后面配置网络的函数,那就很奇怪了,网卡的ip是如何配置上的呢? @@ -606,9 +608,81 @@ runcmd: 但这仅仅只是写入,若要执行这些命令,还需要你在 /etc/cloud/cloud.cfg 中配置 `scripts_user`,这样 cloud-init 才会去执行它。 +## 8.6.5 如何读取修改缓存数据 + +当虚拟机启动时,cloudinit 会将 configdrive 里的数据读取并缓存,缓存文件是一个被序列化的二进制文件,名字固定为 `obj.pkl`。 + +序列化的函数存在于 stages.py 文件 + +- `_pkl_store()`:序列化 +- `_pkl_load()`:反序列化 + +![](http://image.iswbm.com/20200807135831.png) + +读取演示 + +``` +>>> from cloudinit import stages; +>>> file=stages._pkl_load("/var/lib/cloud/instances/1242171a-bf72-4a3a-acb0-9e5393e97865/obj.pkl") +>>> file.network_json +>>> file.metadata +``` + +写入演示 + +``` +>>> file.metadata["vhost_queues"]=1 +>>> stages._pkl_store(file, "/home/obj.pkl") +True +``` + + + +## 8.6.16 如何判断是新虚拟机 + +在 stages.py 下有一个函数 `is_new_instance()` + +![](http://image.iswbm.com/20200807161244.png) + +其中 `previous` 和 `nw_timestramp` 都是从 `/var/lib/cloud/instances/[instance_uud]/` 目录下直接读取的,导致旧虚拟机的信息。 + +而在 `/var/lib/cloud/instance` 目录下的信息,都是从 `/dev/sr0` 从读取的最新信息。 + +通过二者对比,就可以知道该虚拟机是否是一台新的虚拟机。 + + + +## 8.6.17 CentOS 6.x 如何更改网卡名 + +参考文章:[Changing the ethX to Ethernet Device Mapping in EL6](https://www.alteeve.com/w/Changing_the_ethX_to_Ethernet_Device_Mapping_in_EL6) + +关闭network + +```shell +$ /etc/init.d/network stop +``` + +修改ifcfg 文件里的 device 和 hwaddr + +```shell +$ vim /etc/sysconfig/network-scripts/ifcfg-eth* +``` + +修改 udev 网卡规则文件 + +```shell +vim /etc/udev/rules.d/70-persistent-net.rules +``` + +重新加载 udev,启动 network + +```shell +$ start_udev && /etc/init.d/network start +``` + -## 8.6.15 相关命令 +## 8.6.18 相关命令 ```shell # 查看机器里有哪几张网卡 diff --git a/source/c08/c08_06.rst b/source/c08/c08_06.rst index 7b04f5b..c5af0f5 100644 --- a/source/c08/c08_06.rst +++ b/source/c08/c08_06.rst @@ -19,7 +19,7 @@ metadata,完成一些虚拟机的初始化工作,这些工作有可能是每 这些任务都是以服务存在,并且安装完成后自动加入了开机自启。 -.. figure:: http://image.python-online.cn/20190430203920.png +.. figure:: http://image.iswbm.com/20190430203920.png :alt: CentOS 7.x CentOS 7.x @@ -533,7 +533,9 @@ ubuntu 的网卡配置不是正常我们常见的 json 或者 yaml cloud-init 进行新ip的配置,而新ip的配置是使用 ``ifup`` 这个命令\ |image27| -使用这种方式并不会将第一次配置的旧ip给清除掉。\ |image28| +使用这种方式并不会将第一次配置的旧ip给清除掉。 + +|image28| 这个问题,目前我只在CentOS6 中遇到过。可以通过修改代码让其先 ``ifdown`` 再 ``ifup`` 就可以解决这个问题。\ |image29| @@ -690,7 +692,84 @@ cloudinit 允许通过 user_data 指定你想在虚拟机启动时,执行的 但这仅仅只是写入,若要执行这些命令,还需要你在 /etc/cloud/cloud.cfg 中配置 ``scripts_user``\ ,这样 cloud-init 才会去执行它。 -8.6.15 相关命令 +8.6.5 如何读取修改缓存数据 +-------------------------- + +当虚拟机启动时,cloudinit 会将 configdrive +里的数据读取并缓存,缓存文件是一个被序列化的二进制文件,名字固定为 +``obj.pkl``\ 。 + +序列化的函数存在于 stages.py 文件 + +- ``_pkl_store()``\ :序列化 +- ``_pkl_load()``\ :反序列化 + +|image42| + +读取演示 + +:: + + >>> from cloudinit import stages; + >>> file=stages._pkl_load("/var/lib/cloud/instances/1242171a-bf72-4a3a-acb0-9e5393e97865/obj.pkl") + >>> file.network_json + >>> file.metadata + +写入演示 + +:: + + >>> file.metadata["vhost_queues"]=1 + >>> stages._pkl_store(file, "/home/obj.pkl") + True + +8.6.16 如何判断是新虚拟机 +------------------------- + +在 stages.py 下有一个函数 ``is_new_instance()`` + +|image43| + +其中 ``previous`` 和 ``nw_timestramp`` 都是从 +``/var/lib/cloud/instances/[instance_uud]/`` +目录下直接读取的,导致旧虚拟机的信息。 + +而在 ``/var/lib/cloud/instance`` 目录下的信息,都是从 ``/dev/sr0`` +从读取的最新信息。 + +通过二者对比,就可以知道该虚拟机是否是一台新的虚拟机。 + +8.6.17 CentOS 6.x 如何更改网卡名 +-------------------------------- + +参考文章:\ `Changing the ethX to Ethernet Device Mapping in +EL6 `__ + +关闭network + +.. code:: shell + + $ /etc/init.d/network stop + +修改ifcfg 文件里的 device 和 hwaddr + +.. code:: shell + + $ vim /etc/sysconfig/network-scripts/ifcfg-eth* + +修改 udev 网卡规则文件 + +.. code:: shell + + vim /etc/udev/rules.d/70-persistent-net.rules + +重新加载 udev,启动 network + +.. code:: shell + + $ start_udev && /etc/init.d/network start + +8.6.18 相关命令 --------------- .. code:: shell @@ -707,49 +786,51 @@ cloudinit 允许通过 user_data 指定你想在虚拟机启动时,执行的 -------------- -|image42| +|image44| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190430204707.png -.. |image2| image:: http://image.python-online.cn/20190430204933.png -.. |image3| image:: http://image.python-online.cn/20190430205449.png -.. |image4| image:: http://image.python-online.cn/20190430211900.png -.. |image5| image:: http://image.python-online.cn/20190430213012.png -.. |image6| image:: http://image.python-online.cn/20190430213337.png -.. |image7| image:: http://image.python-online.cn/20190430213429.png -.. |image8| image:: http://image.python-online.cn/20190430213729.png -.. |image9| image:: http://image.python-online.cn/20190430225605.png -.. |image10| image:: http://image.python-online.cn/20190430225726.png -.. |image11| image:: http://image.python-online.cn/20190430230214.png -.. |image12| image:: http://image.python-online.cn/FpqcyL4hWwpaAGzsdreQwXvH4Rx8 -.. |image13| image:: http://image.python-online.cn/20190430230839.png -.. |image14| image:: http://image.python-online.cn/20190430231108.png -.. |image15| image:: http://image.python-online.cn/20190623091911.png -.. |image16| image:: http://image.python-online.cn/20190708175813.png -.. |image17| image:: http://image.python-online.cn/20190910160035.png -.. |image18| image:: http://image.python-online.cn/20190910150305.png -.. |image19| image:: http://image.python-online.cn/20190910142222.png -.. |image20| image:: http://image.python-online.cn/20190910153637.png -.. |image21| image:: http://image.python-online.cn/20190910171359.png -.. |image22| image:: http://image.python-online.cn/20190910171538.png -.. |image23| image:: http://image.python-online.cn/20190906091102.png -.. |image24| image:: http://image.python-online.cn/20190909172153.png -.. |image25| image:: http://image.python-online.cn/20190429104357.png -.. |image26| image:: http://image.python-online.cn/20190829141059.png -.. |image27| image:: http://image.python-online.cn/Fp1TeHSiIMIQoZygbW9VSfAagB_d -.. |image28| image:: http://image.python-online.cn/Fh-5SQ8qYjhJEKovI6LmIpabSy2c -.. |image29| image:: http://image.python-online.cn/20190430231812.png -.. |image30| image:: http://image.python-online.cn/20190430232309.png -.. |image31| image:: http://image.python-online.cn/20190429205735.png -.. |image32| image:: http://image.python-online.cn/20190430232911.png -.. |image33| image:: http://image.python-online.cn/20190911175423.png -.. |image34| image:: http://image.python-online.cn/20190911174648.png -.. |image35| image:: http://image.python-online.cn/20190911173615.png -.. |image36| image:: http://image.python-online.cn/20190911195024.png -.. |image37| image:: http://image.python-online.cn/20190911202425.png -.. |image38| image:: http://image.python-online.cn/20190911202551.png -.. |image39| image:: http://image.python-online.cn/20190911203953.png -.. |image40| image:: http://image.python-online.cn/20190911204805.png -.. |image41| image:: http://image.python-online.cn/20190911205518.png -.. |image42| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20190430204707.png +.. |image2| image:: http://image.iswbm.com/20190430204933.png +.. |image3| image:: http://image.iswbm.com/20190430205449.png +.. |image4| image:: http://image.iswbm.com/20190430211900.png +.. |image5| image:: http://image.iswbm.com/20190430213012.png +.. |image6| image:: http://image.iswbm.com/20190430213337.png +.. |image7| image:: http://image.iswbm.com/20190430213429.png +.. |image8| image:: http://image.iswbm.com/20190430213729.png +.. |image9| image:: http://image.iswbm.com/20190430225605.png +.. |image10| image:: http://image.iswbm.com/20190430225726.png +.. |image11| image:: http://image.iswbm.com/20190430230214.png +.. |image12| image:: http://image.iswbm.com/FpqcyL4hWwpaAGzsdreQwXvH4Rx8 +.. |image13| image:: http://image.iswbm.com/20190430230839.png +.. |image14| image:: http://image.iswbm.com/20190430231108.png +.. |image15| image:: http://image.iswbm.com/20190623091911.png +.. |image16| image:: http://image.iswbm.com/20190708175813.png +.. |image17| image:: http://image.iswbm.com/20190910160035.png +.. |image18| image:: http://image.iswbm.com/20190910150305.png +.. |image19| image:: http://image.iswbm.com/20190910142222.png +.. |image20| image:: http://image.iswbm.com/20190910153637.png +.. |image21| image:: http://image.iswbm.com/20190910171359.png +.. |image22| image:: http://image.iswbm.com/20190910171538.png +.. |image23| image:: http://image.iswbm.com/20190906091102.png +.. |image24| image:: http://image.iswbm.com/20190909172153.png +.. |image25| image:: http://image.iswbm.com/20190429104357.png +.. |image26| image:: http://image.iswbm.com/20190829141059.png +.. |image27| image:: http://image.iswbm.com/Fp1TeHSiIMIQoZygbW9VSfAagB_d +.. |image28| image:: http://image.iswbm.com/Fh-5SQ8qYjhJEKovI6LmIpabSy2c +.. |image29| image:: http://image.iswbm.com/20190430231812.png +.. |image30| image:: http://image.iswbm.com/20190430232309.png +.. |image31| image:: http://image.iswbm.com/20190429205735.png +.. |image32| image:: http://image.iswbm.com/20190430232911.png +.. |image33| image:: http://image.iswbm.com/20190911175423.png +.. |image34| image:: http://image.iswbm.com/20190911174648.png +.. |image35| image:: http://image.iswbm.com/20190911173615.png +.. |image36| image:: http://image.iswbm.com/20190911195024.png +.. |image37| image:: http://image.iswbm.com/20190911202425.png +.. |image38| image:: http://image.iswbm.com/20190911202551.png +.. |image39| image:: http://image.iswbm.com/20190911203953.png +.. |image40| image:: http://image.iswbm.com/20190911204805.png +.. |image41| image:: http://image.iswbm.com/20190911205518.png +.. |image42| image:: http://image.iswbm.com/20200807135831.png +.. |image43| image:: http://image.iswbm.com/20200807161244.png +.. |image44| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c08/c08_07.md b/source/c08/c08_07.md index adf8987..42abc54 100644 --- a/source/c08/c08_07.md +++ b/source/c08/c08_07.md @@ -8,11 +8,11 @@ (若没有lspci命令,则安装 yum install pciutils -y) -![](http://image.python-online.cn/20190419144135.png) +![](http://image.iswbm.com/20190419144135.png) 此时查看,驱动是 nvidia -![](http://image.python-online.cn/20190419144044.png) +![](http://image.iswbm.com/20190419144044.png) ``` [root@ws_compute11 ~]# lspci -nnk -d 10de:1bb3 @@ -36,15 +36,15 @@ 未创建虚拟机 -![](http://image.python-online.cn/20190422201117.png) +![](http://image.iswbm.com/20190422201117.png) 创建gpu虚拟机成功,driver 由 nvidia 变为 vfio-pci -![](http://image.python-online.cn/20190422201041.png) +![](http://image.iswbm.com/20190422201041.png) 删除虚拟机成功 -![](http://image.python-online.cn/20190422201117.png) +![](http://image.iswbm.com/20190422201117.png) ### 8.7.2.2 隐藏虚拟机标识 @@ -52,7 +52,7 @@ (若没有cpuid,则安装 yum install -y cpuid) -![](http://image.python-online.cn/20190422205222.png) +![](http://image.iswbm.com/20190422205222.png) 如何隐藏 hypervisor id,只需要在xml的feature加上这段。 @@ -64,7 +64,7 @@ 然后destroy再start一下虚拟机,在虚拟机内部再次查看。 -![](http://image.python-online.cn/20190422204755.png) +![](http://image.iswbm.com/20190422204755.png) 在Pike 版本,OpenStack 已经可以根据镜像里是否有`img_hide_hypervisor_id=true` 的property,来选择是否加上隐藏hypervisor_id的xml。 @@ -82,15 +82,15 @@ openstack image set IMG-UUID --property img_hide_hypervisor_id=true 具体 Pike版的代码如何实现,主要函数是这段 -![](http://image.python-online.cn/20190528105408.png) +![](http://image.iswbm.com/20190528105408.png) 记得在镜像meta里添加 img_hide_hypervisor_id 的字段 -![](http://image.python-online.cn/20190528105021.png) +![](http://image.iswbm.com/20190528105021.png) ### 8.7.2.3. 是否直通成功 -在虚拟机内部执行 nvidia-smi![](http://image.python-online.cn/20190528114526.png) +在虚拟机内部执行 nvidia-smi![](http://image.iswbm.com/20190528114526.png) ### 8.7.2.4 称重器 @@ -98,7 +98,7 @@ openstack image set IMG-UUID --property img_hide_hypervisor_id=true 因此,需要增加一个称重器,,当创建普通类型的虚拟机时,含 GPU (通过pci_device 设备数来决定)的宿主机的权重为最低,为了保证这点,这个称重器的权重系数应最大,暂定为200(可以通过配置项 ws_gpu_weight_multiplier 设置)。 -![](http://image.python-online.cn/20190606185531.png) +![](http://image.iswbm.com/20190606185531.png) ## 8.7.3 使用说明 diff --git a/source/c08/c08_07.rst b/source/c08/c08_07.rst index 15c40f9..38c58d3 100644 --- a/source/c08/c08_07.rst +++ b/source/c08/c08_07.rst @@ -153,16 +153,16 @@ GPU 作为一种硬件资源,同样需要在模板中配置,配置方式是 |image12| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190419144135.png -.. |image2| image:: http://image.python-online.cn/20190419144044.png -.. |image3| image:: http://image.python-online.cn/20190422201117.png -.. |image4| image:: http://image.python-online.cn/20190422201041.png -.. |image5| image:: http://image.python-online.cn/20190422201117.png -.. |image6| image:: http://image.python-online.cn/20190422205222.png -.. |image7| image:: http://image.python-online.cn/20190422204755.png -.. |image8| image:: http://image.python-online.cn/20190528105408.png -.. |image9| image:: http://image.python-online.cn/20190528105021.png -.. |image10| image:: http://image.python-online.cn/20190528114526.png -.. |image11| image:: http://image.python-online.cn/20190606185531.png +.. |image1| image:: http://image.iswbm.com/20190419144135.png +.. |image2| image:: http://image.iswbm.com/20190419144044.png +.. |image3| image:: http://image.iswbm.com/20190422201117.png +.. |image4| image:: http://image.iswbm.com/20190422201041.png +.. |image5| image:: http://image.iswbm.com/20190422201117.png +.. |image6| image:: http://image.iswbm.com/20190422205222.png +.. |image7| image:: http://image.iswbm.com/20190422204755.png +.. |image8| image:: http://image.iswbm.com/20190528105408.png +.. |image9| image:: http://image.iswbm.com/20190528105021.png +.. |image10| image:: http://image.iswbm.com/20190528114526.png +.. |image11| image:: http://image.iswbm.com/20190606185531.png .. |image12| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c08/c08_08.md b/source/c08/c08_08.md index 14161ec..f0ca601 100644 --- a/source/c08/c08_08.md +++ b/source/c08/c08_08.md @@ -19,7 +19,7 @@ 如下图所示 -![](http://image.python-online.cn/20190514202013.png) +![](http://image.iswbm.com/20190514202013.png) 红色部分是虚拟机内部需要增加的网卡设备。 @@ -80,7 +80,7 @@ TYPE=OVSBridge 确保控制节点都安装了 `neutron-openvswitch-agent` 和 `neutron-dhcp-agent` 等几个包 -![](http://image.python-online.cn/20190514202442.png) +![](http://image.iswbm.com/20190514202442.png) 确保如下几个配置无误 @@ -115,7 +115,7 @@ systemctl restart neutron-openvswitch-agent 通过 ovs-vsctl show 检查环境是否正常 -![](http://image.python-online.cn/20190514202736.png) +![](http://image.iswbm.com/20190514202736.png) @@ -129,7 +129,7 @@ systemctl restart neutron-openvswitch-agent 1. 启用DHCP后,dhcp server 会占用一个allocation_pools中的ip。 2. 子网启用了dhcp,ConfigDrive不会携带静态的ip地址(类型为ipv4,左图),而是携带了将类型变更为ipv4_dhcp(右图),虚拟机启动时发送广播消息DHCP discover从dhcp server获得真正的ip地址。 - ![](http://image.python-online.cn/20190514203612.png) + ![](http://image.iswbm.com/20190514203612.png) 3. 若子网启用了dhcp,并且创建了虚拟机,此时 disable dhcp,只是表象上 dhcp 被关了,而实际 dhcp port 被是占用着,因为 dhcp server 还要给之前创建的虚拟机提供服务。如果再用这个子网创建的虚拟机会使用dhcp ip而不是使用 static ip,而当有删除(新增)其他子网的动作时,这个dhcp-port 又会被删除。如果一个子网开启了dhcp,并且这个子网下**没有虚拟机**,更新子网 disable dhcp,dhcp-port**会立即删除**。再用这个子网创建的虚拟机会使用static ip。如果一个子网开启了dhcp,并且这个子网下**有虚拟机**,更新子网 disable dhcp,dhcp-port**不会立即删除**。再用这个子网创建的虚拟机会使用dhcp获取ip。 4. dnsmasq进程是由dhcp-agent 服务管理的。如果停用 dhcp-agent 时,dnsmasq 进程并不会关闭。如果同一个网络下有多个dnsmasq ,不会影响正常的dhcp获取ip。 5. 如果三个控制节点上的 dhcp-agent 都是关闭状态,此时创建虚拟机时,ip仍然会正常分配、port会正常创建,但由于nova-compute等不到neutron的port plugged事件,过一段时间就超时导致创建虚拟机失败。而如果在超时范围内将dhcp-agent启动起来,就可以立即创建成功。 @@ -151,15 +151,15 @@ systemctl restart neutron-openvswitch-agent 比如 local 阶段作为第一个执行的。 -![](http://image.python-online.cn/20190430204707.png) +![](http://image.iswbm.com/20190430204707.png) 而后的几个服务(如init阶段),都需要保证 后于 local 已经启动过才能运行。 -![](http://image.python-online.cn/20190430204933.png) +![](http://image.iswbm.com/20190430204933.png) 在 **CentOS 6.x** 中,由于系统过于古老并没有 systemd ,它的服务启动顺序是由 `/etc/rc.d/rc3.d` 目录进行管理的,按照编号进行从小到大执行。 -![](http://image.python-online.cn/20190430205449.png) +![](http://image.iswbm.com/20190430205449.png) 这个启动顺序相当重要,在 CentOS6.x 上会因此出现问题 @@ -169,7 +169,7 @@ systemctl restart neutron-openvswitch-agent 从以信息可知,如果创建静态ip的虚拟机,NetworkManager 这个服务必须在 cloudinit-local 之后启动才可正常从配置文件中读取 ip 并配置。而当你在镜像里安装 NetworkManager后,默认情况下它的启动顺序是会在 cloudinit-local 之前的。 -![img](http://image.python-online.cn/20190430211900.png) +![img](http://image.iswbm.com/20190430211900.png) **解决方法**也很简单,将 `S23NetworkManager` 重命名为 `S54NetworkManager` 即可。 diff --git a/source/c08/c08_08.rst b/source/c08/c08_08.rst index f23daba..64457db 100644 --- a/source/c08/c08_08.rst +++ b/source/c08/c08_08.rst @@ -225,7 +225,7 @@ cloudinit-local 之后启动才可正常从配置文件中读取 ip 并配置。而当你在镜像里安装 NetworkManager后,默认情况下它的启动顺序是会在 cloudinit-local 之前的。 -.. figure:: http://image.python-online.cn/20190430211900.png +.. figure:: http://image.iswbm.com/20190430211900.png :alt: img img @@ -254,12 +254,12 @@ setup_dhcp_port(),从这个函数里可以知道,dhcp-port的创建顺序: |image8| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190514202013.png -.. |image2| image:: http://image.python-online.cn/20190514202442.png -.. |image3| image:: http://image.python-online.cn/20190514202736.png -.. |image4| image:: http://image.python-online.cn/20190514203612.png -.. |image5| image:: http://image.python-online.cn/20190430204707.png -.. |image6| image:: http://image.python-online.cn/20190430204933.png -.. |image7| image:: http://image.python-online.cn/20190430205449.png +.. |image1| image:: http://image.iswbm.com/20190514202013.png +.. |image2| image:: http://image.iswbm.com/20190514202442.png +.. |image3| image:: http://image.iswbm.com/20190514202736.png +.. |image4| image:: http://image.iswbm.com/20190514203612.png +.. |image5| image:: http://image.iswbm.com/20190430204707.png +.. |image6| image:: http://image.iswbm.com/20190430204933.png +.. |image7| image:: http://image.iswbm.com/20190430205449.png .. |image8| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c08/c08_09.md b/source/c08/c08_09.md index 39d5279..3a1a520 100644 --- a/source/c08/c08_09.md +++ b/source/c08/c08_09.md @@ -201,7 +201,7 @@ import jsonrpclib server = jsonrpclib.Server("http://localhost:8080") ``` -![](http://image.python-online.cn/20190623185008.png) +![](http://image.iswbm.com/20190623185008.png) 再来看第二种python-jsonrpc,写起来貌似有些复杂。 @@ -237,13 +237,13 @@ http_client = pyjsonrpc.HttpClient( ) ``` -![](http://image.python-online.cn/20190623165341.png) +![](http://image.iswbm.com/20190623165341.png) 还记得上面我提到过的 zabbix API,因为我有接触过,所以也拎出来讲讲。zabbix API 也是基于 json-rpc 2.0协议实现的。 因为内容较多,这里只带大家打个,zabbix 是如何调用的:直接指明要调用 zabbix server 的哪个方法,要传给这个方法的参数有哪些。 -![](http://image.python-online.cn/20190623171138.png) +![](http://image.iswbm.com/20190623171138.png) **03、基于 zerorpc** @@ -293,11 +293,11 @@ c = zerorpc.Client() c.connect("tcp://127.0.0.1:4242") ``` -![](http://image.python-online.cn/20190623155955.png) +![](http://image.iswbm.com/20190623155955.png) 客户端除了可以使用zerorpc框架实现代码调用之外,它还支持使用“命令行”的方式调用。 -![](http://image.python-online.cn/20190623162725.png) +![](http://image.iswbm.com/20190623162725.png) 客户端可以使用命令行,那服务端是不是也可以呢? @@ -315,7 +315,7 @@ zerorpc --server --bind tcp://127.0.0.1:1234 time zerorpc --client --connect tcp://127.0.0.1:1234 strftime %Y/%m/%d ``` -![](http://image.python-online.cn/20190623191042.png) +![](http://image.iswbm.com/20190623191042.png) ## 8.9.3 往rpc中引入消息中间件 @@ -375,7 +375,7 @@ AMQP(Advanced Message Queuing Protocol)是一种基于队列的可靠消息服 为了让你明白这几者的关系,我画了一张模型图。 -![](http://image.python-online.cn/20190630160025.png) +![](http://image.iswbm.com/20190630160025.png) 关于AMQP,有几下几点值得注意: @@ -450,7 +450,7 @@ def consumer(): 其他模型我不太清楚,在 OpenStack 中的应用模型是这样的 -![](http://image.python-online.cn/20190623201427.png) +![](http://image.iswbm.com/20190623201427.png) 至于为什么要如此设计,前面我已经给出了自己的观点。 @@ -465,7 +465,7 @@ openstack.common.rpc是旧的实现,oslo messaging是对openstack.common.rpc 1. oslo.messaging.rpc(实现了客户端-服务器远程过程调用) 2. oslo.messaging.notify(实现了事件的通知机制) -因为 notify 实现是太简单了,所以这里我就不多说了,如果有人想要看这方面内容,可以收藏我的博客(http://python-online.cn) ,我会更新补充 notify 的内容。 +因为 notify 实现是太简单了,所以这里我就不多说了,如果有人想要看这方面内容,可以收藏我的博客(http://iswbm.com) ,我会更新补充 notify 的内容。 OpenStack RPC 模块提供了 rpc.call,rpc.cast, rpc.fanout_cast 三种 RPC 调用方法,发送和接收 RPC 请求。 @@ -485,7 +485,7 @@ rpc.call 和 rpc.cast 从实现代码上看,他们的区别很小,就是call transport_url=rabbit://user:passwd@host:5672 ``` - ![](http://image.python-online.cn/20190526182125.png) + ![](http://image.iswbm.com/20190526182125.png) ```python def get_transport(conf, url=None, allowed_remote_exmods=None): @@ -513,25 +513,25 @@ rpc.call 和 rpc.cast 从实现代码上看,他们的区别很小,就是call rpc server 要获取消息,需要定义target,就像一个门牌号一样。 - ![](http://image.python-online.cn/20190526184854.png) + ![](http://image.iswbm.com/20190526184854.png) rpc client 要发送消息,也需要有target,说明消息要发到哪去。 - ![](http://image.python-online.cn/20190526185217.png) + ![](http://image.iswbm.com/20190526185217.png) - endpoints:是可供别人远程调用的对象 - RPC服务器暴露出endpoint,每个 endpoint 包涵一系列的可被远程客户端通过 transport 调用的方法。直观理解,可以参考nova-conductor创建rpc server的代码,这边的endpoints就是 `nova/manager.py:ConductorManager()`![](http://image.python-online.cn/20190526221219.png) + RPC服务器暴露出endpoint,每个 endpoint 包涵一系列的可被远程客户端通过 transport 调用的方法。直观理解,可以参考nova-conductor创建rpc server的代码,这边的endpoints就是 `nova/manager.py:ConductorManager()`![](http://image.iswbm.com/20190526221219.png) -- dispatcher:分发器,这是 rpc server 才有的概念 ![](http://image.python-online.cn/20190526220809.png)只有通过它 server 端才知道接收到的rpc请求,要交给谁处理,怎么处理? +- dispatcher:分发器,这是 rpc server 才有的概念 ![](http://image.iswbm.com/20190526220809.png)只有通过它 server 端才知道接收到的rpc请求,要交给谁处理,怎么处理? 在client端,是这样指定要调用哪个方法的。 - ![](http://image.python-online.cn/20190527220820.png) + ![](http://image.iswbm.com/20190527220820.png) 而在server端,是如何知道要执行这个方法的呢?这就是dispatcher 要干的事,它从 endpoint 里找到这个方法,然后执行,最后返回。 - ![](http://image.python-online.cn/20190527220012.png) + ![](http://image.iswbm.com/20190527220012.png) - Serializer:在 python 对象和message(notification) 之间数据做序列化或是反序列化的基类。 @@ -636,29 +636,29 @@ server.wait() 首先在这里先定义合法的 `notification_event_types`,相当于添加白名单。 -![](http://image.python-online.cn/20190526172514.png) +![](http://image.iswbm.com/20190526172514.png) 然后在调用处,使用 `rpc.get_notifier` 来发送消息给ceilometer。 -![](http://image.python-online.cn/20190526172725.png) +![](http://image.iswbm.com/20190526172725.png) 继续查看 `rpc.get_notifier` 做了什么事?如何实现直接info 就能发送消息的。 -![](http://image.python-online.cn/20190526173314.png) +![](http://image.iswbm.com/20190526173314.png) 当你使用的event_types 不在白名单内,或者是异常信息。就会给打印warn日志 -![](http://image.python-online.cn/20190526175100.png) +![](http://image.iswbm.com/20190526175100.png) 在rabbit里查看队列,notification 是 topic -![](http://image.python-online.cn/20190526180708.png) +![](http://image.iswbm.com/20190526180708.png) 而 debug ,info 等是event priority -![](http://image.python-online.cn/20190526181433.png) +![](http://image.iswbm.com/20190526181433.png) diff --git a/source/c08/c08_09.rst b/source/c08/c08_09.rst index f0c6701..8309c93 100644 --- a/source/c08/c08_09.rst +++ b/source/c08/c08_09.rst @@ -543,7 +543,7 @@ messaging也对RPC API 进行了重新设计,对多种 transport 2. oslo.messaging.notify(实现了事件的通知机制) 因为 notify -实现是太简单了,所以这里我就不多说了,如果有人想要看这方面内容,可以收藏我的博客(http://python-online.cn) +实现是太简单了,所以这里我就不多说了,如果有人想要看这方面内容,可以收藏我的博客(http://iswbm.com) ,我会更新补充 notify 的内容。 OpenStack RPC 模块提供了 rpc.call,rpc.cast, rpc.fanout_cast 三种 RPC @@ -787,26 +787,26 @@ rpc server 和rpc client 的四个重要方法 |image22| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190623185008.png -.. |image2| image:: http://image.python-online.cn/20190623165341.png -.. |image3| image:: http://image.python-online.cn/20190623171138.png -.. |image4| image:: http://image.python-online.cn/20190623155955.png -.. |image5| image:: http://image.python-online.cn/20190623162725.png -.. |image6| image:: http://image.python-online.cn/20190623191042.png -.. |image7| image:: http://image.python-online.cn/20190630160025.png -.. |image8| image:: http://image.python-online.cn/20190623201427.png -.. |image9| image:: http://image.python-online.cn/20190526182125.png -.. |image10| image:: http://image.python-online.cn/20190526184854.png -.. |image11| image:: http://image.python-online.cn/20190526185217.png -.. |image12| image:: http://image.python-online.cn/20190526221219.png -.. |image13| image:: http://image.python-online.cn/20190526220809.png -.. |image14| image:: http://image.python-online.cn/20190527220820.png -.. |image15| image:: http://image.python-online.cn/20190527220012.png -.. |image16| image:: http://image.python-online.cn/20190526172514.png -.. |image17| image:: http://image.python-online.cn/20190526172725.png -.. |image18| image:: http://image.python-online.cn/20190526173314.png -.. |image19| image:: http://image.python-online.cn/20190526175100.png -.. |image20| image:: http://image.python-online.cn/20190526180708.png -.. |image21| image:: http://image.python-online.cn/20190526181433.png +.. |image1| image:: http://image.iswbm.com/20190623185008.png +.. |image2| image:: http://image.iswbm.com/20190623165341.png +.. |image3| image:: http://image.iswbm.com/20190623171138.png +.. |image4| image:: http://image.iswbm.com/20190623155955.png +.. |image5| image:: http://image.iswbm.com/20190623162725.png +.. |image6| image:: http://image.iswbm.com/20190623191042.png +.. |image7| image:: http://image.iswbm.com/20190630160025.png +.. |image8| image:: http://image.iswbm.com/20190623201427.png +.. |image9| image:: http://image.iswbm.com/20190526182125.png +.. |image10| image:: http://image.iswbm.com/20190526184854.png +.. |image11| image:: http://image.iswbm.com/20190526185217.png +.. |image12| image:: http://image.iswbm.com/20190526221219.png +.. |image13| image:: http://image.iswbm.com/20190526220809.png +.. |image14| image:: http://image.iswbm.com/20190527220820.png +.. |image15| image:: http://image.iswbm.com/20190527220012.png +.. |image16| image:: http://image.iswbm.com/20190526172514.png +.. |image17| image:: http://image.iswbm.com/20190526172725.png +.. |image18| image:: http://image.iswbm.com/20190526173314.png +.. |image19| image:: http://image.iswbm.com/20190526175100.png +.. |image20| image:: http://image.iswbm.com/20190526180708.png +.. |image21| image:: http://image.iswbm.com/20190526181433.png .. |image22| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c08/c08_10.md b/source/c08/c08_10.md index b5fdd36..e2d53e0 100644 --- a/source/c08/c08_10.md +++ b/source/c08/c08_10.md @@ -1,4 +1,4 @@ -# 8.16 修改 KVM 镜像文件的三种方法 +# 8.10 修改 KVM 镜像文件的三种方法 ![](http://image.iswbm.com/20200602135014.png) @@ -6,7 +6,7 @@ ## 8.16.1 使用 guestfish -![](http://image.python-online.cn/20191111112221.png) +![](http://image.iswbm.com/20191111112221.png) guestfish 里的命令有限,大概会 600 多个,不能 cd,操作文件时,需要使用绝对路径。 @@ -14,10 +14,10 @@ guestfish 里的命令有限,大概会 600 多个,不能 cd,操作文件 ## 8.16.2 使用 guestmount -![](http://image.python-online.cn/20191111112421.png) +![](http://image.iswbm.com/20191111112421.png) 使用完后,记得 umount /home/wangbm/mount ## 8.16.3 使用 virt-* 命令 -![](http://image.python-online.cn/20191111112548.png) \ No newline at end of file +![](http://image.iswbm.com/20191111112548.png) \ No newline at end of file diff --git a/source/c08/c08_10.rst b/source/c08/c08_10.rst index 46e08f3..20abedb 100644 --- a/source/c08/c08_10.rst +++ b/source/c08/c08_10.rst @@ -1,4 +1,4 @@ -8.16 修改 KVM 镜像文件的三种方法 +8.10 修改 KVM 镜像文件的三种方法 ================================ |image0| @@ -26,7 +26,7 @@ cd,操作文件时,需要使用绝对路径。 |image3| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20191111112221.png -.. |image2| image:: http://image.python-online.cn/20191111112421.png -.. |image3| image:: http://image.python-online.cn/20191111112548.png +.. |image1| image:: http://image.iswbm.com/20191111112221.png +.. |image2| image:: http://image.iswbm.com/20191111112421.png +.. |image3| image:: http://image.iswbm.com/20191111112548.png diff --git a/source/c08/c08_11.md b/source/c08/c08_11.md index 05883df..9bf4b88 100644 --- a/source/c08/c08_11.md +++ b/source/c08/c08_11.md @@ -14,7 +14,7 @@ error: Domain not found: no domain with matching name 'instance-00000699' `/var/log/messages` 报错如下 -![](http://image.python-online.cn/20190530175817.png) +![](http://image.iswbm.com/20190530175817.png) 解决方法 diff --git a/source/c08/c08_11.rst b/source/c08/c08_11.rst index 432b22e..a40a42e 100644 --- a/source/c08/c08_11.rst +++ b/source/c08/c08_11.rst @@ -87,6 +87,6 @@ ConfigDrive 是一个 iso9660 格式的文件,只读。 |image2| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190530175817.png +.. |image1| image:: http://image.iswbm.com/20190530175817.png .. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c08/c08_12.md b/source/c08/c08_12.md index 1ad11bb..5a3ef06 100644 --- a/source/c08/c08_12.md +++ b/source/c08/c08_12.md @@ -18,11 +18,11 @@ 从源代码中看,最开始是 nova-conductor (nova/conductor/manager.py)在给 nova-compute 发创建请求前,会先让 nova-scheduler 选出一台资源充足的计算节点。 -![](http://image.python-online.cn/20190424212211.png) +![](http://image.iswbm.com/20190424212211.png) nova-scheduler 的调度主要由两部分组成(nova/scheduler/filter_scheduler.py:FilterScheduler._schedule()) -![](http://image.python-online.cn/20190424213430.png) +![](http://image.iswbm.com/20190424213430.png) - 过滤器:filter,将不满足条件(硬性条件,比如内存,cpu,磁盘,pci设备等)的计算节点,直接过滤掉。意义:从过滤器出来的那些计算节点,理论上都可以创建虚拟机。 - 称重器:weigher,对满足硬性条件的众多主机按照一定的规则进行权重配比。意义:经过称重器计算,选出你更希望在哪台节点上创建虚拟机。 @@ -31,23 +31,23 @@ nova-scheduler 的调度主要由两部分组成(nova/scheduler/filter_schedul - hosts:多个 host_state 的集合,包含有当前可用的计算节点信息(资源,ip等)。其中单个元素是 HostState (nova/scheduler/host_manager.py)类的实例。如果你想添加其他原来没有的信息,比如 compute 的 id,可以在 `_update_from_compute_node` 函数中添加。它会从compute_nodes 表中取得你想要的信息。 - ![](http://image.python-online.cn/20190424214653.png) + ![](http://image.iswbm.com/20190424214653.png) - spec_obj:你所要请求创建的虚拟机信息(模板,镜像等)。它是从 objects.RequestSpec.from_primitives 中取得的 - ![](http://image.python-online.cn/20190424214540.png) + ![](http://image.iswbm.com/20190424214540.png) 过滤器,它的代码如下: -![](http://image.python-online.cn/20190424221602.png) +![](http://image.iswbm.com/20190424221602.png) 称重器,它的规则主要看这段代码。 -![](http://image.python-online.cn/20190424215735.png) +![](http://image.iswbm.com/20190424215735.png) 我在代码中,加了几段日志。从左到右,三个不同颜色的内容分别为,原始权值,配重系数(越高说明越占比越大,越影响最终结果),经过 nomalize 后的权值(只有 0 和 1)。 -![](http://image.python-online.cn/20190424220008.png) +![](http://image.iswbm.com/20190424220008.png) 那最终的权值如何计算呢? @@ -66,7 +66,7 @@ LOG.debug("Selected host: %(host)s", {'host': chosen_host}) 当指定宿主机进行虚拟机的创建后,以上所有的过滤器都会无效(不会走代码)。 -![](http://image.python-online.cn/20191011103832.png) +![](http://image.iswbm.com/20191011103832.png) --- diff --git a/source/c08/c08_12.rst b/source/c08/c08_12.rst index be4958c..aca845c 100644 --- a/source/c08/c08_12.rst +++ b/source/c08/c08_12.rst @@ -91,13 +91,13 @@ nova-scheduler 选择到主机后,在日志中会打印三条DEBUG信息,可 |image9| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190424212211.png -.. |image2| image:: http://image.python-online.cn/20190424213430.png -.. |image3| image:: http://image.python-online.cn/20190424214653.png -.. |image4| image:: http://image.python-online.cn/20190424214540.png -.. |image5| image:: http://image.python-online.cn/20190424221602.png -.. |image6| image:: http://image.python-online.cn/20190424215735.png -.. |image7| image:: http://image.python-online.cn/20190424220008.png -.. |image8| image:: http://image.python-online.cn/20191011103832.png +.. |image1| image:: http://image.iswbm.com/20190424212211.png +.. |image2| image:: http://image.iswbm.com/20190424213430.png +.. |image3| image:: http://image.iswbm.com/20190424214653.png +.. |image4| image:: http://image.iswbm.com/20190424214540.png +.. |image5| image:: http://image.iswbm.com/20190424221602.png +.. |image6| image:: http://image.iswbm.com/20190424215735.png +.. |image7| image:: http://image.iswbm.com/20190424220008.png +.. |image8| image:: http://image.iswbm.com/20191011103832.png .. |image9| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c08/c08_13.md b/source/c08/c08_13.md index a472ab7..246870f 100644 --- a/source/c08/c08_13.md +++ b/source/c08/c08_13.md @@ -56,7 +56,7 @@ Netfilter是Linux操作系统核心层内部的一个数据包处理模块,它 **动作** -![](http://image.python-online.cn/20190706114314.png) +![](http://image.iswbm.com/20190706114314.png) ### 命令相关 @@ -87,7 +87,7 @@ Chain POSTROUTING (policy ACCEPT 21M packets, 1241M bytes) 多出来的几个字段如何理解呢?我摘取《[朱双印的个人日志](http://www.zsythink.net/archives/1493)》的解释到这边。 -![](http://image.python-online.cn/20190706093904.png) +![](http://image.iswbm.com/20190706093904.png) iptables 默认为我们配置了域名反解(根据ip解析成域名),这个过程效率很低,我们可以指定参数 `-n` 跳过这个过程,从下面的例子可以看到 `in` 从上面的 `any` 变成了 `*` (0.0.0.0) @@ -195,7 +195,7 @@ iptables -t filter -I INPUT 2 -s 172.20.20.202 -j REJECT iptables -t filter -P FORWARD DROP ``` -![](http://image.python-online.cn/20190706160632.png) +![](http://image.iswbm.com/20190706160632.png) 保存规则 @@ -229,7 +229,7 @@ arp的中文释义是地址解析协议,全英文 address resolution protocol `arp -e` 是一个很常用的命令,用于查看与本机有过通信的机器的arp table,主要是 ip与mac地址的映射。如果在 /etc/hosts 里有填写ip与域名的对应关系,Address一列就会显示域名。你也可以使用 `arp -a` 实现相同功能,只是 `-a` 是标准输出格式,没有像使用 `-e` 一样类似表格一样的输出效果。 -![](http://image.python-online.cn/20190804162402.png) +![](http://image.iswbm.com/20190804162402.png) 此时,我们ping一下 172.20.22.3 这个ip,显然是可以的,因为这里的其对应的mac地址是真实准确的。 diff --git a/source/c08/c08_13.rst b/source/c08/c08_13.rst index c61670f..cd83c95 100644 --- a/source/c08/c08_13.rst +++ b/source/c08/c08_13.rst @@ -317,9 +317,9 @@ cache里没有这个ip,就会重新发送arp广播,获取到正确的mac地 |image5| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190706114314.png -.. |image2| image:: http://image.python-online.cn/20190706093904.png -.. |image3| image:: http://image.python-online.cn/20190706160632.png -.. |image4| image:: http://image.python-online.cn/20190804162402.png +.. |image1| image:: http://image.iswbm.com/20190706114314.png +.. |image2| image:: http://image.iswbm.com/20190706093904.png +.. |image3| image:: http://image.iswbm.com/20190706160632.png +.. |image4| image:: http://image.iswbm.com/20190804162402.png .. |image5| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c08/c08_14.md b/source/c08/c08_14.md index c12b178..a8fc4fc 100644 --- a/source/c08/c08_14.md +++ b/source/c08/c08_14.md @@ -112,7 +112,7 @@ use_ipv6=true 才能将 IPv6 的 Port 信息写入ConfigDrive,也只有这样cloudinit才能自动为我们配置上 IPv6 的 ip。 -![](http://image.python-online.cn/20190716175250.png) +![](http://image.iswbm.com/20190716175250.png) 而对于 Neutron 来说,要使用 IPv6,当然要先创建一个 IPv6 的子网,这里要注意,IPv6 子网所属的网络,必须为flat,这个一定要注意,因为你不指定的话,默认就为 vlan。 @@ -195,11 +195,11 @@ neutron port-create --fixed-ip \ 在一张网卡上配置多个版本的IP(一个IPv4和一个IPv6),只在一个配置文件中配置就可以支持,因此cloudinit的处理的时候,会将同一个tap设备的归为一个同一张网卡。 -![](http://image.python-online.cn/20190716180655.png) +![](http://image.iswbm.com/20190716180655.png) 也就是说,cloudinit本身很轻松地就可以支持单网卡多版本IP的配置。 -![](http://image.python-online.cn/20190716180952.png) +![](http://image.iswbm.com/20190716180952.png) 而对于多线的IP,由于 Nova 写入ConfigDrive时,尽管一个Port上有多个IPv4或者多个IPv6,同个版本的都会只取第一个写入,自然从cloudinit那侧就无法进一步操作。 @@ -233,7 +233,7 @@ neutron port-create --fixed-ip \ }], ``` -- Neutron 接口可以不改,因为创建Port的接口参数中的 fixed_ips 是一个数组类型,所以我们可以把这些版本信息,运营商信息放进这个数组的每一个元素中。![](http://image.python-online.cn/20190804110647.png) +- Neutron 接口可以不改,因为创建Port的接口参数中的 fixed_ips 是一个数组类型,所以我们可以把这些版本信息,运营商信息放进这个数组的每一个元素中。![](http://image.iswbm.com/20190804110647.png) 所以我们只要修改Neutron 创建 Port 的代码逻辑。 @@ -245,7 +245,7 @@ neutron port-create --fixed-ip \ 最后另外再说一点无关紧要的,cloudinit 解析后,每张网卡一个ip与一张网卡两个ip的区别如下。 -![](http://image.python-online.cn/20190716180726.png) +![](http://image.iswbm.com/20190716180726.png) @@ -259,39 +259,39 @@ ovs-dpctl del-dp ovs-system && rmmod openvswitch && insmod ./openvswitch.ko && CentOS 6.x 在使用 ipv6 方面有问题 -![](http://image.python-online.cn/20190829103805.png) +![](http://image.iswbm.com/20190829103805.png) 导致在网卡配置文件里的配置的ip 都为none -![](http://image.python-online.cn/20190829104544.png) +![](http://image.iswbm.com/20190829104544.png) 上面的 callbak 是 这个函数 -![](http://image.python-online.cn/20190829104806.png) +![](http://image.iswbm.com/20190829104806.png) centos6.x 的网络信息不是从 latest 里的 network_data.json 里读取的,而是从 content/0000 里读取 -![](http://image.python-online.cn/20190829110541.png) +![](http://image.iswbm.com/20190829110541.png) 这是因为 centos 6.x 配置网络是在 local 阶段做的,而local阶段做的话,就会从 content/0000 -![](http://image.python-online.cn/20190829105558.png) +![](http://image.iswbm.com/20190829105558.png) 而 centos7.x 其实也会走 on_first_boot 去配置网络,但是cloudinit 在centos7.x 里 sources.DSMODE_PASS -![](http://image.python-online.cn/20190829112446.png) +![](http://image.iswbm.com/20190829112446.png) 导致在 local 阶段,这里配置网络被pass掉,自然也就不会从 content/0000 读取了。 -![](http://image.python-online.cn/20190829111917.png) +![](http://image.iswbm.com/20190829111917.png) centos 6.x 配置网络是在 on_first_boot 函数里,这是 local 阶段就会执行的。 -![](http://image.python-online.cn/20190829161243.png) +![](http://image.iswbm.com/20190829161243.png) @@ -299,7 +299,7 @@ centos 6.x 配置网络是在 on_first_boot 函数里,这是 local 阶段就 如果你给你的ubuntu配置上了ipv6的ip,那当你使用 apt-get install 软件包的时候,会使用ipv6去源安装,这将会使你的安装过程卡住: -![](http://image.python-online.cn/20190926171038.png) +![](http://image.iswbm.com/20190926171038.png) 如何解决这个问题呢? diff --git a/source/c08/c08_14.rst b/source/c08/c08_14.rst index aec531c..43552b4 100644 --- a/source/c08/c08_14.rst +++ b/source/c08/c08_14.rst @@ -328,19 +328,19 @@ centos 6.x 配置网络是在 on_first_boot 函数里,这是 local |image15| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190716175250.png -.. |image2| image:: http://image.python-online.cn/20190716180655.png -.. |image3| image:: http://image.python-online.cn/20190716180952.png -.. |image4| image:: http://image.python-online.cn/20190804110647.png -.. |image5| image:: http://image.python-online.cn/20190716180726.png -.. |image6| image:: http://image.python-online.cn/20190829103805.png -.. |image7| image:: http://image.python-online.cn/20190829104544.png -.. |image8| image:: http://image.python-online.cn/20190829104806.png -.. |image9| image:: http://image.python-online.cn/20190829110541.png -.. |image10| image:: http://image.python-online.cn/20190829105558.png -.. |image11| image:: http://image.python-online.cn/20190829112446.png -.. |image12| image:: http://image.python-online.cn/20190829111917.png -.. |image13| image:: http://image.python-online.cn/20190829161243.png -.. |image14| image:: http://image.python-online.cn/20190926171038.png +.. |image1| image:: http://image.iswbm.com/20190716175250.png +.. |image2| image:: http://image.iswbm.com/20190716180655.png +.. |image3| image:: http://image.iswbm.com/20190716180952.png +.. |image4| image:: http://image.iswbm.com/20190804110647.png +.. |image5| image:: http://image.iswbm.com/20190716180726.png +.. |image6| image:: http://image.iswbm.com/20190829103805.png +.. |image7| image:: http://image.iswbm.com/20190829104544.png +.. |image8| image:: http://image.iswbm.com/20190829104806.png +.. |image9| image:: http://image.iswbm.com/20190829110541.png +.. |image10| image:: http://image.iswbm.com/20190829105558.png +.. |image11| image:: http://image.iswbm.com/20190829112446.png +.. |image12| image:: http://image.iswbm.com/20190829111917.png +.. |image13| image:: http://image.iswbm.com/20190829161243.png +.. |image14| image:: http://image.iswbm.com/20190926171038.png .. |image15| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c08/c08_15.md b/source/c08/c08_15.md index c1b1bfb..8a302ce 100644 --- a/source/c08/c08_15.md +++ b/source/c08/c08_15.md @@ -2,27 +2,27 @@ ![](http://image.iswbm.com/20200602135014.png) -neutron api 的入口是在这里![](http://image.python-online.cn/20190804111844.png) +neutron api 的入口是在这里![](http://image.iswbm.com/20190804111844.png) -在这里会校验并打印请求的信息![](http://image.python-online.cn/20190804111715.png) +在这里会校验并打印请求的信息![](http://image.iswbm.com/20190804111715.png) -而对网络、子网、port操作的逻辑处理代码的入口都是从这里开始的![](http://image.python-online.cn/20190803181706.png) +而对网络、子网、port操作的逻辑处理代码的入口都是从这里开始的![](http://image.iswbm.com/20190803181706.png) ## 8.15.1 创建Port -从 neutron api 请求过来后,就会经过这里![](http://image.python-online.cn/20190803182042.png) +从 neutron api 请求过来后,就会经过这里![](http://image.iswbm.com/20190803182042.png) -把 port 信息打印一下![](http://image.python-online.cn/20190803182223.png) +把 port 信息打印一下![](http://image.iswbm.com/20190803182223.png) 点进上面的` self._create_port_db()`,可以看到这里先是创建了一个空壳的port -![](http://image.python-online.cn/20190804091016.png) +![](http://image.iswbm.com/20190804091016.png) -再分配ip![](http://image.python-online.cn/20190804091226.png) +再分配ip![](http://image.iswbm.com/20190804091226.png) 上图有一个函数 `_allocate_ips_for_port`,相当重要,一般人只要从这里关注即可 -![](http://image.python-online.cn/20190804094131.png) +![](http://image.iswbm.com/20190804094131.png) 它会先根据port创建请求里的内容,去数据库中一一比对,找出符合条件的子网,当然在找的过程中,会校验请求参数的准确性,比如它是指定ip和subnet创建的port,那么会检查这个ip是否在subnet内。 @@ -30,9 +30,9 @@ neutron api 的入口是在这里![](http://image.python-online.cn/2019080411184 校验完参数后,会把创建这个port所需的信息都整理到最后返回的 fixed_ip_list 里。如果指定了ip,这个list里的元素就会有 ip_address,否则就只有 subnet_id。 -![](http://image.python-online.cn/20190804092214.png) +![](http://image.iswbm.com/20190804092214.png) -![](http://image.python-online.cn/20190804091911.png) +![](http://image.iswbm.com/20190804091911.png) @@ -42,11 +42,11 @@ neutron api 的入口是在这里![](http://image.python-online.cn/2019080411184 当不指定子网、也不指定ip时(也就是不传fixed_ip),而且 cluster 里 即 有ipv4 的子网也有ipv6的子网,这时候 neutron 会去创建一个既有ipv4也有ipv6的port,只要有一个version的子网里没有可用ip,都会失败。 -![](http://image.python-online.cn/20190809213209.png) +![](http://image.iswbm.com/20190809213209.png) 这里是遍历一个version的所有的子网列表,只要能找到一个子网有可用ip,就返回,如果遍历完都没有找到ip,那就要抛出异常了。 -![](http://image.python-online.cn/20190809213223.png) +![](http://image.iswbm.com/20190809213223.png) ```python class IpAddressGenerationFailureAllSubnets(IpAddressGenerationFailure): diff --git a/source/c08/c08_15.rst b/source/c08/c08_15.rst index 68fb625..219142c 100644 --- a/source/c08/c08_15.rst +++ b/source/c08/c08_15.rst @@ -57,16 +57,16 @@ subnet_id。 message = _("No more IP addresses available.") .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20190804111844.png -.. |image2| image:: http://image.python-online.cn/20190804111715.png -.. |image3| image:: http://image.python-online.cn/20190803181706.png -.. |image4| image:: http://image.python-online.cn/20190803182042.png -.. |image5| image:: http://image.python-online.cn/20190803182223.png -.. |image6| image:: http://image.python-online.cn/20190804091016.png -.. |image7| image:: http://image.python-online.cn/20190804091226.png -.. |image8| image:: http://image.python-online.cn/20190804094131.png -.. |image9| image:: http://image.python-online.cn/20190804092214.png -.. |image10| image:: http://image.python-online.cn/20190804091911.png -.. |image11| image:: http://image.python-online.cn/20190809213209.png -.. |image12| image:: http://image.python-online.cn/20190809213223.png +.. |image1| image:: http://image.iswbm.com/20190804111844.png +.. |image2| image:: http://image.iswbm.com/20190804111715.png +.. |image3| image:: http://image.iswbm.com/20190803181706.png +.. |image4| image:: http://image.iswbm.com/20190803182042.png +.. |image5| image:: http://image.iswbm.com/20190803182223.png +.. |image6| image:: http://image.iswbm.com/20190804091016.png +.. |image7| image:: http://image.iswbm.com/20190804091226.png +.. |image8| image:: http://image.iswbm.com/20190804094131.png +.. |image9| image:: http://image.iswbm.com/20190804092214.png +.. |image10| image:: http://image.iswbm.com/20190804091911.png +.. |image11| image:: http://image.iswbm.com/20190809213209.png +.. |image12| image:: http://image.iswbm.com/20190809213223.png diff --git a/source/c08/c08_16.md b/source/c08/c08_16.md index 153d9e4..eb9283b 100644 --- a/source/c08/c08_16.md +++ b/source/c08/c08_16.md @@ -1,4 +1,4 @@ -# 9.16 详解 Neutron 的 QoS +# 8.16 详解 Neutron 的 QoS QoS 的全称是 Quality of Service,也就是服务质量。 @@ -246,8 +246,6 @@ $ nova meta b1575f81-0a33-4872-995e-2c2dd48d52c8 set __system__vif_inbound_avera $ nova meta b1575f81-0a33-4872-995e-2c2dd48d52c8 set __system__vif_inbound_average=12500 __system__vif_inbound_burst=10000 __system__vif_outbound_average=12500 __system__vif_outbound_burst=10000 ``` - - ## 3. 带宽单位换算 带宽,英文名 Bandwidth,在不同领域的含义各不相同,而在网络服务中,带宽是指单位时间内的流经数据量。 diff --git a/source/c08/c08_16.rst b/source/c08/c08_16.rst index bb69764..aeb792a 100644 --- a/source/c08/c08_16.rst +++ b/source/c08/c08_16.rst @@ -1,4 +1,4 @@ -9.16 详解 Neutron 的 QoS +8.16 详解 Neutron 的 QoS ======================== QoS 的全称是 Quality of Service,也就是服务质量。 diff --git a/source/c08/c09_16.rst b/source/c08/c09_16.rst deleted file mode 100644 index 337a7ac..0000000 --- a/source/c08/c09_16.rst +++ /dev/null @@ -1,252 +0,0 @@ -9.16 详解 Neutron 的 QoS -======================== - -QoS 的全称是 Quality of Service,也就是服务质量。 - -Neutron 支持的 -`QoS `__ -类型有 - -- bandwidth_limit(带宽限速):实现带宽速速 -- DSCP(差分服务代码点):给网络流量包添加一个 DSCP - 标记,以此实现流量的优先级 -- minimum_bandwidth:暂时没用过 - -1. 开户 QoS 支持 ----------------- - -在控制节点上执行两条命令修改配置 - -.. code:: shell - - $ openstack-config --set /etc/neutron/neutron.conf DEFAULT service_plugins neutron.services.qos.qos_plugin.QoSPlugin - - $ openstack-config --set /etc/neutron/plugins/ml2/ml2_conf.ini ml2 extension_drivers port_security,qos - -然后重启 neutron-server - -.. code:: shell - - $ crm resource cleanup openstack-neutron-server-clone - -在计算节点上执行命令修改配置 - -.. code:: shell - - $ openstack-config --set /etc/neutron/plugins/ml2/openvswitch_agent.ini agent extensions qos - -然后重启对应的 neutron-agent 服务 - -.. code:: shell - - $ systemctl restart neutron-openvswitch-agent - -DSCP 差分服务代码点 -------------------- - -DSCP 大小为 6 个 bit,也就是最大可表示 64 个分类。 - -但在 Neutron 里可用的标记只有这些 - -:: - - 0, 8, 10, 12, 14, 16, 18, 20, - 22, 24, 26, 28, 30, 32, 34, 36, - 38, 40, 46, 48, 56 - -可以看到,它们都是0-56 间的偶数,但是排除 2-6, 42, 44 和 50-54。 - -|image0| - -原理 -~~~~ - -在 IP 协议分组里有一个 ToS(服务类型) 的字段,就是用来表示 ToS 的。 - -ToS 字段,总共 8 个 bit - -|image1| - -**前面 3 个 bit** - -为优选权子字段,现在已经废弃,这个字段默认值是000,从wireshark抓包结果看,表示的是: - -.. figure:: https://s4.51cto.com/images/blog/201804/25/ede8e1de3c98c2fdfeb044cb0cf74034.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk= - :alt: 003::每天五分钟入门TCP/IP协议栈::IP协议之TOS字段说明 - - 003::每天五分钟入门TCP/IP协议栈::IP协议之TOS字段说明 - -但是在某些协议中仍然是有用的,比如 OSPFv2 协议 - -|image2| - -关于 Precedence (优先级),有如下几种 - -.. code:: shell - - 111 - Network Control - 110 - Internetwork Control - 101 - CRITIC/ECP - 100 - Flash Override - 011 - Flash - 010 - Immediate - 001 - Priority - 000 – Routine - -**中间 4 个bit** - -这四个 bit -组合在一起,表示了该数据报对应的服务类别,这个应用层的服务类别是不同的。这里所说的服务类别,是指: - -:: - - 1000 -- minimize delay 最小延迟 - 0100 -- maximize throughput 最大吞吐量 - 0010 -- maximize reliability 最高可靠性 - 0001 -- minimize monetary cost 最小费用 - 0000 -- normal service 一般服务 - -IP首部中的ToS字段,只能表示一种服务类别,也就是:这4bit字段中,最多只能有一个bit字段为1。 - -看下不同应用下该4bit字段对应的值: -|003::每天五分钟入门TCP/IP协议栈::IP协议之TOS字段说明| -翻译过来就是: -|003::每天五分钟入门TCP/IP协议栈::IP协议之TOS字段说明| -**最小延迟**\ ,对应于对延迟敏感的应用,如telnet和人login等。 -**最大吞吐量**\ ,对应于对吞吐量要求比较高的应用,如FTP文件应用,对文件传输吞吐量有比较高的要求。 -**最高可靠性**\ ,对网络传输可靠性要求高的应用,如使用SNMP的应用、路由协议等等。 -**最小费用**\ ,如NNTP这种用户网络新闻等。 - -**最后 1 个bit** - -这个1bit末尾,没有被使用,必须强制设置为0 - -最后,很重要的一点,只有当\ **网络设备(如交换机等)能够支持**\ (能够识别IP首部中的ToS字段)识别ToS字段时,这给字段设置才有意义。 - -创建 policy -~~~~~~~~~~~ - -.. code:: shell - - $ neutron qos-policy-create qos-dscp --shared - Created a new policy: - +-----------------+--------------------------------------+ - | Field | Value | - +-----------------+--------------------------------------+ - | created_at | 2020-07-01T06:43:23Z | - | description | | - | id | ee7e7a83-c67d-4f27-b77c-3345553e5abe | - | name | qos-dscp | - | project_id | 2ac17c7c792d45eaa764c30bac37fad9 | - | revision_number | 1 | - | rules | | - | shared | True | - | tenant_id | 2ac17c7c792d45eaa764c30bac37fad9 | - | updated_at | 2020-07-01T06:43:23Z | - +-----------------+--------------------------------------+ - -创建 rule -~~~~~~~~~ - -创建时需要指定 QOS_POLICY,创建完后,就会自动添加到 QOS_POLICY - -.. code:: shell - - $ neutron qos-dscp-marking-rule-create --dscp-mark 14 ee7e7a83-c67d-4f27-b77c-3345553e5abe - Created a new dscp_marking_rule: - +-----------+--------------------------------------+ - | Field | Value | - +-----------+--------------------------------------+ - | dscp_mark | 14 | - | id | 1d045cf3-eb31-440b-9a74-a9d5fea6a7e0 | - +-----------+--------------------------------------+ - -绑定policy到 port -~~~~~~~~~~~~~~~~~ - -.. code:: shell - - $ neutron port-update --qos-policy qos-dscp - -关闭 QoS -~~~~~~~~ - -.. code:: shell - - $ neutron port-update --no-qos-policy - -查看规则 -~~~~~~~~ - -过滤出 tos 后就能看到 ToS(Type of Service) 的值 - -.. code:: shell - - $ ovs-ofctl dump-flows br-int | grep tos - -然后再对照这个表,找到对应的 DSCP 的 decimal ,如果 tos 是 64,那么 DSCP -mark 就是 16,其实除以 4 就可以了,也不用对照表。其中这个 16 -需要跟交换上支持的一样。 - -|image5| - -其他 -~~~~ - -DSCP 是以集群为粒度,一个集群只要创建一个就行,需要的时候将其绑定到 port -上就可以。 - -2. bandwidth_limit 带宽限速 ---------------------------- - -华云 QoS -:https://support.huawei.com/enterprise/zh/doc/EDOC1100055155/101c0e7b - -http://blog.chinaunix.net/uid-20530497-id-2490382.html - -https://www.zhihu.com/question/21053403 - -qos 查询命令 - -.. code:: shell - - $ neutron qos-policy-list - $ neutron qos-bandwidth-limit-rule-list 16fbb0c2-b7ac-4053-a85f-75fb72c3ab55 - -在 dpdk 宿主机上查询限速 - -.. code:: shell - - # 查询 ingress(宿主机角度) - $ virsh dumpxml 4f6a0708-aeb8-4208-bea8-2c51e6a94948 - - # 查询 egress(虚拟机角度) - $ ovs-vsctl list interface vhu198063e9-97 - - # 查询 ingress(虚拟机角度) - $ ovs-appctl -t ovs-vswitchd qos/show vhu198063e9-97 - -|image6| - -3. 参考文章 ------------ - -- https://blog.51cto.com/mangguostudy/2107799 - -- https://www.jianshu.com/p/4b5cc3845f2c - -- https://blog.csdn.net/u011641885/article/details/45640313 - --------------- - -|image7| - -.. |image0| image:: http://image.iswbm.com/20200701155207.png -.. |image1| image:: http://img.wandouip.com/crawler/article/2019411/546f47120fa14a2a1cfc44c9e8a48e71 -.. |image2| image:: http://image.iswbm.com/20200701170223.png -.. |003::每天五分钟入门TCP/IP协议栈::IP协议之TOS字段说明| image:: https://s4.51cto.com/images/blog/201804/25/3c42c64b7240ef12b991f69644a145ac.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk= -.. |003::每天五分钟入门TCP/IP协议栈::IP协议之TOS字段说明| image:: https://s4.51cto.com/images/blog/201804/25/4f03b09e8081d8fc7073f29870bc1c95.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk= -.. |image5| image:: http://image.iswbm.com/20200701155207.png -.. |image6| image:: http://image.iswbm.com/20200709171517.png -.. |image7| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c09/c09_01.md b/source/c09/c09_01.md index a4321dd..e8ec5cc 100644 --- a/source/c09/c09_01.md +++ b/source/c09/c09_01.md @@ -25,11 +25,11 @@ 前段时间,在微博上刷到了一条推荐。内容是这样的 -![微博截图](http://image.python-online.cn/20200211211522.png) +![微博截图](http://image.iswbm.com/20200211211522.png) 出于好奇,我点开了,放大再放大,emmm,有点意思吖… -![img](http://image.python-online.cn/20200211211657.png) +![img](http://image.iswbm.com/20200211211657.png) 这四个字,对于像我这样腼腆的DS男来说,还真不好意思说,说出来,万一被拒绝了咋办? @@ -41,11 +41,11 @@ 这里我以一张高圆圆的图来做一下演示,原图是这样的(分辨率是:2000*1328)。 -![](http://image.python-online.cn/20200214104413.png) +![](http://image.iswbm.com/20200214104413.png) 使用我写好的脚本运行后,就生成了这样一张图,请你点击,放大再放大。(惊喜? -![](http://image.python-online.cn/save.jpeg) +![](http://image.iswbm.com/save.jpeg) 然后将这张图片发给你的女神,具体话术你自己想咯。 @@ -67,7 +67,7 @@ 用 Excel 画了个图,每一方格代表一个像素,其中若我的字体的大小设置 5(非字号5,而是每个字占用5个像素),效果大概就是如下这样子。 -![](http://image.python-online.cn/20200214104646.png) +![](http://image.iswbm.com/20200214104646.png) 我只要每个像素取出一个像素值,并使用这个像素做为该字的颜色即可,在像素量够多的情况下,从远处看,是能看到我们原来图像的轮廓的。 diff --git a/source/c09/c09_01.rst b/source/c09/c09_01.rst index 79a9a17..f016d28 100644 --- a/source/c09/c09_01.rst +++ b/source/c09/c09_01.rst @@ -25,14 +25,14 @@ 前段时间,在微博上刷到了一条推荐。内容是这样的 -.. figure:: http://image.python-online.cn/20200211211522.png +.. figure:: http://image.iswbm.com/20200211211522.png :alt: 微博截图 微博截图 出于好奇,我点开了,放大再放大,emmm,有点意思吖… -.. figure:: http://image.python-online.cn/20200211211657.png +.. figure:: http://image.iswbm.com/20200211211657.png :alt: img img @@ -155,8 +155,8 @@ |image4| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.python-online.cn/20200214104413.png -.. |image2| image:: http://image.python-online.cn/save.jpeg -.. |image3| image:: http://image.python-online.cn/20200214104646.png +.. |image1| image:: http://image.iswbm.com/20200214104413.png +.. |image2| image:: http://image.iswbm.com/save.jpeg +.. |image3| image:: http://image.iswbm.com/20200214104646.png .. |image4| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c09/c09_02.md b/source/c09/c09_02.md index 1ca292a..1e66691 100644 --- a/source/c09/c09_02.md +++ b/source/c09/c09_02.md @@ -99,13 +99,13 @@ Hazeover:干扰调节神器,把活动窗口除久的其他范围调暗 ## 4. 图片影音 -Snipaste:截图工具,支持钉图 +[Snipaste](https://bitbucket.org/liule/snipaste/downloads/):截图工具,支持钉图 iShot:截图工具,支持长截图/滚动截图,钉图 Capture Gif:Gif 录制(不推荐) -Kap Beta:录屏开源免费软件,支持GIF导出,快捷键:`Command Shift 5` +[Kap](https://getkap.co/):录屏开源免费软件,支持GIF导出,快捷键:`Command Shift 5` ScreenFlow:视频录制 @@ -121,14 +121,16 @@ Permute 3,精致小巧的视频格式转换工具。 PP 鸭,好用的多格式图片压缩软件。 +Squash for Mac:图片压缩(不能压缩GIF) + +ImageOptim:用过最好用的图片压缩软件(支持 Gif ) + GIF Brewery 3,视频转Gif动图。Mac App Store 免费下载 PhotoBulk,图片批处理修改大小、格式、加水印 Mac App Store 购买下载 PDFGuru:PDF 阅读器 - - ## 5. 文件管理 FreeDownloadManagger:下载管理 @@ -163,6 +165,8 @@ iText,精准的 OCR 文字识别工具。 思维导图:MindNode,Xmind +[KeyCastr](https://github.com/keycastr/keycastr/releases):在录制视频或者 Gif 时显示你所按的键位 + ## 7. 系统管理 CCleaner:系统清理、软件卸载 diff --git a/source/c09/c09_02.rst b/source/c09/c09_02.rst index e85037a..cc5857a 100644 --- a/source/c09/c09_02.rst +++ b/source/c09/c09_02.rst @@ -107,13 +107,13 @@ Hazeover:干扰调节神器,把活动窗口除久的其他范围调暗 4. 图片影音 ----------- -Snipaste:截图工具,支持钉图 +`Snipaste `__\ :截图工具,支持钉图 iShot:截图工具,支持长截图/滚动截图,钉图 Capture Gif:Gif 录制(不推荐) -Kap Beta:录屏开源免费软件,支持GIF导出,快捷键:\ ``Command Shift 5`` +`Kap `__\ :录屏开源免费软件,支持GIF导出,快捷键:\ ``Command Shift 5`` ScreenFlow:视频录制 @@ -129,6 +129,10 @@ Permute 3,精致小巧的视频格式转换工具。 PP 鸭,好用的多格式图片压缩软件。 +Squash for Mac:图片压缩(不能压缩GIF) + +ImageOptim:用过最好用的图片压缩软件(支持 Gif ) + GIF Brewery 3,视频转Gif动图。Mac App Store 免费下载 PhotoBulk,图片批处理修改大小、格式、加水印 Mac App Store 购买下载 @@ -172,6 +176,9 @@ iText,精准的 OCR 文字识别工具。 思维导图:MindNode,Xmind +`KeyCastr `__\ :在录制视频或者 +Gif 时显示你所按的键位 + 7. 系统管理 ----------- diff --git a/source/c09/c09_03.md b/source/c09/c09_03.md index 3ca5c78..848f6b7 100644 --- a/source/c09/c09_03.md +++ b/source/c09/c09_03.md @@ -70,3 +70,6 @@ https://www.draw.io/ +## 黑科技 + +果核剥壳(破解工具):https://www.ghpym.com/special/softlist \ No newline at end of file diff --git a/source/c09/c09_03.rst b/source/c09/c09_03.rst index 62ba4f1..5c1f7e6 100644 --- a/source/c09/c09_03.rst +++ b/source/c09/c09_03.rst @@ -60,5 +60,10 @@ JSON:http://www.yyyweb.com/demo/inner-show/json-generator.html 代码分享:https://paste.ubuntu.com/ +黑科技 +------ + +果核剥壳(破解工具):https://www.ghpym.com/special/softlist + .. |image0| image:: http://image.iswbm.com/20200602135014.png diff --git a/source/c09/c09_06.md b/source/c09/c09_06.md new file mode 100644 index 0000000..75b5138 --- /dev/null +++ b/source/c09/c09_06.md @@ -0,0 +1,155 @@ +# 9.6 赶紧收藏!学习 Python 的好网站 + +![](http://image.iswbm.com/20200602135014.png) + +## 学习 Python + +### Python 中文学习大本营 + +网站连接:http://www.pythondoc.com/ + +![](http://image.iswbm.com/20200802110436.png) + +### Python Cookbook + +网站链接:https://python3-cookbook.readthedocs.io/zh_CN/latest/preface.html + +![](http://image.iswbm.com/20200802111158.png) + + + +### Scrapy Cookbook + +网站链接:https://scrapy-cookbook.readthedocs.io/zh_CN/latest/ + +![](http://image.iswbm.com/20200802111532.png) + + + +### PyCrumbs + +搜集了各种免费Python的资料。 + +网站链接:https://github.com/kirang89/pycrumbs + +![](http://image.iswbm.com/20200802113311.png) + + + +### 实验楼:Python + +网站链接:https://www.shiyanlou.com/ + +![](http://image.iswbm.com/20200802113658.png) + +### 魔法学院:Python + +网站链接:http://www.nowamagic.net/academy/category/13/ + +![](http://image.iswbm.com/20200802114241.png) + + + +### Python 3 标准库实例教程 + +**网站链接**:https://learnku.com/docs/pymotw + +![](http://image.iswbm.com/20200508201333.png) + + + +### Django Web 框架 + +网站链接:https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Django + +该网站可以让你从0开始学习Web,包括前端(HTML,CSS,JS)、后端(Django) + +![](http://image.iswbm.com/20200525080531.png) + +在服务端网页编程里,重点介绍了 Django + +![](http://image.iswbm.com/20200525080715.png) + + + +### Python3 源码剖析 + +网站链接:https://flaggo.github.io/python3-source-code-analysis/ + +![](http://image.iswbm.com/image-20200701123010074.png) + +参考学习地址:https://realpython.com/cpython-source-code-guide/ + +基于 Python 3.6 的源码分析:https://he11olx.com/ + +![](http://image.iswbm.com/20201004150826.png) + +### 书栈网:Python + +网站链接:https://www.bookstack.cn/explore?cid=13&tab=popular + +![](http://image.iswbm.com/20200802114734.png) + +### 追梦人物:DRF 实战教程 + +专栏链接:https://zhuanlan.zhihu.com/djstudyteam + +![](http://image.iswbm.com/20200802120804.png) + +### Python Tips 刷题挑战 + +网站链接:http://www.pythontip.com/coding/code_oj + +![](http://image.iswbm.com/20200802121125.png) + + + +### Python Tips 设计模式 + +网站链接:http://www.pythontip.com/python-patterns/detail/abstract_factory + +![](http://image.iswbm.com/20200802121331.png) + +## 综合性网站 + +### 书栈网 + +在这个网站里,收录了很多优秀的技术书籍,你想要,想找的,基本上都能搜得出来。 + +**网站链接**:https://www.bookstack.cn/rank?tab=popular + +![](http://image.iswbm.com/20200104144109.png) + +### 魔法学院 + + **网站链接**:http://www.nowamagic.net/academy/ + +![](http://image.iswbm.com/20200112210558.png) + + + +## 学习 Linux + +### Linux 手册 + +网站链接:https://man.linuxde.net/ + +![](http://image.iswbm.com/image-20200704204307530.png) + +### 每天一个linux命令 + +网站链接:www.cnblogs.com/peida/archive/2012/12/05/2803591.html + +### 实验楼:Linux 基础入门 + +网站链接:https://www.shiyanlou.com/courses/1 + +![](http://image.iswbm.com/20200704204506.png) + +网站链接:https://www.shiyanlou.com/courses/68 + +![](http://image.iswbm.com/20200704204558.png) + + + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c09/c09_06.rst b/source/c09/c09_06.rst new file mode 100644 index 0000000..40e1d7b --- /dev/null +++ b/source/c09/c09_06.rst @@ -0,0 +1,183 @@ +9.6 赶紧收藏!学习 Python 的好网站 +================================== + +|image0| + +学习 Python +----------- + +Python 中文学习大本营 +~~~~~~~~~~~~~~~~~~~~~ + +网站连接:http://www.pythondoc.com/ + +|image1| + +Python Cookbook +~~~~~~~~~~~~~~~ + +网站链接:https://python3-cookbook.readthedocs.io/zh_CN/latest/preface.html + +|image2| + +Scrapy Cookbook +~~~~~~~~~~~~~~~ + +网站链接:https://scrapy-cookbook.readthedocs.io/zh_CN/latest/ + +|image3| + +PyCrumbs +~~~~~~~~ + +搜集了各种免费Python的资料。 + +网站链接:https://github.com/kirang89/pycrumbs + +|image4| + +实验楼:Python +~~~~~~~~~~~~~~ + +网站链接:https://www.shiyanlou.com/ + +|image5| + +魔法学院:Python +~~~~~~~~~~~~~~~~ + +网站链接:http://www.nowamagic.net/academy/category/13/ + +|image6| + +Python 3 标准库实例教程 +~~~~~~~~~~~~~~~~~~~~~~~ + +**网站链接**\ :https://learnku.com/docs/pymotw + +|image7| + +Django Web 框架 +~~~~~~~~~~~~~~~ + +网站链接:https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Django + +该网站可以让你从0开始学习Web,包括前端(HTML,CSS,JS)、后端(Django) + +|image8| + +在服务端网页编程里,重点介绍了 Django + +|image9| + +Python3 源码剖析 +~~~~~~~~~~~~~~~~ + +网站链接:https://flaggo.github.io/python3-source-code-analysis/ + +|image10| + +参考学习地址:https://realpython.com/cpython-source-code-guide/ + +基于 Python 3.6 的源码分析:https://he11olx.com/ + +|image11| + +书栈网:Python +~~~~~~~~~~~~~~ + +网站链接:https://www.bookstack.cn/explore?cid=13&tab=popular + +|image12| + +追梦人物:DRF 实战教程 +~~~~~~~~~~~~~~~~~~~~~~ + +专栏链接:https://zhuanlan.zhihu.com/djstudyteam + +|image13| + +Python Tips 刷题挑战 +~~~~~~~~~~~~~~~~~~~~ + +网站链接:http://www.pythontip.com/coding/code_oj + +|image14| + +Python Tips 设计模式 +~~~~~~~~~~~~~~~~~~~~ + +网站链接:http://www.pythontip.com/python-patterns/detail/abstract_factory + +|image15| + +综合性网站 +---------- + +书栈网 +~~~~~~ + +在这个网站里,收录了很多优秀的技术书籍,你想要,想找的,基本上都能搜得出来。 + +**网站链接**\ :https://www.bookstack.cn/rank?tab=popular + +|image16| + +魔法学院 +~~~~~~~~ + +**网站链接**\ :http://www.nowamagic.net/academy/ + +|image17| + +学习 Linux +---------- + +Linux 手册 +~~~~~~~~~~ + +网站链接:https://man.linuxde.net/ + +|image18| + +每天一个linux命令 +~~~~~~~~~~~~~~~~~ + +网站链接:www.cnblogs.com/peida/archive/2012/12/05/2803591.html + +实验楼:Linux 基础入门 +~~~~~~~~~~~~~~~~~~~~~~ + +网站链接:https://www.shiyanlou.com/courses/1 + +|image19| + +网站链接:https://www.shiyanlou.com/courses/68 + +|image20| + +|image21| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200802110436.png +.. |image2| image:: http://image.iswbm.com/20200802111158.png +.. |image3| image:: http://image.iswbm.com/20200802111532.png +.. |image4| image:: http://image.iswbm.com/20200802113311.png +.. |image5| image:: http://image.iswbm.com/20200802113658.png +.. |image6| image:: http://image.iswbm.com/20200802114241.png +.. |image7| image:: http://image.iswbm.com/20200508201333.png +.. |image8| image:: http://image.iswbm.com/20200525080531.png +.. |image9| image:: http://image.iswbm.com/20200525080715.png +.. |image10| image:: http://image.iswbm.com/image-20200701123010074.png +.. |image11| image:: http://image.iswbm.com/20201004150826.png +.. |image12| image:: http://image.iswbm.com/20200802114734.png +.. |image13| image:: http://image.iswbm.com/20200802120804.png +.. |image14| image:: http://image.iswbm.com/20200802121125.png +.. |image15| image:: http://image.iswbm.com/20200802121331.png +.. |image16| image:: http://image.iswbm.com/20200104144109.png +.. |image17| image:: http://image.iswbm.com/20200112210558.png +.. |image18| image:: http://image.iswbm.com/image-20200704204307530.png +.. |image19| image:: http://image.iswbm.com/20200704204506.png +.. |image20| image:: http://image.iswbm.com/20200704204558.png +.. |image21| image:: http://image.iswbm.com/20200607174235.png + diff --git a/source/c09/c09_07.md b/source/c09/c09_07.md new file mode 100644 index 0000000..14668f8 --- /dev/null +++ b/source/c09/c09_07.md @@ -0,0 +1,20 @@ +# 9.7 解决网页上不能复制的几个小技巧 + +![](http://image.iswbm.com/20200602135014.png) + +## 1. 解决网页鼠标限制 + +``` +解决网页不能选中,在console中输入:document.onselectstart=true +解决网页不能复制,在console中输入:document.oncopy=true +解决网页不能右键,在console中输入:document.oncontextmenu=true +``` + +## 2. 在网页上为所欲为 + +```javascript +document.body.contentEditable='true' +``` + +在知乎上的回答设置了禁止转载后,就算你执行了上面的命令,也是无法复制,但是可以剪切,剪切完后你再 Ctrl+Z 后退 。 + diff --git a/source/c09/c09_07.rst b/source/c09/c09_07.rst new file mode 100644 index 0000000..2ffb815 --- /dev/null +++ b/source/c09/c09_07.rst @@ -0,0 +1,26 @@ +9.7 解决网页上不能复制的几个小技巧 +================================== + +|image0| + +1. 解决网页鼠标限制 +------------------- + +:: + + 解决网页不能选中,在console中输入:document.onselectstart=true + 解决网页不能复制,在console中输入:document.oncopy=true + 解决网页不能右键,在console中输入:document.oncontextmenu=true + +2. 在网页上为所欲为 +------------------- + +.. code:: javascript + + document.body.contentEditable='true' + +在知乎上的回答设置了禁止转载后,就算你执行了上面的命令,也是无法复制,但是可以剪切,剪切完后你再 +Ctrl+Z 后退 。 + +.. |image0| image:: http://image.iswbm.com/20200602135014.png + diff --git a/source/c10/c10_01.md b/source/c10/c10_01.md index 8b9bb69..598e01b 100644 --- a/source/c10/c10_01.md +++ b/source/c10/c10_01.md @@ -1,277 +1,384 @@ -# 10.1 网络知识扫盲:一篇文章搞懂 DNS +# 10.1 使用 Python 远程登陆服务器的利器 ![](http://image.iswbm.com/20200602135014.png) -## 1. DNS 是什么? +在使用 Python 写一些脚本的时候,在某些情况下,我们需要频繁登陆远程服务去执行一次命令,并返回一些结果。 -DNS (Domain Name System 的缩写)的作用非常简单,就是根据域名查出IP地址。你可以把它想象成一本巨大的电话本。 +在 shell 环境中,我们是这样子做的。 -举例来说,如果你要访问域名`math.stackexchange.com`,首先要通过DNS查出它的IP地址是`151.101.129.69`。 +```shell +$ sshpass -p ${passwd} ssh -p ${port} -l ${user} -o StrictHostKeyChecking=no xx.xx.xx.xx "ls -l" +``` -## 2. 域名的层级 +然后你会发现,你的输出有很多你并不需要,但是又不去不掉的一些信息(也许有方法,请留言交流),类似这样 -由于后面我会讲到 DNS 的解析过程,因此需要你对域名的层级有一些了解 +```shell +host: xx.xx.xx.xx, port: xx +Warning: Permanently added '[xx.xx.xx.xx]:xx' (RSA) to the list of known hosts. +Login failure: [Errno 1] This server is not registered to rmp platform, please confirm whether cdn server. +total 4 +-rw-r--r-- 1 root root 239 Mar 30 2018 admin-openrc +``` -- 根域名 :`.root` 或者 `.` ,通常是省略的 -- 顶级域名,如 `.com`,`.cn` 等 -- 次级域名,如 `baidu.com` 里的 `baidu`,这个用户是可以注册购买的 -- 主机域名,比如 `baike.baidu.com` 里的`baike`,这个用户是可分配的 +对于直接使用 shell 命令,来执行命令的,可以直接使用管道,或者将标准输出重定向到文件的方法取得执行命令返回的结果 -``` -主机名.次级域名.顶级域名.根域名 +## 1. 使用 subprocess -baike.baidu.com.root -``` +若是使用 Python 来做这件事,通常我们会第一时间,想到使用 os.popen,os.system,commands,subprocess 等一些命令执行库来间接获取 。 +但是据我所知,这些库获取的 output 不仅只有标准输出,还包含标准错误(也就是上面那些多余的信息) +所以每次都要对 output 进行的数据清洗,然后整理格式化,才能得到我们想要的数据。 -## 3. DNS 解析过程 +用 subprocess 举个例子,就像这样子 -咱们以访问 `www.163.com` 这个域名为例,来看一看当你访问 www.163.com 时,会发生哪些事: +```python +import subprocess +ssh_cmd = "sshpass -p ${passwd} ssh -p 22 -l root -o StrictHostKeyChecking=no xx.xx.xx.xx 'ls -l'" +status, output = subprocess.getstatusoutput(ssh_cmd) -1. 先查找本地 DNS 缓存(自己的电脑上),有则返回,没有则进入下一步 -2. 查看本地 hosts 文件有没有相应的映射记录,有则返回,没有则进入下一步 -3. 向本地 DNS 服务器(一般都是你的网络接入服务器商提供,比如中国电信,中国移动)发送请求进行查询,本地DNS服务器收到请求后,会先查下自己的缓存记录,如果查到了直接返回就结束了,如果没有查到,本地DNS服务器就会向DNS的根域名服务器发起查询请求:请问老大, `www.163.com` 的ip是啥? -4. 根域名服务器收到请求后,看到这是个 `.com` 的域名,就回信说:这个域名是由 `.com` 老弟管理的,你去问他好了,这是`.com`老弟的联系方式(ip1)。 -5. 本地 DNS 服务器接收到回信后,照着老大哥给的联系方式(ip1),马上给 `.com` 这个顶级域名服务器发起请求:请问 `.com` 大大,`www.163.com` 的ip 是啥? -6. `.com` 顶级域名服务器接收到请求后,看到这是 `163.com` 的域名,就回信说:这个域名是 `.163.com` 老弟管理的,你就去问他就行了,这是他的联系方式(ip2) -7. 本地 DNS 服务器接收到回信后,按照前辈的指引(ip2),又向 `.163.com` 这个权威域名服务器发起请求:请问 `163.com` 大大,请问 `www.163.com` 的ip是啥? -8. `163.com` 权威域名服务器接收到请求后,确认了是自己管理的域名,马上查了下自己的小本本,把 `www.163.com` 的ip告诉了 本地DNS服务器。 -9. 本地DNS服务器接收到回信后,非常地开心,这下总算拿到了`www.163.com`的ip了,马上把这个消息告诉了要求查询的客户(就是你的电脑)。由于这个过程比较漫长,本地DNS服务器为了节省时间,也为了尽量不去打扰各位老大哥,就把这个查询结果偷偷地记在了自己的小本本上,方便下次有人来查询时,可以快速回应。 +# 数据清理,格式化的就不展示了 + +``` -总结起来就是三句话 -1. 从"根域名服务器"查到"顶级域名服务器"的NS记录和A记录(IP地址) -2. 从"顶级域名服务器"查到"次级域名服务器"的NS记录和A记录(IP地址) -3. 从"次级域名服务器"查出"主机名"的IP地址 -![](http://image.iswbm.com/464291-20170703113844956-354755333.jpg) +通过以上的文字 + 代码的展示 ,可以感觉到 ssh 登陆的几大痛点 -## 4. DNS的缓存时间 +- **痛点一**:需要额外安装 sshpass(如果不免密的话) +- **痛点二**:干扰信息太多,数据清理、格式化相当麻烦 +- **痛点三**:代码实现不够优雅(有点土),可读性太差 +- **痛点四**:ssh 连接不能复用,一次连接仅能执行一次 +- **痛点五**:代码无法全平台,仅能在 Linux 和 OSX 上使用 -上面的几个步骤里,可以看到有两个地方会缓存 DNS 的查询记录,有了缓存,在一定程度上会提高查询效率,但同时在准确率上会有所损失。 -因此我们在配置 DNS 解析的时候,会有一个 TTL 参数(Time To Live),意思就是这个缓存可以存活多长时间,过了这个时间,本地 DNS 就会删除这条记录,删除了缓存后,你再访问,就要重新走一遍上面的流程,获取最新的地址。 -![](http://image.iswbm.com/image-20200531141521689.png) +为了解决这几个问题,我搜索了全网关于 Python ssh 的文章,没有看到有完整介绍这方面的技巧的。 -## 5. DNS 的记录类型 +为此,我就翻阅了一个很火的 Github 项目: awesome-python-cn (https://github.com/BingmingWong/awesome-python-cn)。 -当我们在阿里云买了一个域名后,可以配置我们主机域名解析规则,也就是 **记录**。 +期望在这里,找到有一些关于 远程连接 的一些好用的库。 -![阿里云 域名云解析](http://image.iswbm.com/image-20200531170212224.png) +还真的被我找到了两个 -常见的 DNS 记录类型如下 +- sh.ssh +- Paramiko -- `A`:地址记录(Address),返回域名指向的IP地址。 -- `NS`:域名服务器记录(Name Server),返回保存下一级域名信息的服务器地址。该记录只能设置为域名,不能设置为IP地址。 -- `MX`:邮件记录(Mail eXchange),返回接收电子邮件的服务器地址。 -- `CNAME`:规范名称记录(Canonical Name),返回另一个域名,即当前查询的域名是另一个域名的跳转,详见下文。 -- `PTR`:逆向查询记录(Pointer Record),只用于从IP地址查询域名,详见下文。 +## 2. 使用 sh.ssh +首先来介绍第一个,`sh.ssh` -## 6. DNS 报文结构 +`sh` 是一个可以让你通过函数的调用来完成 Linxu/OSX 系统命令的一个库,非常好用,关于它有机会也写篇介绍。 -后面我将使用 wireshark 抓取 DNS 的数据包,但是在开始之前 ,得先了解一下 DNS 的报文结构 +```shell +$ python3 -m pip install sh +``` -![](http://image.iswbm.com/image-20200531152824672.png) -- 事务 ID:DNS 报文的 ID 标识。对于请求报文和其对应的应答报文,该字段的值是相同的。通过它可以区分 DNS 应答报文是对哪个请求进行响应的。 -- 标志:DNS 报文中的标志字段。 -- 问题计数:DNS 查询请求的数目。 -- 回答资源记录数:DNS 响应的数目。 -- 权威名称服务器计数:权威名称服务器的数目。 -- 附加资源记录数:额外的记录数目(权威名称服务器对应 IP 地址的数目)。 -## 7. Wireshark抓包实战 +今天只介绍它其中的一个函数:`ssh` -打开 Wireshark 后,使用 `ping 163.com` 来发起 DNS 解析请求,使用 `DNS` 关键字在Wireshark 过滤。 +通常两台机器互访,为了方便,可设置免密登陆,这样就不需要输入密码。 -从抓取的报文整体来看,我们可以粗略获取几个信息 +这段代码可以实现免密登陆,并执行我们的命令 `ls -l` -1. DNS 是应用层协议,传输层协议使用的是 UDP -2. DNS 默认端口是 53 +```python +from sh import ssh +output=ssh("root@xx.xx.xx.xx", "-p 22", "ls -l") +print(output) +``` -![](http://image.iswbm.com/20200531175736.png) +但有可能 ,我们并不想设置互信免密,为了使这段代码更通用,我假定我们没有设置免密,只能使用密码进行登陆。 -请求和应答的报文的截图我放在了下面,接下来我将逐个分析。 +问题就来了,要输入密码,必须得使用交互式的方法来输入呀,在 Python 中要如何实现呢? -**请求** +原来 ssh 方法接收一个 `_out` 参数,这个参数可以为一个字符串,表示文件路径,也可以是一个文件对象(或者类文件对象),还可以是一个回调函数,意思是当有标准输出时,就会调用将输出内容传给这个函数。 -![](http://image.iswbm.com/20200531175811.png) +这就好办了呀。 -**应答** +我只要识别到有 `password:` 字样,就往标准输入写入我的密码就好了呀。 -![](http://image.iswbm.com/image-20200531153110621.png) +完整代码如下: -### Transaction ID +```python +import sys +from sh import ssh -请求和应答的事务ID应当是一个:0xd0d7 +aggregated = "" +def ssh_interact(char, stdin): + global aggregated + sys.stdout.write(char.encode()) + sys.stdout.flush() + aggregated += char + if aggregated.endswith("password: "): + stdin.put("you_password\n") -### Flags +output=ssh("root@xx.xx.xx.xx", "-p 22", "ls -l",_tty_in=True, _out_bufsize=0, _out=ssh_interact) +print(output) +``` -标志字段里的内容比较多,每个字段的含义如下 +这是官方文档(http://amoffat.github.io/sh/tutorials/interacting_with_processes.html?highlight=ssh)给的一些信息,写的一个demo。 -- QR(Response):查询请求/响应的标志信息。查询请求时,值为 0;响应时,值为 1。 -- Opcode:操作码。其中,0 表示标准查询;1 表示反向查询;2 表示服务器状态请求。 -- AA(Authoritative):授权应答,该字段在响应报文中有效。值为 1 时,表示名称服务器是权威服务器;值为 0 时,表示不是权威服务器。 -- TC(Truncated):表示是否被截断。值为 1 时,表示响应已超过 512 字节并已被截断,只返回前 512 个字节。 -- RD(Recursion Desired):期望递归。该字段能在一个查询中设置,并在响应中返回。该标志告诉名称服务器必须处理这个查询,这种方式被称为一个递归查询。如果该位为 0,且被请求的名称服务器没有一个授权回答,它将返回一个能解答该查询的其他名称服务器列表。这种方式被称为迭代查询。 -- RA(Recursion Available):可用递归。该字段只出现在响应报文中。当值为 1 时,表示服务器支持递归查询。 -- Z:保留字段,在所有的请求和应答报文中,它的值必须为 0。 -- rcode(Reply code):返回码字段,表示响应的差错状态。当值为 0 时,表示没有错误;当值为 1 时,表示报文格式错误(Format error),服务器不能理解请求的报文;当值为 2 时,表示域名服务器失败(Server failure),因为服务器的原因导致没办法处理这个请求;当值为 3 时,表示名字错误(Name Error),只有对授权域名解析服务器有意义,指出解析的域名不存在;当值为 4 时,表示查询类型不支持(Not Implemented),即域名服务器不支持查询类型;当值为 5 时,表示拒绝(Refused),一般是服务器由于设置的策略拒绝给出应答,如服务器不希望对某些请求者给出应答。 +尝试运行后,发现程序会一直在运行中,永远不会返回,不会退出,回调函数也永远不会进入。 -### Answer RRs +通过调试查看源代码,仍然查不到问题所在,于是去 [Github](https://github.com/amoffat/sh/issues/393) 上搜了下,原来在 2017 年就已经存在这个问题了,到现在 2020 年了还没有修复,看来使用 `sh.ssh` 的人并不多,于是我又“追问”了下,期望能得到回复。 -回答资源记录数,在应答包里为 2,说明返回了两条查询结果,你可以在 Answer 字段里看到。 +![](http://image.iswbm.com/20200228085749.png) -### Authority RRs +以上这个问题,只有在需要输入密码才会出现,如果设置了机器互信是没有问题的。 -权威名称服务器计数 +为了感受 `sh.ssh` 的使用效果,我设置了机器互信免密,然后使用如下这段代码。 -### Additionnal RRs +```python +from sh import ssh -附加资源记录数 +my_server=ssh.bake("root@xx.xx.xx.xx", "-p 22") -### Answers +# 相当于执行登陆一次执行一次命令,执行完就退出登陆 +print(my_server.ls()) -应答的主要内容,这里返回两条结果,每条结果里的字段有 +# 可在 sleep 期间,手动登陆服务器,使用 top ,查看当前有多少终端在连接 +time.sleep(5) -```shell -Name: 查询的域名 -Type: A表示IPv4,AAAA 表示IPv6 -Class: 表示Internet,几乎总是它 -Time to live: 生存时间 -Data length: 数据长度 -Address: 查询到的 IP 地址 +# 再次执行这条命令时,登陆终端数将 +1,执行完后,又将 -1 +print(my_server.ifconfig()) ``` +惊奇地发现使用 `bake` 这种方式,`my_server.ls()` 和 `my_server.ifconfig()` 这种看似是通过同一个ssh连接,执行两次命令,可实际上,你可以在远程机器上,执行 top 命令看到已连接的终端的变化,会先 `+1` 再 `-1`,说明两次命令的执行是通过两次连接实现的。 +如此看来,使用 `sh.ssh` 可以解决痛点一(如果上述问题能得到解决)、痛点二、痛点三。 -## 8. DNS 劫持 与 HTTP 劫持 +但是它仍然无法复用 ssh 连接,还是不太方便,不是我理想中的最佳方案。 -通过上面的讲解,我们都知道了,DNS 完成了一次域名到 IP 的映射查询,当你在访问 www.baidu.com 时,能正确返回给你 百度首页的 ip。 +最重要的一点是, `sh` 这个模块,仅支持 Linxu/OSX ,在 Windows 你得使用它的兄弟库 - `pbs` ,然后我又去 pypi 看了一眼 [pbs](https://pypi.org/project/pbs/),已经 “年久失修”,没人维护了。 -但如果此时 DNS 解析出现了一些问题,当你想要访问 www.baidu.com 时,却返回给你 www.google.com 的ip,这就是我们常说的 DNS 劫持。 +![](http://image.iswbm.com/20200228093627.png) -与之容易混淆的有 HTTP 劫持。 +至此,我离 “卒”,就差最后一根稻草了。 -那什么是 HTTP 劫持呢? -你一定见过当你在访问 某个网站时,右下角也突然弹出了一个扎眼的广告弹窗。这就是 HTTP 劫持。 -借助别人文章里的例子,它们俩的区别就好比是 +## 3. 使用 paramiko -- DNS劫持是你想去机场的时候,把你给丢到火车站。 +带着最后一丝希望,我尝试使用了 `paramiko` 这个库,终于在 `paramiko` 这里,找回了本应属于 Python 的那种优雅。 -- HTTP劫持是你去机场途中,有人给你塞小广告。 +你可以通过如下命令去安装它 -**那么 DNS劫持 是如何产生的呢?** +``` +$ python3 -m pip install paramiko +``` -下面大概说几种DNS劫持方法: -**1.本机DNS劫持** -攻击者通过某些手段使用户的计算机感染上木马病毒,或者恶意软件之后,恶意修改本地DNS配置,比如修改本地hosts文件,缓存等 +然后接下来,就介绍几种常用的 ssh 登陆的方法 -**2. 路由DNS劫持** +### 方法1:基于用户名和密码的 sshclient 方式登录 -很多用户默认路由器的默认密码,攻击者可以侵入到路由管理员账号中,修改路由器的默认配置 +然后你可以参考如下这段代码,在 Linux/OSX 系统下进行远程连接 -**3.攻击DNS服务器** +```python +import paramiko -直接攻击DNS服务器,例如对DNS服务器进行DDOS攻击,可以是DNS服务器宕机,出现异常请求,还可以利用某些手段感染dns服务器的缓存,使给用户返回来的是恶意的ip地址 +ssh = paramiko.SSHClient() +# 允许连接不在know_hosts文件中的主机 +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) -## 9. 工具的使用 +# 建立连接 +ssh.connect("xx.xx.xx.xx", username="root", port=22, password="you_password") -### dig 命令 +# 使用这个连接执行命令 +ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("ls -l") -dig是一个在类Unix命令行模式下查询DNS包括NS记录,A记录,MX记录等相关信息的工具。 +# 获取输出 +print(ssh_stdout.read()) -通过 dig (参数:`+trace`)命令,我们可以看到上面描述的 DNS 解析的详细过程 +# 关闭连接 +ssh.close() +``` -![](http://image.iswbm.com/image-20200531162810531.png) -从返回的结果,我们可以看得出几点信息 -1. 我们的本地 DNS 服务器 ip 为 192.168.1.1,端口为53,你可以在 /etc/resolv.conf 里看到这个配置 -2. 根域名服务器目前全球一共只有十三台,从a.root-servers.net. `到`m.root-servers.net. ,它们对应的ip地址,已经内置在本地DNS服务器中。 +### 方法2:基于用户名和密码的 transport 方式登录 -如果你只想看到结果,可以使用 `+short` 参数,可以直接返回 www.163.com 对应着哪几个ip +方法1 是传统的连接服务器、执行命令、关闭的一个操作,多个操作需要连接多次,无法复用连接[**痛点四**]。 -![](http://image.iswbm.com/image-20200531164525384.png) +有时候需要登录上服务器执行多个操作,比如执行命令、上传/下载文件,方法1 则无法实现,那就可以使用 transport 的方法。 -你也可以加个 `@` 参数 ,指定从某个 DNS 服务器进行查询 +```python +import paramiko -![](http://image.iswbm.com/image-20200531170427834.png) +# 建立连接 +trans = paramiko.Transport(("xx.xx.xx.xx", 22)) +trans.connect(username="root", password="you_passwd") -如果你只想查看指定的记录类型 +# 将sshclient的对象的transport指定为以上的trans +ssh = paramiko.SSHClient() +ssh._transport = trans -![](http://image.iswbm.com/image-20200531170543250.png) +# 剩下的就和上面一样了 +ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("ls -l") +print(ssh_stdout.read()) +# 关闭连接 +trans.close() +``` -### host 命令 -`host` 命令 可以看作`dig`命令的简化版本,返回当前请求域名的各种记录。 +### 方法3:基于公钥密钥的 SSHClient 方式登录 -![](http://image.iswbm.com/image-20200531171610902.png) +```python +import paramiko +# 指定本地的RSA私钥文件 +# 如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数 +pkey = paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa', password='12345') +# 建立连接 +ssh = paramiko.SSHClient() +ssh.connect(hostname='xx.xx.xx.xx', + port=22, + username='you_username', + pkey=pkey) -### whois命令 +# 执行命令 +stdin, stdout, stderr = ssh.exec_command('ls -l') -`whois`命令用来查看域名的注册情况。 +# 结果放到stdout中,如果有错误将放到stderr中 +print(stdout.read()) -![](http://image.iswbm.com/image-20200531171905345.png) +# 关闭连接 +ssh.close() +``` -### nslookup命令 -nslookup也是常用的一个查询 DNS 解析结果的工具 -```shell -$ nslookup [查询的域名] [指定DNS服务器] +### 方法4:基于密钥的 Transport 方式登录 + +```python +import paramiko + +# 指定本地的RSA私钥文件 +# 如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数 +pkey = paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa', password='12345') + +# 建立连接 +trans = paramiko.Transport(('xx.xx.xx.xx', 22)) +trans.connect(username='you_username', pkey=pkey) + +# 将sshclient的对象的transport指定为以上的trans +ssh = paramiko.SSHClient() +ssh._transport = trans + +# 执行命令,和传统方法一样 +stdin, stdout, stderr = ssh.exec_command('df -hl') +print(stdout.read().decode()) + +# 关闭连接 +trans.close() ``` -![](http://image.iswbm.com/image-20200531145109182.png) -你也可以指定公网的域名服务器进行查询,比如常见的 114.114.114.114 -![](http://image.iswbm.com/image-20200531145449577.png) +以上四种方法,可以帮助你实现远程登陆服务器执行命令,如果需要复用连接:一次连接执行多次命令,可以使用 **方法二** 和 **方法四** +用完后,记得关闭连接。 +### 实现 sftp 文件传输 -## 10. 手动清理本地缓存 +同时,paramiko 做为 ssh 的完美解决方案,它非常专业,利用它还可以实现 sftp 文件传输。 -MacOS +```python +import paramiko -```shell -$ sudo dscacheutil -flushcache -$ sudo killall -HUP mDNSResponder +# 实例化一个trans对象# 实例化一个transport对象 +trans = paramiko.Transport(('xx.xx.xx.xx', 22)) + +# 建立连接 +trans.connect(username='you_username', password='you_passwd') + +# 实例化一个 sftp对象,指定连接的通道 +sftp = paramiko.SFTPClient.from_transport(trans) + +# 发送文件 +sftp.put(localpath='/tmp/11.txt', remotepath='/tmp/22.txt') + +# 下载文件 +sftp.get(remotepath='/tmp/22.txt', localpath='/tmp/33.txt') +trans.close() ``` -Windows -```shell -$ ipconfig /flushdns + +到这里,Paramiko 已经完胜了,但是仍然有一个痛点我们没有提及,就是多平台,说的就是 Windows,这里就有一件好事,一件坏事了,。 + +好事就是:paramiko 支持 windows + +坏事就是:你需要做很多复杂的准备,你可 google 解决,但是我建议你直接放弃,坑太深了。 + +![](http://image.iswbm.com/20200228111654.png) + +### 注意事项 + +使用 paramiko 的时候,有一点需要注意一下,这个也是我自己 "踩坑" 后才发现的,其实我觉得这个设计挺好的,如果你不需要等待它返回数据,可以直接实现异步效果,只不过对于不知道这个设计的人,确实是个容易掉坑的点 + +就是在执行 `ssh.exec_command(cmd)` 时,这个命令并不是同步阻塞的。 + +比如下面这段代码,执行时,你会发现 脚本立马就结束退出了,并不会等待 5 s 后,再 执行 ssh.close() + +```python +import paramiko + +trans = paramiko.Transport(("172.20.42.1", 57891)) +trans.connect(username="root", password="youpassword") +ssh = paramiko.SSHClient() +ssh._transport = trans +stdin, stdout, stderr = ssh.exec_command("sleep 5;echo ok") +ssh.close() ``` -Linux -```shell -# 使用NSCD的DNS缓存 -$ sudo /etc/init.d/nscd restart -# 服务器或者路由器使用DNSMASQ -$ sudo dnsmasq restart +但是如果改成这样,加上一行 stdout.read(), paramiko 就知道,你需要这个执行的结果,就会在 read() 进行阻塞。 + +```python +import paramiko + +trans = paramiko.Transport(("172.20.42.1", 57891)) +trans.connect(username="root", password="youpassword") +ssh = paramiko.SSHClient() +ssh._transport = trans +stdin, stdout, stderr = ssh.exec_command("sleep 5;echo ok") + +# 加上一行 read() +print(stdout.read()) +ssh.close() ``` -![](http://image.iswbm.com/20200607174235.png) +## 4. 写在最后 + +经过了一番对比,和一些实例的展示,可以看出 Paramiko 是一个专业、让人省心的 ssh 利器,个人认为 Paramiko 模块是运维人员必学模块之一,如果你恰好需要在 Python 代码中实现 ssh 到远程服务器去获取一些信息,那么我把 Paramiko 推荐给你。 + +最后,希望这篇文章,能给你带来帮助。 + + + +## 5. 参考链接 + +- https://github.com/paramiko/paramiko +- http://docs.paramiko.org +- https://www.liujiangblog.com/blog/15/ +- http://docs.paramiko.org/en/stable/ + + + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c10/c10_01.rst b/source/c10/c10_01.rst index bdabd65..70ac45e 100644 --- a/source/c10/c10_01.rst +++ b/source/c10/c10_01.rst @@ -1,353 +1,405 @@ -10.1 网络知识扫盲:一篇文章搞懂 DNS -=================================== +10.1 使用 Python 远程登陆服务器的利器 +===================================== |image0| -1. DNS 是什么? ---------------- +在使用 Python +写一些脚本的时候,在某些情况下,我们需要频繁登陆远程服务去执行一次命令,并返回一些结果。 -DNS (Domain Name System -的缩写)的作用非常简单,就是根据域名查出IP地址。你可以把它想象成一本巨大的电话本。 +在 shell 环境中,我们是这样子做的。 -举例来说,如果你要访问域名\ ``math.stackexchange.com``\ ,首先要通过DNS查出它的IP地址是\ ``151.101.129.69``\ 。 +.. code:: shell -2. 域名的层级 -------------- + $ sshpass -p ${passwd} ssh -p ${port} -l ${user} -o StrictHostKeyChecking=no xx.xx.xx.xx "ls -l" -由于后面我会讲到 DNS 的解析过程,因此需要你对域名的层级有一些了解 +然后你会发现,你的输出有很多你并不需要,但是又不去不掉的一些信息(也许有方法,请留言交流),类似这样 -- 根域名 :\ ``.root`` 或者 ``.`` ,通常是省略的 -- 顶级域名,如 ``.com``\ ,\ ``.cn`` 等 -- 次级域名,如 ``baidu.com`` 里的 ``baidu``\ ,这个用户是可以注册购买的 -- 主机域名,比如 ``baike.baidu.com`` - 里的\ ``baike``\ ,这个用户是可分配的 +.. code:: shell -:: + host: xx.xx.xx.xx, port: xx + Warning: Permanently added '[xx.xx.xx.xx]:xx' (RSA) to the list of known hosts. + Login failure: [Errno 1] This server is not registered to rmp platform, please confirm whether cdn server. + total 4 + -rw-r--r-- 1 root root 239 Mar 30 2018 admin-openrc - 主机名.次级域名.顶级域名.根域名 - - baike.baidu.com.root - -3. DNS 解析过程 ---------------- - -咱们以访问 ``www.163.com`` 这个域名为例,来看一看当你访问 www.163.com -时,会发生哪些事: - -1. 先查找本地 DNS 缓存(自己的电脑上),有则返回,没有则进入下一步 -2. 查看本地 hosts 文件有没有相应的映射记录,有则返回,没有则进入下一步 -3. 向本地 DNS - 服务器(一般都是你的网络接入服务器商提供,比如中国电信,中国移动)发送请求进行查询,本地DNS服务器收到请求后,会先查下自己的缓存记录,如果查到了直接返回就结束了,如果没有查到,本地DNS服务器就会向DNS的根域名服务器发起查询请求:请问老大, - ``www.163.com`` 的ip是啥? -4. 根域名服务器收到请求后,看到这是个 ``.com`` - 的域名,就回信说:这个域名是由 ``.com`` - 老弟管理的,你去问他好了,这是\ ``.com``\ 老弟的联系方式(ip1)。 -5. 本地 DNS 服务器接收到回信后,照着老大哥给的联系方式(ip1),马上给 - ``.com`` 这个顶级域名服务器发起请求:请问 ``.com`` - 大大,\ ``www.163.com`` 的ip 是啥? -6. ``.com`` 顶级域名服务器接收到请求后,看到这是 ``163.com`` - 的域名,就回信说:这个域名是 ``.163.com`` - 老弟管理的,你就去问他就行了,这是他的联系方式(ip2) -7. 本地 DNS 服务器接收到回信后,按照前辈的指引(ip2),又向 ``.163.com`` - 这个权威域名服务器发起请求:请问 ``163.com`` 大大,请问 - ``www.163.com`` 的ip是啥? -8. ``163.com`` - 权威域名服务器接收到请求后,确认了是自己管理的域名,马上查了下自己的小本本,把 - ``www.163.com`` 的ip告诉了 本地DNS服务器。 -9. 本地DNS服务器接收到回信后,非常地开心,这下总算拿到了\ ``www.163.com``\ 的ip了,马上把这个消息告诉了要求查询的客户(就是你的电脑)。由于这个过程比较漫长,本地DNS服务器为了节省时间,也为了尽量不去打扰各位老大哥,就把这个查询结果偷偷地记在了自己的小本本上,方便下次有人来查询时,可以快速回应。 - -总结起来就是三句话 - -1. 从“根域名服务器”查到“顶级域名服务器”的NS记录和A记录(IP地址) -2. 从“顶级域名服务器”查到“次级域名服务器”的NS记录和A记录(IP地址) -3. 从“次级域名服务器”查出“主机名”的IP地址 +对于直接使用 shell +命令,来执行命令的,可以直接使用管道,或者将标准输出重定向到文件的方法取得执行命令返回的结果 -|image1| +1. 使用 subprocess +------------------ -4. DNS的缓存时间 ----------------- +若是使用 Python 来做这件事,通常我们会第一时间,想到使用 +os.popen,os.system,commands,subprocess 等一些命令执行库来间接获取 。 -上面的几个步骤里,可以看到有两个地方会缓存 DNS -的查询记录,有了缓存,在一定程度上会提高查询效率,但同时在准确率上会有所损失。 +但是据我所知,这些库获取的 output +不仅只有标准输出,还包含标准错误(也就是上面那些多余的信息) -因此我们在配置 DNS 解析的时候,会有一个 TTL 参数(Time To -Live),意思就是这个缓存可以存活多长时间,过了这个时间,本地 DNS -就会删除这条记录,删除了缓存后,你再访问,就要重新走一遍上面的流程,获取最新的地址。 +所以每次都要对 output +进行的数据清洗,然后整理格式化,才能得到我们想要的数据。 -|image2| +用 subprocess 举个例子,就像这样子 -5. DNS 的记录类型 ------------------ +.. code:: python -当我们在阿里云买了一个域名后,可以配置我们主机域名解析规则,也就是 -**记录**\ 。 + import subprocess + ssh_cmd = "sshpass -p ${passwd} ssh -p 22 -l root -o StrictHostKeyChecking=no xx.xx.xx.xx 'ls -l'" + status, output = subprocess.getstatusoutput(ssh_cmd) -.. figure:: http://image.iswbm.com/image-20200531170212224.png - :alt: 阿里云 域名云解析 + # 数据清理,格式化的就不展示了 + - 阿里云 域名云解析 +通过以上的文字 + 代码的展示 ,可以感觉到 ssh 登陆的几大痛点 -常见的 DNS 记录类型如下 +- **痛点一**\ :需要额外安装 sshpass(如果不免密的话) +- **痛点二**\ :干扰信息太多,数据清理、格式化相当麻烦 +- **痛点三**\ :代码实现不够优雅(有点土),可读性太差 +- **痛点四**\ :ssh 连接不能复用,一次连接仅能执行一次 +- **痛点五**\ :代码无法全平台,仅能在 Linux 和 OSX 上使用 -- ``A``\ :地址记录(Address),返回域名指向的IP地址。 +为了解决这几个问题,我搜索了全网关于 Python ssh +的文章,没有看到有完整介绍这方面的技巧的。 -- ``NS``\ :域名服务器记录(Name - Server),返回保存下一级域名信息的服务器地址。该记录只能设置为域名,不能设置为IP地址。 -- ``MX``\ :邮件记录(Mail eXchange),返回接收电子邮件的服务器地址。 -- ``CNAME``\ :规范名称记录(Canonical - Name),返回另一个域名,即当前查询的域名是另一个域名的跳转,详见下文。 -- ``PTR``\ :逆向查询记录(Pointer - Record),只用于从IP地址查询域名,详见下文。 +为此,我就翻阅了一个很火的 Github 项目: awesome-python-cn +(https://github.com/BingmingWong/awesome-python-cn)。 -6. DNS 报文结构 ---------------- +期望在这里,找到有一些关于 远程连接 的一些好用的库。 -后面我将使用 wireshark 抓取 DNS 的数据包,但是在开始之前 ,得先了解一下 -DNS 的报文结构 +还真的被我找到了两个 -|image3| +- sh.ssh +- Paramiko -- 事务 ID:DNS 报文的 ID - 标识。对于请求报文和其对应的应答报文,该字段的值是相同的。通过它可以区分 - DNS 应答报文是对哪个请求进行响应的。 -- 标志:DNS 报文中的标志字段。 -- 问题计数:DNS 查询请求的数目。 -- 回答资源记录数:DNS 响应的数目。 -- 权威名称服务器计数:权威名称服务器的数目。 -- 附加资源记录数:额外的记录数目(权威名称服务器对应 IP 地址的数目)。 +2. 使用 sh.ssh +-------------- -7. Wireshark抓包实战 --------------------- +首先来介绍第一个,\ ``sh.ssh`` -打开 Wireshark 后,使用 ``ping 163.com`` 来发起 DNS 解析请求,使用 -``DNS`` 关键字在Wireshark 过滤。 +``sh`` 是一个可以让你通过函数的调用来完成 Linxu/OSX +系统命令的一个库,非常好用,关于它有机会也写篇介绍。 -从抓取的报文整体来看,我们可以粗略获取几个信息 +.. code:: shell -1. DNS 是应用层协议,传输层协议使用的是 UDP -2. DNS 默认端口是 53 + $ python3 -m pip install sh -|image4| +今天只介绍它其中的一个函数:\ ``ssh`` + +通常两台机器互访,为了方便,可设置免密登陆,这样就不需要输入密码。 -请求和应答的报文的截图我放在了下面,接下来我将逐个分析。 +这段代码可以实现免密登陆,并执行我们的命令 ``ls -l`` -**请求** +.. code:: python -|image5| + from sh import ssh + output=ssh("root@xx.xx.xx.xx", "-p 22", "ls -l") + print(output) -**应答** +但有可能 +,我们并不想设置互信免密,为了使这段代码更通用,我假定我们没有设置免密,只能使用密码进行登陆。 -|image6| +问题就来了,要输入密码,必须得使用交互式的方法来输入呀,在 Python +中要如何实现呢? -Transaction ID -~~~~~~~~~~~~~~ +原来 ssh 方法接收一个 ``_out`` +参数,这个参数可以为一个字符串,表示文件路径,也可以是一个文件对象(或者类文件对象),还可以是一个回调函数,意思是当有标准输出时,就会调用将输出内容传给这个函数。 -请求和应答的事务ID应当是一个:0xd0d7 +这就好办了呀。 -Flags -~~~~~ +我只要识别到有 ``password:`` 字样,就往标准输入写入我的密码就好了呀。 -标志字段里的内容比较多,每个字段的含义如下 +完整代码如下: -- QR(Response):查询请求/响应的标志信息。查询请求时,值为 - 0;响应时,值为 1。 -- Opcode:操作码。其中,0 表示标准查询;1 表示反向查询;2 - 表示服务器状态请求。 -- AA(Authoritative):授权应答,该字段在响应报文中有效。值为 1 - 时,表示名称服务器是权威服务器;值为 0 时,表示不是权威服务器。 -- TC(Truncated):表示是否被截断。值为 1 时,表示响应已超过 512 - 字节并已被截断,只返回前 512 个字节。 -- RD(Recursion - Desired):期望递归。该字段能在一个查询中设置,并在响应中返回。该标志告诉名称服务器必须处理这个查询,这种方式被称为一个递归查询。如果该位为 - 0,且被请求的名称服务器没有一个授权回答,它将返回一个能解答该查询的其他名称服务器列表。这种方式被称为迭代查询。 -- RA(Recursion Available):可用递归。该字段只出现在响应报文中。当值为 - 1 时,表示服务器支持递归查询。 -- Z:保留字段,在所有的请求和应答报文中,它的值必须为 0。 -- rcode(Reply code):返回码字段,表示响应的差错状态。当值为 0 - 时,表示没有错误;当值为 1 时,表示报文格式错误(Format - error),服务器不能理解请求的报文;当值为 2 - 时,表示域名服务器失败(Server - failure),因为服务器的原因导致没办法处理这个请求;当值为 3 - 时,表示名字错误(Name - Error),只有对授权域名解析服务器有意义,指出解析的域名不存在;当值为 - 4 时,表示查询类型不支持(Not - Implemented),即域名服务器不支持查询类型;当值为 5 - 时,表示拒绝(Refused),一般是服务器由于设置的策略拒绝给出应答,如服务器不希望对某些请求者给出应答。 +.. code:: python -Answer RRs -~~~~~~~~~~ + import sys + from sh import ssh -回答资源记录数,在应答包里为 2,说明返回了两条查询结果,你可以在 Answer -字段里看到。 + aggregated = "" + def ssh_interact(char, stdin): + global aggregated + sys.stdout.write(char.encode()) + sys.stdout.flush() + aggregated += char + if aggregated.endswith("password: "): + stdin.put("you_password\n") -Authority RRs -~~~~~~~~~~~~~ + output=ssh("root@xx.xx.xx.xx", "-p 22", "ls -l",_tty_in=True, _out_bufsize=0, _out=ssh_interact) + print(output) -权威名称服务器计数 +这是官方文档(http://amoffat.github.io/sh/tutorials/interacting_with_processes.html?highlight=ssh)给的一些信息,写的一个demo。 -Additionnal RRs -~~~~~~~~~~~~~~~ +尝试运行后,发现程序会一直在运行中,永远不会返回,不会退出,回调函数也永远不会进入。 -附加资源记录数 +通过调试查看源代码,仍然查不到问题所在,于是去 +`Github `__ 上搜了下,原来在 +2017 年就已经存在这个问题了,到现在 2020 年了还没有修复,看来使用 +``sh.ssh`` 的人并不多,于是我又“追问”了下,期望能得到回复。 -Answers -~~~~~~~ +|image1| -应答的主要内容,这里返回两条结果,每条结果里的字段有 +以上这个问题,只有在需要输入密码才会出现,如果设置了机器互信是没有问题的。 -.. code:: shell +为了感受 ``sh.ssh`` +的使用效果,我设置了机器互信免密,然后使用如下这段代码。 + +.. code:: python - Name: 查询的域名 - Type: A表示IPv4,AAAA 表示IPv6 - Class: 表示Internet,几乎总是它 - Time to live: 生存时间 - Data length: 数据长度 - Address: 查询到的 IP 地址 + from sh import ssh -8. DNS 劫持 与 HTTP 劫持 ------------------------- + my_server=ssh.bake("root@xx.xx.xx.xx", "-p 22") -通过上面的讲解,我们都知道了,DNS 完成了一次域名到 IP -的映射查询,当你在访问 www.baidu.com 时,能正确返回给你 百度首页的 ip。 + # 相当于执行登陆一次执行一次命令,执行完就退出登陆 + print(my_server.ls()) -但如果此时 DNS 解析出现了一些问题,当你想要访问 www.baidu.com -时,却返回给你 www.google.com 的ip,这就是我们常说的 DNS 劫持。 + # 可在 sleep 期间,手动登陆服务器,使用 top ,查看当前有多少终端在连接 + time.sleep(5) -与之容易混淆的有 HTTP 劫持。 + # 再次执行这条命令时,登陆终端数将 +1,执行完后,又将 -1 + print(my_server.ifconfig()) -那什么是 HTTP 劫持呢? +惊奇地发现使用 ``bake`` 这种方式,\ ``my_server.ls()`` 和 +``my_server.ifconfig()`` +这种看似是通过同一个ssh连接,执行两次命令,可实际上,你可以在远程机器上,执行 +top 命令看到已连接的终端的变化,会先 ``+1`` 再 +``-1``\ ,说明两次命令的执行是通过两次连接实现的。 -你一定见过当你在访问 -某个网站时,右下角也突然弹出了一个扎眼的广告弹窗。这就是 HTTP 劫持。 +如此看来,使用 ``sh.ssh`` +可以解决痛点一(如果上述问题能得到解决)、痛点二、痛点三。 -借助别人文章里的例子,它们俩的区别就好比是 +但是它仍然无法复用 ssh 连接,还是不太方便,不是我理想中的最佳方案。 -- DNS劫持是你想去机场的时候,把你给丢到火车站。 +最重要的一点是, ``sh`` 这个模块,仅支持 Linxu/OSX ,在 Windows +你得使用它的兄弟库 - ``pbs`` ,然后我又去 pypi 看了一眼 +`pbs `__\ ,已经 “年久失修”,没人维护了。 -- HTTP劫持是你去机场途中,有人给你塞小广告。 +|image2| -**那么 DNS劫持 是如何产生的呢?** +至此,我离 “卒”,就差最后一根稻草了。 -下面大概说几种DNS劫持方法: +3. 使用 paramiko +---------------- -**1.本机DNS劫持** +带着最后一丝希望,我尝试使用了 ``paramiko`` 这个库,终于在 ``paramiko`` +这里,找回了本应属于 Python 的那种优雅。 -攻击者通过某些手段使用户的计算机感染上木马病毒,或者恶意软件之后,恶意修改本地DNS配置,比如修改本地hosts文件,缓存等 +你可以通过如下命令去安装它 -**2. 路由DNS劫持** +:: -很多用户默认路由器的默认密码,攻击者可以侵入到路由管理员账号中,修改路由器的默认配置 + $ python3 -m pip install paramiko -**3.攻击DNS服务器** +然后接下来,就介绍几种常用的 ssh 登陆的方法 -直接攻击DNS服务器,例如对DNS服务器进行DDOS攻击,可以是DNS服务器宕机,出现异常请求,还可以利用某些手段感染dns服务器的缓存,使给用户返回来的是恶意的ip地址 +方法1:基于用户名和密码的 sshclient 方式登录 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -9. 工具的使用 -------------- +然后你可以参考如下这段代码,在 Linux/OSX 系统下进行远程连接 -dig 命令 -~~~~~~~~ +.. code:: python -dig是一个在类Unix命令行模式下查询DNS包括NS记录,A记录,MX记录等相关信息的工具。 + import paramiko -通过 dig (参数:\ ``+trace``\ )命令,我们可以看到上面描述的 DNS -解析的详细过程 + ssh = paramiko.SSHClient() + # 允许连接不在know_hosts文件中的主机 + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) -|image7| + # 建立连接 + ssh.connect("xx.xx.xx.xx", username="root", port=22, password="you_password") -从返回的结果,我们可以看得出几点信息 + # 使用这个连接执行命令 + ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("ls -l") -1. 我们的本地 DNS 服务器 ip 为 192.168.1.1,端口为53,你可以在 - /etc/resolv.conf 里看到这个配置 -2. 根域名服务器目前全球一共只有十三台,从a.root-servers.net. - ``到``\ m.root-servers.net. - ,它们对应的ip地址,已经内置在本地DNS服务器中。 + # 获取输出 + print(ssh_stdout.read()) -如果你只想看到结果,可以使用 ``+short`` 参数,可以直接返回 www.163.com -对应着哪几个ip + # 关闭连接 + ssh.close() -|image8| +方法2:基于用户名和密码的 transport 方式登录 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -你也可以加个 ``@`` 参数 ,指定从某个 DNS 服务器进行查询 +方法1 +是传统的连接服务器、执行命令、关闭的一个操作,多个操作需要连接多次,无法复用连接[**痛点四**]。 -|image9| +有时候需要登录上服务器执行多个操作,比如执行命令、上传/下载文件,方法1 +则无法实现,那就可以使用 transport 的方法。 -如果你只想查看指定的记录类型 +.. code:: python -|image10| + import paramiko -host 命令 -~~~~~~~~~ + # 建立连接 + trans = paramiko.Transport(("xx.xx.xx.xx", 22)) + trans.connect(username="root", password="you_passwd") -``host`` 命令 -可以看作\ ``dig``\ 命令的简化版本,返回当前请求域名的各种记录。 + # 将sshclient的对象的transport指定为以上的trans + ssh = paramiko.SSHClient() + ssh._transport = trans -|image11| + # 剩下的就和上面一样了 + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("ls -l") + print(ssh_stdout.read()) -whois命令 -~~~~~~~~~ + # 关闭连接 + trans.close() -``whois``\ 命令用来查看域名的注册情况。 +方法3:基于公钥密钥的 SSHClient 方式登录 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -|image12| +.. code:: python -nslookup命令 -~~~~~~~~~~~~ + import paramiko -nslookup也是常用的一个查询 DNS 解析结果的工具 + # 指定本地的RSA私钥文件 + # 如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数 + pkey = paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa', password='12345') -.. code:: shell + # 建立连接 + ssh = paramiko.SSHClient() + ssh.connect(hostname='xx.xx.xx.xx', + port=22, + username='you_username', + pkey=pkey) - $ nslookup [查询的域名] [指定DNS服务器] + # 执行命令 + stdin, stdout, stderr = ssh.exec_command('ls -l') -|image13| + # 结果放到stdout中,如果有错误将放到stderr中 + print(stdout.read()) -你也可以指定公网的域名服务器进行查询,比如常见的 114.114.114.114 + # 关闭连接 + ssh.close() -|image14| +方法4:基于密钥的 Transport 方式登录 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -10. 手动清理本地缓存 --------------------- +.. code:: python -MacOS + import paramiko -.. code:: shell + # 指定本地的RSA私钥文件 + # 如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数 + pkey = paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa', password='12345') - $ sudo dscacheutil -flushcache - $ sudo killall -HUP mDNSResponder + # 建立连接 + trans = paramiko.Transport(('xx.xx.xx.xx', 22)) + trans.connect(username='you_username', pkey=pkey) -Windows + # 将sshclient的对象的transport指定为以上的trans + ssh = paramiko.SSHClient() + ssh._transport = trans -.. code:: shell + # 执行命令,和传统方法一样 + stdin, stdout, stderr = ssh.exec_command('df -hl') + print(stdout.read().decode()) - $ ipconfig /flushdns + # 关闭连接 + trans.close() -Linux +以上四种方法,可以帮助你实现远程登陆服务器执行命令,如果需要复用连接:一次连接执行多次命令,可以使用 +**方法二** 和 **方法四** -.. code:: shell +用完后,记得关闭连接。 + +实现 sftp 文件传输 +~~~~~~~~~~~~~~~~~~ + +同时,paramiko 做为 ssh 的完美解决方案,它非常专业,利用它还可以实现 +sftp 文件传输。 + +.. code:: python + + import paramiko + + # 实例化一个trans对象# 实例化一个transport对象 + trans = paramiko.Transport(('xx.xx.xx.xx', 22)) - # 使用NSCD的DNS缓存 - $ sudo /etc/init.d/nscd restart + # 建立连接 + trans.connect(username='you_username', password='you_passwd') - # 服务器或者路由器使用DNSMASQ - $ sudo dnsmasq restart + # 实例化一个 sftp对象,指定连接的通道 + sftp = paramiko.SFTPClient.from_transport(trans) -|image15| + # 发送文件 + sftp.put(localpath='/tmp/11.txt', remotepath='/tmp/22.txt') + + # 下载文件 + sftp.get(remotepath='/tmp/22.txt', localpath='/tmp/33.txt') + trans.close() + +到这里,Paramiko +已经完胜了,但是仍然有一个痛点我们没有提及,就是多平台,说的就是 +Windows,这里就有一件好事,一件坏事了,。 + +好事就是:paramiko 支持 windows + +坏事就是:你需要做很多复杂的准备,你可 google +解决,但是我建议你直接放弃,坑太深了。 + +|image3| + +注意事项 +~~~~~~~~ + +使用 paramiko 的时候,有一点需要注意一下,这个也是我自己 “踩坑” +后才发现的,其实我觉得这个设计挺好的,如果你不需要等待它返回数据,可以直接实现异步效果,只不过对于不知道这个设计的人,确实是个容易掉坑的点 + +就是在执行 ``ssh.exec_command(cmd)`` 时,这个命令并不是同步阻塞的。 + +比如下面这段代码,执行时,你会发现 脚本立马就结束退出了,并不会等待 5 s +后,再 执行 ssh.close() + +.. code:: python + + import paramiko + + trans = paramiko.Transport(("172.20.42.1", 57891)) + trans.connect(username="root", password="youpassword") + ssh = paramiko.SSHClient() + ssh._transport = trans + stdin, stdout, stderr = ssh.exec_command("sleep 5;echo ok") + ssh.close() + +但是如果改成这样,加上一行 stdout.read(), paramiko +就知道,你需要这个执行的结果,就会在 read() 进行阻塞。 + +.. code:: python + + import paramiko + + trans = paramiko.Transport(("172.20.42.1", 57891)) + trans.connect(username="root", password="youpassword") + ssh = paramiko.SSHClient() + ssh._transport = trans + stdin, stdout, stderr = ssh.exec_command("sleep 5;echo ok") + + # 加上一行 read() + print(stdout.read()) + ssh.close() + +4. 写在最后 +----------- + +经过了一番对比,和一些实例的展示,可以看出 Paramiko +是一个专业、让人省心的 ssh 利器,个人认为 Paramiko +模块是运维人员必学模块之一,如果你恰好需要在 Python 代码中实现 ssh +到远程服务器去获取一些信息,那么我把 Paramiko 推荐给你。 + +最后,希望这篇文章,能给你带来帮助。 + +5. 参考链接 +----------- + +- https://github.com/paramiko/paramiko +- http://docs.paramiko.org +- https://www.liujiangblog.com/blog/15/ +- http://docs.paramiko.org/en/stable/ + +|image4| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/464291-20170703113844956-354755333.jpg -.. |image2| image:: http://image.iswbm.com/image-20200531141521689.png -.. |image3| image:: http://image.iswbm.com/image-20200531152824672.png -.. |image4| image:: http://image.iswbm.com/20200531175736.png -.. |image5| image:: http://image.iswbm.com/20200531175811.png -.. |image6| image:: http://image.iswbm.com/image-20200531153110621.png -.. |image7| image:: http://image.iswbm.com/image-20200531162810531.png -.. |image8| image:: http://image.iswbm.com/image-20200531164525384.png -.. |image9| image:: http://image.iswbm.com/image-20200531170427834.png -.. |image10| image:: http://image.iswbm.com/image-20200531170543250.png -.. |image11| image:: http://image.iswbm.com/image-20200531171610902.png -.. |image12| image:: http://image.iswbm.com/image-20200531171905345.png -.. |image13| image:: http://image.iswbm.com/image-20200531145109182.png -.. |image14| image:: http://image.iswbm.com/image-20200531145449577.png -.. |image15| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20200228085749.png +.. |image2| image:: http://image.iswbm.com/20200228093627.png +.. |image3| image:: http://image.iswbm.com/20200228111654.png +.. |image4| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c10/c10_02.md b/source/c10/c10_02.md index 316caf8..496da16 100644 --- a/source/c10/c10_02.md +++ b/source/c10/c10_02.md @@ -1,149 +1,253 @@ -# 10.2 网络知识扫盲:如何理解 OSI七层模型 +# 10.2 pretty_errors 解决bug 洁癖 ![](http://image.iswbm.com/20200602135014.png) -OSI (Open System Interconnect),即开放式系统互联。一般都叫OSI参考模型,是ISO(国际标准化组织)组织在1985年研究的网络互联模型。 +当我们写的一个脚本或程序发生各种不可预知的异常时,如果我们没有进行捕获处理的时候,通常都会致使程序崩溃退出,并且会在终端打印出一堆 **密密麻麻** 的 traceback 堆栈信息来告诉我们,是哪个地方出了问题。 -![img](https://pic4.zhimg.com/80/v2-854e3df8ea850c977c30cb1deb1f64db_1440w.jpg) +就像这样子,天呐,密集恐惧症要犯了都 -为什么要有这个模型呢? +![](http://image.iswbm.com/image-20200307210853246.png) -主要是为了实现开放系统环境中的互连性、互操作性和应用的可移植性。 +上面这段 traceback -有了这个模型,各个层的实现就可以独立开发,复用性强,增加自由度的同时,也提高了生产效率。 +- 只有黑白两个颜色,无法像代码高亮那样,对肉眼实现太不友好了 +- 无法直接显示报错的代码,排查问题慢人一步,效率太低 -## 数据的发送过程 +那有没有一种办法,可以解决这些问题呢? -数据包的发起方,一定都是从高层发起,然后从上往下传输数据,下层为上层提供传输服务,下方不关心传输内容,上层不关心下层如何传输。 +当然有了,在 Python 中,没有什么问题是一个库解决不了的,如果有,那就等你去开发这个库。 -就如下面这张图所示 +今天要介绍的这个库呢,叫做 `pretty-errors` ,从名字上就可以知道它的用途,是用来美化错误信息的。 -![](http://image.iswbm.com/20200526233356.png) +通过这条命令你可以安装它 +```shell +$ python3 -m pip install pretty-errors +``` -## 应用层 -代表协议有:HTTP,HTTPS,FTP,SMTP,TELNET +## 1. 环境要求 -由于日常开发中,我们接触的基本都是 HTTP,这里就以 HTTP 为例。 +由于使用了 `pretty-errors` 后,你的 traceback 信息输出,会有代码高亮那样的效果,因此当你在使用测试使用 `pretty-error` 时,请确保你使用的终端可以输出带有颜色的字体。 -这层负责的是真正业务上的内容,比如你在你的浏览器上,请求一个百度的首页。 +在 windows 上你可以使用 Powershell,cmder 等 -览器会将你请求的信息,封装成一个 HTTP 数据包,这个数据包头会包含一些基本的头部信息及请求体(如果有的话)。然后交由下一层处理。 +在 Mac 上你可以使用自带的终端,或者安装一个更好用的 iTerm2 -## 表示层 +## 2. 效果对比 -代表协议有:ASCII,SSL/TLS 等 +------ -这一层的作用是,将应用处理的信息转换为适合网络传输的格式。 +随便写一个没有使用 pretty-errors ,并且报错了的程序,是这样子的。 -比如我只会说中文,而日本友人只会说日文,那么我们两个是无法交流的。但如果我们都会说英文,交流时我先在心里想好要说的话是什么,再用英语说出来,日本友人听到英文,在心里转换为日语,他就能弄懂我的意思,此时表示层就是各自在心里转化语言。 +![](http://image.iswbm.com/image-20200307212823345.png) -当浏览器请求回一堆数据,是解析成文本还是图片,就由表示层决定。数据的压缩、加密、打包等功能也都在这层完成。 +而使用了 pretty_errors 后,报错信息被美化成这样了。 -## 会话层 +![](http://image.iswbm.com/image-20200307213534278.png) -代表协议有: `ADSP`、`RPC` 等。 +是不是感觉清楚了不少,那种密密麻麻带来的焦虑感是不是都消失了呢? -负责建立和断开通信连接(数据流动的逻辑通路),以及数据的分割等数据传输相关的管理。 +当然这段代码少,你可能还没感受到,那就来看下 该项目在 Github上的一张效果对比图吧 -比如,何时建立连接,何时断开连接以及保持多久的连接,都是由会话层来进行管理的。 +![](https://warehouse-camo.cmh1.psfhosted.org/31399c5a034c3989b9e99b35249e8f2f0d40e102/68747470733a2f2f692e696d6775722e636f6d2f306a7045716f622e706e67) -## 传输层 +## 3. 配置全局可用 -代表协议有 :TCP,UDP等 +可以看到使用了 pretty_errors 后,无非就是把过滤掉了一些干扰我们视线的无用信息,然后把有用的关键信息给我们高亮显示。 -**传输层起着可靠传输的作用。** +既然既然这样,那 pretty_errors 应该也能支持我们如何自定义我们选用什么样的颜色,怎么排版吧? -对于 DNS 域名解析,传输层协议是 UDP +答案是显而易见的。 -而对于 HTTP 请求,传输层协议是 TCP +pretty_errors 和其他库不太一样,在一定程度上(如果你使用全局配置的话),它并不是开箱即用的,你在使用它之前可能需要做一下配置。 -**那这两种协议有什么区别呢?** +使用这一条命令,会让你进行配置,可以让你在该环境中运行其他脚本时的 traceback 输出都自动美化。 -`TCP` 协议提供可靠的通信传输,简单说就是确认目标能通信的情况下才会传输数据(因此需要三次握手),传输过程如果丢了数据,也会重发。而 `UDP` 协议则不然,不会确认目标能否通信,只会根据协议发到对方地址的端口。至于对方收不收到,丢不丢包,一概不管。 +```shell +$ python3 -m pretty_errors +``` -无论是哪种协议,其协议头信息都会包含本地端口号,和目标端口号(还有报文长度)。 +![](http://image.iswbm.com/image-20200307214742135.png) -加上这层头信息后,封装好的数据包再交由网络层。 +配置完成后,你再运行任何脚本,traceback 都会自动美化了。 -## 网络层 +不仅是在我的 iTerm 终端下 -代表协议有:IP,ICMP 协议等,其也叫IP层,主要应用是路由器(并非我们家中的路由器,扩展阅读:https://www.zhihu.com/question/52176116)。 +![](http://image.iswbm.com/image-20200307213534278.png) -**网络层负责将数据传输到目标地址。** +在 PyCharm 中也会 -网络层将数据从发送端的主机发送到接收端的主机,两台主机间可能会存在很多数据链路,但网络层就是负责找出一条相对顺畅的通路将数据传递过去。传输的地址使用的是IP地址。 +![](http://image.iswbm.com/image-20200307215530270.png) -IP地址和我们的住址有点相似,我们的住址可以从省到市再到街逐步缩小范围,直至我们住址。IP地址也有这样的能力,通过不断转发到更近的IP地址,最终可以到达目标地址。如何选择这条路,就看网络层了。 +唯一的缺点就是,原先在 PyCharm 中的 traceback 可以直接点击 `文件路径` 直接跳转到对应错误文件代码行,而你如果是在 VSCode 可以使用 下面自定义配置的方案解决这个问题(下面会讲到,参数是:`display_link`)。 -这好比是快递公司的路线规划者。快递公司有很多集散中心,根据集散中心的情况(是否拥堵),找出一条经过n个集散中心的路径将货物(数据)沿路运过去。 +![](http://image.iswbm.com/image-20200307215834623.png) -在这层里,会给数据包再加一些头信息,包括本地IP地址,目标IP地址(还有数据包的生存空间TTL),同时还会有一个Protocol字段 表示上层到底是 UDP还是TCP协议,这个是用于对端在接收到这个数据包后知道如何解析。 +因此,有些情况下,你并不想设置 `pretty_errors` 全局可用。 -加上这些头信息后,再把**数据报**(或者说**分组**、**报文**)传递给链路层。 +那怎么取消之前的配置呢? +只需要再次输出 `python -m pretty_errors`,输出入 `C` 即可清除。 +![](http://image.iswbm.com/image-20200307214600749.png) -## 链路层 -代表协议有:HDLC,PPP,SLIP 等,其也叫 MAC 层,主要应用是交换机,网桥。 -**该层负责物理层面(一个以太网相连)上互连的节点之间的通信传输。** +## 4. 单文件中使用 -数据链路层会将0、1序列划分为具有意义的数据帧传送给对端(数据帧的生成与接收)。举个例子可能会更好理解,暂且把需要传输的数据看作为不同来源的水,如果直接倒入池子中时,是无法重新分辨出不同来源的水的。但如果将不同来源的灌入瓶子中并打上记号,那就能区分出不同来源的水。这也就是为什么要划分为具有意义的数据帧传送给对端。 +取消全局可用后,你可以根据自己需要,在你需要使用 `pretty-errors` 的脚本文件中导入` pretty_errors `,即可使用 -数据链路层可以看作是快递公司的司机,他们驾驶着汽车,将打包好的货物(数据帧)从一个城市(物理节点)运输到另一个城市。 +```python +import pretty_errors +``` -在这层里,会给数据包再加一些头信息,包括本地的mac地址,目标mac地址,同时还有一个type字段表示上层协议是使用的是IP协议,还是其他什么协议,也是用于对端在接收到这个数据包后知道如何解析。 +就像这样 -需要注意的是,数据链路层只负责将数据运送给物理相连的两端,并不负责直接发送到最终地址。 +```python +import pretty_errors -加上这些头信息后,再把**数据帧**传递给物理层。 +def foo(): + 1/0 +if __name__ == "__main__": + foo() +``` +值得一提的是,使用这种方式,若是你的脚本中,出现语法错误,则输出的异常信息还是按照之前的方式展示,并不会被美化。 -这一层主要解决两个问题: +因此,为了让美化更彻底,官方推荐你使用 `python -m pretty_errors` -**第一个问题**:网络包是发给谁的,由谁来接收 +## 5. 自定义设置 -链路层协议头会包含目标MAC和源MAC +上面的例子里,我们使用的都是 `pretty_errors` 的默认美化格式,展示的信息并没有那么全。 -**第二个问题**:大家都在发包,谁先发,谁后发? +比如 -对于这个问题,有多种算法可以解决 +- 它并没有展示报错文件的绝对路径,这将使我们很难定位到是哪个文件里的代码出现错误。 +- 如果能把具体报错的代码,给我们展示在终端屏幕上,就不需要我们再到源码文件中排查原因了。 -方法一:信道划分,就跟马路上分多个车道一样,你走你的,我走我的,互不影响 +如果使用了 `pretty_errors` 导致异常信息有丢失,那还不如不使用 `pretty_errors` 呢。 -方法二:轮流协议,还是以交通举例,今天单号出行,明天双号出行,轮着来 +不过,可以告诉你的是,`pretty_errors` 并没有你想象的那么简单。 -方法三:随机接入协议,不管三七二十一,有事儿先出门,发现特堵,就回去。错过高峰再出。 +它足够开放,支持自定义配置,可以由你选择你需要展示哪些信息,怎么展示? +这里举一个例子 +```python +import pretty_errors -## 物理层 +# 【重点】进行配置 +pretty_errors.configure( + separator_character = '*', + filename_display = pretty_errors.FILENAME_EXTENDED, + line_number_first = True, + display_link = True, + lines_before = 5, + lines_after = 2, + line_color = pretty_errors.RED + '> ' + pretty_errors.default_config.line_color, + code_color = ' ' + pretty_errors.default_config.line_color, +) -代表协议有: `RS 232C`、`RS 449/422/423`、`V.24` 和 `X.21`、`X.21bis` 等。 +# 原来的代码 +def foo(): + 1/0 -物理层负责0、1比特流(0、1序列)与电压高低、光的闪灭之间的互换。 +if __name__ == "__main__": + foo() +``` -物理层其实就是我们日常能接触的物理介质,比如光纤、电缆、还有空气(还有集线器、中继器、调制解调器),根据这些传输介质的不同,二进制流(0和1)会相应地转化成光信号,电信号,电磁波信号。 +在你像上面这样使用 `pretty_errrs.configure` 进行配置时,抛出的的异常信息就变成这样了。 -物理层是 `OSI` 七层模型的物理基础,没有它就谈不上数据传输了。 +![](http://image.iswbm.com/image-20200308121949011.png) -## 参考文章 +当然了,`pretty_errors.configure()` 还可以接收很多的参数,你可以根据你自己的需要进行配置。 -- https://juejin.im/post/59eb06b1f265da430f313c7f +### 5.1 设置颜色 +- `header_color`:设置标题行的颜色。 +- `timestamp_color`:设置时间戳颜色 +- `default_color`:设置默认的颜色 +- `filename_color`:设置文件名颜色 +- `line_number_color`:设置行号颜色。 +- `function_color`:设置函数颜色。 +- `link_color`:设置链接的颜色。 +在设置颜色的时候,`pretty_errors` 提供了一些常用的 颜色常量供你直接调取。 ---- +- `BLACK`:黑色 +- `GREY`:灰色 +- `RED`:红色 +- `GREEN`:绿色 +- `YELLOW`:黄色 +- `BLUE`:蓝色 +- `MAGENTA`:品红色 +- `CYAN`:蓝绿色 +- `WHITE`:白色 +而每一种颜色,都相应的匹配的 `BRIGHT_` 变体 和 `_BACKGROUND` 变体, +其中,`_BACKGROUND` 用于设置背景色,举个例子如下。 + +![](http://image.iswbm.com/image-20200308125431779.png) + +### 5.2 设置显示内容 + +- `line_number_first` + 启用后,将首先显示行号,而不是文件名。 +- `lines_before` : 显示发生异常处的前几行代码 +- `lines_after`: 显示发生异常处的后几行代码 +- `display_link`:启用后,将在错误位置下方写入链接,VScode将允许您单击该链接。 +- `separator_character`:用于创建标题行的字符。默认情况下使用连字符。如果设置为 `''` 或者 `None` ,标题将被禁用。 +- `display_timestamp`:启用时,时间戳将写入回溯头中。 +- `display_locals` + 启用后,将显示在顶部堆栈框架代码中的局部变量及其值。 + +- `display_trace_locals` + 启用后,其他堆栈框架代码中出现的局部变量将与它们的值一起显示。 + +### 5.3 设置怎么显示 + +- `line_length`:设置每行的长度,默认为0,表示每行的输出将与控制台尺寸相匹配,如果你设置的长度将好与控制台宽度匹配,则可能需要禁用`full_line_newline`,以防止出现明显的双换行符。 + +- `full_line_newline`:当输出的字符满行时,是否要插入换行符。 + +- `timestamp_function` + 调用该函数以生成时间戳。默认值为`time.perf_counter`。 + +- `top_first` + 启用后,堆栈跟踪将反转,首先显示堆栈顶部。 + +- `display_arrow` + 启用后,将针对语法错误显示一个箭头,指向有问题的令牌。 + +- `truncate_code` + 启用后,每行代码将被截断以适合行长。 + +- `stack_depth` + 要显示的堆栈跟踪的最大条目数。什么时候`0`将显示整个堆栈,这是默认值。 + +- `exception_above` + 启用后,异常将显示在堆栈跟踪上方。 + +- `exception_below`: + 启用后,异常显示在堆栈跟踪下方。 + +- `reset_stdout` + 启用后,重置转义序列将写入stdout和stderr;如果您的控制台留下错误的颜色,请启用此选项。 + +- `filename_display` + + 设置文件名的展示方式,有三个选项: `pretty_errors.FILENAME_COMPACT` 、`pretty_errors.FILENAME_EXTENDED`,或者`pretty_errors.FILENAME_FULL` + + + +以上,就是我对 `pretty_errors` 的使用体验,总的来说,这个库功能非常强大,使用效果也特别酷炫,它就跟 PEP8 规范一样,没有它是可以,但是有了它会更好一样。对于某些想自定义错误输出场景的人,`pretty_errors` 会是一个不错的解决方案,明哥把它推荐给你。 ![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c10/c10_02.rst b/source/c10/c10_02.rst index db03718..bf2c4bf 100644 --- a/source/c10/c10_02.rst +++ b/source/c10/c10_02.rst @@ -1,160 +1,286 @@ -10.2 网络知识扫盲:如何理解 OSI七层模型 -======================================= +10.2 pretty_errors 解决bug 洁癖 +=============================== |image0| -OSI (Open System -Interconnect),即开放式系统互联。一般都叫OSI参考模型,是ISO(国际标准化组织)组织在1985年研究的网络互联模型。 +当我们写的一个脚本或程序发生各种不可预知的异常时,如果我们没有进行捕获处理的时候,通常都会致使程序崩溃退出,并且会在终端打印出一堆 +**密密麻麻** 的 traceback 堆栈信息来告诉我们,是哪个地方出了问题。 -.. figure:: https://pic4.zhimg.com/80/v2-854e3df8ea850c977c30cb1deb1f64db_1440w.jpg - :alt: img +就像这样子,天呐,密集恐惧症要犯了都 - img +|image1| + +上面这段 traceback + +- 只有黑白两个颜色,无法像代码高亮那样,对肉眼实现太不友好了 +- 无法直接显示报错的代码,排查问题慢人一步,效率太低 + +那有没有一种办法,可以解决这些问题呢? + +当然有了,在 Python +中,没有什么问题是一个库解决不了的,如果有,那就等你去开发这个库。 + +今天要介绍的这个库呢,叫做 ``pretty-errors`` +,从名字上就可以知道它的用途,是用来美化错误信息的。 + +通过这条命令你可以安装它 + +.. code:: shell + + $ python3 -m pip install pretty-errors -为什么要有这个模型呢? +1. 环境要求 +----------- -主要是为了实现开放系统环境中的互连性、互操作性和应用的可移植性。 +由于使用了 ``pretty-errors`` 后,你的 traceback +信息输出,会有代码高亮那样的效果,因此当你在使用测试使用 +``pretty-error`` 时,请确保你使用的终端可以输出带有颜色的字体。 -有了这个模型,各个层的实现就可以独立开发,复用性强,增加自由度的同时,也提高了生产效率。 +在 windows 上你可以使用 Powershell,cmder 等 + +在 Mac 上你可以使用自带的终端,或者安装一个更好用的 iTerm2 + +2. 效果对比 +----------- -数据的发送过程 -------------- -数据包的发起方,一定都是从高层发起,然后从上往下传输数据,下层为上层提供传输服务,下方不关心传输内容,上层不关心下层如何传输。 +随便写一个没有使用 pretty-errors ,并且报错了的程序,是这样子的。 -就如下面这张图所示 +|image2| -|image1| +而使用了 pretty_errors 后,报错信息被美化成这样了。 -应用层 ------- +|image3| -代表协议有:HTTP,HTTPS,FTP,SMTP,TELNET +是不是感觉清楚了不少,那种密密麻麻带来的焦虑感是不是都消失了呢? -由于日常开发中,我们接触的基本都是 HTTP,这里就以 HTTP 为例。 +当然这段代码少,你可能还没感受到,那就来看下 该项目在 +Github上的一张效果对比图吧 -这层负责的是真正业务上的内容,比如你在你的浏览器上,请求一个百度的首页。 +|image4| -览器会将你请求的信息,封装成一个 HTTP -数据包,这个数据包头会包含一些基本的头部信息及请求体(如果有的话)。然后交由下一层处理。 +3. 配置全局可用 +--------------- -表示层 ------- +可以看到使用了 pretty_errors +后,无非就是把过滤掉了一些干扰我们视线的无用信息,然后把有用的关键信息给我们高亮显示。 -代表协议有:ASCII,SSL/TLS 等 +既然既然这样,那 pretty_errors +应该也能支持我们如何自定义我们选用什么样的颜色,怎么排版吧? -这一层的作用是,将应用处理的信息转换为适合网络传输的格式。 +答案是显而易见的。 -比如我只会说中文,而日本友人只会说日文,那么我们两个是无法交流的。但如果我们都会说英文,交流时我先在心里想好要说的话是什么,再用英语说出来,日本友人听到英文,在心里转换为日语,他就能弄懂我的意思,此时表示层就是各自在心里转化语言。 +pretty_errors +和其他库不太一样,在一定程度上(如果你使用全局配置的话),它并不是开箱即用的,你在使用它之前可能需要做一下配置。 -当浏览器请求回一堆数据,是解析成文本还是图片,就由表示层决定。数据的压缩、加密、打包等功能也都在这层完成。 +使用这一条命令,会让你进行配置,可以让你在该环境中运行其他脚本时的 +traceback 输出都自动美化。 -会话层 ------- +.. code:: shell -代表协议有: ``ADSP``\ 、\ ``RPC`` 等。 + $ python3 -m pretty_errors -负责建立和断开通信连接(数据流动的逻辑通路),以及数据的分割等数据传输相关的管理。 +|image5| -比如,何时建立连接,何时断开连接以及保持多久的连接,都是由会话层来进行管理的。 +配置完成后,你再运行任何脚本,traceback 都会自动美化了。 -传输层 ------- +不仅是在我的 iTerm 终端下 -代表协议有 :TCP,UDP等 +|image6| -**传输层起着可靠传输的作用。** +在 PyCharm 中也会 -对于 DNS 域名解析,传输层协议是 UDP +|image7| -而对于 HTTP 请求,传输层协议是 TCP +唯一的缺点就是,原先在 PyCharm 中的 traceback 可以直接点击 ``文件路径`` +直接跳转到对应错误文件代码行,而你如果是在 VSCode 可以使用 +下面自定义配置的方案解决这个问题(下面会讲到,参数是:\ ``display_link``\ )。 -**那这两种协议有什么区别呢?** +|image8| -``TCP`` -协议提供可靠的通信传输,简单说就是确认目标能通信的情况下才会传输数据(因此需要三次握手),传输过程如果丢了数据,也会重发。而 -``UDP`` -协议则不然,不会确认目标能否通信,只会根据协议发到对方地址的端口。至于对方收不收到,丢不丢包,一概不管。 +因此,有些情况下,你并不想设置 ``pretty_errors`` 全局可用。 -无论是哪种协议,其协议头信息都会包含本地端口号,和目标端口号(还有报文长度)。 +那怎么取消之前的配置呢? -加上这层头信息后,封装好的数据包再交由网络层。 +只需要再次输出 ``python -m pretty_errors``\ ,输出入 ``C`` 即可清除。 -网络层 ------- +|image9| -代表协议有:IP,ICMP -协议等,其也叫IP层,主要应用是路由器(并非我们家中的路由器,扩展阅读:https://www.zhihu.com/question/52176116)。 +4. 单文件中使用 +--------------- -**网络层负责将数据传输到目标地址。** +取消全局可用后,你可以根据自己需要,在你需要使用 ``pretty-errors`` +的脚本文件中导入\ ``pretty_errors``\ ,即可使用 -网络层将数据从发送端的主机发送到接收端的主机,两台主机间可能会存在很多数据链路,但网络层就是负责找出一条相对顺畅的通路将数据传递过去。传输的地址使用的是IP地址。 +.. code:: python -IP地址和我们的住址有点相似,我们的住址可以从省到市再到街逐步缩小范围,直至我们住址。IP地址也有这样的能力,通过不断转发到更近的IP地址,最终可以到达目标地址。如何选择这条路,就看网络层了。 + import pretty_errors -这好比是快递公司的路线规划者。快递公司有很多集散中心,根据集散中心的情况(是否拥堵),找出一条经过n个集散中心的路径将货物(数据)沿路运过去。 +就像这样 -在这层里,会给数据包再加一些头信息,包括本地IP地址,目标IP地址(还有数据包的生存空间TTL),同时还会有一个Protocol字段 -表示上层到底是 -UDP还是TCP协议,这个是用于对端在接收到这个数据包后知道如何解析。 +.. code:: python -加上这些头信息后,再把\ **数据报**\ (或者说\ **分组**\ 、\ **报文**\ )传递给链路层。 + import pretty_errors -链路层 ------- + def foo(): + 1/0 -代表协议有:HDLC,PPP,SLIP 等,其也叫 MAC 层,主要应用是交换机,网桥。 + if __name__ == "__main__": + foo() -**该层负责物理层面(一个以太网相连)上互连的节点之间的通信传输。** +值得一提的是,使用这种方式,若是你的脚本中,出现语法错误,则输出的异常信息还是按照之前的方式展示,并不会被美化。 -数据链路层会将0、1序列划分为具有意义的数据帧传送给对端(数据帧的生成与接收)。举个例子可能会更好理解,暂且把需要传输的数据看作为不同来源的水,如果直接倒入池子中时,是无法重新分辨出不同来源的水的。但如果将不同来源的灌入瓶子中并打上记号,那就能区分出不同来源的水。这也就是为什么要划分为具有意义的数据帧传送给对端。 +因此,为了让美化更彻底,官方推荐你使用 ``python -m pretty_errors`` -数据链路层可以看作是快递公司的司机,他们驾驶着汽车,将打包好的货物(数据帧)从一个城市(物理节点)运输到另一个城市。 +5. 自定义设置 +------------- -在这层里,会给数据包再加一些头信息,包括本地的mac地址,目标mac地址,同时还有一个type字段表示上层协议是使用的是IP协议,还是其他什么协议,也是用于对端在接收到这个数据包后知道如何解析。 +上面的例子里,我们使用的都是 ``pretty_errors`` +的默认美化格式,展示的信息并没有那么全。 -需要注意的是,数据链路层只负责将数据运送给物理相连的两端,并不负责直接发送到最终地址。 +比如 -加上这些头信息后,再把\ **数据帧**\ 传递给物理层。 +- 它并没有展示报错文件的绝对路径,这将使我们很难定位到是哪个文件里的代码出现错误。 +- 如果能把具体报错的代码,给我们展示在终端屏幕上,就不需要我们再到源码文件中排查原因了。 -这一层主要解决两个问题: +如果使用了 ``pretty_errors`` 导致异常信息有丢失,那还不如不使用 +``pretty_errors`` 呢。 -**第一个问题**\ :网络包是发给谁的,由谁来接收 +不过,可以告诉你的是,\ ``pretty_errors`` 并没有你想象的那么简单。 -链路层协议头会包含目标MAC和源MAC +它足够开放,支持自定义配置,可以由你选择你需要展示哪些信息,怎么展示? -**第二个问题**\ :大家都在发包,谁先发,谁后发? +这里举一个例子 -对于这个问题,有多种算法可以解决 +.. code:: python -方法一:信道划分,就跟马路上分多个车道一样,你走你的,我走我的,互不影响 + import pretty_errors -方法二:轮流协议,还是以交通举例,今天单号出行,明天双号出行,轮着来 + # 【重点】进行配置 + pretty_errors.configure( + separator_character = '*', + filename_display = pretty_errors.FILENAME_EXTENDED, + line_number_first = True, + display_link = True, + lines_before = 5, + lines_after = 2, + line_color = pretty_errors.RED + '> ' + pretty_errors.default_config.line_color, + code_color = ' ' + pretty_errors.default_config.line_color, + ) -方法三:随机接入协议,不管三七二十一,有事儿先出门,发现特堵,就回去。错过高峰再出。 + # 原来的代码 + def foo(): + 1/0 -物理层 ------- + if __name__ == "__main__": + foo() -代表协议有: ``RS 232C``\ 、\ ``RS 449/422/423``\ 、\ ``V.24`` 和 -``X.21``\ 、\ ``X.21bis`` 等。 +在你像上面这样使用 ``pretty_errrs.configure`` +进行配置时,抛出的的异常信息就变成这样了。 -物理层负责0、1比特流(0、1序列)与电压高低、光的闪灭之间的互换。 +|image10| -物理层其实就是我们日常能接触的物理介质,比如光纤、电缆、还有空气(还有集线器、中继器、调制解调器),根据这些传输介质的不同,二进制流(0和1)会相应地转化成光信号,电信号,电磁波信号。 +当然了,\ ``pretty_errors.configure()`` +还可以接收很多的参数,你可以根据你自己的需要进行配置。 -物理层是 ``OSI`` 七层模型的物理基础,没有它就谈不上数据传输了。 +5.1 设置颜色 +~~~~~~~~~~~~ -参考文章 --------- +- ``header_color``\ :设置标题行的颜色。 +- ``timestamp_color``\ :设置时间戳颜色 +- ``default_color``\ :设置默认的颜色 +- ``filename_color``\ :设置文件名颜色 +- ``line_number_color``\ :设置行号颜色。 +- ``function_color``\ :设置函数颜色。 +- ``link_color``\ :设置链接的颜色。 -- https://juejin.im/post/59eb06b1f265da430f313c7f +在设置颜色的时候,\ ``pretty_errors`` 提供了一些常用的 +颜色常量供你直接调取。 --------------- +- ``BLACK``\ :黑色 +- ``GREY``\ :灰色 +- ``RED``\ :红色 +- ``GREEN``\ :绿色 +- ``YELLOW``\ :黄色 +- ``BLUE``\ :蓝色 +- ``MAGENTA``\ :品红色 +- ``CYAN``\ :蓝绿色 +- ``WHITE``\ :白色 -|image2| +而每一种颜色,都相应的匹配的 ``BRIGHT_`` 变体 和 ``_BACKGROUND`` 变体, + +其中,\ ``_BACKGROUND`` 用于设置背景色,举个例子如下。 + +|image11| + +5.2 设置显示内容 +~~~~~~~~~~~~~~~~ + +- ``line_number_first`` 启用后,将首先显示行号,而不是文件名。 +- ``lines_before`` : 显示发生异常处的前几行代码 +- ``lines_after``\ : 显示发生异常处的后几行代码 +- ``display_link``\ :启用后,将在错误位置下方写入链接,VScode将允许您单击该链接。 +- ``separator_character``\ :用于创建标题行的字符。默认情况下使用连字符。如果设置为 + ``''`` 或者 ``None`` ,标题将被禁用。 +- ``display_timestamp``\ :启用时,时间戳将写入回溯头中。 +- ``display_locals`` + 启用后,将显示在顶部堆栈框架代码中的局部变量及其值。 + +- ``display_trace_locals`` + 启用后,其他堆栈框架代码中出现的局部变量将与它们的值一起显示。 + +5.3 设置怎么显示 +~~~~~~~~~~~~~~~~ + +- ``line_length``\ :设置每行的长度,默认为0,表示每行的输出将与控制台尺寸相匹配,如果你设置的长度将好与控制台宽度匹配,则可能需要禁用\ ``full_line_newline``\ ,以防止出现明显的双换行符。 + +- ``full_line_newline``\ :当输出的字符满行时,是否要插入换行符。 + +- ``timestamp_function`` + 调用该函数以生成时间戳。默认值为\ ``time.perf_counter``\ 。 + +- ``top_first`` 启用后,堆栈跟踪将反转,首先显示堆栈顶部。 + +- ``display_arrow`` + 启用后,将针对语法错误显示一个箭头,指向有问题的令牌。 + +- ``truncate_code`` 启用后,每行代码将被截断以适合行长。 + +- ``stack_depth`` + 要显示的堆栈跟踪的最大条目数。什么时候\ ``0``\ 将显示整个堆栈,这是默认值。 + +- ``exception_above`` 启用后,异常将显示在堆栈跟踪上方。 + +- ``exception_below``\ : 启用后,异常显示在堆栈跟踪下方。 + +- ``reset_stdout`` + 启用后,重置转义序列将写入stdout和stderr;如果您的控制台留下错误的颜色,请启用此选项。 + +- ``filename_display`` + + 设置文件名的展示方式,有三个选项: ``pretty_errors.FILENAME_COMPACT`` + 、\ ``pretty_errors.FILENAME_EXTENDED``\ ,或者\ ``pretty_errors.FILENAME_FULL`` + +以上,就是我对 ``pretty_errors`` +的使用体验,总的来说,这个库功能非常强大,使用效果也特别酷炫,它就跟 +PEP8 +规范一样,没有它是可以,但是有了它会更好一样。对于某些想自定义错误输出场景的人,\ ``pretty_errors`` +会是一个不错的解决方案,明哥把它推荐给你。 + +|image12| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200526233356.png -.. |image2| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/image-20200307210853246.png +.. |image2| image:: http://image.iswbm.com/image-20200307212823345.png +.. |image3| image:: http://image.iswbm.com/image-20200307213534278.png +.. |image4| image:: https://warehouse-camo.cmh1.psfhosted.org/31399c5a034c3989b9e99b35249e8f2f0d40e102/68747470733a2f2f692e696d6775722e636f6d2f306a7045716f622e706e67 +.. |image5| image:: http://image.iswbm.com/image-20200307214742135.png +.. |image6| image:: http://image.iswbm.com/image-20200307213534278.png +.. |image7| image:: http://image.iswbm.com/image-20200307215530270.png +.. |image8| image:: http://image.iswbm.com/image-20200307215834623.png +.. |image9| image:: http://image.iswbm.com/image-20200307214600749.png +.. |image10| image:: http://image.iswbm.com/image-20200308121949011.png +.. |image11| image:: http://image.iswbm.com/image-20200308125431779.png +.. |image12| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c10/c10_03.md b/source/c10/c10_03.md index f8286a3..ad4d53c 100644 --- a/source/c10/c10_03.md +++ b/source/c10/c10_03.md @@ -1,574 +1,187 @@ -# 10.3 网络知识扫盲:详解TCP的三次握手与四次挥手 +# 10.3 少有人知的 Python "重试机制":tenacity ![](http://image.iswbm.com/20200602135014.png) +为了避免由于一些网络或等其他不可控因素,而引起的功能性问题。比如在发送请求时,会因为网络不稳定,往往会有请求超时的问题。 +这种情况下,我们通常会在代码中加入重试的代码。重试的代码本身不难实现,但如何写得优雅、易用,是我们要考虑的问题。 -## 1. TCP 协议是什么? +这里要给大家介绍的是一个第三方库 - `Tenacity` ,它实现了几乎我们可以使用到的所有重试场景,比如: -TCP 是 Transmission Control Protocol 的缩写,意思是传输控制协议。一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 定义。 +1. 在什么情况下才进行重试? +2. 重试几次呢? +3. 重试多久后结束? +4. 每次重试的间隔多长呢? +5. 重试失败后的回调? -从这个定义里,有很多初学就首先懵了。 +在使用它之前 ,先要安装它 -什么是面向连接? - -什么是可靠的通信协议? - -什么是面向字节流的? - - - -为了让你对 TCP 有个初步的了解,我打算先从这个定义入手。 - -### 什么是面向连接? - -面向连接,是相对于另一个传输层协议 UDP 而言的(后面会单独介绍)。 - -TCP 是面向连接的,所以在开始传输数据前要先经历三次握手建立连接。 - -而 UDP 即刻就可以传输数据,并不需要先三次握手来建立连接。 - -一个更可靠,而一个更开放。 - -就好比,你去医院看病,如果是专家号,一般要提前预约,对只要预约(三次握手建立了连接)上了,你去了就不会看不上病。这是 TCP 。 - -而如果你没有预约,就直接跑过去,那不好意思,你只能看普通门诊,而普通门诊等的人很多,你就不一定能看得上病了。这是 UDP。 - -既然是连接,必然是一对一的,就像绳子的两端。所以 TCP 是一对一发送消息。 - -而 UDP 协议不需要连接,可以一对一,也可以一对多,也可以多对多发送消息。 - -### 什么是可靠的通信协议? - -可不可靠,也是相对于 UDP 而言的。 - -TCP 自身有三次握手和超时重传等机制,所以无论网络如何变化,主要不是主机宕机等原因都可以保证一个报文可以到达目标主机。 - -与之对比, UDP 就比较不负责任了,不管你收不收得到,反正我就无脑发,网络拥堵我也发,它的职责是发出去。 - -### 什么是面向字节流的? - -与面向字节流相对的是,UDP 的面向报文。 - -面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率。若太短,会使IP太小。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这也就是说,应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。 - -面向字节流的话,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。 - - -## 2. 完整解读 TCP 报文格式 - -搞懂一个通信协议,了解它的报文格式是必经之路。 - -TCP 的报文段结构,可以从下面这张图中非常清晰的看到。 - -![TCP 报文首部](http://image.iswbm.com/20200606095627.png) - -接下来,我会一个一个讲解这些字段的内容。 - -**源端口** 和 **目标端口**:各占 2 个字节。2 个字节,也就是 16个 bit,这应该也能说明为什么计算机端口的范围是 1-65535 (0 不使用,2^16=65536,最大位65536不使用)了吧?有了源端口和目标端口,加上 IP 首部里的源IP和目标IP,就可以唯一确定一个连接。 - -**序列号**:共占用 4个字节。说明序列号的范围是 [0, 2^32-1],也就是 [0, 4294967296]。当序号增加到 4294967296 后,下一个序号将回到0重新开始。在建立连接时由计算机生成的随机数作为其初始值(ISN,即Initial Sequence Number,初始序列号),通过 SYN 包传给接收端主机,每发送一次数据,就**累加**一次该「数据字节数」的大小(其中要注意的是 SYN 和 FIN 包的 seq 也要消耗一个序号)。**用来解决网络包乱序问题。** - -**确认号**:共占用 4个字节。说明确认号的范围是 [0, 2^32-1],也就是 [0, 4294967296]。它表示**期望**收到对方下一次数据的序列号(所以 ack 一般都是上次接收成功的数据字节序号加1),发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。**用来解决不丢包的问题**。TCP在接收到数据后 200ms 才会发送ACK包,这种设定是为了等待是否有数据可以一起发送的。 - -**数据偏移(图中为报文首部)**:共占 4 个bit,它表示的是TCP报文的数据起始处距离TCP报文起始处的距离有多远。实际生活中我们说距离多远,我们的单位通常是米,而这里距离有多远,单位是 4 个字节(也就是 32bit)。由于 4 个bit,能表示的最大整数是 15,也就说明 TCP 报文里数据开始的位置距离报文起点是 60 个字节(4*15)。这意味着 TCP 的首部(除数据外的都叫首部)长度是 20-60 个字节。 - -**窗口**:共占 16 个bit,因此最大的窗口大小为 2^16-1 = 65535 = 64k。这是早期的设计,对于现在的网络应用,可能会不太够,因此可以在选项里加一个 **窗口扩大选项**,来传输更多的数据。窗口指的是发送本报文段的一方的接受窗口(而不是自己的发送窗口)。窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。总之,窗口值作为接收方让发送方设置其发送窗口的依据。 - -**保留**:占 6个bit,保留为今后使用,目前应置为0。 - -**紧急指针**:占16个bit。紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据) 。因此,在紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时,TCP就告诉应用程序恢复到正常操作。值得注意的是,**即使窗口为0时也可以发送紧急数据**。 - -**标志位:** - -- **SYN**(SYNchronization): 在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使SYN=1和ACK=1,因此SYN置为1就表示这是一个连接请求或连接接受报文。 -- **ACK**(ACKnowledgment):仅当ACK = 1时确认号字段才有效,当ACK = 0时确认号无效。TCP规定,在连接建立后所有的传送的报文段都必须把ACK置为1,如果你可以看下我后面 wireshark 抓的包里除了 最初建立连接的 SYN 包之外,其他的包也都有 ACK 标志。 -- **RST**(ReSet):当 RST=1 时,表示 TCP 连接中出现异常(如主机崩溃或其他原因)必须强制断开连接,然后再重新建立连接进行传输。RST置为1还用来拒绝一个非法的报文段或拒绝打开一个连接。 -- **FIN**(Finish):当FIN=1 时,表示今后不会再有数据发送,希望断开连接。 -- **PSH**(Push) 当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下,TCP就可以使用推送(push)操作。这时,发送方TCP把PSH置为1,并立即创建一个报文段发送出去。接收方TCP收到PSH=1的报文段,就尽快地(即“推送”向前)交付接收应用进程。而不用再等到整个缓存都填满了后再向上交付。 -- **URG**(Urgent):当URG=1时,表明开户了urgent mode,紧急指针就开始生效了。 - -**选项**:长度可变,最长可达40个字节。当没有使用“选项”时,TCP的首部长度是20字节。 - -- MSS 选项:TCP报文段中的数据字段的最大长度,后面有详解。 -- 窗口扩大选项:占用三个字节,使得接收端可接收更多的数据,由 2^16-1 扩充到 2^(16+14)-1,其中这个14是窗口移位记数器的最大值。详情请参见:TCP/IP详解 卷1 协议 P262 -- 时间戳选项:共占 10 个字节,其中最主要的字段是时间戳字段(4字节)和时间戳回送回答字段(4字节)。 - - - -## 3. 如何模拟 TCP 连接? - -只搞懂报文格式,没有实战的话,就永远只停留在字面上,无法深刻地理解它。 - -所以接下来我会使用 wireshark 进行对三次握手、数据传输、四次挥手进行一次抓包并分析这个过程。 - -但是在开始之前 ,首先要学会模拟建立一个 tcp 连接,好能让我们轻松使用过滤器来显示结果。 - -为此我使用 Python 写了两个小脚本 - -**1、服务端** - -监听 13200 端口,如果有客户端连接就发送 hello 字符串 - -```python -# tcp_server.py - -import socket # 导入 socket 模块 -import time - -s = socket.socket() # 创建 socket 对象 -host = socket.gethostname() # 获取本地主机名 -port = 13200 # 设置端口 -s.bind((host, port)) # 绑定端口 - -s.listen(5) # 等待客户端连接 -while True: - c, addr = s.accept() # 建立客户端连接 - c.send('hello'.encode("utf-8")) - c.send('world'.encode("utf-8")) - time.sleep(1) - c.close() # 关闭连接 +```shell +$ pip install tenacity ``` -运行后,可以使用 lsof 命令查看 13200 端口是否处于监听中 -![](http://image.iswbm.com/image-20200601221524846.png) -**2、客户端** +### **最基本的重试** -连接 13200 端口,并接收并打印服务端发送的内容 +无条件重试,重试之间无间隔 ```python -# tcp_client.py +from tenacity import retry -import socket # 导入 socket 模块 -import time +@retry +def test_retry(): + print("等待重试,重试无间隔执行...") + raise Exception -s = socket.socket() # 创建 socket 对象 -host = socket.gethostname() # 获取本地主机名 -port = 13200 # 设置端口号 - -s.connect((host, port)) -print(s.recv(1024)) -time.sleep(2) -s.close() +test_retry() ``` +无条件重试,但是在重试之前要等待 2 秒 +```python +from tenacity import retry, wait_fixed -## 4. Wireshark 抓包实战分析 - -一切准备就绪后,打开我们的 wireshark ,并设置捕获过滤器 port=13200 - -![](http://image.iswbm.com/image-20200601222110435.png) - -然后开启抓包,最后执行上面的 客户端代码` tcp_client.py`,就可以在 wireshark 上看到如下内容。 - -![](http://image.iswbm.com/image-20200602234904143.png) - - - -### 三次握手 - -三次握手的过程可以参考下面这张图来帮助理解 - -![](http://image.iswbm.com/20200605130951.png) - -使用 wireshark 抓到的三次握手的包如下所示 - -![wireshare 三次握手](http://image.iswbm.com/image-20200603003018160.png) - -客户端要连接上服务端,首先要发送一个 SYN 包表示请求连接。这个SYN 包的 seq 为0。这是第一次握手。 - -当服务端接收这个 SYN 包时,知道了有人要连接自己,就发了一个 ACK 包说: 你要连接这件事,我已经知道啦。但是连接是双方的事情,我也要连接客户端呀,因此 服务端实际上也会发送一个 SYN 包给客户端,请求连接。此时 ACK 和 SYN 如果分开发,服务端觉得太麻烦了,于是就把这两个包合并在一起发,所以实际上只发一个 SYN+ACK 的包。这一点说重要也不重要,说不重要也重要,因为面试的时候经常会问到,**为什么不是四次握手呢?**答案就在这里,**因为一个包可以解决的事情没必要发两个包**。**这是第二次握手。** - -当客户端接收到服务端发送的 SYN+ACK 包时,知道服务端同意了自己的请求,并且也要求连接自己,有来就有往,客户端连忙回了个 ACK 包表示同意。**这就是第三次握手。** - - - -### 数据传输 - -在上面的 Python 代码中,服务端会向客户端发送了两次数据: `hello` 和 `world` - -那么这个数据是在哪里发送的呢? - -仔细看 wireshark 抓到的包,有两个 PSH 的包,意思就是有数据传输的意思。 - -打开这两个包分析一下 - -首先是第一个包 - -![](http://image.iswbm.com/image-20200602235431620.png) - -然后是第二个包 - -这里需要你理解的有两点 - -**1、为什么这里的 seq 为6呢?** - -因为第一次的 seq 为1,len=5,一共发了5个字节,所以第二次发送,要从6开始计数啦。 - -**2、为什么第一次 ack 为1,而第二次ack还是1呢?** - -因为客户端没有向服务端发送数据,所以 ack 将始终为1,直到客户端要向服务端发送数据。 - -![](http://image.iswbm.com/image-20200602235723214.png) - -### 四次挥手 - -四次挥手的过程可以参考下面这张图来帮助理解 - -![](http://image.iswbm.com/20200605192855.png) - -使用 wireshark 抓到的四次挥手的包如下所示 - -![wireshark 四次挥手](http://image.iswbm.com/image-20200603001339731.png) - -在服务端发送完两次数据后,调用一次了 close 方法,发送了一个 FIN 包请求关闭连接,**这是第一次挥手**,这个 FIN 包里的 seq 为11,是两次发送的数据长度+1,很容易理解,ack 始终为 1,上面讲过了也好理解。 - -当客户端收到了服务端发来的 FIN 包后,知道了服务端要关闭连接了,于是就回了一个 ACK 的应答包(**这是第二次挥手**),告诉服务端:恩,我知道了。但由于客户端这边还有一些事情要做(可能是还有数据要发送之类的,在 Python 代码里我通过 time.sleep 来模拟),所以要晚点才能关闭连接。这里的 ACK 包,seq 号 是取第一次挥手的 ack 号,而 ack 号是取 第一次挥手的 seq +1. - -等客户端事情也做完了(time.sleep 结束),也会主动发送一个 FIN 包(代码里是通过调用 close 方法实现)告诉服务端:我这边也结束了,可以关闭连接啦。这是第三次挥手。这个 FIN 包里的 seq 号还是取第一次挥手的 ack 号,而 ack 号也是取 第一次挥手的 seq +1,这和第二次挥手时是一样的。 - -既然是一样的,那为什么不一起发送呢? - -这个问题很好。当服务端数据都发送完了要关闭连接,而客户端自己也没什么事情 要做了也要关闭连接,确实是可以一起发送。这时候就四次挥手就变成了三次挥手,所以挥手并不总是四次的。 - -上面解析了三次挥手,还差最后一次。 - -最后一次挥手,就是服务端接收到客户端的 FIN 包后,知道了客户端要关闭连接了,就回了一个 ACK 应答包。此时的 seq 为第三次挥手的 ack,而 ack 为 第三次挥手的 seq +1。 - -至此,四次挥手全部完成。 - -## 5. 拷问灵魂的四个问题 - -### 问题1:为什么要三次握手? - -在建立连接前要经历三次握手,几乎是人尽皆知的事情。 - -但是为什么需要三次握手,这是一个值得思考的问题。 - -在大多数的文章里面,讲到三次握手都会用形象的比喻来跟你解释,比如和女朋友打电话的场景。 +@retry(wait=wait_fixed(2)) +def test_retry(): + print("等待重试...") + raise Exception -```shell -她:“你可以听到了吗?” -我:“可以呀,你呢,你可以听到我的吗?” -她:“我也可以听到了。” # 确认对应可以听到了再对话 -我:“你吃饭了吗?“ -她:“吃啦。“ +test_retry() ``` -从这个例子里,可以提炼出一点,就是三次握手就是在确保连接的双方都能发送且接收到对方的消息。 - -这个例子是好的,但是只讲这个例子又是不够的。 - -这会让读者对三次握手停留在表层,导致无法真正去学习 TCP 的精髓之处。 - -接下来,我会说说我对 TCP 的理解。 - -关于 为什么需要握手(注意:这里还没开始讨论为什么要三次握手),我认为应该有两个理由: - -1. 同步起始序列号,为后续数据传输做准备 -2. 保证双方都可能发送数据且能接收数据 - -关于第一点,其实两次握手就可以,客户端把自己的 seq 通过 SYN 包告诉服务端,而服务端把自己的 seq 通过 SYN+ACK 包告诉客户端。 - -而第二点呢,必须要三次握手才能保证,这个大家应该能够理解,不再赘述。 - - -**除此之外,在网络上,你会经常看到还有第三个理由** -他们的论据是在 RFC 793 中可以找出下面这句话 +### 设置停止基本条件 -> The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion. +只重试7 次 -翻译一下,就是三次握手的最主要原因是为了防止旧的重复连接初始化造成混乱。 - -怎么理解这句话呢?举个例子吧 - -由于网络环境是错综复杂的,当我们发送了一个SYN包 a 后,很有可能过了很久还没有到达目标机器,此时,客户端会重新发送一个 SYN 包 b重新请求连接。 - -![](http://image.iswbm.com/20200605200027.png) - -b 包比 a 包先到达了目标机器(即使a包是先发的),当目标机器收到了 b 包,就会回复给源机器一个回包,当后面 a 包也到达了目标机器后,对于目标机器来说,虽然a 和 b 是来源于同一机器 同一端口,但是它才不管是不是重复连接,因为对于目标机器来说,只要来请求连接我都欢迎,收一个我回一个,至于哪个才是最新的连接,哪个是重复的?它不管,它把这个职责交还给了客户端,毕竟哪个包才是最新的,它最清楚了。 - -那问题就来了,源机器是如何决定 a 包过期的呢? - -源机器 收到了来自目标机器 对 a 包的 ACK 回应后,通过自身的上下文信息,知道了这是一个历史连接(序列号过期或超时),那么客户端就会发送 `RST` 报文给服务端,表示中止这一次连接。 - -由此,我们可以看到,三次握手可以解决这个重复连接的问题。 - -这里请注意,我说的是 **可以解决**,而不是说 **因此我们需要三次握手**。 - -没有第三次握手会有多个重复连接导致浪费资源,是建立在三次请求才会建立连接的基础上才会出现的问题,这不是设计三次请求的原因。只是三次握手刚好也解决了这个问题,这个逻辑要搞清楚。 - -### 问题2:为什么不是握手两次? - -这个问题可以转换成『只握手两次就建立连接会出现什么样的问题?』 +```python +from tenacity import retry, stop_after_attempt -还是用给女朋友打电话这个例子,男朋友如果没有跟女朋友确认对方是否可以听到自己的话,就自己一直在说说说,最后只能尴尬收场。这就是我们所说的不可靠的连接,只是单向,而不是双向。 +@retry(stop=stop_after_attempt(7)) +def test_retry(): + print("等待重试...") + raise Exception -```shell -她:“你可以听到了吗?” -我:“可以呀” # 没有向对方确认是否可以听到自己就开始一直说说说 -我:“你吃饭了吗?“ -我:“人呢?“ -我:“喂?“ -我:“去哪啦?“ +test_retry() ``` -在实际应用上,其实只握手两次还会出现更严重的问题,那就是资源浪费。 - -还是上面那个例子,a 包由于网络拥堵,迟迟没有发到目标机器 ,由于超时源机器会重新发送一个 SYN 包 b,如果只进行了两次握手,目标机器就建立了连接,那么当 b 包到达后,目标机器又会创建一个连接,而这个连接是无用的、多余的。 +重试 10 秒后不再重试 -![](http://image.iswbm.com/20200605201138.png) - -这里仅仅假设只超时重发一次就成功了,如果超时重发了 10 次,甚至更多呢?本来TCP 传输只需要一个连接就行了,现在服务端却创建了 n 个 连接,对于服务器资源来说无疑是非常浪费的。 - -### 问题3:为什么不是握手四次? - -看到这里,你应该很清楚 三次握手的流程了。 - -那么握手四次是什么样的呢? +```python +from tenacity import retry, stop_after_delay -还是以给女朋友打电话的例子来说明 +@retry(stop=stop_after_delay(10)) +def test_retry(): + print("等待重试...") + raise Exception -```shell -她:“你可以听到了吗?” -我:“可以呀!” -我:“你呢,你可以听到我的吗?” -她:“我也可以听到了。” +test_retry() ``` -和三次握手相对比,其实就是把原来第二次握手的内容拆分成两次发送。 - -![](http://image.iswbm.com/20200605202450.png) +或者上面两个条件满足一个就结束重试 -所以为什么不握手四次? - -因为三次握手就可以完成的事,为什么要四次握手呢?没必要。 - -### 问题4:为什么不握手五次或更多? +```python +from tenacity import retry, stop_after_delay, stop_after_attempt -这个问题有点迷,你可能还不太清楚,还是以跟女朋友打电话为例 +@retry(stop=(stop_after_delay(10) | stop_after_attempt(7))) +def test_retry(): + print("等待重试...") + raise Exception -```shell -她:“你可以听到了吗?” -我:“可以呀,你呢,你可以听到我的吗?” -她:“恩,我也可以听到了。你呢,现在还可以听到吗?” -我:“可以呀,现在你那边还听到我的吗?” -她:“是的,可以,你呢,可以听到我现在说的吗” -我:“可以听到,那你呢?” -... -... +test_retry() ``` -在每一次跟确认可以听到对方的声音时,还生怕这个消息对方收不到这个消息,所以两个人就一直在确认,跟个zz一样。 - -所以你问我,为什么不握手五次或更多? - -因为三次是基本保障,再多一个,就是多余,容易死循环。 - - - -## 6. MTU 和 MSS 是什么? - -### MTU - -Maximum Transmission Unit,最大传输单元。 - -在TCP/IP协议族中,指的是**IP数据报**能经过一个**物理网络**的**最大报文长度**,其中包括了IP首部(从20个字节到60个字节不等)。 - -由此我们知道,MTU 为多大跟链路层的介质有关,我们接触最多的以太网的 MTU 设为1500字节。 - -其他的你可以参考 下面这张图(摘自维基百科) - -![](http://image.iswbm.com/image-20200604204657243.png) - -如果上层协议(如 TCP)交给IP协议的内容实在是太多,使得 IP 报文的大小超过了 MTU ,以以太网为例,如果 IP 报文大小超过了1500 Bytes ,那么**IP报文就必须要分片传输**,到达目的主机或目的路由器之后由其进行重组分片。 - -IP分片发生在IP层,不仅源端主机会进行分片,中间的路由器也有可能分片,因为不同的网络的MTU是不一样的,如果传输路径上的某个网络的MTU比源端网络的MTU要小,路由器就可能对IP数据报再次进行分片。而分片数据的重组只会发生在目的端的IP层。 - -### MSS - -Maximum Segment Size ,它表示的是 TCP 报文段中的数据字段的最大长度。 - -数据字段加上TCP首部才等于整个的TCP报文段。所以MSS并不是整个TCP报文段的最大长度,而是“TCP报文段长度减去TCP首部长度”。 - -MSS 和 MTU 的关系是: - -MSS = MTU - IP首部大小 - TCP首部大小 - -![](http://image.iswbm.com/tcp_pdus.png) - - - -**那为什么要规定一个最大报文长度MSS呢?** - -这并不是考虑接受方的接收缓存可能存放不下TCP报文段中的数据。实际上,MSS与接收窗口值没有关系。我们知道,TCP报文段的数据部分,至少要加上40字节的首部(TCP首部20字节和IP首部20字节,这里还没有考虑首部中的可选部分)才能组装成一个IP数据报。 - -若选择较小的MSS长度,网络的利用率就降低。设想在极端情况下,当TCP报文段只含有1字节的数据时,在IP层传输的数据报的开销至少有40字节(包括TCP报文段的首部和IP数据报的首部)。这样,对网络的利用率就不会超过1/41。到了数据链路层还要加上一些开销。 - -但反过来,若TCP报文段非常长,那么在IP层传输时就有可能要分解成多个短数据报片。在终点要把收到的各个短数据报片组成成原来的TCP报文段,当传输出错时还要进行重传。 - -IP层是没有超时重传机制的,如果IP层对一个数据包进行了分片,只要有一个分片丢失了,只能依赖于传输层进行重传,结果是所有的分片都要重传一遍,这个代价有点大。 - -因此,MSS应尽可能大些,只要在IP层传输时不需要分片就行。由于IP数据报所经历的路径是动态变化的,因此在这条路径上确定的不需要的分片的MSS,如果改走另一条路径就可能需要进行分片。**因此最佳的MSS是很难确定的**。 - -在连接过程中,双方都把自己能够支持的MSS写入这一字段,以后就按照这个数值传输数据,两个传送方向可以有不同的MSS值。若主机未填写这一项,则MSS的默认值是536字节长。因此,所有在互联网上的主机都应该接受的报文段长度是536+20(固定首部长度)=556字节。 - - +### 设置何时进行重试 -## 7. 网络编程的常规步骤 - -上面为了方便抓包,我使用了 Python 写了一个服务器和客户端程序进行通信。 - -这里有必要说一下,面向 TCP 进行网络编程的常规步骤 - -![](http://image.iswbm.com/20200605204727.png) - -如果是服务端: - -1. 用函数socket() 创建一个socket; - -2. 用函数setsockopt() 设置socket属性; **可选步骤** - -3. 用函数bind() 绑定IP地址、端口等信息到socket上; - -4. 用函数listen() 开启监听; - -5. 用函数accept() 接收客户端上来的连接; - -6. 用函数send()和recv() 或者 read()和write() 收发数据; - -7. 关闭网络连接; - -8. 关闭监听; - -而如果是客户端: - -1. 用函数socket() 创建一个socket; - -2. 用函数setsockopt() 设置socket属性 ;**可选步骤** - -3. 用函数bind() 绑定IP地址、端口等信息到socket上; **可选步骤** -4. 用函数connect() 对方的IP地址和端口连接服务器 ; -5. 用函数send()和recv() 或者 read()和write() 收发数据; -6. 关闭网络连接; - -其中最主要、最关键的有三个函数: - -### connect() - -它是一个阻塞函数,通过 TCP 三次握手与服务器建立连接。 - -一般的情况下 客户端的connect函数 默认是阻塞行为 直到三次握手阶段成功为止。 - -### listen() - -不是一个阻塞函数: 它会将套接字 和 套接字对应队列的长度告诉Linux内核 - - 他是被动连接的 一直监听来自不同客户端的请求 listen函数只要 作用将socketfd 变成被动的连接监听socket 其中参数backlog作用 设置内核中队列的长度 。 - -### accpet() - -是一个阻塞函数,它会从处于 established 状态的队列中取出完成的连接。 - -当队列中没有完成连接时候就会阻塞,直到取出队列中已完成连接的用户连接为止。 - -那如果服务器没有及时调用 accept 函数取走完成连接的队列怎么办呢? - -服务器的连接队列满掉后,服务器不会对再对建立新连接的 SYN 进行应答,所以客户端的 connect 就会返回 ETIMEDOUT。 - -## 8. 注意事项 - -### ack 和 ACK 有区别吗? - -上面的分析三次握手和四次挥手时,有一个细节问题,可能不是那么重要,但是需要你搞清楚。 - -就是 ack 和 ACK 是否一致?答案是否定的 - -如果是 大写的 ACK ,表示的是标志位里的 flag,除了最初建立连接时的 SYN 包之外,后续的所有包此位都会被置为 1。 - -如果是 小写的 ack,表示的是希望确认号,表示的是希望接收到对方下一次数据的序列号, ack 一般都是上次接收成功的数据字节序号加1。 - -### TCP 包最多可传输多少数据? - -对于TCP协议来说,整个包的最大长度是由最大传输大小(MSS,Maxitum Segment Size)决定,MSS就是TCP数据包每次能够传输的最大数据分段。 - -为了达到最佳的传输效能 TCP协议在建立连接的时候通常要协商双方的MSS值。 - -通讯双方会根据双方提供的 MSS值的较小值来确定为这次连接的 MSS值。 - -在以太网中,MTU 为 1500 Bytes,减去IP数据包包头的大小20Bytes 和 TCP数据段的包头20Bytes,TCP 层最大的 MSS 为 1460。 - - - -## 9. 异常情况分析 - -### 试图与一个不存在的端口建立连接(主机正常) - -这里的不存在的端口是指在服务器端没有程序监听在该端口。我们的客户端就调用connect,试图与其建立连接。这时会发生什么呢? - -这种情况下我们在客户端通常会收到如下异常内容: +在出现特定错误/异常(比如请求超时)的情况下,再进行重试 ```python -Traceback (most recent call last): - File "/Users/MING/Code/Python/tcp_client.py", line 8, in - s.connect((host, port)) -ConnectionRefusedError: [Errno 61] Connection refused -``` +from requests import exceptions +from tenacity import retry, retry_if_exception_type -试想一下,服务端本来就没有程序监听在这个接口,因此在服务端是无法完成连接的建立过程的。我们参考三次握手的流程可以知道当客户端的SYNC包到达服务端时,TCP协议没有找到监听的套接字,就会向客户端发送一个错误的报文,告诉客户端产生了错误。而该错误报文就是一个包含RST的报文。这种异常情况也很容易模拟,我们只需要写一个小程序,连接服务器上没有监听的端口即可。如下是通过wireshark捕获的数据包,可以看到红色部分的RST报文。 +@retry(retry=retry_if_exception_type(exceptions.Timeout)) +def test_retry(): + print("等待重试...") + raise exceptions.Timeout -![](http://image.iswbm.com/image-20200604223625787.png) +test_retry() +``` +在满足自定义条件时,再进行重试。 +如下示例,当 `test_retry` 函数返回值为 False 时,再进行重试 -### 试图与一个某端口建立连接但该主机已经宕机(主机宕机) +```python +from tenacity import retry, stop_after_attempt, retry_if_result -这也是一种比较常见的情况,当某台服务器主机宕机了,而客户端并不知道,因此会重复发送SYNC数据包. +def is_false(value): + return value is False -如下图所示,可以看到客户端每隔一段时间就会向服务端发送一个SYNC数据包。这里面具体的时间是跟TCP协议相关的,具体时间不同的操作系统实现可能稍有不同。 +@retry(stop=stop_after_attempt(3), + retry=retry_if_result(is_false)) +def test_retry(): + return False -![](http://image.iswbm.com/image-20200604224127512.png) +test_retry() +``` -### 建立连接时,服务器应用被阻塞(或者僵死) +### 重试后错误重新抛出 -还有一种异常情况是,客户端建立连接的过程中服务端应用处于僵死状态,这种情况在实际中也会经常出现(我们假设仅仅应用程序僵死,而内核没有僵死)。 +当出现异常后,tenacity 会进行重试,若重试后还是失败,默认情况下,往上抛出的异常会变成 RetryError,而不是最根本的原因。 -对于TCP的服务端来说,当它收到SYN数据包时,就会创建一个套接字的数据结构并给客户端回复ACK,再次收到客户端的ACK时会将套接字数据结构的状态转换为ESTABLISHED,并将其加入就绪队列。 +因此可以加一个参数(`reraise=True`),使得当重试失败后,往外抛出的异常还是原来的那个。 -当上面的套接字处于就绪队列时,accept函数才被唤醒了,可以从套接字中读取数据。 +```python +from tenacity import retry, stop_after_attempt -在 accept 返回之前,客户端也是可以发送数据的,因为数据的发送与接收都是在内核态进行的。客户端发送数据后,服务端的网卡会先接收,然后通过中断通知IP层,再上传到TCP层。TCP层根据目的端口和地址将数据存入关联的缓冲区。 +@retry(stop=stop_after_attempt(7), reraise=True) +def test_retry(): + print("等待重试...") + raise Exception -到此,可以得出几点结论。 +test_retry() +``` -1. 在 accept 返回之前,三次握手已经完成。 -2. TCP的客户端是否可以发送数据与服务端程序是否工作没有关系。 -但是如果内核也处于僵死状态,那情况可就完全不一样了。 -此时由于机器完全卡死,TCP服务端无法接受任何消息,自然也无法给客户端发送任何应答报文,也不会有后续发送数据的环节了。 +### 设置回调函数 +当最后一次重试失败后,可以执行一个回调函数 +```python +from tenacity import * -## 10. 参考文章 +def return_last_value(retry_state): + print("执行回调函数") + return retry_state.outcome.result() # 表示返回原函数的返回值 -[TCP报文段的首部格式](https://blog.csdn.net/qq_32998153/article/details/79680704) +def is_false(value): + return value is False -[TCP 、UDP、IP包的最大长度](https://www.cnblogs.com/jiangzhaowei/p/9273854.html) +@retry(stop=stop_after_attempt(3), + retry_error_callback=return_last_value, + retry=retry_if_result(is_false)) +def test_retry(): + print("等待重试中...") + return False -[理解了这些异常现象才敢说真正懂了TCP协议](https://network.51cto.com/art/201905/596543.htm) +print(test_retry()) +``` -[近 40 张图解被问千百遍的 TCP 三次握手和四次挥手面试题](https://mp.weixin.qq.com/s/tH8RFmjrveOmgLvk9hmrkw) +输出如下 +```shell +等待重试中... +等待重试中... +等待重试中... +执行回调函数 +False +``` ---- ![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c10/c10_03.rst b/source/c10/c10_03.rst index e63f91b..64c04b8 100644 --- a/source/c10/c10_03.rst +++ b/source/c10/c10_03.rst @@ -1,698 +1,190 @@ -10.3 网络知识扫盲:详解TCP的三次握手与四次挥手 -============================================== +10.3 少有人知的 Python “重试机制”:tenacity +=========================================== |image0| -1. TCP 协议是什么? -------------------- +为了避免由于一些网络或等其他不可控因素,而引起的功能性问题。比如在发送请求时,会因为网络不稳定,往往会有请求超时的问题。 -TCP 是 Transmission Control Protocol -的缩写,意思是传输控制协议。一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC -793 定义。 +这种情况下,我们通常会在代码中加入重试的代码。重试的代码本身不难实现,但如何写得优雅、易用,是我们要考虑的问题。 -从这个定义里,有很多初学就首先懵了。 +这里要给大家介绍的是一个第三方库 - ``Tenacity`` +,它实现了几乎我们可以使用到的所有重试场景,比如: -什么是面向连接? +1. 在什么情况下才进行重试? +2. 重试几次呢? +3. 重试多久后结束? +4. 每次重试的间隔多长呢? +5. 重试失败后的回调? -什么是可靠的通信协议? +在使用它之前 ,先要安装它 -什么是面向字节流的? +.. code:: shell -为了让你对 TCP 有个初步的了解,我打算先从这个定义入手。 + $ pip install tenacity -什么是面向连接? +**最基本的重试** ~~~~~~~~~~~~~~~~ -面向连接,是相对于另一个传输层协议 UDP 而言的(后面会单独介绍)。 - -TCP 是面向连接的,所以在开始传输数据前要先经历三次握手建立连接。 - -而 UDP 即刻就可以传输数据,并不需要先三次握手来建立连接。 - -一个更可靠,而一个更开放。 - -就好比,你去医院看病,如果是专家号,一般要提前预约,对只要预约(三次握手建立了连接)上了,你去了就不会看不上病。这是 -TCP 。 - -而如果你没有预约,就直接跑过去,那不好意思,你只能看普通门诊,而普通门诊等的人很多,你就不一定能看得上病了。这是 -UDP。 - -既然是连接,必然是一对一的,就像绳子的两端。所以 TCP 是一对一发送消息。 - -而 UDP 协议不需要连接,可以一对一,也可以一对多,也可以多对多发送消息。 - -什么是可靠的通信协议? -~~~~~~~~~~~~~~~~~~~~~~ - -可不可靠,也是相对于 UDP 而言的。 - -TCP -自身有三次握手和超时重传等机制,所以无论网络如何变化,主要不是主机宕机等原因都可以保证一个报文可以到达目标主机。 - -与之对比, UDP -就比较不负责任了,不管你收不收得到,反正我就无脑发,网络拥堵我也发,它的职责是发出去。 - -什么是面向字节流的? -~~~~~~~~~~~~~~~~~~~~ - -与面向字节流相对的是,UDP 的面向报文。 - -面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率。若太短,会使IP太小。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这也就是说,应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。 - -面向字节流的话,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。 - -2. 完整解读 TCP 报文格式 ------------------------- - -搞懂一个通信协议,了解它的报文格式是必经之路。 - -TCP 的报文段结构,可以从下面这张图中非常清晰的看到。 - -.. figure:: http://image.iswbm.com/20200606095627.png - :alt: TCP 报文首部 - - TCP 报文首部 - -接下来,我会一个一个讲解这些字段的内容。 - -**源端口** 和 **目标端口**\ :各占 2 个字节。2 个字节,也就是 16个 -bit,这应该也能说明为什么计算机端口的范围是 1-65535 (0 -不使用,2^16=65536,最大位65536不使用)了吧?有了源端口和目标端口,加上 -IP 首部里的源IP和目标IP,就可以唯一确定一个连接。 - -**序列号**\ :共占用 4个字节。说明序列号的范围是 [0, 2^32-1],也就是 [0, -4294967296]。当序号增加到 4294967296 -后,下一个序号将回到0重新开始。在建立连接时由计算机生成的随机数作为其初始值(ISN,即Initial -Sequence Number,初始序列号),通过 SYN -包传给接收端主机,每发送一次数据,就\ **累加**\ 一次该「数据字节数」的大小(其中要注意的是 -SYN 和 FIN 包的 seq 也要消耗一个序号)。\ **用来解决网络包乱序问题。** - -**确认号**\ :共占用 4个字节。说明确认号的范围是 [0, 2^32-1],也就是 [0, -4294967296]。它表示\ **期望**\ 收到对方下一次数据的序列号(所以 ack -一般都是上次接收成功的数据字节序号加1),发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。\ **用来解决不丢包的问题**\ 。TCP在接收到数据后 -200ms 才会发送ACK包,这种设定是为了等待是否有数据可以一起发送的。 - -**数据偏移(图中为报文首部)**\ :共占 4 -个bit,它表示的是TCP报文的数据起始处距离TCP报文起始处的距离有多远。实际生活中我们说距离多远,我们的单位通常是米,而这里距离有多远,单位是 -4 个字节(也就是 32bit)。由于 4 个bit,能表示的最大整数是 15,也就说明 -TCP 报文里数据开始的位置距离报文起点是 60 个字节(4*15)。这意味着 TCP -的首部(除数据外的都叫首部)长度是 20-60 个字节。 - -**窗口**\ :共占 16 个bit,因此最大的窗口大小为 2^16-1 = 65535 = -64k。这是早期的设计,对于现在的网络应用,可能会不太够,因此可以在选项里加一个 -**窗口扩大选项**\ ,来传输更多的数据。窗口指的是发送本报文段的一方的接受窗口(而不是自己的发送窗口)。窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。总之,窗口值作为接收方让发送方设置其发送窗口的依据。 - -**保留**\ :占 6个bit,保留为今后使用,目前应置为0。 - -**紧急指针**\ :占16个bit。紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据) -。因此,在紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时,TCP就告诉应用程序恢复到正常操作。值得注意的是,\ **即使窗口为0时也可以发送紧急数据**\ 。 - -**标志位:** - -- **SYN**\ (SYNchronization): - 在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使SYN=1和ACK=1,因此SYN置为1就表示这是一个连接请求或连接接受报文。 -- **ACK**\ (ACKnowledgment):仅当ACK = 1时确认号字段才有效,当ACK = - 0时确认号无效。TCP规定,在连接建立后所有的传送的报文段都必须把ACK置为1,如果你可以看下我后面 - wireshark 抓的包里除了 最初建立连接的 SYN 包之外,其他的包也都有 ACK - 标志。 -- **RST**\ (ReSet):当 RST=1 时,表示 TCP - 连接中出现异常(如主机崩溃或其他原因)必须强制断开连接,然后再重新建立连接进行传输。RST置为1还用来拒绝一个非法的报文段或拒绝打开一个连接。 -- **FIN**\ (Finish):当FIN=1 - 时,表示今后不会再有数据发送,希望断开连接。 -- **PSH**\ (Push) - 当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下,TCP就可以使用推送(push)操作。这时,发送方TCP把PSH置为1,并立即创建一个报文段发送出去。接收方TCP收到PSH=1的报文段,就尽快地(即“推送”向前)交付接收应用进程。而不用再等到整个缓存都填满了后再向上交付。 -- **URG**\ (Urgent):当URG=1时,表明开户了urgent - mode,紧急指针就开始生效了。 - -**选项**\ :长度可变,最长可达40个字节。当没有使用“选项”时,TCP的首部长度是20字节。 - -- MSS 选项:TCP报文段中的数据字段的最大长度,后面有详解。 -- 窗口扩大选项:占用三个字节,使得接收端可接收更多的数据,由 2^16-1 - 扩充到 - 2^(16+14)-1,其中这个14是窗口移位记数器的最大值。详情请参见:TCP/IP详解 - 卷1 协议 P262 -- 时间戳选项:共占 10 - 个字节,其中最主要的字段是时间戳字段(4字节)和时间戳回送回答字段(4字节)。 - -3. 如何模拟 TCP 连接? ----------------------- - -只搞懂报文格式,没有实战的话,就永远只停留在字面上,无法深刻地理解它。 - -所以接下来我会使用 wireshark -进行对三次握手、数据传输、四次挥手进行一次抓包并分析这个过程。 - -但是在开始之前 ,首先要学会模拟建立一个 tcp -连接,好能让我们轻松使用过滤器来显示结果。 - -为此我使用 Python 写了两个小脚本 - -**1、服务端** - -监听 13200 端口,如果有客户端连接就发送 hello 字符串 +无条件重试,重试之间无间隔 .. code:: python - # tcp_server.py - - import socket # 导入 socket 模块 - import time - - s = socket.socket() # 创建 socket 对象 - host = socket.gethostname() # 获取本地主机名 - port = 13200 # 设置端口 - s.bind((host, port)) # 绑定端口 - - s.listen(5) # 等待客户端连接 - while True: - c, addr = s.accept() # 建立客户端连接 - c.send('hello'.encode("utf-8")) - c.send('world'.encode("utf-8")) - time.sleep(1) - c.close() # 关闭连接 - -运行后,可以使用 lsof 命令查看 13200 端口是否处于监听中 + from tenacity import retry -|image1| + @retry + def test_retry(): + print("等待重试,重试无间隔执行...") + raise Exception -**2、客户端** + test_retry() -连接 13200 端口,并接收并打印服务端发送的内容 +无条件重试,但是在重试之前要等待 2 秒 .. code:: python - # tcp_client.py - - import socket # 导入 socket 模块 - import time - - s = socket.socket() # 创建 socket 对象 - host = socket.gethostname() # 获取本地主机名 - port = 13200 # 设置端口号 - - s.connect((host, port)) - print(s.recv(1024)) - time.sleep(2) - s.close() - -4. Wireshark 抓包实战分析 -------------------------- - -一切准备就绪后,打开我们的 wireshark ,并设置捕获过滤器 port=13200 - -|image2| - -然后开启抓包,最后执行上面的 客户端代码\ ``tcp_client.py``\ ,就可以在 -wireshark 上看到如下内容。 - -|image3| - -三次握手 -~~~~~~~~ - -三次握手的过程可以参考下面这张图来帮助理解 - -|image4| - -使用 wireshark 抓到的三次握手的包如下所示 - -.. figure:: http://image.iswbm.com/image-20200603003018160.png - :alt: wireshare 三次握手 - - wireshare 三次握手 - -客户端要连接上服务端,首先要发送一个 SYN 包表示请求连接。这个SYN 包的 -seq 为0。这是第一次握手。 - -当服务端接收这个 SYN 包时,知道了有人要连接自己,就发了一个 ACK 包说: -你要连接这件事,我已经知道啦。但是连接是双方的事情,我也要连接客户端呀,因此 -服务端实际上也会发送一个 SYN 包给客户端,请求连接。此时 ACK 和 SYN -如果分开发,服务端觉得太麻烦了,于是就把这两个包合并在一起发,所以实际上只发一个 -SYN+ACK -的包。这一点说重要也不重要,说不重要也重要,因为面试的时候经常会问到,\ **为什么不是四次握手呢?**\ 答案就在这里,\ **因为一个包可以解决的事情没必要发两个包**\ 。\ **这是第二次握手。** - -当客户端接收到服务端发送的 SYN+ACK -包时,知道服务端同意了自己的请求,并且也要求连接自己,有来就有往,客户端连忙回了个 -ACK 包表示同意。\ **这就是第三次握手。** - -数据传输 -~~~~~~~~ - -在上面的 Python 代码中,服务端会向客户端发送了两次数据: ``hello`` 和 -``world`` - -那么这个数据是在哪里发送的呢? - -仔细看 wireshark 抓到的包,有两个 PSH 的包,意思就是有数据传输的意思。 - -打开这两个包分析一下 - -首先是第一个包 - -|image5| - -然后是第二个包 - -这里需要你理解的有两点 - -**1、为什么这里的 seq 为6呢?** - -因为第一次的 seq -为1,len=5,一共发了5个字节,所以第二次发送,要从6开始计数啦。 - -**2、为什么第一次 ack 为1,而第二次ack还是1呢?** - -因为客户端没有向服务端发送数据,所以 ack -将始终为1,直到客户端要向服务端发送数据。 - -|image6| - -四次挥手 -~~~~~~~~ - -四次挥手的过程可以参考下面这张图来帮助理解 - -|image7| - -使用 wireshark 抓到的四次挥手的包如下所示 - -.. figure:: http://image.iswbm.com/image-20200603001339731.png - :alt: wireshark 四次挥手 - - wireshark 四次挥手 - -在服务端发送完两次数据后,调用一次了 close 方法,发送了一个 FIN -包请求关闭连接,\ **这是第一次挥手**\ ,这个 FIN 包里的 seq -为11,是两次发送的数据长度+1,很容易理解,ack 始终为 -1,上面讲过了也好理解。 - -当客户端收到了服务端发来的 FIN -包后,知道了服务端要关闭连接了,于是就回了一个 ACK -的应答包(\ **这是第二次挥手**\ ),告诉服务端:恩,我知道了。但由于客户端这边还有一些事情要做(可能是还有数据要发送之类的,在 -Python 代码里我通过 time.sleep 来模拟),所以要晚点才能关闭连接。这里的 -ACK 包,seq 号 是取第一次挥手的 ack 号,而 ack 号是取 第一次挥手的 seq -+1. - -等客户端事情也做完了(time.sleep 结束),也会主动发送一个 FIN -包(代码里是通过调用 close -方法实现)告诉服务端:我这边也结束了,可以关闭连接啦。这是第三次挥手。这个 -FIN 包里的 seq 号还是取第一次挥手的 ack 号,而 ack 号也是取 第一次挥手的 -seq +1,这和第二次挥手时是一样的。 - -既然是一样的,那为什么不一起发送呢? - -这个问题很好。当服务端数据都发送完了要关闭连接,而客户端自己也没什么事情 -要做了也要关闭连接,确实是可以一起发送。这时候就四次挥手就变成了三次挥手,所以挥手并不总是四次的。 - -上面解析了三次挥手,还差最后一次。 - -最后一次挥手,就是服务端接收到客户端的 FIN -包后,知道了客户端要关闭连接了,就回了一个 ACK 应答包。此时的 seq -为第三次挥手的 ack,而 ack 为 第三次挥手的 seq +1。 - -至此,四次挥手全部完成。 - -5. 拷问灵魂的四个问题 ---------------------- - -问题1:为什么要三次握手? -~~~~~~~~~~~~~~~~~~~~~~~~~ - -在建立连接前要经历三次握手,几乎是人尽皆知的事情。 - -但是为什么需要三次握手,这是一个值得思考的问题。 - -在大多数的文章里面,讲到三次握手都会用形象的比喻来跟你解释,比如和女朋友打电话的场景。 - -.. code:: shell - - 她:“你可以听到了吗?” - 我:“可以呀,你呢,你可以听到我的吗?” - 她:“我也可以听到了。” # 确认对应可以听到了再对话 - 我:“你吃饭了吗?“ - 她:“吃啦。“ - -从这个例子里,可以提炼出一点,就是三次握手就是在确保连接的双方都能发送且接收到对方的消息。 + from tenacity import retry, wait_fixed -这个例子是好的,但是只讲这个例子又是不够的。 + @retry(wait=wait_fixed(2)) + def test_retry(): + print("等待重试...") + raise Exception -这会让读者对三次握手停留在表层,导致无法真正去学习 TCP 的精髓之处。 + test_retry() -接下来,我会说说我对 TCP 的理解。 - -关于 -为什么需要握手(注意:这里还没开始讨论为什么要三次握手),我认为应该有两个理由: - -1. 同步起始序列号,为后续数据传输做准备 -2. 保证双方都可能发送数据且能接收数据 - -关于第一点,其实两次握手就可以,客户端把自己的 seq 通过 SYN -包告诉服务端,而服务端把自己的 seq 通过 SYN+ACK 包告诉客户端。 - -而第二点呢,必须要三次握手才能保证,这个大家应该能够理解,不再赘述。 - -**除此之外,在网络上,你会经常看到还有第三个理由** - -他们的论据是在 RFC 793 中可以找出下面这句话 - - The principle reason for the three-way handshake is to prevent old - duplicate connection initiations from causing confusion. - -翻译一下,就是三次握手的最主要原因是为了防止旧的重复连接初始化造成混乱。 - -怎么理解这句话呢?举个例子吧 - -由于网络环境是错综复杂的,当我们发送了一个SYN包 a -后,很有可能过了很久还没有到达目标机器,此时,客户端会重新发送一个 SYN -包 b重新请求连接。 - -|image8| - -b 包比 a 包先到达了目标机器(即使a包是先发的),当目标机器收到了 b -包,就会回复给源机器一个回包,当后面 a -包也到达了目标机器后,对于目标机器来说,虽然a 和 b 是来源于同一机器 -同一端口,但是它才不管是不是重复连接,因为对于目标机器来说,只要来请求连接我都欢迎,收一个我回一个,至于哪个才是最新的连接,哪个是重复的?它不管,它把这个职责交还给了客户端,毕竟哪个包才是最新的,它最清楚了。 - -那问题就来了,源机器是如何决定 a 包过期的呢? - -源机器 收到了来自目标机器 对 a 包的 ACK -回应后,通过自身的上下文信息,知道了这是一个历史连接(序列号过期或超时),那么客户端就会发送 -``RST`` 报文给服务端,表示中止这一次连接。 - -由此,我们可以看到,三次握手可以解决这个重复连接的问题。 - -这里请注意,我说的是 **可以解决**\ ,而不是说 -**因此我们需要三次握手**\ 。 - -没有第三次握手会有多个重复连接导致浪费资源,是建立在三次请求才会建立连接的基础上才会出现的问题,这不是设计三次请求的原因。只是三次握手刚好也解决了这个问题,这个逻辑要搞清楚。 - -问题2:为什么不是握手两次? -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -这个问题可以转换成『只握手两次就建立连接会出现什么样的问题?』 - -还是用给女朋友打电话这个例子,男朋友如果没有跟女朋友确认对方是否可以听到自己的话,就自己一直在说说说,最后只能尴尬收场。这就是我们所说的不可靠的连接,只是单向,而不是双向。 - -.. code:: shell - - 她:“你可以听到了吗?” - 我:“可以呀” # 没有向对方确认是否可以听到自己就开始一直说说说 - 我:“你吃饭了吗?“ - 我:“人呢?“ - 我:“喂?“ - 我:“去哪啦?“ - -在实际应用上,其实只握手两次还会出现更严重的问题,那就是资源浪费。 - -还是上面那个例子,a 包由于网络拥堵,迟迟没有发到目标机器 -,由于超时源机器会重新发送一个 SYN 包 -b,如果只进行了两次握手,目标机器就建立了连接,那么当 b -包到达后,目标机器又会创建一个连接,而这个连接是无用的、多余的。 - -|image9| - -这里仅仅假设只超时重发一次就成功了,如果超时重发了 10 -次,甚至更多呢?本来TCP 传输只需要一个连接就行了,现在服务端却创建了 n -个 连接,对于服务器资源来说无疑是非常浪费的。 - -问题3:为什么不是握手四次? -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -看到这里,你应该很清楚 三次握手的流程了。 - -那么握手四次是什么样的呢? - -还是以给女朋友打电话的例子来说明 - -.. code:: shell - - 她:“你可以听到了吗?” - 我:“可以呀!” - 我:“你呢,你可以听到我的吗?” - 她:“我也可以听到了。” - -和三次握手相对比,其实就是把原来第二次握手的内容拆分成两次发送。 - -|image10| - -所以为什么不握手四次? - -因为三次握手就可以完成的事,为什么要四次握手呢?没必要。 - -问题4:为什么不握手五次或更多? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -这个问题有点迷,你可能还不太清楚,还是以跟女朋友打电话为例 - -.. code:: shell - - 她:“你可以听到了吗?” - 我:“可以呀,你呢,你可以听到我的吗?” - 她:“恩,我也可以听到了。你呢,现在还可以听到吗?” - 我:“可以呀,现在你那边还听到我的吗?” - 她:“是的,可以,你呢,可以听到我现在说的吗” - 我:“可以听到,那你呢?” - ... - ... - -在每一次跟确认可以听到对方的声音时,还生怕这个消息对方收不到这个消息,所以两个人就一直在确认,跟个zz一样。 - -所以你问我,为什么不握手五次或更多? - -因为三次是基本保障,再多一个,就是多余,容易死循环。 - -6. MTU 和 MSS 是什么? ----------------------- - -MTU -~~~ - -Maximum Transmission Unit,最大传输单元。 - -在TCP/IP协议族中,指的是\ **IP数据报**\ 能经过一个\ **物理网络**\ 的\ **最大报文长度**\ ,其中包括了IP首部(从20个字节到60个字节不等)。 - -由此我们知道,MTU 为多大跟链路层的介质有关,我们接触最多的以太网的 MTU -设为1500字节。 - -其他的你可以参考 下面这张图(摘自维基百科) - -|image11| - -如果上层协议(如 TCP)交给IP协议的内容实在是太多,使得 IP -报文的大小超过了 MTU ,以以太网为例,如果 IP 报文大小超过了1500 Bytes -,那么\ **IP报文就必须要分片传输**\ ,到达目的主机或目的路由器之后由其进行重组分片。 - -IP分片发生在IP层,不仅源端主机会进行分片,中间的路由器也有可能分片,因为不同的网络的MTU是不一样的,如果传输路径上的某个网络的MTU比源端网络的MTU要小,路由器就可能对IP数据报再次进行分片。而分片数据的重组只会发生在目的端的IP层。 - -MSS -~~~ - -Maximum Segment Size ,它表示的是 TCP 报文段中的数据字段的最大长度。 - -数据字段加上TCP首部才等于整个的TCP报文段。所以MSS并不是整个TCP报文段的最大长度,而是“TCP报文段长度减去TCP首部长度”。 - -MSS 和 MTU 的关系是: - -MSS = MTU - IP首部大小 - TCP首部大小 - -|image12| - -**那为什么要规定一个最大报文长度MSS呢?** - -这并不是考虑接受方的接收缓存可能存放不下TCP报文段中的数据。实际上,MSS与接收窗口值没有关系。我们知道,TCP报文段的数据部分,至少要加上40字节的首部(TCP首部20字节和IP首部20字节,这里还没有考虑首部中的可选部分)才能组装成一个IP数据报。 - -若选择较小的MSS长度,网络的利用率就降低。设想在极端情况下,当TCP报文段只含有1字节的数据时,在IP层传输的数据报的开销至少有40字节(包括TCP报文段的首部和IP数据报的首部)。这样,对网络的利用率就不会超过1/41。到了数据链路层还要加上一些开销。 - -但反过来,若TCP报文段非常长,那么在IP层传输时就有可能要分解成多个短数据报片。在终点要把收到的各个短数据报片组成成原来的TCP报文段,当传输出错时还要进行重传。 - -IP层是没有超时重传机制的,如果IP层对一个数据包进行了分片,只要有一个分片丢失了,只能依赖于传输层进行重传,结果是所有的分片都要重传一遍,这个代价有点大。 - -因此,MSS应尽可能大些,只要在IP层传输时不需要分片就行。由于IP数据报所经历的路径是动态变化的,因此在这条路径上确定的不需要的分片的MSS,如果改走另一条路径就可能需要进行分片。\ **因此最佳的MSS是很难确定的**\ 。 - -在连接过程中,双方都把自己能够支持的MSS写入这一字段,以后就按照这个数值传输数据,两个传送方向可以有不同的MSS值。若主机未填写这一项,则MSS的默认值是536字节长。因此,所有在互联网上的主机都应该接受的报文段长度是536+20(固定首部长度)=556字节。 - -7. 网络编程的常规步骤 ---------------------- - -上面为了方便抓包,我使用了 Python 写了一个服务器和客户端程序进行通信。 - -这里有必要说一下,面向 TCP 进行网络编程的常规步骤 - -|image13| - -如果是服务端: - -1. 用函数socket() 创建一个socket; - -2. 用函数setsockopt() 设置socket属性; **可选步骤** - -3. 用函数bind() 绑定IP地址、端口等信息到socket上; - -4. 用函数listen() 开启监听; - -5. 用函数accept() 接收客户端上来的连接; - -6. 用函数send()和recv() 或者 read()和write() 收发数据; - -7. 关闭网络连接; - -8. 关闭监听; - -而如果是客户端: - -1. 用函数socket() 创建一个socket; - -2. 用函数setsockopt() 设置socket属性 ;\ **可选步骤** - -3. 用函数bind() 绑定IP地址、端口等信息到socket上; **可选步骤** -4. 用函数connect() 对方的IP地址和端口连接服务器 ; -5. 用函数send()和recv() 或者 read()和write() 收发数据; -6. 关闭网络连接; - -其中最主要、最关键的有三个函数: - -connect() -~~~~~~~~~ - -它是一个阻塞函数,通过 TCP 三次握手与服务器建立连接。 - -一般的情况下 客户端的connect函数 默认是阻塞行为 -直到三次握手阶段成功为止。 +设置停止基本条件 +~~~~~~~~~~~~~~~~ -listen() -~~~~~~~~ +只重试7 次 -不是一个阻塞函数: 它会将套接字 和 套接字对应队列的长度告诉Linux内核 +.. code:: python -他是被动连接的 一直监听来自不同客户端的请求 listen函数只要 -作用将socketfd 变成被动的连接监听socket 其中参数backlog作用 -设置内核中队列的长度 。 + from tenacity import retry, stop_after_attempt -accpet() -~~~~~~~~ + @retry(stop=stop_after_attempt(7)) + def test_retry(): + print("等待重试...") + raise Exception -是一个阻塞函数,它会从处于 established 状态的队列中取出完成的连接。 + test_retry() -当队列中没有完成连接时候就会阻塞,直到取出队列中已完成连接的用户连接为止。 +重试 10 秒后不再重试 -那如果服务器没有及时调用 accept 函数取走完成连接的队列怎么办呢? +.. code:: python -服务器的连接队列满掉后,服务器不会对再对建立新连接的 SYN -进行应答,所以客户端的 connect 就会返回 ETIMEDOUT。 + from tenacity import retry, stop_after_delay -8. 注意事项 ------------ + @retry(stop=stop_after_delay(10)) + def test_retry(): + print("等待重试...") + raise Exception -ack 和 ACK 有区别吗? -~~~~~~~~~~~~~~~~~~~~~ + test_retry() -上面的分析三次握手和四次挥手时,有一个细节问题,可能不是那么重要,但是需要你搞清楚。 +或者上面两个条件满足一个就结束重试 -就是 ack 和 ACK 是否一致?答案是否定的 +.. code:: python -如果是 大写的 ACK ,表示的是标志位里的 flag,除了最初建立连接时的 SYN -包之外,后续的所有包此位都会被置为 1。 + from tenacity import retry, stop_after_delay, stop_after_attempt -如果是 小写的 -ack,表示的是希望确认号,表示的是希望接收到对方下一次数据的序列号, ack -一般都是上次接收成功的数据字节序号加1。 + @retry(stop=(stop_after_delay(10) | stop_after_attempt(7))) + def test_retry(): + print("等待重试...") + raise Exception -TCP 包最多可传输多少数据? -~~~~~~~~~~~~~~~~~~~~~~~~~~ + test_retry() -对于TCP协议来说,整个包的最大长度是由最大传输大小(MSS,Maxitum Segment -Size)决定,MSS就是TCP数据包每次能够传输的最大数据分段。 +设置何时进行重试 +~~~~~~~~~~~~~~~~ -为了达到最佳的传输效能 TCP协议在建立连接的时候通常要协商双方的MSS值。 +在出现特定错误/异常(比如请求超时)的情况下,再进行重试 -通讯双方会根据双方提供的 MSS值的较小值来确定为这次连接的 MSS值。 +.. code:: python -在以太网中,MTU 为 1500 Bytes,减去IP数据包包头的大小20Bytes 和 -TCP数据段的包头20Bytes,TCP 层最大的 MSS 为 1460。 + from requests import exceptions + from tenacity import retry, retry_if_exception_type -9. 异常情况分析 ---------------- + @retry(retry=retry_if_exception_type(exceptions.Timeout)) + def test_retry(): + print("等待重试...") + raise exceptions.Timeout -试图与一个不存在的端口建立连接(主机正常) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + test_retry() -这里的不存在的端口是指在服务器端没有程序监听在该端口。我们的客户端就调用connect,试图与其建立连接。这时会发生什么呢? +在满足自定义条件时,再进行重试。 -这种情况下我们在客户端通常会收到如下异常内容: +如下示例,当 ``test_retry`` 函数返回值为 False 时,再进行重试 .. code:: python - Traceback (most recent call last): - File "/Users/MING/Code/Python/tcp_client.py", line 8, in - s.connect((host, port)) - ConnectionRefusedError: [Errno 61] Connection refused + from tenacity import retry, stop_after_attempt, retry_if_result -试想一下,服务端本来就没有程序监听在这个接口,因此在服务端是无法完成连接的建立过程的。我们参考三次握手的流程可以知道当客户端的SYNC包到达服务端时,TCP协议没有找到监听的套接字,就会向客户端发送一个错误的报文,告诉客户端产生了错误。而该错误报文就是一个包含RST的报文。这种异常情况也很容易模拟,我们只需要写一个小程序,连接服务器上没有监听的端口即可。如下是通过wireshark捕获的数据包,可以看到红色部分的RST报文。 + def is_false(value): + return value is False -|image14| + @retry(stop=stop_after_attempt(3), + retry=retry_if_result(is_false)) + def test_retry(): + return False -试图与一个某端口建立连接但该主机已经宕机(主机宕机) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + test_retry() -这也是一种比较常见的情况,当某台服务器主机宕机了,而客户端并不知道,因此会重复发送SYNC数据包. +重试后错误重新抛出 +~~~~~~~~~~~~~~~~~~ -如下图所示,可以看到客户端每隔一段时间就会向服务端发送一个SYNC数据包。这里面具体的时间是跟TCP协议相关的,具体时间不同的操作系统实现可能稍有不同。 +当出现异常后,tenacity +会进行重试,若重试后还是失败,默认情况下,往上抛出的异常会变成 +RetryError,而不是最根本的原因。 -|image15| +因此可以加一个参数(\ ``reraise=True``\ ),使得当重试失败后,往外抛出的异常还是原来的那个。 -建立连接时,服务器应用被阻塞(或者僵死) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. code:: python -还有一种异常情况是,客户端建立连接的过程中服务端应用处于僵死状态,这种情况在实际中也会经常出现(我们假设仅仅应用程序僵死,而内核没有僵死)。 + from tenacity import retry, stop_after_attempt -对于TCP的服务端来说,当它收到SYN数据包时,就会创建一个套接字的数据结构并给客户端回复ACK,再次收到客户端的ACK时会将套接字数据结构的状态转换为ESTABLISHED,并将其加入就绪队列。 + @retry(stop=stop_after_attempt(7), reraise=True) + def test_retry(): + print("等待重试...") + raise Exception -当上面的套接字处于就绪队列时,accept函数才被唤醒了,可以从套接字中读取数据。 + test_retry() -在 accept -返回之前,客户端也是可以发送数据的,因为数据的发送与接收都是在内核态进行的。客户端发送数据后,服务端的网卡会先接收,然后通过中断通知IP层,再上传到TCP层。TCP层根据目的端口和地址将数据存入关联的缓冲区。 +设置回调函数 +~~~~~~~~~~~~ -到此,可以得出几点结论。 +当最后一次重试失败后,可以执行一个回调函数 -1. 在 accept 返回之前,三次握手已经完成。 -2. TCP的客户端是否可以发送数据与服务端程序是否工作没有关系。 +.. code:: python -但是如果内核也处于僵死状态,那情况可就完全不一样了。 + from tenacity import * -此时由于机器完全卡死,TCP服务端无法接受任何消息,自然也无法给客户端发送任何应答报文,也不会有后续发送数据的环节了。 + def return_last_value(retry_state): + print("执行回调函数") + return retry_state.outcome.result() # 表示返回原函数的返回值 -10. 参考文章 ------------- + def is_false(value): + return value is False -`TCP报文段的首部格式 `__ + @retry(stop=stop_after_attempt(3), + retry_error_callback=return_last_value, + retry=retry_if_result(is_false)) + def test_retry(): + print("等待重试中...") + return False -`TCP -、UDP、IP包的最大长度 `__ + print(test_retry()) -`理解了这些异常现象才敢说真正懂了TCP协议 `__ +输出如下 -`近 40 张图解被问千百遍的 TCP -三次握手和四次挥手面试题 `__ +.. code:: shell --------------- + 等待重试中... + 等待重试中... + 等待重试中... + 执行回调函数 + False -|image16| +|image1| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/image-20200601221524846.png -.. |image2| image:: http://image.iswbm.com/image-20200601222110435.png -.. |image3| image:: http://image.iswbm.com/image-20200602234904143.png -.. |image4| image:: http://image.iswbm.com/20200605130951.png -.. |image5| image:: http://image.iswbm.com/image-20200602235431620.png -.. |image6| image:: http://image.iswbm.com/image-20200602235723214.png -.. |image7| image:: http://image.iswbm.com/20200605192855.png -.. |image8| image:: http://image.iswbm.com/20200605200027.png -.. |image9| image:: http://image.iswbm.com/20200605201138.png -.. |image10| image:: http://image.iswbm.com/20200605202450.png -.. |image11| image:: http://image.iswbm.com/image-20200604204657243.png -.. |image12| image:: http://image.iswbm.com/tcp_pdus.png -.. |image13| image:: http://image.iswbm.com/20200605204727.png -.. |image14| image:: http://image.iswbm.com/image-20200604223625787.png -.. |image15| image:: http://image.iswbm.com/image-20200604224127512.png -.. |image16| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c10/c10_04.md b/source/c10/c10_04.md index 4b665fb..09e772d 100644 --- a/source/c10/c10_04.md +++ b/source/c10/c10_04.md @@ -1,785 +1,92 @@ -# 10.4 全网最全(中文) tcpdump 抓包指南 +# 10.4 Linux 上最优雅的命令调用方式:sh ![](http://image.iswbm.com/20200602135014.png) -今天要给大家介绍的一个 Unix 下的一个 **网络数据采集分析工具**,也就是我们常说的抓包工具。 +在编写 Python 脚本的时候,很经常需要我们去调用系统的命令,方法有很多种,比如 os.popen,os.system,commands,还有 subprocess。 -与它功能类似的工具有 wireshark ,不同的是,wireshark 有图形化界面,而 tcpdump 则只有命令行。 +今天明哥要介绍一种更加优雅的方法,就是 `sh` 这个第三方库,它能让你像调用方法那样去调用系统中的命令。 -由于我本人更习惯使用命令行的方式进行抓包,因此今天先跳过 wireshark,直接给大家介绍这个 tcpdump 神器。 - -这篇文章,我肝了好几天,借助于Linux 的 man 帮助命令,我把 tcpdump 的用法全部研究了个遍,才形成了本文,不夸张的说,应该可以算是中文里把 tcpdump 讲得最清楚明白,并且还最全的文章了(至少我从百度、谷歌的情况来看),所以本文值得你收藏分享,就怕你错过了,就再也找不到像这样把 tcpdump 讲得直白而且特全的文章了。 - -![](http://image.iswbm.com/20200630095709.png) - -在讲解之前,有两点需要声明: - -1. 第三节到第六节里的 tcpdump 命令示例,只为了说明参数的使用,并不一定就能抓到包,如果要精准抓到你所需要的包,需要配合第五节的逻辑逻辑运算符进行组合搭配。 -2. 不同 Linux 发行版下、不同版本的 tcpdump 可能有小许差异, 本文是基于 CentOS 7.2 的 4.5.1 版本的tcpdump 进行学习的,若在你的环境中无法使用,请参考 `man tcpdump` 进行针对性学习。 - -## 1. tcpdump 核心参数图解 - -大家都知道,网络上的流量、数据包,非常的多,因此要想抓到我们所需要的数据包,就需要我们定义一个精准的过滤器,把这些目标数据包,从巨大的数据包网络中抓取出来。 - -所以学习抓包工具,其实就是学习如何定义过滤器的过程。 - -而在 tcpdump 的世界里,过滤器的实现,都是通过一个又一个的参数组合起来,一个参数不够精准,那就再加一个,直到我们能过滤掉无用的数据包,只留下我们感兴趣的数据包。 - -tcpdump 的参数非常的多,初学者在没有掌握 tcpdump 时,会对这个命令的众多参数产生很多的疑惑。 - -就比如下面这个命令,我们要通过 `host` 参数指定 host ip 进行过滤 - -```shell -$ tcpdump host 192.168.10.100 -``` - -`主程序` + `参数名`+ `参数值` 这样的组合才是我们正常认知里面命令行该有的样子。 - -可 tcpdump 却不走寻常路,我们居然还可以在 host 前再加一个限定词,来缩小过滤的范围? - -```shell -$ tcpdump src host 192.168.10.100 -``` - -从字面上理解,确实很容易理解,但是这不符合编写命令行程序的正常逻辑,导致我们会有所疑虑: - -1. 除了 src ,dst,可还有其它可以用的限定词? - -2. src,host 应该如何理解它们,叫参数名?不合适,因为 src 明显不合适。 - - -如果你在网上看到有关 tcpdump 的博客、教程,无一不是给你一个参数组合,告诉你这是实现了怎样的一个过滤器?这样的教学方式,很容易让你依赖别人的文章来使用 tcpdump,而不能将 tcpdump 这样神器消化,达到灵活应用,灵活搭配过滤器的效果。 - -上面加了 src 本身就颠覆了我们的认知,你可知道在 src 之前还可以加更多的条件,比如 tcp, udp, icmp 等词,在你之前的基础上再过滤一层。 - -```shell -$ tcpdump tcp src host 192.168.10.100 -``` - - - -这种参数的不确定性,让大多数人对 tcpdump 的学习始终无法得其精髓。 - -因此,在学习 tcpdump 之前,我觉得有必要要先让你知道:**tcpdump 的参数是如何组成的?这非常重要。** - -为此,我画了一张图,方便你直观的理解 tcpdump 的各种参数: - -![](http://image.iswbm.com/20200628111325.png) - -1. option 可选参数:将在后边一一解释。 -2. proto 类过滤器:根据协议进行过滤,可识别的关键词有: tcp, udp, icmp, ip, ip6, arp, rarp,ether,wlan, fddi, tr, decnet -3. type 类过滤器:可识别的关键词有:host, net, port, portrange,这些词后边需要再接参数。 -4. direction 类过滤器:根据数据流向进行过滤,可识别的关键字有:src, dst,同时你可以使用逻辑运算符进行组合,比如 src or dst - -proto、type、direction 这三类过滤器的内容比较简单,也最常用,因此我将其放在最前面,也就是 **第三节:常规过滤规则**一起介绍。 - -而 option 可选的参数非常多,有的甚至也不经常用到,因此我将其放到后面一点,也就是 **第四节:可选参数解析** - -当你看完前面六节,你对 tcpdump 的认识会上了一个台阶,至少能够满足你 80% 的使用需求。 - -你一定会问了,还有 20% 呢? - -其实 tcpdump 还有一些过滤关键词,它不符合以上四种过滤规则,可能需要你单独记忆。关于这部分我会在 **第六节:特殊过滤规则** 里进行介绍。 - -## 2. 理解 tcpdump 的输出 - -### 2.1 输出内容结构 - -tcpdump 输出的内容虽然多,却很规律。 - -这里以我随便抓取的一个 tcp 包为例来看一下 - -```shell -21:26:49.013621 IP 172.20.20.1.15605 > 172.20.20.2.5920: Flags [P.], seq 49:97, ack 106048, win 4723, length 48 -``` - -从上面的输出来看,可以总结出: - -1. 第一列:时分秒毫秒 21:26:49.013621 -2. 第二列:网络协议 IP -3. 第三列:发送方的ip地址+端口号,其中172.20.20.1是 ip,而15605 是端口号 -4. 第四列:箭头 >, 表示数据流向 -5. 第五列:接收方的ip地址+端口号,其中 172.20.20.2 是 ip,而5920 是端口号 -6. 第六列:冒号 -7. 第七列:数据包内容,包括Flags 标识符,seq 号,ack 号,win 窗口,数据长度 length,其中 [P.] 表示 PUSH 标志位为 1,更多标识符见下面 - - - -### 2.2 Flags 标识符 - -使用 tcpdump 抓包后,会遇到的 TCP 报文 Flags,有以下几种: - -- `[S]` : SYN(开始连接) -- `[P]` : PSH(推送数据) -- `[F]` : FIN (结束连接) -- `[R]` : RST(重置连接) -- `[.]` : 没有 Flag (意思是除上面四种类型外的其他情况,有可能是 ACK 也有可能是 URG) - -## 3. 常规过滤规则 - -### 3.1 基于IP地址过滤:host - -使用 `host` 就可以指定 host ip 进行过滤 - -```shell -$ tcpdump host 192.168.10.100 -``` - -数据包的 ip 可以再细分为源ip和目标ip两种 +使用前,当然是安装它,`sh` 支持 Python 2 也支持 Python3,这里以 Python 3 为例。 ```shell -# 根据源ip进行过滤 -$ tcpdump -i eth2 src 192.168.10.100 - -# 根据目标ip进行过滤 -$ tcpdump -i eth2 dst 192.168.10.200 -``` - -### 3.2 基于网段进行过滤:net - -若你的ip范围是一个网段,可以直接这样指定 - -```shell -$ tcpdump net 192.168.10.0/24 +$ python3 -m pip install sh ``` -网段同样可以再细分为源网段和目标网段 +这里要注意一点,虽然在 Windows 上也可以安装成功,但是并不能使用,如果你尝试在 Windows 导入 它,会友好地提示你,该库只适用于 Linux 及 OSX 系统,假如你也想要在 Windows 使用,它推荐你使用它的 兄弟库 - `pbs` (https://pypi.org/project/pbs/)。 -```shell -# 根据源网段进行过滤 -$ tcpdump src net 192.168 - -# 根据目标网段进行过滤 -$ tcpdump dst net 192.168 -``` +![](http://image.iswbm.com/20200227201644.png) -### 3.3 基于端口进行过滤:port +安装完成后,就可以直接使用它了,以下几个示例,非常简单,简单到我感觉只要 demo ,而不需要任何的中文解释就可以让你知道他是如何使用的。 -使用 `port` 就可以指定特定端口进行过滤 -```shell -$ tcpdump port 8088 -``` -端口同样可以再细分为源端口,目标端口 +### 1. 列出目录文件 -```shell -# 根据源端口进行过滤 -$ tcpdump src port 8088 +使用 `ls` -# 根据目标端口进行过滤 -$ tcpdump dst port 8088 +```python +>>> import sh +>>> sh.ls("/home", "-l", color="never") +total 4 +drwx------ 3 centos centos 4096 Mar 8 2019 centos ``` -如果你想要同时指定两个端口你可以这样写 +使用 `glob` -```shell -$ tcpdump port 80 or port 8088 +```python +>>> sh.glob("/etc/*.conf") +['/etc/mke2fs.conf', '/etc/dnsmasq.conf', '/etc/asound.conf'] ``` -但也可以简写成这样 -```shell -$ tcpdump port 80 or 8088 -``` -如果你的想抓取的不再是一两个端口,而是一个范围,一个一个指定就非常麻烦了,此时你可以这样指定一个端口段。 +### 调用程序 -```shell -$ tcpdump portrange 8000-8080 -$ tcpdump src portrange 8000-8080 -$ tcpdump dst portrange 8000-8080 +```python +>>> import sh +>>> r=sh.Command('/root/test.py') +>>> r() +hello,world ``` +上面我们的 ls ,也可以通过这种方式执行,只是不能再加参数了。 - -对于一些常见协议的默认端口,我们还可以直接使用协议名,而不用具体的端口号 - -比如 http == 80,https == 443 等 - -```shell -$ tcpdump tcp port http +```python +sh.Command("ls")() ``` -### 3.4 基于协议进行过滤:proto - -常见的网络协议有:tcp, udp, icmp, http, ip,ipv6 等 +### 管道 -若你只想查看 icmp 的包,可以直接这样写 - -```shell -$ tcpdump icmp +```python +>>> print(sh.sort(sh.du(sh.glob('*'),'-shc'),'-rn')) +712K distribute-0.6.49.tar.gz +672K setuptools-1.1.5.tar.gz +548K get-pip.py ``` -protocol 可选值:ip, ip6, arp, rarp, atalk, aarp, decnet, sca, lat, mopdl, moprc, iso, stp, ipx, or netbeui - +管道是有序的,默认由内而外,但如果需要并行呢?加个_piped=True - -### 3.5 基本IP协议的版本进行过滤 - -当你想查看 tcp 的包,你也许会这样子写 - -```shell -$ tcpdump tcp +```python +>>> for line in sh.tr(sh.tail("-f", "/home/mysql/mysql/log/alert.log", _piped=True), "[:upper:]", "[:lower:]", _iter=True): +... print line +... +innodb: doublewrite buffer not found: creating new + +innodb: doublewrite buffer created + +innodb: 127 rollback segment(s) active. + +innodb: creating foreign key constraint system tables + +innodb: foreign key constraint system tables created ``` -这样子写也没问题,就是不够精准,为什么这么说呢? - -ip 根据版本的不同,可以再细分为 IPv4 和 IPv6 两种,如果你只指定了 tcp,这两种其实都会包含在内。 - - - -那有什么办法,能够将 IPv4 和 IPv6 区分开来呢? - -很简单,如果是 IPv4 的 tcp 包 ,就这样写(友情提示:数字 6 表示的是 tcp 在ip报文中的编号。) - -```shell -$ tcpdump 'ip proto tcp' - -# or - -$ tcpdump ip proto 6 - -# or - -$ tcpdump 'ip protochain tcp' - -# or - -$ tcpdump ip protochain 6 -``` - -而如果是 IPv6 的 tcp 包 ,就这样写 - -```shell -$ tcpdump 'ip6 proto tcp' - -# or - -$ tcpdump ip6 proto 6 - -# or - -$ tcpdump 'ip6 protochain tcp' - -# or - -$ tcpdump ip6 protochain 6 -``` - - - -关于上面这几个命令示例,有两点需要注意: - -1. 跟在 proto 和 protochain 后面的如果是 tcp, udp, icmp ,那么过滤器需要用引号包含,这是因为 tcp,udp, icmp 是 tcpdump 的关键字。 -2. 跟在ip 和 ip6 关键字后面的 proto 和 protochain 是两个新面孔,看起来用法类似,它们是否等价,又有什么区别呢? - -关于第二点,网络上没有找到很具体的答案,我只能通过 `man tcpdump` 的提示, 给出自己的个人猜测,但不保证正确。 - -proto 后面跟的 `` 的关键词是固定的,只能是 ip, ip6, arp, rarp, atalk, aarp, decnet, sca, lat, mopdl, moprc, iso, stp, ipx, or netbeui 这里面的其中一个。 - -而 protochain 后面跟的 protocol 要求就没有那么严格,它可以是任意词,只要 tcpdump 的 IP 报文头部里的 protocol 字段为 `` 就能匹配上。 - - - -理论上来讲,下面两种写法效果是一样的 - -```shell -$ tcpdump 'ip && tcp' -$ tcpdump 'ip proto tcp' -``` - -同样的,这两种写法也是一样的 - -```shell -$ tcpdump 'ip6 && tcp' -$ tcpdump 'ip6 proto tcp' -``` - - - -## 4. 可选参数解析 - -### 4.1 设置不解析域名提升速度 - -- `-n`:不把ip转化成域名,直接显示 ip,避免执行 DNS lookups 的过程,速度会快很多 -- `-nn`:不把协议和端口号转化成名字,速度也会快很多。 -- `-N`:不打印出host 的域名部分.。比如,,如果设置了此选现,tcpdump 将会打印'nic' 而不是 'nic.ddn.mil'. - -### 4.2 过滤结果输出到文件 - -使用 tcpdump 工具抓到包后,往往需要再借助其他的工具进行分析,比如常见的 wireshark 。 - -而要使用wireshark ,我们得将 tcpdump 抓到的包数据生成到文件中,最后再使用 wireshark 打开它即可。 - -使用 `-w` 参数后接一个以 `.pcap` 后缀命令的文件名,就可以将 tcpdump 抓到的数据保存到文件中。 - -```shell -$ tcpdump icmp -w icmp.pcap -``` - -### 4.3 从文件中读取包数据 - -使用 `-w` 是写入数据到文件,而使用 `-r` 是从文件中读取数据。 - -读取后,我们照样可以使用上述的过滤器语法进行过滤分析。 - -```shell -$ tcpdump icmp -r all.pcap -``` - - - -### 4.4 控制详细内容的输出 - -- `-v`:产生详细的输出. 比如包的TTL,id标识,数据包长度,以及IP包的一些选项。同时它还会打开一些附加的包完整性检测,比如对IP或ICMP包头部的校验和。 -- `-vv`:产生比-v更详细的输出. 比如NFS回应包中的附加域将会被打印, SMB数据包也会被完全解码。(摘自网络,目前我还未使用过) -- `-vvv`:产生比-vv更详细的输出。比如 telent 时所使用的SB, SE 选项将会被打印, 如果telnet同时使用的是图形界面,其相应的图形选项将会以16进制的方式打印出来(摘自网络,目前我还未使用过) - -### 4.5 控制时间的显示 - -- `-t `:在每行的输出中不输出时间 -- `-tt`:在每行的输出中会输出时间戳 -- `-ttt`:输出每两行打印的时间间隔(以毫秒为单位) -- `-tttt`:在每行打印的时间戳之前添加日期的打印(此种选项,输出的时间最直观) - -### 4.6 显示数据包的头部 - -- `-x`:以16进制的形式打印每个包的头部数据(但不包括数据链路层的头部) -- `-xx`:以16进制的形式打印每个包的头部数据(包括数据链路层的头部) -- `-X`:以16进制和 ASCII码形式打印出每个包的数据(但不包括连接层的头部),这在分析一些新协议的数据包很方便。 -- `-XX`:以16进制和 ASCII码形式打印出每个包的数据(包括连接层的头部),这在分析一些新协议的数据包很方便。 - -### 4.7 过滤指定网卡的数据包 - -- `-i`:指定要过滤的网卡接口,如果要查看所有网卡,可以 `-i any` - -### 4.8 过滤特定流向的数据包 - -- `-Q`: 选择是入方向还是出方向的数据包,可选项有:in, out, inout,也可以使用 --direction=[direction] 这种写法 - -### 4.9 其他常用的一些参数 - -- `-A`:以ASCII码方式显示每一个数据包(不显示链路层头部信息). 在抓取包含网页数据的数据包时, 可方便查看数据 - -- `-l` : 基于行的输出,便于你保存查看,或者交给其它工具分析 -- `-q` : 简洁地打印输出。即打印很少的协议相关信息, 从而输出行都比较简短. -- `-c` : 捕获 count 个包 tcpdump 就退出 -- `-s` : tcpdump 默认只会截取前 `96` 字节的内容,要想截取所有的报文内容,可以使用 `-s number`, `number` 就是你要截取的报文字节数,如果是 0 的话,表示截取报文全部内容。 -- `-S` : 使用绝对序列号,而不是相对序列号 -- `-C`:file-size,tcpdump 在把原始数据包直接保存到文件中之前, 检查此文件大小是否超过file-size. 如果超过了, 将关闭此文件,另创一个文件继续用于原始数据包的记录. 新创建的文件名与-w 选项指定的文件名一致, 但文件名后多了一个数字.该数字会从1开始随着新创建文件的增多而增加. file-size的单位是百万字节(nt: 这里指1,000,000个字节,并非1,048,576个字节, 后者是以1024字节为1k, 1024k字节为1M计算所得, 即1M=1024 * 1024 = 1,048,576) -- `-F`:使用file 文件作为过滤条件表达式的输入, 此时命令行上的输入将被忽略. - -### 4.10 对输出内容进行控制的参数 - -- `-D` : 显示所有可用网络接口的列表 -- `-e` : 每行的打印输出中将包括数据包的数据链路层头部信息 -- `-E` : 揭秘IPSEC数据 -- `-L` :列出指定网络接口所支持的数据链路层的类型后退出 -- `-Z`:后接用户名,在抓包时会受到权限的限制。如果以root用户启动tcpdump,tcpdump将会有超级用户权限。 -- `-d`:打印出易读的包匹配码 -- `-dd`:以C语言的形式打印出包匹配码. -- `-ddd`:以十进制数的形式打印出包匹配码 - -## 5. 过滤规则组合 - -有编程基础的同学,对于下面三个逻辑运算符应该不陌生了吧 - -- and:所有的条件都需要满足,也可以表示为 `&&` -- or:只要有一个条件满足就可以,也可以表示为 `||` -- not:取反,也可以使用 `!` - -举个例子,我想需要抓一个来自`10.5.2.3`,发往任意主机的3389端口的包 - -```shell -$ tcpdump src 10.5.2.3 and dst port 3389 -``` - -当你在使用多个过滤器进行组合时,有可能需要用到括号,而括号在 shell 中是特殊符号,因为你需要使用引号将其包含。例子如下: - -```shell -$ tcpdump 'src 10.0.2.4 and (dst port 3389 or 22)' -``` - -而在单个过滤器里,常常会判断一条件是否成立,这时候,就要使用下面两个符号 - -- `=`:判断二者相等 -- `==`:判断二者相等 -- `!=`:判断二者不相等 - -当你使用这两个符号时,tcpdump 还提供了一些关键字的接口来方便我们进行判断,比如 - -- if:表示网卡接口名、 -- proc:表示进程名 -- pid:表示进程 id -- svc:表示 service class -- dir:表示方向,in 和 out -- eproc:表示 effective process name -- epid:表示 effective process ID - -比如我现在要过滤来自进程名为 `nc` 发出的流经 en0 网卡的数据包,或者不流经 en0 的入方向数据包,可以这样子写 - -```shell -$ tcpdump "( if=en0 and proc =nc ) || (if != en0 and dir=in)" -``` - - - -## 6. 特殊过滤规则 - -### 6.1 根据 tcpflags 进行过滤 - -通过[上一篇文章](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488180&idx=1&sn=09526224732ebfcccb52847f27298c70&chksm=e8867256dff1fb40c9f47bafd0e87a9237c5a9ebf33c8a3d0a598276b496d29cdaa3fbff8d26&token=1970357830&lang=zh_CN#rd),我们知道了 tcp 的首部有一个标志位。 - -![TCP 报文首部](http://image.iswbm.com/20200606095627.png) - -tcpdump 支持我们根据数据包的标志位进行过滤 - -``` -proto [ expr:size ] -``` - -- `proto`:可以是熟知的协议之一(如ip,arp,tcp,udp,icmp,ipv6) - -- `expr`:可以是数值,也可以是一个表达式,表示与指定的协议头开始处的字节偏移量。 -- `size`:是可选的,表示从字节偏移量开始取的字节数量。 - -接下来,我将举几个例子,让人明白它的写法,不过在那之前,有几个点需要你明白,这在后面的例子中会用到: - -**1、**tcpflags 可以理解为是一个别名常量,相当于 13,它代表着与指定的协议头开头相关的字节偏移量,也就是标志位,所以 tcp[tcpflags] 等价于 tcp[13] ,对应下图中的报文位置。 - -![](http://image.iswbm.com/20200628222034.png) - -**2、**tcp-fin, tcp-syn, tcp-rst, tcp-push, tcp-ack, tcp-urg 这些同样可以理解为别名常量,分别代表 1,2,4,8,16,32,64。这些数字是如何计算出来的呢? - -以 tcp-syn 为例,你可以参照下面这张图,计算出来的值 是就是 2 - -![](http://image.iswbm.com/20200628222010.png) - -由于数字不好记忆,所以一般使用这样的“别名常量”表示。 - -因此当下面这个表达式成立时,就代表这个包是一个 syn 包。 - -```shell -tcp[tcpflags] == tcp-syn -``` - - - -要抓取特定数据包,方法有很多种。 - -下面以最常见的 syn包为例,演示一下如何用 tcpdump 抓取到 syn 包,而其他的类型的包也是同样的道理。 - -据我总结,主要有三种写法: - -1、第一种写法:使用数字表示偏移量 - -```shell -$ tcpdump -i eth0 "tcp[13] & 2 != 0" -``` - -2、第二种写法:使用别名常量表示偏移量 - -```shell -$ tcpdump -i eth0 "tcp[tcpflags] & tcp-syn != 0" -``` - -3、第三种写法:使用混合写法 - -```shell -$ tcpdump -i eth0 "tcp[tcpflags] & 2 != 0" - -# or - -$ tcpdump -i eth0 "tcp[13] & tcp-syn != 0" -``` - - - -如果我想同时捕获多种类型的包呢,比如 syn + ack 包 - -1、第一种写法 - -```shell -$ tcpdump -i eth0 'tcp[13] == 2 or tcp[13] == 16' -``` - -2、第二种写法 - -```shell -$ tcpdump -i eth0 'tcp[tcpflags] == tcp-syn or tcp[tcpflags] == tcp-ack' -``` - -3、第三种写法 - -```shell -$ tcpdump -i eth0 "tcp[tcpflags] & (tcp-syn|tcp-ack) != 0" -``` - -4、第四种写法:注意这里是 单个等号,而不是像上面一样两个等号,18(syn+ack) = 2(syn) + 16(ack) - -```shell -$ tcpdump -i eth0 'tcp[13] = 18' - -# or - -$ tcpdump -i eth0 'tcp[tcpflags] = 18' -``` - - - -tcp 中有 类似 tcp-syn 的别名常量,其他协议也是有的,比如 icmp 协议,可以使用的别名常量有 - -```shell -icmp-echoreply, icmp-unreach, icmp-sourcequench, -icmp-redirect, icmp-echo, icmp-routeradvert, -icmp-routersolicit, icmp-timx-ceed, icmp-paramprob, -icmp-tstamp, icmp-tstampreply,icmp-ireq, -icmp-ireqreply, icmp-maskreq, icmp-maskreply -``` - - - -### 6.2 基于包大小进行过滤 - -若你想查看指定大小的数据包,也是可以的 - -```shell -$ tcpdump less 32 -$ tcpdump greater 64 -$ tcpdump <= 128 -``` - - - -### 6.3 根据 mac 地址进行过滤 - -例子如下,其中 ehost 是记录在 /etc/ethers 里的 name - -```shell -$ tcpdump ether host [ehost] -$ tcpdump ether dst [ehost] -$ tcpdump ether src [ehost] -``` - - - -### 6.4 过滤通过指定网关的数据包 - -```shell -$ tcpdump gateway [host] -``` - - - -### 6.5 过滤广播/多播数据包 - -```shell -$ tcpdump ether broadcast -$ tcpdump ether multicast - -$ tcpdump ip broadcast -$ tcpdump ip multicast - -$ tcpdump ip6 multicast -``` - - - -## 7. 如何抓取到更精准的包? - -先给你抛出一个问题:如果我只想抓取 HTTP 的 POST 请求该如何写呢? - -如果只学习了上面的内容,恐怕你还是无法写法满足这个抓取需求的过滤器。 - -在学习之前,我先给出答案,然后再剖析一下,这个过滤器是如何生效的,居然能让我们对包内的内容进行判断。 - -```shell -$ tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4]' -``` - -命令里的可选参数,在前面的内容里已经详细讲过了。这里不再细讲。 - -本节的重点是引号里的内容,看起来很复杂的样子。 - -将它逐一分解,我们只要先理解了下面几种用法,就能明白 - -- `tcp[n]`:表示 tcp 报文里 第 n 个字节 - -- `tcp[n:c]`:表示 tcp 报文里从第n个字节开始取 c 个字节,tcp[12:1] 表示从报文的第12个字节(因为有第0个字节,所以这里的12其实表示的是13)开始算起取一个字节,也就是 8 个bit。查看 [tcp 的报文首部结构](https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure),可以得知这 8 个bit 其实就是下图中的红框圈起来的位置,而在这里我们只要前面 4个bit,也就是实际数据在整个报文首部中的偏移量。 - - ![](http://image.iswbm.com/20200629085659.png) - -- `&`:是[位运算](https://en.wikipedia.org/wiki/Bitwise_operation)里的 and 操作符,比如 `0011 & 0010 = 0010` -- `>>`:是位运算里的右移操作,比如 `0111 >> 2 = 0001` -- `0xf0`:是 10 进制的 240 的 16 进制表示,但对于位操作来说,10进制和16进制都将毫无意义,我们需要的是二进制,将其转换成二进制后是:11110000,这个数有什么特点呢?前面个 4bit 全部是 1,后面4个bit全部是0,往后看你就知道这个特点有什么用了。 - -分解完后,再慢慢合并起来看 - -1、`tcp[12:1] & 0xf0` 其实并不直观,但是我们将它换一种写法,就好看多了,假设 tcp 报文中的 第12 个字节是这样组成的 `10110000`,那么这个表达式就可以变成 10110110 && 11110000 = 10110000,得到了 10110000 后,再进入下一步。 - -2、`tcp[12:1] & 0xf0) >> 2` :如果你不理解 tcp 报文首部里的数据偏移,请先点击这个前往我的[上一篇文章](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247488180&idx=1&sn=09526224732ebfcccb52847f27298c70&chksm=e8867256dff1fb40c9f47bafd0e87a9237c5a9ebf33c8a3d0a598276b496d29cdaa3fbff8d26&token=1970357830&lang=zh_CN#rd),搞懂数据偏移的意义,否则我保证你这里会绝对会听懵了。 - -`tcp[12:1] & 0xf0) >> 2` 这个表达式实际是 `(tcp[12:1] & 0xf0) >> 4 ) << 2` 的简写形式。所以要搞懂 `tcp[12:1] & 0xf0) >> 2` 只要理解了`(tcp[12:1] & 0xf0) >> 4 ) << 2` 就行了 。 - -从上一步我们算出了 `tcp[12:1] & 0xf0` 的值其实是一个字节,也就是 8 个bit,但是你再回去看下上面的 tcp 报文首部结构图,表示数据偏移量的只有 4个bit,也就是说 上面得到的值 10110000,前面 4 位(1011)才是正确的偏移量,那么为了得到 1011,只需要将 10110000 右移4位即可,也就是 `tcp[12:1] & 0xf0) >> 4`,至此我们是不是已经得出了实际数据的正确位置呢,很遗憾还没有,前一篇文章里我们讲到 Data Offset 的单位是 4个字节,因为要将 1011 乘以 4才可以,除以4在位运算中相当于左移2位,也就是 `<<2`,与前面的 `>>4` 结合起来一起算的话,最终的运算可以简化为 `>>2`。 - -至此,我们终于得出了实际数据开始的位置是 `tcp[12:1] & 0xf0) >> 2` (单位是字节)。 - -找到了数据的起点后,可别忘了我们的目的是从数据中打到 HTTP 请求的方法,是 GET 呢 还是 POST ,或者是其他的? - -有了上面的经验,我们自然懂得使用 `tcp[((tcp[12:1] & 0xf0) >> 2):4]` 从数据开始的位置再取出四个字节,然后将结果与 `GET ` (注意 GET最后还有个空格)的 16进制写法(也就是 `0x47455420`)进行比对。 - -```shell -0x47 --> 71 --> G -0x45 --> 69 --> E -0x54 --> 84 --> T -0x20 --> 32 --> 空格 -``` - -![](http://image.iswbm.com/20200629130407.png) - -如果相等,则该表达式为True,tcpdump 认为这就是我们所需要抓的数据包,将其输出到我们的终端屏幕上。 - - - -## 8. 抓包实战应用例子 - -### 8.1 提取 HTTP 的 User-Agent - -从 HTTP 请求头中提取 HTTP 的 User-Agent: - -```bash -$ tcpdump -nn -A -s1500 -l | grep "User-Agent:" -``` - -通过 `egrep` 可以同时提取User-Agent 和主机名(或其他头文件): - -```bash -$ tcpdump -nn -A -s1500 -l | egrep -i 'User-Agent:|Host:' -``` - -### 8.2 抓取 HTTP GET 和 POST 请求 - -抓取 HTTP GET 请求包: - -```bash -$ tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420' - -# or - -$ tcpdump -vvAls0 | grep 'GET' -``` - -可以抓取 HTTP POST 请求包: - -```bash -$ tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504f5354' - -# or - -$ tcpdump -vvAls0 | grep 'POST' -``` - -注意:该方法不能保证抓取到 HTTP POST 有效数据流量,因为一个 POST 请求会被分割为多个 TCP 数据包。 - -### 8.3 找出发包数最多的 IP - -找出一段时间内发包最多的 IP,或者从一堆报文中找出发包最多的 IP,可以使用下面的命令: - -```bash -$ tcpdump -nnn -t -c 200 | cut -f 1,2,3,4 -d '.' | sort | uniq -c | sort -nr | head -n 20 -``` - -- **cut -f 1,2,3,4 -d '.'** : 以 `.` 为分隔符,打印出每行的前四列。即 IP 地址。 -- **sort | uniq -c** : 排序并计数 -- **sort -nr** : 按照数值大小逆向排序 - -### 8.4 抓取 DNS 请求和响应 - -DNS 的默认端口是 53,因此可以通过端口进行过滤 - -```shell -$ tcpdump -i any -s0 port 53 -``` - - - -### 8.5 切割 pcap 文件 - -当抓取大量数据并写入文件时,可以自动切割为多个大小相同的文件。例如,下面的命令表示每 3600 秒创建一个新文件 `capture-(hour).pcap`,每个文件大小不超过 `200*1000000` 字节: - -```bash -$ tcpdump -w /tmp/capture-%H.pcap -G 3600 -C 200 -``` - -这些文件的命名为 `capture-{1-24}.pcap`,24 小时之后,之前的文件就会被覆盖。 - -### 8.6 提取 HTTP POST 请求中的密码 - -从 HTTP POST 请求中提取密码和主机名: - -```shell -$ tcpdump -s 0 -A -n -l | egrep -i "POST /|pwd=|passwd=|password=|Host:" -``` - -### 8.7 提取 HTTP 请求的 URL - -提取 HTTP 请求的主机名和路径: - -```shell -$ tcpdump -s 0 -v -n -l | egrep -i "POST /|GET /|Host:" -``` - -### 8.8 抓取 HTTP 有效数据包 - -抓取 80 端口的 HTTP 有效数据包,排除 TCP 连接建立过程的数据包(SYN / FIN / ACK): - -```shell -$ tcpdump 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)' -``` - -### 8.9 结合 Wireshark 进行分析 - -通常 `Wireshark`(或 tshark)比 tcpdump 更容易分析应用层协议。一般的做法是在远程服务器上先使用 `tcpdump` 抓取数据并写入文件,然后再将文件拷贝到本地工作站上用 `Wireshark` 分析。 - -还有一种更高效的方法,可以通过 ssh 连接将抓取到的数据实时发送给 Wireshark 进行分析。以 MacOS 系统为例,可以通过 `brew cask install wireshark` 来安装,然后通过下面的命令来分析: - -```shell -$ ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - not port 22' | /Applications/Wireshark.app/Contents/MacOS/Wireshark -k -i - -``` - -例如,如果想分析 DNS 协议,可以使用下面的命令: - -```shell -$ ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - port 53' | /Applications/Wireshark.app/Contents/MacOS/Wireshark -k -i - -``` - -抓取到的数据: - -![](https://hugo-picture.oss-cn-beijing.aliyuncs.com/images/20200210170101.png) - -`-c` 选项用来限制抓取数据的大小。如果不限制大小,就只能通过 `ctrl-c` 来停止抓取,这样一来不仅关闭了 tcpdump,也关闭了 wireshark。 - - - -到这里,我已经将我所知道的 tcpdump 的用法全部说了一遍,如果你有认真地看完本文,相信会有不小的收获,掌握一个上手的抓包工具,对于以后我们学习网络、分析网络协议、以及定位网络问题,会很有帮助,而 tcpdump 是我推荐的一个抓包工具。 - -## 9. 参考文章 - -1. [FreeBSD Manual Pages About tcpdump](https://www.freebsd.org/cgi/man.cgi?query=tcpdump&apropos=0&sektion=0&manpath=FreeBSD+7.2-RELEASE&format=html) -3. [Linux tcpdump命令详解](https://www.cnblogs.com/ggjucheng/archive/2012/01/14/2322659.html) -4. [一份快速实用的 tcpdump 命令参考手册](http://team.jiunile.com/blog/2019/06/tcpdump.html) -4. [超详细的网络抓包神器 tcpdump 使用指南](https://fuckcloudnative.io/posts/tcpdump-examples/) -5. [[译]tcpdump 示例教程](https://colobu.com/2019/07/16/a-tcpdump-tutorial-with-examples/) -6. [[英]tcpdump 示例教程](https://danielmiessler.com/study/tcpdump/) ---- ![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c10/c10_04.rst b/source/c10/c10_04.rst index e32a599..c521e0b 100644 --- a/source/c10/c10_04.rst +++ b/source/c10/c10_04.rst @@ -1,920 +1,98 @@ -10.4 全网最全(中文) tcpdump 抓包指南 -==================================== +10.4 Linux 上最优雅的命令调用方式:sh +===================================== |image0| -今天要给大家介绍的一个 Unix 下的一个 -**网络数据采集分析工具**\ ,也就是我们常说的抓包工具。 +在编写 Python +脚本的时候,很经常需要我们去调用系统的命令,方法有很多种,比如 +os.popen,os.system,commands,还有 subprocess。 -与它功能类似的工具有 wireshark ,不同的是,wireshark 有图形化界面,而 -tcpdump 则只有命令行。 +今天明哥要介绍一种更加优雅的方法,就是 ``sh`` +这个第三方库,它能让你像调用方法那样去调用系统中的命令。 -由于我本人更习惯使用命令行的方式进行抓包,因此今天先跳过 -wireshark,直接给大家介绍这个 tcpdump 神器。 - -这篇文章,我肝了好几天,借助于Linux 的 man 帮助命令,我把 tcpdump -的用法全部研究了个遍,才形成了本文,不夸张的说,应该可以算是中文里把 -tcpdump -讲得最清楚明白,并且还最全的文章了(至少我从百度、谷歌的情况来看),所以本文值得你收藏分享,就怕你错过了,就再也找不到像这样把 -tcpdump 讲得直白而且特全的文章了。 - -|image1| - -在讲解之前,有两点需要声明: - -1. 第三节到第六节里的 tcpdump - 命令示例,只为了说明参数的使用,并不一定就能抓到包,如果要精准抓到你所需要的包,需要配合第五节的逻辑逻辑运算符进行组合搭配。 -2. 不同 Linux 发行版下、不同版本的 tcpdump 可能有小许差异, 本文是基于 - CentOS 7.2 的 4.5.1 版本的tcpdump - 进行学习的,若在你的环境中无法使用,请参考 ``man tcpdump`` - 进行针对性学习。 - -1. tcpdump 核心参数图解 ------------------------ - -大家都知道,网络上的流量、数据包,非常的多,因此要想抓到我们所需要的数据包,就需要我们定义一个精准的过滤器,把这些目标数据包,从巨大的数据包网络中抓取出来。 - -所以学习抓包工具,其实就是学习如何定义过滤器的过程。 - -而在 tcpdump -的世界里,过滤器的实现,都是通过一个又一个的参数组合起来,一个参数不够精准,那就再加一个,直到我们能过滤掉无用的数据包,只留下我们感兴趣的数据包。 - -tcpdump 的参数非常的多,初学者在没有掌握 tcpdump -时,会对这个命令的众多参数产生很多的疑惑。 - -就比如下面这个命令,我们要通过 ``host`` 参数指定 host ip 进行过滤 - -.. code:: shell - - $ tcpdump host 192.168.10.100 - -``主程序`` + ``参数名``\ + ``参数值`` -这样的组合才是我们正常认知里面命令行该有的样子。 - -可 tcpdump 却不走寻常路,我们居然还可以在 host -前再加一个限定词,来缩小过滤的范围? - -.. code:: shell - - $ tcpdump src host 192.168.10.100 - -从字面上理解,确实很容易理解,但是这不符合编写命令行程序的正常逻辑,导致我们会有所疑虑: - -1. 除了 src ,dst,可还有其它可以用的限定词? - -2. src,host 应该如何理解它们,叫参数名?不合适,因为 src 明显不合适。 - -如果你在网上看到有关 tcpdump -的博客、教程,无一不是给你一个参数组合,告诉你这是实现了怎样的一个过滤器?这样的教学方式,很容易让你依赖别人的文章来使用 -tcpdump,而不能将 tcpdump -这样神器消化,达到灵活应用,灵活搭配过滤器的效果。 - -上面加了 src 本身就颠覆了我们的认知,你可知道在 src -之前还可以加更多的条件,比如 tcp, udp, icmp -等词,在你之前的基础上再过滤一层。 - -.. code:: shell - - $ tcpdump tcp src host 192.168.10.100 - -这种参数的不确定性,让大多数人对 tcpdump 的学习始终无法得其精髓。 - -因此,在学习 tcpdump 之前,我觉得有必要要先让你知道:\ **tcpdump -的参数是如何组成的?这非常重要。** - -为此,我画了一张图,方便你直观的理解 tcpdump 的各种参数: - -|image2| - -1. option 可选参数:将在后边一一解释。 -2. proto 类过滤器:根据协议进行过滤,可识别的关键词有: tcp, udp, icmp, - ip, ip6, arp, rarp,ether,wlan, fddi, tr, decnet -3. type 类过滤器:可识别的关键词有:host, net, port, - portrange,这些词后边需要再接参数。 -4. direction 类过滤器:根据数据流向进行过滤,可识别的关键字有:src, - dst,同时你可以使用逻辑运算符进行组合,比如 src or dst - -proto、type、direction -这三类过滤器的内容比较简单,也最常用,因此我将其放在最前面,也就是 -**第三节:常规过滤规则**\ 一起介绍。 - -而 option -可选的参数非常多,有的甚至也不经常用到,因此我将其放到后面一点,也就是 -**第四节:可选参数解析** - -当你看完前面六节,你对 tcpdump 的认识会上了一个台阶,至少能够满足你 80% -的使用需求。 - -你一定会问了,还有 20% 呢? - -其实 tcpdump -还有一些过滤关键词,它不符合以上四种过滤规则,可能需要你单独记忆。关于这部分我会在 -**第六节:特殊过滤规则** 里进行介绍。 - -2. 理解 tcpdump 的输出 ----------------------- - -2.1 输出内容结构 -~~~~~~~~~~~~~~~~ - -tcpdump 输出的内容虽然多,却很规律。 - -这里以我随便抓取的一个 tcp 包为例来看一下 - -.. code:: shell - - 21:26:49.013621 IP 172.20.20.1.15605 > 172.20.20.2.5920: Flags [P.], seq 49:97, ack 106048, win 4723, length 48 - -从上面的输出来看,可以总结出: - -1. 第一列:时分秒毫秒 21:26:49.013621 -2. 第二列:网络协议 IP -3. 第三列:发送方的ip地址+端口号,其中172.20.20.1是 ip,而15605 是端口号 -4. 第四列:箭头 >, 表示数据流向 -5. 第五列:接收方的ip地址+端口号,其中 172.20.20.2 是 ip,而5920 - 是端口号 -6. 第六列:冒号 -7. 第七列:数据包内容,包括Flags 标识符,seq 号,ack 号,win - 窗口,数据长度 length,其中 [P.] 表示 PUSH 标志位为 - 1,更多标识符见下面 - -2.2 Flags 标识符 -~~~~~~~~~~~~~~~~ - -使用 tcpdump 抓包后,会遇到的 TCP 报文 Flags,有以下几种: - -- ``[S]`` : SYN(开始连接) -- ``[P]`` : PSH(推送数据) -- ``[F]`` : FIN (结束连接) -- ``[R]`` : RST(重置连接) -- ``[.]`` : 没有 Flag (意思是除上面四种类型外的其他情况,有可能是 ACK - 也有可能是 URG) - -3. 常规过滤规则 ---------------- - -3.1 基于IP地址过滤:host -~~~~~~~~~~~~~~~~~~~~~~~~ - -使用 ``host`` 就可以指定 host ip 进行过滤 - -.. code:: shell - - $ tcpdump host 192.168.10.100 - -数据包的 ip 可以再细分为源ip和目标ip两种 - -.. code:: shell - - # 根据源ip进行过滤 - $ tcpdump -i eth2 src 192.168.10.100 - - # 根据目标ip进行过滤 - $ tcpdump -i eth2 dst 192.168.10.200 - -3.2 基于网段进行过滤:net -~~~~~~~~~~~~~~~~~~~~~~~~~ - -若你的ip范围是一个网段,可以直接这样指定 - -.. code:: shell - - $ tcpdump net 192.168.10.0/24 - -网段同样可以再细分为源网段和目标网段 - -.. code:: shell - - # 根据源网段进行过滤 - $ tcpdump src net 192.168 - - # 根据目标网段进行过滤 - $ tcpdump dst net 192.168 - -3.3 基于端口进行过滤:port -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -使用 ``port`` 就可以指定特定端口进行过滤 - -.. code:: shell - - $ tcpdump port 8088 - -端口同样可以再细分为源端口,目标端口 - -.. code:: shell - - # 根据源端口进行过滤 - $ tcpdump src port 8088 - - # 根据目标端口进行过滤 - $ tcpdump dst port 8088 - -如果你想要同时指定两个端口你可以这样写 - -.. code:: shell - - $ tcpdump port 80 or port 8088 - -但也可以简写成这样 - -.. code:: shell - - $ tcpdump port 80 or 8088 - -如果你的想抓取的不再是一两个端口,而是一个范围,一个一个指定就非常麻烦了,此时你可以这样指定一个端口段。 - -.. code:: shell - - $ tcpdump portrange 8000-8080 - $ tcpdump src portrange 8000-8080 - $ tcpdump dst portrange 8000-8080 - -对于一些常见协议的默认端口,我们还可以直接使用协议名,而不用具体的端口号 - -比如 http == 80,https == 443 等 - -.. code:: shell - - $ tcpdump tcp port http - -3.4 基于协议进行过滤:proto -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -常见的网络协议有:tcp, udp, icmp, http, ip,ipv6 等 - -若你只想查看 icmp 的包,可以直接这样写 - -.. code:: shell - - $ tcpdump icmp - -protocol 可选值:ip, ip6, arp, rarp, atalk, aarp, decnet, sca, lat, -mopdl, moprc, iso, stp, ipx, or netbeui - -3.5 基本IP协议的版本进行过滤 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -当你想查看 tcp 的包,你也许会这样子写 - -.. code:: shell - - $ tcpdump tcp - -这样子写也没问题,就是不够精准,为什么这么说呢? - -ip 根据版本的不同,可以再细分为 IPv4 和 IPv6 两种,如果你只指定了 -tcp,这两种其实都会包含在内。 - -那有什么办法,能够将 IPv4 和 IPv6 区分开来呢? - -很简单,如果是 IPv4 的 tcp 包 ,就这样写(友情提示:数字 6 表示的是 tcp -在ip报文中的编号。) - -.. code:: shell - - $ tcpdump 'ip proto tcp' - - # or - - $ tcpdump ip proto 6 - - # or - - $ tcpdump 'ip protochain tcp' - - # or - - $ tcpdump ip protochain 6 - -而如果是 IPv6 的 tcp 包 ,就这样写 - -.. code:: shell - - $ tcpdump 'ip6 proto tcp' - - # or - - $ tcpdump ip6 proto 6 - - # or - - $ tcpdump 'ip6 protochain tcp' - - # or - - $ tcpdump ip6 protochain 6 - -关于上面这几个命令示例,有两点需要注意: - -1. 跟在 proto 和 protochain 后面的如果是 tcp, udp, icmp - ,那么过滤器需要用引号包含,这是因为 tcp,udp, icmp 是 tcpdump - 的关键字。 -2. 跟在ip 和 ip6 关键字后面的 proto 和 protochain - 是两个新面孔,看起来用法类似,它们是否等价,又有什么区别呢? - -关于第二点,网络上没有找到很具体的答案,我只能通过 ``man tcpdump`` -的提示, 给出自己的个人猜测,但不保证正确。 - -proto 后面跟的 ```` 的关键词是固定的,只能是 ip, ip6, arp, -rarp, atalk, aarp, decnet, sca, lat, mopdl, moprc, iso, stp, ipx, or -netbeui 这里面的其中一个。 - -而 protochain 后面跟的 protocol 要求就没有那么严格,它可以是任意词,只要 -tcpdump 的 IP 报文头部里的 protocol 字段为 ```` 就能匹配上。 - -理论上来讲,下面两种写法效果是一样的 - -.. code:: shell - - $ tcpdump 'ip && tcp' - $ tcpdump 'ip proto tcp' - -同样的,这两种写法也是一样的 - -.. code:: shell - - $ tcpdump 'ip6 && tcp' - $ tcpdump 'ip6 proto tcp' - -4. 可选参数解析 ---------------- - -4.1 设置不解析域名提升速度 -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- ``-n``\ :不把ip转化成域名,直接显示 ip,避免执行 DNS lookups - 的过程,速度会快很多 -- ``-nn``\ :不把协议和端口号转化成名字,速度也会快很多。 -- ``-N``\ :不打印出host 的域名部分.。比如,,如果设置了此选现,tcpdump - 将会打印’nic’ 而不是 ‘nic.ddn.mil’. - -4.2 过滤结果输出到文件 -~~~~~~~~~~~~~~~~~~~~~~ - -使用 tcpdump 工具抓到包后,往往需要再借助其他的工具进行分析,比如常见的 -wireshark 。 - -而要使用wireshark ,我们得将 tcpdump -抓到的包数据生成到文件中,最后再使用 wireshark 打开它即可。 - -使用 ``-w`` 参数后接一个以 ``.pcap`` 后缀命令的文件名,就可以将 tcpdump -抓到的数据保存到文件中。 - -.. code:: shell - - $ tcpdump icmp -w icmp.pcap - -4.3 从文件中读取包数据 -~~~~~~~~~~~~~~~~~~~~~~ - -使用 ``-w`` 是写入数据到文件,而使用 ``-r`` 是从文件中读取数据。 - -读取后,我们照样可以使用上述的过滤器语法进行过滤分析。 - -.. code:: shell - - $ tcpdump icmp -r all.pcap - -4.4 控制详细内容的输出 -~~~~~~~~~~~~~~~~~~~~~~ - -- ``-v``\ :产生详细的输出. - 比如包的TTL,id标识,数据包长度,以及IP包的一些选项。同时它还会打开一些附加的包完整性检测,比如对IP或ICMP包头部的校验和。 -- ``-vv``\ :产生比-v更详细的输出. 比如NFS回应包中的附加域将会被打印, - SMB数据包也会被完全解码。(摘自网络,目前我还未使用过) -- ``-vvv``\ :产生比-vv更详细的输出。比如 telent 时所使用的SB, SE - 选项将会被打印, - 如果telnet同时使用的是图形界面,其相应的图形选项将会以16进制的方式打印出来(摘自网络,目前我还未使用过) - -4.5 控制时间的显示 -~~~~~~~~~~~~~~~~~~ - -- ``-t``\ :在每行的输出中不输出时间 -- ``-tt``\ :在每行的输出中会输出时间戳 -- ``-ttt``\ :输出每两行打印的时间间隔(以毫秒为单位) -- ``-tttt``\ :在每行打印的时间戳之前添加日期的打印(此种选项,输出的时间最直观) - -4.6 显示数据包的头部 -~~~~~~~~~~~~~~~~~~~~ - -- ``-x``\ :以16进制的形式打印每个包的头部数据(但不包括数据链路层的头部) -- ``-xx``\ :以16进制的形式打印每个包的头部数据(包括数据链路层的头部) -- ``-X``\ :以16进制和 - ASCII码形式打印出每个包的数据(但不包括连接层的头部),这在分析一些新协议的数据包很方便。 -- ``-XX``\ :以16进制和 - ASCII码形式打印出每个包的数据(包括连接层的头部),这在分析一些新协议的数据包很方便。 - -4.7 过滤指定网卡的数据包 -~~~~~~~~~~~~~~~~~~~~~~~~ - -- ``-i``\ :指定要过滤的网卡接口,如果要查看所有网卡,可以 ``-i any`` - -4.8 过滤特定流向的数据包 -~~~~~~~~~~~~~~~~~~~~~~~~ - -- ``-Q``\ : 选择是入方向还是出方向的数据包,可选项有:in, out, - inout,也可以使用 –direction=[direction] 这种写法 - -4.9 其他常用的一些参数 -~~~~~~~~~~~~~~~~~~~~~~ - -- ``-A``\ :以ASCII码方式显示每一个数据包(不显示链路层头部信息). - 在抓取包含网页数据的数据包时, 可方便查看数据 - -- ``-l`` : 基于行的输出,便于你保存查看,或者交给其它工具分析 -- ``-q`` : 简洁地打印输出。即打印很少的协议相关信息, - 从而输出行都比较简短. -- ``-c`` : 捕获 count 个包 tcpdump 就退出 -- ``-s`` : tcpdump 默认只会截取前 ``96`` - 字节的内容,要想截取所有的报文内容,可以使用 ``-s number``\ , - ``number`` 就是你要截取的报文字节数,如果是 0 - 的话,表示截取报文全部内容。 -- ``-S`` : 使用绝对序列号,而不是相对序列号 -- ``-C``\ :file-size,tcpdump 在把原始数据包直接保存到文件中之前, - 检查此文件大小是否超过file-size. 如果超过了, - 将关闭此文件,另创一个文件继续用于原始数据包的记录. 新创建的文件名与-w - 选项指定的文件名一致, - 但文件名后多了一个数字.该数字会从1开始随着新创建文件的增多而增加. - file-size的单位是百万字节(nt: - 这里指1,000,000个字节,并非1,048,576个字节, 后者是以1024字节为1k, - 1024k字节为1M计算所得, 即1M=1024 * 1024 = 1,048,576) -- ``-F``\ :使用file 文件作为过滤条件表达式的输入, - 此时命令行上的输入将被忽略. - -4.10 对输出内容进行控制的参数 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- ``-D`` : 显示所有可用网络接口的列表 -- ``-e`` : 每行的打印输出中将包括数据包的数据链路层头部信息 -- ``-E`` : 揭秘IPSEC数据 -- ``-L`` :列出指定网络接口所支持的数据链路层的类型后退出 -- ``-Z``\ :后接用户名,在抓包时会受到权限的限制。如果以root用户启动tcpdump,tcpdump将会有超级用户权限。 -- ``-d``\ :打印出易读的包匹配码 -- ``-dd``\ :以C语言的形式打印出包匹配码. -- ``-ddd``\ :以十进制数的形式打印出包匹配码 - -5. 过滤规则组合 ---------------- - -有编程基础的同学,对于下面三个逻辑运算符应该不陌生了吧 - -- and:所有的条件都需要满足,也可以表示为 ``&&`` -- or:只要有一个条件满足就可以,也可以表示为 ``||`` -- not:取反,也可以使用 ``!`` - -举个例子,我想需要抓一个来自\ ``10.5.2.3``\ ,发往任意主机的3389端口的包 - -.. code:: shell - - $ tcpdump src 10.5.2.3 and dst port 3389 - -当你在使用多个过滤器进行组合时,有可能需要用到括号,而括号在 shell -中是特殊符号,因为你需要使用引号将其包含。例子如下: - -.. code:: shell - - $ tcpdump 'src 10.0.2.4 and (dst port 3389 or 22)' - -而在单个过滤器里,常常会判断一条件是否成立,这时候,就要使用下面两个符号 - -- ``=``\ :判断二者相等 -- ``==``\ :判断二者相等 -- ``!=``\ :判断二者不相等 - -当你使用这两个符号时,tcpdump -还提供了一些关键字的接口来方便我们进行判断,比如 - -- if:表示网卡接口名、 -- proc:表示进程名 -- pid:表示进程 id -- svc:表示 service class -- dir:表示方向,in 和 out -- eproc:表示 effective process name -- epid:表示 effective process ID - -比如我现在要过滤来自进程名为 ``nc`` 发出的流经 en0 -网卡的数据包,或者不流经 en0 的入方向数据包,可以这样子写 - -.. code:: shell - - $ tcpdump "( if=en0 and proc =nc ) || (if != en0 and dir=in)" - -6. 特殊过滤规则 ---------------- - -6.1 根据 tcpflags 进行过滤 -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -通过\ `上一篇文章 `__\ ,我们知道了 -tcp 的首部有一个标志位。 - -.. figure:: http://image.iswbm.com/20200606095627.png - :alt: TCP 报文首部 - - TCP 报文首部 - -tcpdump 支持我们根据数据包的标志位进行过滤 - -:: - - proto [ expr:size ] - -- ``proto``\ :可以是熟知的协议之一(如ip,arp,tcp,udp,icmp,ipv6) - -- ``expr``\ :可以是数值,也可以是一个表达式,表示与指定的协议头开始处的字节偏移量。 -- ``size``\ :是可选的,表示从字节偏移量开始取的字节数量。 - -接下来,我将举几个例子,让人明白它的写法,不过在那之前,有几个点需要你明白,这在后面的例子中会用到: - -**1、**\ tcpflags 可以理解为是一个别名常量,相当于 -13,它代表着与指定的协议头开头相关的字节偏移量,也就是标志位,所以 -tcp[tcpflags] 等价于 tcp[13] ,对应下图中的报文位置。 - -|image3| - -**2、**\ tcp-fin, tcp-syn, tcp-rst, tcp-push, tcp-ack, tcp-urg -这些同样可以理解为别名常量,分别代表 -1,2,4,8,16,32,64。这些数字是如何计算出来的呢? - -以 tcp-syn 为例,你可以参照下面这张图,计算出来的值 是就是 2 - -|image4| - -由于数字不好记忆,所以一般使用这样的“别名常量”表示。 - -因此当下面这个表达式成立时,就代表这个包是一个 syn 包。 - -.. code:: shell - - tcp[tcpflags] == tcp-syn - -要抓取特定数据包,方法有很多种。 - -下面以最常见的 syn包为例,演示一下如何用 tcpdump 抓取到 syn -包,而其他的类型的包也是同样的道理。 - -据我总结,主要有三种写法: - -1、第一种写法:使用数字表示偏移量 - -.. code:: shell - - $ tcpdump -i eth0 "tcp[13] & 2 != 0" - -2、第二种写法:使用别名常量表示偏移量 - -.. code:: shell - - $ tcpdump -i eth0 "tcp[tcpflags] & tcp-syn != 0" - -3、第三种写法:使用混合写法 - -.. code:: shell - - $ tcpdump -i eth0 "tcp[tcpflags] & 2 != 0" - - # or - - $ tcpdump -i eth0 "tcp[13] & tcp-syn != 0" - -如果我想同时捕获多种类型的包呢,比如 syn + ack 包 - -1、第一种写法 - -.. code:: shell - - $ tcpdump -i eth0 'tcp[13] == 2 or tcp[13] == 16' - -2、第二种写法 - -.. code:: shell - - $ tcpdump -i eth0 'tcp[tcpflags] == tcp-syn or tcp[tcpflags] == tcp-ack' - -3、第三种写法 - -.. code:: shell - - $ tcpdump -i eth0 "tcp[tcpflags] & (tcp-syn|tcp-ack) != 0" - -4、第四种写法:注意这里是 -单个等号,而不是像上面一样两个等号,18(syn+ack) = 2(syn) + 16(ack) - -.. code:: shell - - $ tcpdump -i eth0 'tcp[13] = 18' - - # or - - $ tcpdump -i eth0 'tcp[tcpflags] = 18' - -tcp 中有 类似 tcp-syn 的别名常量,其他协议也是有的,比如 icmp -协议,可以使用的别名常量有 - -.. code:: shell - - icmp-echoreply, icmp-unreach, icmp-sourcequench, - icmp-redirect, icmp-echo, icmp-routeradvert, - icmp-routersolicit, icmp-timx-ceed, icmp-paramprob, - icmp-tstamp, icmp-tstampreply,icmp-ireq, - icmp-ireqreply, icmp-maskreq, icmp-maskreply - -6.2 基于包大小进行过滤 -~~~~~~~~~~~~~~~~~~~~~~ - -若你想查看指定大小的数据包,也是可以的 - -.. code:: shell - - $ tcpdump less 32 - $ tcpdump greater 64 - $ tcpdump <= 128 - -6.3 根据 mac 地址进行过滤 -~~~~~~~~~~~~~~~~~~~~~~~~~ - -例子如下,其中 ehost 是记录在 /etc/ethers 里的 name - -.. code:: shell - - $ tcpdump ether host [ehost] - $ tcpdump ether dst [ehost] - $ tcpdump ether src [ehost] - -6.4 过滤通过指定网关的数据包 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code:: shell - - $ tcpdump gateway [host] - -6.5 过滤广播/多播数据包 -~~~~~~~~~~~~~~~~~~~~~~~ - -.. code:: shell - - $ tcpdump ether broadcast - $ tcpdump ether multicast - - $ tcpdump ip broadcast - $ tcpdump ip multicast - - $ tcpdump ip6 multicast - -7. 如何抓取到更精准的包? -------------------------- - -先给你抛出一个问题:如果我只想抓取 HTTP 的 POST 请求该如何写呢? - -如果只学习了上面的内容,恐怕你还是无法写法满足这个抓取需求的过滤器。 - -在学习之前,我先给出答案,然后再剖析一下,这个过滤器是如何生效的,居然能让我们对包内的内容进行判断。 - -.. code:: shell - - $ tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4]' - -命令里的可选参数,在前面的内容里已经详细讲过了。这里不再细讲。 - -本节的重点是引号里的内容,看起来很复杂的样子。 - -将它逐一分解,我们只要先理解了下面几种用法,就能明白 - -- ``tcp[n]``\ :表示 tcp 报文里 第 n 个字节 - -- ``tcp[n:c]``\ :表示 tcp 报文里从第n个字节开始取 c 个字节,tcp[12:1] - 表示从报文的第12个字节(因为有第0个字节,所以这里的12其实表示的是13)开始算起取一个字节,也就是 - 8 个bit。查看 `tcp - 的报文首部结构 `__\ ,可以得知这 - 8 个bit 其实就是下图中的红框圈起来的位置,而在这里我们只要前面 - 4个bit,也就是实际数据在整个报文首部中的偏移量。 - - |image5| - -- ``&``\ :是\ `位运算 `__\ 里的 - and 操作符,比如 ``0011 & 0010 = 0010`` -- ``>>``\ :是位运算里的右移操作,比如 ``0111 >> 2 = 0001`` -- ``0xf0``\ :是 10 进制的 240 的 16 - 进制表示,但对于位操作来说,10进制和16进制都将毫无意义,我们需要的是二进制,将其转换成二进制后是:11110000,这个数有什么特点呢?前面个 - 4bit 全部是 1,后面4个bit全部是0,往后看你就知道这个特点有什么用了。 - -分解完后,再慢慢合并起来看 - -1、\ ``tcp[12:1] & 0xf0`` -其实并不直观,但是我们将它换一种写法,就好看多了,假设 tcp 报文中的 第12 -个字节是这样组成的 ``10110000``\ ,那么这个表达式就可以变成 10110110 && -11110000 = 10110000,得到了 10110000 后,再进入下一步。 - -2、\ ``tcp[12:1] & 0xf0) >> 2`` :如果你不理解 tcp -报文首部里的数据偏移,请先点击这个前往我的\ `上一篇文章 `__\ ,搞懂数据偏移的意义,否则我保证你这里会绝对会听懵了。 - -``tcp[12:1] & 0xf0) >> 2`` 这个表达式实际是 -``(tcp[12:1] & 0xf0) >> 4 ) << 2`` 的简写形式。所以要搞懂 -``tcp[12:1] & 0xf0) >> 2`` -只要理解了\ ``(tcp[12:1] & 0xf0) >> 4 ) << 2`` 就行了 。 - -从上一步我们算出了 ``tcp[12:1] & 0xf0`` 的值其实是一个字节,也就是 8 -个bit,但是你再回去看下上面的 tcp 报文首部结构图,表示数据偏移量的只有 -4个bit,也就是说 上面得到的值 10110000,前面 4 -位(1011)才是正确的偏移量,那么为了得到 1011,只需要将 10110000 -右移4位即可,也就是 -``tcp[12:1] & 0xf0) >> 4``\ ,至此我们是不是已经得出了实际数据的正确位置呢,很遗憾还没有,前一篇文章里我们讲到 -Data Offset 的单位是 4个字节,因为要将 1011 乘以 -4才可以,除以4在位运算中相当于左移2位,也就是 ``<<2``\ ,与前面的 -``>>4`` 结合起来一起算的话,最终的运算可以简化为 ``>>2``\ 。 - -至此,我们终于得出了实际数据开始的位置是 ``tcp[12:1] & 0xf0) >> 2`` -(单位是字节)。 - -找到了数据的起点后,可别忘了我们的目的是从数据中打到 HTTP 请求的方法,是 -GET 呢 还是 POST ,或者是其他的? - -有了上面的经验,我们自然懂得使用 ``tcp[((tcp[12:1] & 0xf0) >> 2):4]`` -从数据开始的位置再取出四个字节,然后将结果与 ``GET`` (注意 -GET最后还有个空格)的 16进制写法(也就是 ``0x47455420``\ )进行比对。 +使用前,当然是安装它,\ ``sh`` 支持 Python 2 也支持 Python3,这里以 +Python 3 为例。 .. code:: shell - 0x47 --> 71 --> G - 0x45 --> 69 --> E - 0x54 --> 84 --> T - 0x20 --> 32 --> 空格 - -|image6| - -如果相等,则该表达式为True,tcpdump -认为这就是我们所需要抓的数据包,将其输出到我们的终端屏幕上。 - -8. 抓包实战应用例子 -------------------- - -8.1 提取 HTTP 的 User-Agent -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -从 HTTP 请求头中提取 HTTP 的 User-Agent: - -.. code:: bash + $ python3 -m pip install sh - $ tcpdump -nn -A -s1500 -l | grep "User-Agent:" +这里要注意一点,虽然在 Windows +上也可以安装成功,但是并不能使用,如果你尝试在 Windows 导入 +它,会友好地提示你,该库只适用于 Linux 及 OSX 系统,假如你也想要在 +Windows 使用,它推荐你使用它的 兄弟库 - ``pbs`` +(https://pypi.org/project/pbs/)。 -通过 ``egrep`` 可以同时提取User-Agent 和主机名(或其他头文件): - -.. code:: bash - - $ tcpdump -nn -A -s1500 -l | egrep -i 'User-Agent:|Host:' - -8.2 抓取 HTTP GET 和 POST 请求 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -抓取 HTTP GET 请求包: - -.. code:: bash - - $ tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420' - - # or - - $ tcpdump -vvAls0 | grep 'GET' - -可以抓取 HTTP POST 请求包: - -.. code:: bash - - $ tcpdump -s 0 -A -vv 'tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504f5354' - - # or - - $ tcpdump -vvAls0 | grep 'POST' - -注意:该方法不能保证抓取到 HTTP POST 有效数据流量,因为一个 POST -请求会被分割为多个 TCP 数据包。 - -8.3 找出发包数最多的 IP -~~~~~~~~~~~~~~~~~~~~~~~ - -找出一段时间内发包最多的 IP,或者从一堆报文中找出发包最多的 -IP,可以使用下面的命令: - -.. code:: bash - - $ tcpdump -nnn -t -c 200 | cut -f 1,2,3,4 -d '.' | sort | uniq -c | sort -nr | head -n 20 - -- **cut -f 1,2,3,4 -d ‘.’** : 以 ``.`` 为分隔符,打印出每行的前四列。即 - IP 地址。 -- **sort \| uniq -c** : 排序并计数 -- **sort -nr** : 按照数值大小逆向排序 - -8.4 抓取 DNS 请求和响应 -~~~~~~~~~~~~~~~~~~~~~~~ - -DNS 的默认端口是 53,因此可以通过端口进行过滤 - -.. code:: shell - - $ tcpdump -i any -s0 port 53 - -8.5 切割 pcap 文件 -~~~~~~~~~~~~~~~~~~ - -当抓取大量数据并写入文件时,可以自动切割为多个大小相同的文件。例如,下面的命令表示每 -3600 秒创建一个新文件 ``capture-(hour).pcap``\ ,每个文件大小不超过 -``200*1000000`` 字节: - -.. code:: bash - - $ tcpdump -w /tmp/capture-%H.pcap -G 3600 -C 200 - -这些文件的命名为 ``capture-{1-24}.pcap``\ ,24 -小时之后,之前的文件就会被覆盖。 - -8.6 提取 HTTP POST 请求中的密码 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -从 HTTP POST 请求中提取密码和主机名: - -.. code:: shell - - $ tcpdump -s 0 -A -n -l | egrep -i "POST /|pwd=|passwd=|password=|Host:" - -8.7 提取 HTTP 请求的 URL -~~~~~~~~~~~~~~~~~~~~~~~~ - -提取 HTTP 请求的主机名和路径: - -.. code:: shell +|image1| - $ tcpdump -s 0 -v -n -l | egrep -i "POST /|GET /|Host:" +安装完成后,就可以直接使用它了,以下几个示例,非常简单,简单到我感觉只要 +demo ,而不需要任何的中文解释就可以让你知道他是如何使用的。 -8.8 抓取 HTTP 有效数据包 -~~~~~~~~~~~~~~~~~~~~~~~~ +1. 列出目录文件 +~~~~~~~~~~~~~~~ -抓取 80 端口的 HTTP 有效数据包,排除 TCP 连接建立过程的数据包(SYN / FIN -/ ACK): +使用 ``ls`` -.. code:: shell +.. code:: python - $ tcpdump 'tcp port 80 and (((ip[2:2] - ((ip[0]&0xf)<<2)) - ((tcp[12]&0xf0)>>2)) != 0)' + >>> import sh + >>> sh.ls("/home", "-l", color="never") + total 4 + drwx------ 3 centos centos 4096 Mar 8 2019 centos -8.9 结合 Wireshark 进行分析 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ +使用 ``glob`` -通常 ``Wireshark``\ (或 tshark)比 tcpdump -更容易分析应用层协议。一般的做法是在远程服务器上先使用 ``tcpdump`` -抓取数据并写入文件,然后再将文件拷贝到本地工作站上用 ``Wireshark`` -分析。 +.. code:: python -还有一种更高效的方法,可以通过 ssh 连接将抓取到的数据实时发送给 -Wireshark 进行分析。以 MacOS 系统为例,可以通过 -``brew cask install wireshark`` 来安装,然后通过下面的命令来分析: + >>> sh.glob("/etc/*.conf") + ['/etc/mke2fs.conf', '/etc/dnsmasq.conf', '/etc/asound.conf'] -.. code:: shell +调用程序 +~~~~~~~~ - $ ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - not port 22' | /Applications/Wireshark.app/Contents/MacOS/Wireshark -k -i - +.. code:: python -例如,如果想分析 DNS 协议,可以使用下面的命令: + >>> import sh + >>> r=sh.Command('/root/test.py') + >>> r() + hello,world -.. code:: shell +上面我们的 ls ,也可以通过这种方式执行,只是不能再加参数了。 - $ ssh root@remotesystem 'tcpdump -s0 -c 1000 -nn -w - port 53' | /Applications/Wireshark.app/Contents/MacOS/Wireshark -k -i - +.. code:: python -抓取到的数据: + sh.Command("ls")() -|image7| +管道 +~~~~ -``-c`` 选项用来限制抓取数据的大小。如果不限制大小,就只能通过 ``ctrl-c`` -来停止抓取,这样一来不仅关闭了 tcpdump,也关闭了 wireshark。 +.. code:: python -到这里,我已经将我所知道的 tcpdump -的用法全部说了一遍,如果你有认真地看完本文,相信会有不小的收获,掌握一个上手的抓包工具,对于以后我们学习网络、分析网络协议、以及定位网络问题,会很有帮助,而 -tcpdump 是我推荐的一个抓包工具。 + >>> print(sh.sort(sh.du(sh.glob('*'),'-shc'),'-rn')) + 712K distribute-0.6.49.tar.gz + 672K setuptools-1.1.5.tar.gz + 548K get-pip.py -9. 参考文章 ------------ +管道是有序的,默认由内而外,但如果需要并行呢?加个_piped=True -1. `FreeBSD Manual Pages About - tcpdump `__ -2. `Linux - tcpdump命令详解 `__ -3. `一份快速实用的 tcpdump - 命令参考手册 `__ -4. `超详细的网络抓包神器 tcpdump - 使用指南 `__ -5. `[译]tcpdump - 示例教程 `__ -6. `[英]tcpdump 示例教程 `__ +.. code:: python --------------- + >>> for line in sh.tr(sh.tail("-f", "/home/mysql/mysql/log/alert.log", _piped=True), "[:upper:]", "[:lower:]", _iter=True): + ... print line + ... + innodb: doublewrite buffer not found: creating new + + innodb: doublewrite buffer created + + innodb: 127 rollback segment(s) active. + + innodb: creating foreign key constraint system tables + + innodb: foreign key constraint system tables created -|image8| +|image2| .. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200630095709.png -.. |image2| image:: http://image.iswbm.com/20200628111325.png -.. |image3| image:: http://image.iswbm.com/20200628222034.png -.. |image4| image:: http://image.iswbm.com/20200628222010.png -.. |image5| image:: http://image.iswbm.com/20200629085659.png -.. |image6| image:: http://image.iswbm.com/20200629130407.png -.. |image7| image:: https://hugo-picture.oss-cn-beijing.aliyuncs.com/images/20200210170101.png -.. |image8| image:: http://image.iswbm.com/20200607174235.png +.. |image1| image:: http://image.iswbm.com/20200227201644.png +.. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c10/c10_05.md b/source/c10/c10_05.md deleted file mode 100644 index 67386e8..0000000 --- a/source/c10/c10_05.md +++ /dev/null @@ -1,36 +0,0 @@ -# 10.5 Wireshark 抓包教程 - -![](http://image.iswbm.com/20200602135014.png) - - - -## 过滤器 - -过滤器按照过滤器的使用时间点不同,可以分为捕获过滤器和显示过滤器。 - -**1. 网络协议过滤** - -比如 TCP,只显示 TCP 协议,HTTP 只显示 HTTP 协议等。在过滤器输入框中直接输入协议名称即可,不区分大小写。 - -**2. IP 地址过滤** - -如 ip.src == 192.168.1.102 显示源地址为 `192.168.1.102`, -而 ip.dst == 192.168.1.102, 目标地址为 `192.168.1.102`。 - -**3. 端口过滤** - -tcp.port == 80, 端口为 80 的 - -tcp.srcport == 80, 只显示 TCP 协议的原端口为 80 的。 - -**4. Http 模式过滤** - -http.request.method == “GET”,只显示 HTTP GET 方法的。 - -**5. 结合逻辑运算符 AND/OR 组成复杂的表达式** - -AND/OR 也可以写成 `&&` / `||` - - - -`Wireshark` 只能查看封包,而不能修改封包的内容,或者发送封包。 \ No newline at end of file diff --git a/source/c10/c10_05.rst b/source/c10/c10_05.rst deleted file mode 100644 index a42d0f4..0000000 --- a/source/c10/c10_05.rst +++ /dev/null @@ -1,38 +0,0 @@ -10.5 Wireshark 抓包教程 -======================= - -|image0| - -过滤器 ------- - -过滤器按照过滤器的使用时间点不同,可以分为捕获过滤器和显示过滤器。 - -**1. 网络协议过滤** - -比如 TCP,只显示 TCP 协议,HTTP 只显示 HTTP -协议等。在过滤器输入框中直接输入协议名称即可,不区分大小写。 - -**2. IP 地址过滤** - -如 ip.src == 192.168.1.102 显示源地址为 ``192.168.1.102``\ , 而 ip.dst -== 192.168.1.102, 目标地址为 ``192.168.1.102``\ 。 - -**3. 端口过滤** - -tcp.port == 80, 端口为 80 的 - -tcp.srcport == 80, 只显示 TCP 协议的原端口为 80 的。 - -**4. Http 模式过滤** - -http.request.method == “GET”,只显示 HTTP GET 方法的。 - -**5. 结合逻辑运算符 AND/OR 组成复杂的表达式** - -AND/OR 也可以写成 ``&&`` / ``||`` - -``Wireshark`` 只能查看封包,而不能修改封包的内容,或者发送封包。 - -.. |image0| image:: http://image.iswbm.com/20200602135014.png - diff --git a/source/c10/c10_06.md b/source/c10/c10_06.md deleted file mode 100644 index 5859d1f..0000000 --- a/source/c10/c10_06.md +++ /dev/null @@ -1,41 +0,0 @@ -# 10.6 通过比较,学习 TCP 与 UDP - -![](http://image.iswbm.com/20200602135014.png) - -## 1. TCP 与 UDP 的区别 - -**1. 连接** - -TCP 是面向连接的,传输数据前要先经历三次握手建立连接 - -UDP 不需要连接,即刻就可以传输数据 - -**2. 可靠性** - -TCP 能够保证数据可靠地,完整地,无重复的到达对端。 - -而 UDP 并不保证,有可能会丢包 - -**3. 连接场景** - -TCP 的连接,就跟线一样,只有两端,所以只能一对一 通信。 - -UDP 则支持一对一,一对多,多对多的通信。 - -**4. 传输控制** - -TCP 有丢包重传,拥塞控制,流量控制的机制 - -UDP 则没有,就算网络非常拥堵,也不会影响 UDP 的发送速率 - -**5. 首部开销** - -TCP 首部长度较长,且是可变长的,会有一定的开销。 - -UDP 首部固定只有 8 个字节,开销较小。 - - - -[面向报文(UDP)和面向字节流(TCP)的区别](https://blog.csdn.net/ce123_zhouwei/article/details/8976006) - -[TCP缓存区与窗口的关系](https://blog.csdn.net/yxccc_914/article/details/52515558) \ No newline at end of file diff --git a/source/c10/c10_06.rst b/source/c10/c10_06.rst deleted file mode 100644 index 3c1507f..0000000 --- a/source/c10/c10_06.rst +++ /dev/null @@ -1,44 +0,0 @@ -10.6 通过比较,学习 TCP 与 UDP -============================== - -|image0| - -1. TCP 与 UDP 的区别 --------------------- - -**1. 连接** - -TCP 是面向连接的,传输数据前要先经历三次握手建立连接 - -UDP 不需要连接,即刻就可以传输数据 - -**2. 可靠性** - -TCP 能够保证数据可靠地,完整地,无重复的到达对端。 - -而 UDP 并不保证,有可能会丢包 - -**3. 连接场景** - -TCP 的连接,就跟线一样,只有两端,所以只能一对一 通信。 - -UDP 则支持一对一,一对多,多对多的通信。 - -**4. 传输控制** - -TCP 有丢包重传,拥塞控制,流量控制的机制 - -UDP 则没有,就算网络非常拥堵,也不会影响 UDP 的发送速率 - -**5. 首部开销** - -TCP 首部长度较长,且是可变长的,会有一定的开销。 - -UDP 首部固定只有 8 个字节,开销较小。 - -`面向报文(UDP)和面向字节流(TCP)的区别 `__ - -`TCP缓存区与窗口的关系 `__ - -.. |image0| image:: http://image.iswbm.com/20200602135014.png - diff --git a/source/c10/c10_07.md b/source/c10/c10_07.md deleted file mode 100644 index 0978459..0000000 --- a/source/c10/c10_07.md +++ /dev/null @@ -1,478 +0,0 @@ -# 10.7 网络知识扫盲:CSRF 跨域攻击与JWT跨域认证 - -![](http://image.iswbm.com/20200602135014.png) - - - -![](http://image.iswbm.com/20200711143644.png) - -## 1. 什么是跨域请求 - -要明白什么叫跨域请求,首先得知道什么叫域。 - -域,是指由 `协议` + `域名` + `端口号` 组成的一个虚拟概念。 - -![](http://image.iswbm.com/20200705171112.png) - -如果两个域的协议、域名、端口号都一样,就称他们为同域,但是只要有其中一个不一样,就不是同域。 - -那么 `跨域请求` 又是什么意思呢? - -简单来说,就是在一个域内请求了另一个域的资源,由于域不一致,会有安全隐患。 - - - -## 2. 跨域请求的安全隐患 - -有一个词,叫 CSRF (Cross-site request forgery)攻击,中文名是 `跨站请求伪造`。 - -简单来说呢,就是攻击者盗用了你的身份,以你的名义发送恶意请求,它能做的坏事有很多,比如以你的名义发邮件,发消息,购物,盗取帐号等。 - -CSRF 的实际工作原理是怎样的? - -比如现在有两个网站,A 网站是真实受信息的网站,而 B网站是危险网站。 - -当你登陆 A 网站后,浏览器会存储 A 网站服务器给你生成的 sessionid 存入 cookie,有了这个 cookie ,就拥有了你的帐号权限,以后请求资料,就不用再次登陆啦。 - -对于真实用户来说,是便利,可对于攻击者来说,却是可乘之机。 - -![Cookie + Session 方法](http://image.iswbm.com/20200707220426.png) - -他们可以使用各种社工学引导你点击他们的链接/网站,然后利用你的浏览器上存储的 cookie ,然后在自己的 网站B 发起对 网站A 的请求,获取一些隐私信息,做一些侵害用户权益的事情。这便是一个完整的 CSRF 攻击。 - - - -## 3. 跨域请求的安全防御 - -完成一次完整的 CSRF 攻击,只要两个步骤: - -1. 登录受信任网站A,并本地已经存储了 Cookie -2. 在不登出A的情况下,访问危险网站B,网站 B 诱导你发 A 发请求。 - -很多浏览器用户对于网络安全是无意识的,因此我们不能指望通过规范用户行为来避免CSRF攻击。 - -那如何从技术手段规避一定的 CSRF 攻击的风险呢? - -1. 利用浏览器的同源策略:最基础的安全策略 -2. 对请求的来源进行验证:Referer Check -3. 使用验证码强制使用户进行交互确认,保证请求是用户发起 -4. CSRF Token,注意不要使用 cookie 来存储token -5. JSON Web Token - -以上是我知道的历史上用来抵御 CSRF 攻击的方法 - -- 有的虽然实现简单,但是不够安全 - -- 有的虽然安全,但是用户体验不好 - -- 有的虽然安全,用户体验好,但是有缺点 - -具体应该选哪一种呢,不妨继续往下看。 - -### 3.1 同源策略 - -浏览器上有一个同源策略(SOP,全称 Same origin policy),它会在一定程度上禁止这种跨域请求的发生。 - -但同源策略是最基本的安全策略,对于低级的 CSRF 攻击 ,它是很有效果的。 - -可以说 Web 是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。 - -同源策略在提升了 Web前端的安全性的同时,也牺牲了Web拓展上的灵活性。 - -设想若把html、js、css、flash,image等文件全部布置在一台服务器上,小网站这样凑活还行,大中网站如果这样做服务器根本受不了的,因此同源策略,就像是双刃剑。不过这些都是有解的。 - -### 3.2 Referer Check - -在 HTTP 协议中,有一个字段叫做 Referer,它记录了HTTP 请求的来源地址。 - -当发生 CSRF 攻击时,这个来源地址,会变成危险网站 B,因此只要在服务端校验这个 Referer 是不是和自己同一个域就可以判断这个请求是跨站请求。 - -![Referer Check 图解](http://image.iswbm.com/20200705193118.png) - -但这种方法,也是有局限性的,在一些非主流的浏览器,或者使用了那些非常古老的浏览器版本,这个 Referer 字段,是有可能会被篡改的。 - -退一步讲,假设你使用了最安全的最新版本的浏览器,这个值无法被篡改,依旧还是有安全隐患。 - -因为有些用户出于某些隐私考虑,会在浏览器设置关闭这个 Referer 字段,也有的网站会使用一些技术手段使用请求不携带 Referer 字段。 - -因此,当你要使用 Referer Check 来做为 防御 CSRF 攻击的主要手段时,请确保你的用户群体使用的一定是最安全的最新版本的浏览器,并且默认用户不会手动关闭 Referer 。 - -### 3.3 加验证码 - -验证码,强制用户必须与应用进行交互,才能完成最终请求。 - -其实加验证码,是能很好遏制 CSRF 攻击,但是网站总不能给所有的操作都加上验证码吧,那样的话,用户估计都跑光光了,因此为了保证用户体验,验证码只能作为一种辅助手段,不能作为主要解决方案。 - -### 3.4 CSRF Token - - CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下,直接利用用户自己的 cookie 来通过安全验证。 - -所以要抵御 CSRF,关键在于要在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中(不然黑客又能拿到了)。 - -业界普遍的防御方案是使用 CSRF Token - -使用 CSRF Token 根据token验证方式的不同,也可以分为两种: - -**第一种**:如图所示 - -![](http://image.iswbm.com/image-20200707221742925.png) - -1. 当用户请求一个更新用户名的页面时,由服务端生成一个随机数 Token,然后放入HTML表单中传给浏览器,并且存入 session 中。 - -2. 当用户提交表单请求时,表单数据会带上这个 Token 发送给服务端 ; -3. 服务端收到表单请求后,会从表单数据里取出 Token,然后和 session 里的 token 进行对比,如果是一样的,就是合法的用户请求,将新的用户名存入数据库,如果不一样,那就是非法的请求,应当拒绝。 - -**第二种**: - -![](http://image.iswbm.com/image-20200707222024941.png) - -1. 当用户请求一个更新用户名的页面时,由服务端生成一个随机数 Token,然后放入HTML表单中,并且会把这个 Token 放在 cookie 里发给浏览器。 -2. 当用户提交表单请求时,表单数据会带上这个 Token 发送给服务端,并且带上携带 token 的 cookie ; -3. 服务端收到表单请求后,会从表单数据里取出 Token,与 cookie 里的 token 进行对比,如果是一样的,就是合法的用户请求,将新的用户名存入数据库,如果不一样,那就是非法的请求,应当拒绝。 - -### 3.5 新增 Header - -使用上面的 CSRF Token 已经可以避免 CSRF 攻击,但是它却有可能又引入了另一个问题。 - -若 CSRF Token 没有使用 cookie,就必须要将 Token 存储在服务端的 Session 中,这样就会面临几个问题 - -1. 服务端每生成一个 Token,都会存放入 session 中,而随着用户请求的增多,服务端的开销会明显增大。 -2. 如果网站有多个子域,分别对应不同的服务器,比如 taobao.com 后台是服务器 a,zhibo.baotao.com 后台是 服务器b, 不同子域要想使用同一个 Token,就要求所有的服务器要能共享这个 Token。一般要有一个中心节点(且应是一个集群)来存储这个Token,这样看下来,架构就变得更加复杂了。 - -想要解决这些问题,可以使用我们接下来要讲的 JWT(全称:JSON Web Token) - -使用了 JWT 后,有了哪些变化呢 - -1. 服务器只负责生成 Token和校验Token,而不再存储Token -2. 将服务器的压力分摊给了所有的客户端。 -3. 服务端的 鉴权不使用 cookie ,而是由新增的 Header 字段:Authorization 里的 JWT 。 - -JWT 是本篇文章重要知识点之一,下面我会详细说说关于 JWT 的内容。 - -## 4. JWT 的工作原理及目的 - -为了让你直观感受 JWT 的工作原理,我画了下面这张图 - -![JWT 工作图解](http://image.iswbm.com/20200705220524.png) - -1. 用户以 Web表单 的形式,将自己的用户名和密码 POST 到后端的接口。 -2. 后端核对用户名和密码成功后,会计算生成JWT Payload 字符串(具体计算方法,后续会讲),然后返回 response 给浏览器。 -3. 浏览器收到 JWT 后,将其保存在 cookie 里或者 localStorage 或者 sessionStorage 里(具体如何选,后面会说)。 -4. 后续在该域上发出的请求,都会将 JWT放入HTTP Header 中的 Authorization 字段。 -5. 后端收到新请求后,会使用密钥验证 JWT 签名。 -6. 验证通过后后端使用 JWT 中包含的用户信息进行其他相关操作,返回相应结果。 - -![](http://image.iswbm.com/20200711144042.png) - -JWT 的诞生并不是解决 CSRF 跨域攻击,而是解决跨域认证的难题。 - -举例来说,A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,这应该如何实现呢? - -一种解决方案是 session 数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。 - -另一种方案是服务器索性不保存 session 数据了,所有数据都保存在客户端,每次请求都发回服务器。 - -JWT 就是这种方案的一个优秀代表。 - -## 5. JWT 如何生成? - -JWT 其实就是一个字符串,比如下面这样 - -```shell -eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ -``` - -仔细观察,会发现它里面有三个 `.` ,以 `.` 为分界,可以将 JWT 分为三部分。 - -![](http://image.iswbm.com/20200705212820.png) - -1. **第一部分**:头部(Header) -2. **第二部分**:载荷(Payload) -3. **第三部分**:签名(Signature) - -![](http://image.iswbm.com/20200705215033.png) - -### 5.1 头部(Header) - -JWT 的头部承载两部分信息: - -- 声明类型:这里是 JWT -- 声明加密的算法:通常直接使用 HMAC SHA256 - -完整的头部就像下面这样的JSON: - -```bash -{ - "typ": "JWT", - "alg": "HS256" -} -``` - -然后将头部进行 Base64URL 算法编码转换,构成了第一部分 - -```shell -eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 -``` - -### 5.2 载荷(Payload) - -载荷,同样也是个 JSON 对象,它是存放有效信息的地方,但不建议存放密码等敏感信息。 - -JWT 规定了7个官方字段,供选用: - -- iss (issuer):签发人 -- exp (expiration time):过期时间 -- sub (subject):主题 -- aud (audience):受众 -- nbf (Not Before):生效时间 -- iat (Issued At):签发时间 -- jti (JWT ID):编号 - -除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。 - -注意,JWT 默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。 - -```json -{ - "sub": "1234567890", - "name": "John Doe", - "admin": true -} -``` - -然后将其进行 Base64URL 算法转换,得到 JWT 的第二部分。 - -```shell -eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 -``` - -### 5.3 签名(Signature) - -Signature 部分是对前两部分的签名,防止数据篡改。 - -首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。 - -``` -HMACSHA256( - base64UrlEncode(header) + "." + - base64UrlEncode(payload), - secret) -``` - -算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用"点"(`.`)分隔,就可以返回给用户。 - -## 6. 如何手动生成 JWT? - -如果你想手动生成一个 JWT 用于测试,有两种方法 - -**第一种:使用 https://jwt.io/ 这个网站 。** - -我使用前面的 header 和 payload,然后使用 secret 密钥:`Python` - -最后生成的 JWT 结果如下 - -```shell -eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.3wGDum3_A8tAt1bdal5CpYbIUlpHfPQxs96Ijx883kI -``` - -![](http://image.iswbm.com/20200706005103.png) - -**第二种:使用 Python 代码生成** - -首先安装一下 pyjwt 这个库 - -```shell -$ pip install pyjwt -``` - -然后就可以在代码中使用它 - -```python -import jwt -import datetime -import uuid - -salt = 'minggezuishuai' - -# 构造header , 这里不写默认的也是 -headers = { - 'typ': 'JWT', - 'alg': 'HS256' -} - -# 构造payload -payload = { - 'user_id': str(uuid.uuid4()), # 自定义用户ID - 'username': "wangbm", # 自定义用户名 - 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5) # 超时时间,取现在时间,五分钟后token失效 -} -token = jwt.encode(payload=payload, key=salt, algorithm="HS256", headers=headers).decode('utf-8') - -# token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiODg4ZjIwZDktMDdlZC00MWJkLWIzMjktMTdjNmYwNThhMTRlIiwidXNlcm5hbWUiOiJ3YW5nYm0iLCJleHAiOjE1OTQ0MzQzMjZ9.kkEMhSx732lO6HWWNPNVQDHR9WuCEVxKgNol-LTbCP8 -``` - -如果你只是测试使用,完全不用写那么多代码,用命令行即可 - -```shell -$ pyjwt --key="minggezuishuai" encode user_id=888f20d9-07ed-41bd-b329-17c6f058a14e username=wangbm exp=+120 -eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiODg4ZjIwZDktMDdlZC00MWJkLWIzMjktMTdjNmYwNThhMTRlIiwidXNlcm5hbWUiOiJ3YW5nYm0iLCJleHAiOjE1OTQ0MzQ4NTl9.A792th12kY1YnBWyVgbr5l6OQ5emRiETIjsnmIl4Ji8 -``` - -## 7. Base64URL 算法 - -前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。 - -JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL 里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成_ 。这就是 Base64URL 算法。 - -## 8. JWT 如何保存? - -关于浏览器应该将 JWT 保存在哪?这个问题,其实也困扰了我很久。 - -如果使用搜索引擎去查,我相信你也一定会被他们绕晕。 - -比如在这篇帖子([When and how to use it](https://blog.logrocket.com/jwt-authentication-best-practices/) )里,作者的观点是,不应该保存在 localstorage 和 session storage,因为这样,第三方的脚本就能直接获取到。 - -作者推荐的做法是,将 JWT 保存在 cookie 里,并设置 HttpOnly。 - -![](http://image.iswbm.com/image-20200705233446534.png) - -再比如这一篇帖子([JWT(JSON Web Token) : Implementation with Node](https://medium.com/@am_pra_veen/jwt-json-web-token-implementation-with-node-d0661d4c7cbb))提到了要把 JWT 保存到 local-storage。 - -![](http://image.iswbm.com/image-20200705233925900.png) - -因此,我决定不再看网络上关于 『应将 JWT 保存的哪?』的文章。而是自己思考,以下是我个人观点,不代表一定正确,仅供参考 。 - -JWT 的保存位置,可以分为如下四种 - -1. 保存在 localStorage -2. 保存在 sessionStorage -3. 保存在 cookie -4. 保存在 cookie 并设置 HttpOnly - -第一种和第二种其实可以归为一类,这一类有个特点,就是该域内的 js 脚本都可以读取,这种情况下 JWT 通过 js 脚本放入 Header 里的 Authorization 字段,会存在 XSS 攻击风险。 - -第三种,与第四种相比,区别在于 cookie 有没有标记 HttpOnly,没有标记 HttpOnly 的 cookie ,客户端可以将 JWT 通过 js 脚本放入 Header 里的 Authorization 字段。这么看好像同时存在CSRF 攻击风险和 XSS 攻击风险,实则不然,我们虽然将 JWT 存储在 cookie 里,但是我们的服务端并没有利用 cookie 里的 JWT 直接去鉴权,而是通过 header 里的 Authorization 去鉴权,因此这种方法只有 XSS 攻击风险,而没有 CSRF 攻击风险。 - -而第四种,加了 HttpOnly 标记,意味着这个 cookie 无法通过js脚本进行读取和修改,杜绝了 XSS 攻击的发生。与此同时,网站自身的 js 脚本也无法利用 cookie 设置 header 的Authorization 字段,因此只能通过 cookie 里的 JWT 去鉴权,所以不可避免还是存在 CSRF 攻击风险。 - -如此看来,好像不管哪一种都有弊端,没有一种完美的解决方案。 - -![](http://image.iswbm.com/image-20200706001903273.png) - -是的,事实也确实如此。 - -所以我的观点是,开发人员应当根据实际情况来选择 JWT 的存储位置。 - -- 当访问量/业务量不是很大时,可以使用 CSRF Token 来防止 CSRF 攻击 -- 而如果访问量/业务量对服务器造成很大压力,或觉得服务器共享 token 对架构要求太高了,那就抛弃CSRF Token 的方式,而改用 JWT。选择了 JWT ,就面临着要将 JWT 存储在哪的问题。 -- 若选择了 JWT ,那么请不要使用 cookie HttpCookie 来存储它,因为使用它还是会有 CSRF 攻击风险。 -- 那另外三种如何选择呢?这三种无论使用哪种,都不可避免有 XSS 攻击风险。我的思路是,XSS 攻击通过其他的手段来规避,这里使用JWT 只有 防御 CSRF 攻击与服务器性能的优化,这两个目标。 -- 那我剩下的三种,我建议是使用 cookie 存储,但不使用 cookie 来鉴权。服务器鉴权还是通过请求里的 Authorization 字段(通过js写入 Header 的)。 - -当然,如果你觉得你通过 `Referer Check` 、`加验证码` 等其他手段,已经可以保证不受 CSRF 攻击的威胁,此时你使用 JWT ,就可以选择使用 JWT + cookie HttpOnly,扼杀 XSS 攻击的可能。 - - - -## 9. JWT 如何发送? - -通过上面第七节的描述,其实我也讲到了 JWT 根据不同场景可以选择两种发送方式 - -- 第一种:将 JWT 放在 Header 里的 `Authorization` 字段,并使用 `Bearer`标注 - -```shell -'Authorization': 'Bearer ' + ${token} -``` - -- 第二种:把 JWT 放入 cookie ,发送给服务端,虽然发送。但是不使用它来鉴权。 - -## 10. JWT 如何校验? - -后端收到请求后,从 Header 中取出 `Authorization` 里的 JWT ,使用之前的签名算法对 header 和 payload 再次计算生成新的签名,并与 JWT 里的签名进行对比,如果一样,说明校验通过,是个合法的 Token。 - -```shell -HMACSHA256( - base64UrlEncode(header) + "." + - base64UrlEncode(payload), - secret) -``` - -验证是个合法的 Token 后,还要检查这个 Token 是否过期,在 JWT 里的 payload 中,有 Token 的过期时间,可以通过它来检查 Token 是否可以用? - -payload 里同时还有用户的相关信息,有了这些信息后,后端就可以知道这是哪个用户的请求了,到这里一切都验证通过,就可以执行相关的业务逻辑了。 - -前面我使用了 `pyjwt` 这个来生成 JWT ,事实上,这个库也可以用来验证 token。 - -使用 jwt 的 decode 会先验签再解码取得 payload 的信息。 - -```python ->>> import jwt ->>> token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiODg4ZjIwZDktMDdlZC00MWJkLWIzMjktMTdjNmYwNThhMTRlIiwidXNlcm5hbWUiOiJ3YW5nYm0iLCJleHAiOjE1OTQ0MzQzMjZ9.kkEMhSx732lO6HWWNPNVQDHR9WuCEVxKgNol-LTbCP8" ->>> jwt.decode(token, 'minggezuishuai', algorithms=['HS256']) -{'user_id': '888f20d9-07ed-41bd-b329-17c6f058a14e', 'username': 'wangbm', 'exp': 1594434326} ->>> -``` - -验签同样也可以使用命令行 - -```shell -$ pyjwt --key="minggezuishuai" decode eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiODg4ZjIwZDktMDdlZC00MWJkLWIzMjktMTdjNmYwNThhMTRlIiwidXNlcm5hbWUiOiJ3YW5nYm0iLCJleHAiOjE1OTQ0MzQ4NTl9.A792th12kY1YnBWyVgbr5l6OQ5emRiETIjsnmIl4Ji8 -{"user_id": "888f20d9-07ed-41bd-b329-17c6f058a14e", "username": "wangbm", "exp": 1594434859} -``` - -如果不想验证签名及有效期,而只是想取下payload,只需加个`--no-verify` 参数即可 - -```shell -$ pyjwt --key="minggezuishuai" decode --no-verify {token} -``` - -更的详细使用方法,可以执行 `pyjwt --help` 学习或者前往官方文档:https://pyjwt.readthedocs.io/en/latest/index.html - -## 11. JWT 的最佳搭配 - -在真正的业务中,是有可能使用 payload 来存放一些用户的敏感信息的,由于 payload 是采用 Base64URL 转换而成,它是可逆的,因此当你在 payload 存放敏感信息时,需要保证 JWT 的安全性,不能让其暴露在 『阳光』下。 - -为此,JWT 最好与 HTTPS 配合使用,利用 HTTPS 的非对称加密来保证 JWT 的安全。 - -具体是如何保障的呢? - -HTTPS 是基于 SSL/TLS 的非对称加密算法工作的。 - -在非对称加密算法的规则下,服务器会拥有一个叫做『私钥』的东西,它是私有的,除了服务器之外,不能再有第二个人知道它。 - -而相对的,所有的客户端(浏览器)同时也会有一个叫做『公钥』的东西,它是对所有人公开的,任何人都可以拥有它。它与『私钥』合称为一个密钥对。 - -公钥和私钥的规则是: - -- **公钥加密的东西,只有私钥能解。**因此如果 JWT 的 payload 里有你的敏感信息,那也不要紧,只要把 JWT 用公钥(**前提是这个公钥得是正确的,下面会说到**)加密一下,那黑客就算拿到了这个密文,也无法解密,因为私钥只有服务器才有。 -- **私钥加密的东西,所有的公钥也都能解。**因此服务器发给客户端的 JWT 的payload 尽量不要有敏感信息。 - -![](http://image.iswbm.com/20200711141903.png) - -那么问题又来了,如果客户端拿到的公钥,是黑客伪造的,客户端拿着这个假公钥加密自己的敏感信息,然后发出去,黑客在拿到这个用自己伪造的公钥加密的数据,非常开心,因为这个公钥对应的私钥在自己手里,自己是可以解密得到里面的数据的。 - -因此如何保证服务器发给客户端(浏览器)的公钥是正确的呢? - -答案是通过**数字证书**来保证。但是由于这个不是本文的重点,因此我将这块内容放在后面的文章详细解释。 - -## 12. 总结写在最后 - -最后,我总结一下,本文的要点: - -1. CSRF 攻击的产生,需要cookie 的『助攻』,否则无法完成。 -2. CRSF 是利用 cookie,而不是盗取 cookie,这点一定要明白。 -3. 但也并不是使用了 cookie 就会有 CSRF 风险,而应该说是用 cookie 去做鉴权才会有 CSRF 风险,参考 CSRF Token (把 token 存储在 cookie 的情况)和 JWT (把 token 存储在 cookie 的情况)。 -4. CSRF Token 和 JWT 虽然都可以做到防御 CSRF 攻击,但其实无论是哪个都无法同时做到防御 CSRF 和 XSS 攻击,在阻止了 CSRF 攻击后, 需要再通过其他手段来减少 XSS 攻击的可能性。 -5. JWT 就是一个由服务端按照一定的规则生成的字符串, -6. JWT 的目的是为了做一个无状态的 session,避免去频繁查询 session,减少了对服务器产生的压力,简化后端架构模型。它的主要用途是解决跨域认证的问题,而解决 CSRF 跨域攻击只是它的附带功能。 -7. payload 是经过 base64URL 算法转换而成的字符串,是可逆的,因此尽量不要存放敏感数据,如若非要存放敏感数据,最好与 HTTPS 协议搭配使用,避免数据泄露。 -8. JWT 的保存位置与方式,没有绝对的方案,具体如何选择要视情况而定。 - -## 13. 参考文章 - -- [咱妈说别乱点链接之浅谈CSRF攻击](https://cloud.tencent.com/developer/article/1004943) -- [JSON Web Token 入门教程](http://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html) -- [Where to Store your JWTs – Cookies vs HTML5 Web Storage](https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage) - -- [JWT 超详细分析](https://www.cnblogs.com/DeadBoy/p/11481146.html) - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c10/c10_07.rst b/source/c10/c10_07.rst deleted file mode 100644 index 8249277..0000000 --- a/source/c10/c10_07.rst +++ /dev/null @@ -1,622 +0,0 @@ -10.7 网络知识扫盲:CSRF 跨域攻击与JWT跨域认证 -============================================= - -|image0| - -|image1| - -1. 什么是跨域请求 ------------------ - -要明白什么叫跨域请求,首先得知道什么叫域。 - -域,是指由 ``协议`` + ``域名`` + ``端口号`` 组成的一个虚拟概念。 - -|image2| - -如果两个域的协议、域名、端口号都一样,就称他们为同域,但是只要有其中一个不一样,就不是同域。 - -那么 ``跨域请求`` 又是什么意思呢? - -简单来说,就是在一个域内请求了另一个域的资源,由于域不一致,会有安全隐患。 - -2. 跨域请求的安全隐患 ---------------------- - -有一个词,叫 CSRF (Cross-site request forgery)攻击,中文名是 -``跨站请求伪造``\ 。 - -简单来说呢,就是攻击者盗用了你的身份,以你的名义发送恶意请求,它能做的坏事有很多,比如以你的名义发邮件,发消息,购物,盗取帐号等。 - -CSRF 的实际工作原理是怎样的? - -比如现在有两个网站,A 网站是真实受信息的网站,而 B网站是危险网站。 - -当你登陆 A 网站后,浏览器会存储 A 网站服务器给你生成的 sessionid 存入 -cookie,有了这个 cookie -,就拥有了你的帐号权限,以后请求资料,就不用再次登陆啦。 - -对于真实用户来说,是便利,可对于攻击者来说,却是可乘之机。 - -.. figure:: http://image.iswbm.com/20200707220426.png - :alt: Cookie + Session 方法 - - Cookie + Session 方法 - -他们可以使用各种社工学引导你点击他们的链接/网站,然后利用你的浏览器上存储的 -cookie ,然后在自己的 网站B 发起对 网站A -的请求,获取一些隐私信息,做一些侵害用户权益的事情。这便是一个完整的 -CSRF 攻击。 - -3. 跨域请求的安全防御 ---------------------- - -完成一次完整的 CSRF 攻击,只要两个步骤: - -1. 登录受信任网站A,并本地已经存储了 Cookie -2. 在不登出A的情况下,访问危险网站B,网站 B 诱导你发 A 发请求。 - -很多浏览器用户对于网络安全是无意识的,因此我们不能指望通过规范用户行为来避免CSRF攻击。 - -那如何从技术手段规避一定的 CSRF 攻击的风险呢? - -1. 利用浏览器的同源策略:最基础的安全策略 -2. 对请求的来源进行验证:Referer Check -3. 使用验证码强制使用户进行交互确认,保证请求是用户发起 -4. CSRF Token,注意不要使用 cookie 来存储token -5. JSON Web Token - -以上是我知道的历史上用来抵御 CSRF 攻击的方法 - -- 有的虽然实现简单,但是不够安全 - -- 有的虽然安全,但是用户体验不好 - -- 有的虽然安全,用户体验好,但是有缺点 - -具体应该选哪一种呢,不妨继续往下看。 - -3.1 同源策略 -~~~~~~~~~~~~ - -浏览器上有一个同源策略(SOP,全称 Same origin -policy),它会在一定程度上禁止这种跨域请求的发生。 - -但同源策略是最基本的安全策略,对于低级的 CSRF 攻击 ,它是很有效果的。 - -可以说 Web -是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。 - -同源策略在提升了 Web前端的安全性的同时,也牺牲了Web拓展上的灵活性。 - -设想若把html、js、css、flash,image等文件全部布置在一台服务器上,小网站这样凑活还行,大中网站如果这样做服务器根本受不了的,因此同源策略,就像是双刃剑。不过这些都是有解的。 - -3.2 Referer Check -~~~~~~~~~~~~~~~~~ - -在 HTTP 协议中,有一个字段叫做 Referer,它记录了HTTP 请求的来源地址。 - -当发生 CSRF 攻击时,这个来源地址,会变成危险网站 -B,因此只要在服务端校验这个 Referer -是不是和自己同一个域就可以判断这个请求是跨站请求。 - -.. figure:: http://image.iswbm.com/20200705193118.png - :alt: Referer Check 图解 - - Referer Check 图解 - -但这种方法,也是有局限性的,在一些非主流的浏览器,或者使用了那些非常古老的浏览器版本,这个 -Referer 字段,是有可能会被篡改的。 - -退一步讲,假设你使用了最安全的最新版本的浏览器,这个值无法被篡改,依旧还是有安全隐患。 - -因为有些用户出于某些隐私考虑,会在浏览器设置关闭这个 Referer -字段,也有的网站会使用一些技术手段使用请求不携带 Referer 字段。 - -因此,当你要使用 Referer Check 来做为 防御 CSRF -攻击的主要手段时,请确保你的用户群体使用的一定是最安全的最新版本的浏览器,并且默认用户不会手动关闭 -Referer 。 - -3.3 加验证码 -~~~~~~~~~~~~ - -验证码,强制用户必须与应用进行交互,才能完成最终请求。 - -其实加验证码,是能很好遏制 CSRF -攻击,但是网站总不能给所有的操作都加上验证码吧,那样的话,用户估计都跑光光了,因此为了保证用户体验,验证码只能作为一种辅助手段,不能作为主要解决方案。 - -3.4 CSRF Token -~~~~~~~~~~~~~~ - -CSRF -攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 -cookie 中,因此黑客可以在不知道这些验证信息的情况下,直接利用用户自己的 -cookie 来通过安全验证。 - -所以要抵御 -CSRF,关键在于要在请求中放入黑客所不能伪造的信息,并且该信息不存在于 -cookie 之中(不然黑客又能拿到了)。 - -业界普遍的防御方案是使用 CSRF Token - -使用 CSRF Token 根据token验证方式的不同,也可以分为两种: - -**第一种**\ :如图所示 - -|image3| - -1. 当用户请求一个更新用户名的页面时,由服务端生成一个随机数 - Token,然后放入HTML表单中传给浏览器,并且存入 session 中。 - -2. 当用户提交表单请求时,表单数据会带上这个 Token 发送给服务端 ; -3. 服务端收到表单请求后,会从表单数据里取出 Token,然后和 session 里的 - token - 进行对比,如果是一样的,就是合法的用户请求,将新的用户名存入数据库,如果不一样,那就是非法的请求,应当拒绝。 - -**第二种**\ : - -|image4| - -1. 当用户请求一个更新用户名的页面时,由服务端生成一个随机数 - Token,然后放入HTML表单中,并且会把这个 Token 放在 cookie - 里发给浏览器。 -2. 当用户提交表单请求时,表单数据会带上这个 Token - 发送给服务端,并且带上携带 token 的 cookie ; -3. 服务端收到表单请求后,会从表单数据里取出 Token,与 cookie 里的 token - 进行对比,如果是一样的,就是合法的用户请求,将新的用户名存入数据库,如果不一样,那就是非法的请求,应当拒绝。 - -3.5 新增 Header -~~~~~~~~~~~~~~~ - -使用上面的 CSRF Token 已经可以避免 CSRF -攻击,但是它却有可能又引入了另一个问题。 - -若 CSRF Token 没有使用 cookie,就必须要将 Token 存储在服务端的 Session -中,这样就会面临几个问题 - -1. 服务端每生成一个 Token,都会存放入 session - 中,而随着用户请求的增多,服务端的开销会明显增大。 -2. 如果网站有多个子域,分别对应不同的服务器,比如 taobao.com - 后台是服务器 a,zhibo.baotao.com 后台是 服务器b, - 不同子域要想使用同一个 Token,就要求所有的服务器要能共享这个 - Token。一般要有一个中心节点(且应是一个集群)来存储这个Token,这样看下来,架构就变得更加复杂了。 - -想要解决这些问题,可以使用我们接下来要讲的 JWT(全称:JSON Web Token) - -使用了 JWT 后,有了哪些变化呢 - -1. 服务器只负责生成 Token和校验Token,而不再存储Token -2. 将服务器的压力分摊给了所有的客户端。 -3. 服务端的 鉴权不使用 cookie ,而是由新增的 Header 字段:Authorization - 里的 JWT 。 - -JWT 是本篇文章重要知识点之一,下面我会详细说说关于 JWT 的内容。 - -4. JWT 的工作原理及目的 ------------------------ - -为了让你直观感受 JWT 的工作原理,我画了下面这张图 - -.. figure:: http://image.iswbm.com/20200705220524.png - :alt: JWT 工作图解 - - JWT 工作图解 - -1. 用户以 Web表单 的形式,将自己的用户名和密码 POST 到后端的接口。 -2. 后端核对用户名和密码成功后,会计算生成JWT Payload - 字符串(具体计算方法,后续会讲),然后返回 response 给浏览器。 -3. 浏览器收到 JWT 后,将其保存在 cookie 里或者 localStorage 或者 - sessionStorage 里(具体如何选,后面会说)。 -4. 后续在该域上发出的请求,都会将 JWT放入HTTP Header 中的 Authorization - 字段。 -5. 后端收到新请求后,会使用密钥验证 JWT 签名。 -6. 验证通过后后端使用 JWT - 中包含的用户信息进行其他相关操作,返回相应结果。 - -|image5| - -JWT 的诞生并不是解决 CSRF 跨域攻击,而是解决跨域认证的难题。 - -举例来说,A 网站和 B -网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,这应该如何实现呢? - -一种解决方案是 session -数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。 - -另一种方案是服务器索性不保存 session -数据了,所有数据都保存在客户端,每次请求都发回服务器。 - -JWT 就是这种方案的一个优秀代表。 - -5. JWT 如何生成? ------------------ - -JWT 其实就是一个字符串,比如下面这样 - -.. code:: shell - - eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ - -仔细观察,会发现它里面有三个 ``.`` ,以 ``.`` 为分界,可以将 JWT -分为三部分。 - -|image6| - -1. **第一部分**\ :头部(Header) -2. **第二部分**\ :载荷(Payload) -3. **第三部分**\ :签名(Signature) - -|image7| - -5.1 头部(Header) -~~~~~~~~~~~~~~~~~~ - -JWT 的头部承载两部分信息: - -- 声明类型:这里是 JWT -- 声明加密的算法:通常直接使用 HMAC SHA256 - -完整的头部就像下面这样的JSON: - -.. code:: bash - - { - "typ": "JWT", - "alg": "HS256" - } - -然后将头部进行 Base64URL 算法编码转换,构成了第一部分 - -.. code:: shell - - eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 - -5.2 载荷(Payload) -~~~~~~~~~~~~~~~~~~~ - -载荷,同样也是个 JSON -对象,它是存放有效信息的地方,但不建议存放密码等敏感信息。 - -JWT 规定了7个官方字段,供选用: - -- iss (issuer):签发人 -- exp (expiration time):过期时间 -- sub (subject):主题 -- aud (audience):受众 -- nbf (Not Before):生效时间 -- iat (Issued At):签发时间 -- jti (JWT ID):编号 - -除了官方字段,你还可以在这个部分定义私有字段,下面就是一个例子。 - -注意,JWT -默认是不加密的,任何人都可以读到,所以不要把秘密信息放在这个部分。 - -.. code:: json - - { - "sub": "1234567890", - "name": "John Doe", - "admin": true - } - -然后将其进行 Base64URL 算法转换,得到 JWT 的第二部分。 - -.. code:: shell - - eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 - -5.3 签名(Signature) -~~~~~~~~~~~~~~~~~~~~~ - -Signature 部分是对前两部分的签名,防止数据篡改。 - -首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 -Header 里面指定的签名算法(默认是 HMAC -SHA256),按照下面的公式产生签名。 - -:: - - HMACSHA256( - base64UrlEncode(header) + "." + - base64UrlEncode(payload), - secret) - -算出签名以后,把 Header、Payload、Signature -三个部分拼成一个字符串,每个部分之间用“点”(\ ``.``\ )分隔,就可以返回给用户。 - -6. 如何手动生成 JWT? ---------------------- - -如果你想手动生成一个 JWT 用于测试,有两种方法 - -**第一种:使用 https://jwt.io/ 这个网站 。** - -我使用前面的 header 和 payload,然后使用 secret 密钥:\ ``Python`` - -最后生成的 JWT 结果如下 - -.. code:: shell - - eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.3wGDum3_A8tAt1bdal5CpYbIUlpHfPQxs96Ijx883kI - -|image8| - -**第二种:使用 Python 代码生成** - -首先安装一下 pyjwt 这个库 - -.. code:: shell - - $ pip install pyjwt - -然后就可以在代码中使用它 - -.. code:: python - - import jwt - import datetime - import uuid - - salt = 'minggezuishuai' - - # 构造header , 这里不写默认的也是 - headers = { - 'typ': 'JWT', - 'alg': 'HS256' - } - - # 构造payload - payload = { - 'user_id': str(uuid.uuid4()), # 自定义用户ID - 'username': "wangbm", # 自定义用户名 - 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5) # 超时时间,取现在时间,五分钟后token失效 - } - token = jwt.encode(payload=payload, key=salt, algorithm="HS256", headers=headers).decode('utf-8') - - # token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiODg4ZjIwZDktMDdlZC00MWJkLWIzMjktMTdjNmYwNThhMTRlIiwidXNlcm5hbWUiOiJ3YW5nYm0iLCJleHAiOjE1OTQ0MzQzMjZ9.kkEMhSx732lO6HWWNPNVQDHR9WuCEVxKgNol-LTbCP8 - -如果你只是测试使用,完全不用写那么多代码,用命令行即可 - -.. code:: shell - - $ pyjwt --key="minggezuishuai" encode user_id=888f20d9-07ed-41bd-b329-17c6f058a14e username=wangbm exp=+120 - eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiODg4ZjIwZDktMDdlZC00MWJkLWIzMjktMTdjNmYwNThhMTRlIiwidXNlcm5hbWUiOiJ3YW5nYm0iLCJleHAiOjE1OTQ0MzQ4NTl9.A792th12kY1YnBWyVgbr5l6OQ5emRiETIjsnmIl4Ji8 - -7. Base64URL 算法 ------------------ - -前面提到,Header 和 Payload 串型化的算法是 Base64URL。这个算法跟 Base64 -算法基本类似,但有一些小的不同。 - -JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 -api.example.com/?token=xxx)。Base64 有三个字符+、/和=,在 URL -里面有特殊含义,所以要被替换掉:=被省略、+替换成-,/替换成\_ 。这就是 -Base64URL 算法。 - -8. JWT 如何保存? ------------------ - -关于浏览器应该将 JWT 保存在哪?这个问题,其实也困扰了我很久。 - -如果使用搜索引擎去查,我相信你也一定会被他们绕晕。 - -比如在这篇帖子(\ `When and how to use -it `__ -)里,作者的观点是,不应该保存在 localstorage 和 session -storage,因为这样,第三方的脚本就能直接获取到。 - -作者推荐的做法是,将 JWT 保存在 cookie 里,并设置 HttpOnly。 - -|image9| - -再比如这一篇帖子(\ `JWT(JSON Web Token) : Implementation with -Node `__\ )提到了要把 -JWT 保存到 local-storage。 - -|image10| - -因此,我决定不再看网络上关于 『应将 JWT -保存的哪?』的文章。而是自己思考,以下是我个人观点,不代表一定正确,仅供参考 -。 - -JWT 的保存位置,可以分为如下四种 - -1. 保存在 localStorage -2. 保存在 sessionStorage -3. 保存在 cookie -4. 保存在 cookie 并设置 HttpOnly - -第一种和第二种其实可以归为一类,这一类有个特点,就是该域内的 js -脚本都可以读取,这种情况下 JWT 通过 js 脚本放入 Header 里的 -Authorization 字段,会存在 XSS 攻击风险。 - -第三种,与第四种相比,区别在于 cookie 有没有标记 HttpOnly,没有标记 -HttpOnly 的 cookie ,客户端可以将 JWT 通过 js 脚本放入 Header 里的 -Authorization 字段。这么看好像同时存在CSRF 攻击风险和 XSS -攻击风险,实则不然,我们虽然将 JWT 存储在 cookie -里,但是我们的服务端并没有利用 cookie 里的 JWT 直接去鉴权,而是通过 -header 里的 Authorization 去鉴权,因此这种方法只有 XSS 攻击风险,而没有 -CSRF 攻击风险。 - -而第四种,加了 HttpOnly 标记,意味着这个 cookie -无法通过js脚本进行读取和修改,杜绝了 XSS -攻击的发生。与此同时,网站自身的 js 脚本也无法利用 cookie 设置 header -的Authorization 字段,因此只能通过 cookie 里的 JWT -去鉴权,所以不可避免还是存在 CSRF 攻击风险。 - -如此看来,好像不管哪一种都有弊端,没有一种完美的解决方案。 - -|image11| - -是的,事实也确实如此。 - -所以我的观点是,开发人员应当根据实际情况来选择 JWT 的存储位置。 - -- 当访问量/业务量不是很大时,可以使用 CSRF Token 来防止 CSRF 攻击 -- 而如果访问量/业务量对服务器造成很大压力,或觉得服务器共享 token - 对架构要求太高了,那就抛弃CSRF Token 的方式,而改用 JWT。选择了 JWT - ,就面临着要将 JWT 存储在哪的问题。 -- 若选择了 JWT ,那么请不要使用 cookie HttpCookie - 来存储它,因为使用它还是会有 CSRF 攻击风险。 -- 那另外三种如何选择呢?这三种无论使用哪种,都不可避免有 XSS - 攻击风险。我的思路是,XSS 攻击通过其他的手段来规避,这里使用JWT 只有 - 防御 CSRF 攻击与服务器性能的优化,这两个目标。 -- 那我剩下的三种,我建议是使用 cookie 存储,但不使用 cookie - 来鉴权。服务器鉴权还是通过请求里的 Authorization 字段(通过js写入 - Header 的)。 - -当然,如果你觉得你通过 ``Referer Check`` 、\ ``加验证码`` -等其他手段,已经可以保证不受 CSRF 攻击的威胁,此时你使用 JWT -,就可以选择使用 JWT + cookie HttpOnly,扼杀 XSS 攻击的可能。 - -9. JWT 如何发送? ------------------ - -通过上面第七节的描述,其实我也讲到了 JWT -根据不同场景可以选择两种发送方式 - -- 第一种:将 JWT 放在 Header 里的 ``Authorization`` 字段,并使用 - ``Bearer``\ 标注 - -.. code:: shell - - 'Authorization': 'Bearer ' + ${token} - -- 第二种:把 JWT 放入 cookie - ,发送给服务端,虽然发送。但是不使用它来鉴权。 - -10. JWT 如何校验? ------------------- - -后端收到请求后,从 Header 中取出 ``Authorization`` 里的 JWT -,使用之前的签名算法对 header 和 payload 再次计算生成新的签名,并与 JWT -里的签名进行对比,如果一样,说明校验通过,是个合法的 Token。 - -.. code:: shell - - HMACSHA256( - base64UrlEncode(header) + "." + - base64UrlEncode(payload), - secret) - -验证是个合法的 Token 后,还要检查这个 Token 是否过期,在 JWT 里的 -payload 中,有 Token 的过期时间,可以通过它来检查 Token 是否可以用? - -payload -里同时还有用户的相关信息,有了这些信息后,后端就可以知道这是哪个用户的请求了,到这里一切都验证通过,就可以执行相关的业务逻辑了。 - -前面我使用了 ``pyjwt`` 这个来生成 JWT ,事实上,这个库也可以用来验证 -token。 - -使用 jwt 的 decode 会先验签再解码取得 payload 的信息。 - -.. code:: python - - >>> import jwt - >>> token="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiODg4ZjIwZDktMDdlZC00MWJkLWIzMjktMTdjNmYwNThhMTRlIiwidXNlcm5hbWUiOiJ3YW5nYm0iLCJleHAiOjE1OTQ0MzQzMjZ9.kkEMhSx732lO6HWWNPNVQDHR9WuCEVxKgNol-LTbCP8" - >>> jwt.decode(token, 'minggezuishuai', algorithms=['HS256']) - {'user_id': '888f20d9-07ed-41bd-b329-17c6f058a14e', 'username': 'wangbm', 'exp': 1594434326} - >>> - -验签同样也可以使用命令行 - -.. code:: shell - - $ pyjwt --key="minggezuishuai" decode eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiODg4ZjIwZDktMDdlZC00MWJkLWIzMjktMTdjNmYwNThhMTRlIiwidXNlcm5hbWUiOiJ3YW5nYm0iLCJleHAiOjE1OTQ0MzQ4NTl9.A792th12kY1YnBWyVgbr5l6OQ5emRiETIjsnmIl4Ji8 - {"user_id": "888f20d9-07ed-41bd-b329-17c6f058a14e", "username": "wangbm", "exp": 1594434859} - -如果不想验证签名及有效期,而只是想取下payload,只需加个\ ``--no-verify`` -参数即可 - -.. code:: shell - - $ pyjwt --key="minggezuishuai" decode --no-verify {token} - -更的详细使用方法,可以执行 ``pyjwt --help`` -学习或者前往官方文档:https://pyjwt.readthedocs.io/en/latest/index.html - -11. JWT 的最佳搭配 ------------------- - -在真正的业务中,是有可能使用 payload 来存放一些用户的敏感信息的,由于 -payload 是采用 Base64URL 转换而成,它是可逆的,因此当你在 payload -存放敏感信息时,需要保证 JWT 的安全性,不能让其暴露在 『阳光』下。 - -为此,JWT 最好与 HTTPS 配合使用,利用 HTTPS 的非对称加密来保证 JWT -的安全。 - -具体是如何保障的呢? - -HTTPS 是基于 SSL/TLS 的非对称加密算法工作的。 - -在非对称加密算法的规则下,服务器会拥有一个叫做『私钥』的东西,它是私有的,除了服务器之外,不能再有第二个人知道它。 - -而相对的,所有的客户端(浏览器)同时也会有一个叫做『公钥』的东西,它是对所有人公开的,任何人都可以拥有它。它与『私钥』合称为一个密钥对。 - -公钥和私钥的规则是: - -- **公钥加密的东西,只有私钥能解。**\ 因此如果 JWT 的 payload - 里有你的敏感信息,那也不要紧,只要把 JWT - 用公钥(\ **前提是这个公钥得是正确的,下面会说到**\ )加密一下,那黑客就算拿到了这个密文,也无法解密,因为私钥只有服务器才有。 -- **私钥加密的东西,所有的公钥也都能解。**\ 因此服务器发给客户端的 JWT - 的payload 尽量不要有敏感信息。 - -|image12| - -那么问题又来了,如果客户端拿到的公钥,是黑客伪造的,客户端拿着这个假公钥加密自己的敏感信息,然后发出去,黑客在拿到这个用自己伪造的公钥加密的数据,非常开心,因为这个公钥对应的私钥在自己手里,自己是可以解密得到里面的数据的。 - -因此如何保证服务器发给客户端(浏览器)的公钥是正确的呢? - -答案是通过\ **数字证书**\ 来保证。但是由于这个不是本文的重点,因此我将这块内容放在后面的文章详细解释。 - -12. 总结写在最后 ----------------- - -最后,我总结一下,本文的要点: - -1. CSRF 攻击的产生,需要cookie 的『助攻』,否则无法完成。 -2. CRSF 是利用 cookie,而不是盗取 cookie,这点一定要明白。 -3. 但也并不是使用了 cookie 就会有 CSRF 风险,而应该说是用 cookie - 去做鉴权才会有 CSRF 风险,参考 CSRF Token (把 token 存储在 cookie - 的情况)和 JWT (把 token 存储在 cookie 的情况)。 -4. CSRF Token 和 JWT 虽然都可以做到防御 CSRF - 攻击,但其实无论是哪个都无法同时做到防御 CSRF 和 XSS 攻击,在阻止了 - CSRF 攻击后, 需要再通过其他手段来减少 XSS 攻击的可能性。 -5. JWT 就是一个由服务端按照一定的规则生成的字符串, -6. JWT 的目的是为了做一个无状态的 session,避免去频繁查询 - session,减少了对服务器产生的压力,简化后端架构模型。它的主要用途是解决跨域认证的问题,而解决 - CSRF 跨域攻击只是它的附带功能。 -7. payload 是经过 base64URL - 算法转换而成的字符串,是可逆的,因此尽量不要存放敏感数据,如若非要存放敏感数据,最好与 - HTTPS 协议搭配使用,避免数据泄露。 -8. JWT 的保存位置与方式,没有绝对的方案,具体如何选择要视情况而定。 - -13. 参考文章 ------------- - -- `咱妈说别乱点链接之浅谈CSRF攻击 `__ -- `JSON Web Token - 入门教程 `__ -- `Where to Store your JWTs – Cookies vs HTML5 Web - Storage `__ - -- `JWT 超详细分析 `__ - -|image13| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200711143644.png -.. |image2| image:: http://image.iswbm.com/20200705171112.png -.. |image3| image:: http://image.iswbm.com/image-20200707221742925.png -.. |image4| image:: http://image.iswbm.com/image-20200707222024941.png -.. |image5| image:: http://image.iswbm.com/20200711144042.png -.. |image6| image:: http://image.iswbm.com/20200705212820.png -.. |image7| image:: http://image.iswbm.com/20200705215033.png -.. |image8| image:: http://image.iswbm.com/20200706005103.png -.. |image9| image:: http://image.iswbm.com/image-20200705233446534.png -.. |image10| image:: http://image.iswbm.com/image-20200705233925900.png -.. |image11| image:: http://image.iswbm.com/image-20200706001903273.png -.. |image12| image:: http://image.iswbm.com/20200711141903.png -.. |image13| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c10/c10_08.md b/source/c10/c10_08.md deleted file mode 100644 index f17ccc4..0000000 --- a/source/c10/c10_08.md +++ /dev/null @@ -1,365 +0,0 @@ -# 10.8 数字证书、签名到底是什么? - -![](http://image.iswbm.com/20200602135014.png) - -我们都知道 HTTP 协议都是明文传输内容,为了保证数据传输的安全,HTTPS 协议就应运而生了,但它其实并不是一个全新的协议,而是HTTP 协议基本之上 再加上SSL/TLS 协议。 - -因此当你访问一个支持 https 的网站时,是需要先进行 SSL/TLS 握手建立连接的。 - -SSL/TLS 握手的目的是为了 **安全** 地协商出一份**对称加密**的密钥,有了这个密钥之后,后续的数据全部使用这个密钥进行加密。 - -这个过程其实挺有趣的,涉及到的知识点,专业名词也很多,比如对称加密,非对称加密,信息摘要,数字签名,数字证书,公钥和私钥。本篇文章,会详细地介绍这些极易混淆的专业名词。 - -在讲解之前,我先给你提出几个摸底问题,如果你已经还不能够熟练回答,那么本篇文章会给你答案: - -1、 对称加密和非对称加密,各有什么优缺点? - -2、对称加密和非对称加密,是排他关系吗?是否可以搭配使用? - -3、摘要和加密有什么区别?有了摘要算法为什么还要有加密算法? - -4、如何在通信时同时做到保密性、高效性? - -5、如何申请数字证书?证书分为哪几种?SSL 证书如何部署? - -6、完整说下证书申请、证书签发、证书下发客户端、客户端验证证书、数据加密传输的整个流程? - -## 1. 对称加密与非对称加密 - -### 对称加密 - -`对称加密`是通信双方共同拥有一把密钥。 - -这把密钥可以把明文加密(encryption)成密文,也可以把密文解密(decryption)成明文。 - -常见的对称加密算法有AES、DES、RC4,其中最常用的是AES。 - -对称加密的优点是:速度快。 - -同时也有一个缺点,就是不那么安全,一旦你的密钥被别人窃取了,所有的数据就会在网络的世界里裸奔。 - -### 非对称加密 - -与 对称加密相对的是 `非对称加密`。 - -通信双方持有不同的密钥。 - -服务端的密钥,称之为 `私钥`(private key),客户端的密钥,称之为 `公钥` (public key)。 - -他们二者的区别是: - -1、私钥应仅在服务端保存,绝不可泄露。而公钥可以存在于任何的客户端,即使黑客拿到了也没有关系。 - -2、公钥加密的密文只有相对应的私钥才能解密 - -![](http://image.iswbm.com/image-20200723233619429.png) - -3、私钥加密的内容,所有与之相对应的公钥都能解密。 - -4、公钥通常用来生成签名,私钥用来验证签名。 - -![](http://image.iswbm.com/image-20200724121333485.png) - -5、公钥和私钥是相对的,两者本身并没有规定哪一个必须是公钥或私钥。这意味着,公钥只要不对外公开,那就可以做为私钥,私钥公开后也可以做为公钥。 - -典型的非对称加密算法有 RSA 。 - -非对称加密的优点,就是安全系数特别高。缺点就是速度会慢一些。 - -### 对称与非对称加密结合 - -当客户端收到的公钥是准确的时候,通信就是安全的。 - -因为用正确公钥加密过的密文,只有服务端的私钥能解。 - -那么如何保证,客户端收到正确的公钥呢? - -答案是:通过非对称加密来协商对称加密的密钥,服务端一旦把正确的公钥安全地送达到客户端后,后续的通信,为了保证高效通信,再采用对称加密来加密数据。 - -具体的过程,后面会更加详细的阐述这一过程。 - - - -## 2. 摘要、签名、证书是啥? - -### 信息摘要 - -一段信息,经过摘要算法得到一串哈希值,就是摘要(dijest)。 - -常见的摘要算法有MD5、SHA1、SHA256、SHA512等。 - -关于摘要,有几点需要你明白的: - -1、摘要算法,是把任意长度的信息,映射成一个定长的字符串。 - -2、摘要算法,两个不同的信息,是有可能算出同一个摘要值的。 - -3、摘要算法与加密算法不同,不存在解密的过程。 - -4、摘要算法不用于数据的保密,而是用于数据的完整性校验。 - -### 数字签名 - -摘要经过私钥的加密后,便有了一个新的名字 -- `数字签名`。 - -`签名` 是在发送方,这是一个加密的过程。 - -`验签` 是在接收方,这是一个解密的过程。 - -那搞懂数字签名的意义是什么?只要回答下面两个问题即可。 - -**第一个问题,有了信息摘要,为何还要有数字签名?** - -答:信息摘要,虽然也不可逆,但却容易却被伪造。所以信息摘要只用于校验完整性,而要保证信息摘要的正确性,就要依靠数字签名啦。 - -数字签名的签名和验签是非对称加密,其他人除非拿到私钥,不然没法伪造。 - -**第二个问题,为什么不对内容直接加密,而是对摘要进行加密。** - -答:由上面我们知道了非对称加密的速度非常慢,如果传输的数据量非常大,那这个加密再解密的时间要远比网络传输的时间来得长,这样反而会得不偿失。 - -如果我们对传输的内容只有完整性要求,而安全性没有要求(意思是传输的内容被人知道了也没关系)。那就可以对摘要进行加密,到客户端这里解密后得到摘要明文,再用这个摘要明文与传输的数据二次计算的摘要进行比较,若一致,则说明传输的内容是完整的,没有被篡改。 - -### 数字证书 - -在数字签名那里,不知道你有没有发现一个问题? - -数字签名是非对称加密,服务端有一个私钥,客户端一个公钥,只有这两个对上了验签。 - -那假如说你(客户端)拿到的公钥并不是服务端给的呢,而是黑客塞给你的呢?而你却把这个假公钥当成真的,那么当你使用这个假公钥加密一些敏感信息时,黑客就可以截取你的这段信息,由于这信息是用黑客自己的公钥加密的,这样一来,黑客拿自己的私钥就能解密得到你的敏感信息。 - -这就是问题所在。 - -要解决这个问题,其实只要保证『公钥』是可信的。只有服务端发给你的公钥你才能拿,而坏人给你的公钥,你要懂得识别并丢弃它。 - -数字证书就应运而生了。 - -要理解数字证书,同样只要搞懂两个问题即可。 - -1. 数字证书是什么东西?其实它就是一个 `.crt` 文件 -2. 数字证书是谁颁发的?由权威证书认证机构颁发,一般我们简称为 CA 机构 - -2. 数字证书如何申请的?或者说如何颁发的? - -为了让你理解这一过程,我画了下面这张图: - -![](http://image.iswbm.com/20200724124502.png) - -1、在自己的服务器上生成一对公钥和私钥。然后将域名、申请者、公钥(注意不是私钥,私钥是无论如何也不能泄露的)等其他信息整合在一起,生成`.csr` 文件。 - -2、将这个 `.csr` 文件发给 CA 机构,CA 机构收到申请后,会通过各种手段验证申请者的组织信息和个人信息,如无异常(组织存在,企业合法,确实是域名的拥有者),CA 就会使用散列算法对`.csr`里的明文信息先做一个HASH,得到一个信息摘要,再用 CA 自己的私钥对这个信息摘要进行加密,生成一串密文,密文即是所说的 **签名**。签名 + `.csr` 明文信息,即是 **证书**。CA 把这个证书返回给申请人 - - - -## 3. 数字证书(Certificate) - -在HTTPS的传输过程中,有一个非常关键的角色--`数字证书`,那什么是数字证书?又有什么作用呢? - -所谓数字证书,是一种用于电脑的身份识别机制。由数字证书颁发机构(CA)对使用私钥创建的签名请求文件做的签名(盖章),表示CA结构对证书持有者的认可。 - -### 3.1 数字证书拥有以下几个优点 - -- 使用数字证书能够提高用户的可信度; -- 数字证书中的公钥,能够与服务端的私钥配对使用,实现数据传输过程中的加密和解密; -- 在证认使用者身份期间,使用者的敏感个人数据并不会被传输至证书持有者的网络系统上; - -### 2.2. 证书类型 - -x509的证书编码格式有两种: - -1. PEM(Privacy-enhanced Electronic Mail)是明文格式的,以 -----BEGIN CERTIFICATE-----开头,已-----END CERTIFICATE-----结尾。中间是经过base64编码的内容,apache需要的证书就是这类编码的证书.查看这类证书的信息的命令为: `openssl x509 -noout -text -in server.pem`。其实PEM就是把DER的内容进行了一次base64编码 -2. DER是二进制格式的证书,查看这类证书的信息的命令为: `openssl x509 -noout -text -inform der -in server.der` - -### 2.3. 扩展名 - -- .crt证书文件,可以是DER(二进制)编码的,也可以是PEM(ASCII (Base64))编码的),在类unix系统中比较常见; -- .cer也是证书,常见于Windows系统。编码类型同样可以是DER或者PEM的,windows下有工具可以转换crt到cer; -- .csr证书签名请求文件,一般是生成请求以后发送给CA,然后CA会给您签名并发回证书 -- .key一般公钥或者密钥都会用这种扩展名,可以是DER编码的或者是PEM编码的。查看DER编码的(公钥或者密钥)的文件的命令为: `openssl rsa -inform DER -noout -text -in xxx.key`。查看PEM编码的(公钥或者密钥)的文件的命令为: `openssl rsa -inform PEM -noout -text -in xxx.key`; -- .p12证书文件,包含一个X509证书和一个被密码保护的私钥 - -### 2.4 证书的种类 - -安全证书主要分为DV、OV和EV三个种类,对应的安全等级为一般、较好和最高三个等级。三者的审核过程、审核标准和对应的域名数量也不同,所以价格在一两百元到几万元不等。 - -#### DV SSL - -DV SSL证书是只验证网站域名所有权的简易型(Class 1级)SSL证书,可10分钟快速颁发,能起到加密传输的作用,但无法向用户证明网站的真实身份。 - -目前市面上的免费证书都是这个类型的,只是提供了对数据的加密,但是对提供证书的个人和机构的身份不做验证。 - -#### OV SSL - -OV SSL,提供加密功能,对申请者做严格的身份审核验证,提供可信×××明。 - -和DV SSL的区别在于,OV SSL 提供了对个人或者机构的审核,能确认对方的身份,安全性更高。 - -所以这部分的证书申请是收费的~ - -#### EV SSL - -超安=EV=最安全、最严格 超安EV SSL证书遵循全球统一的严格身份验证标准,是目前业界安全级别最高的顶级 (Class 4级)SSL证书。 - -金融证券、银行、第三方支付、网上商城等,重点强调网站安全、企业可信形象的网站,涉及交易支付、客户隐私信息和账号密码的传输。 - -这部分的验证要求最高,申请费用也是最贵的。 - -选择签发机构时,最好选择行业认可的全球范围内都可以使用的ca机构签发的证书。目前我们国内的证书能够符合标准的还不是特别多,主要原因是有一些证书不能够被国外的浏览器所认可,在使用的时候需要进行一定的额外操作。 - - - ---- - - - -根据保护域名的数量需求,SSL证书又分为: - -**单域名版:**只保护一个域名,例如 [www.abc.com](http://www.abc.com/) 或者 login.abc.com 之类的单个域名 - -**多域名版:**一张证书可以保护多个域名,例如同时保护 [www.abc.com](http://www.abc.com/) , [www.bcd.com,](https://blog.csdn.net/) pay.efg.com 等 - -**通配符版:**一张证书保护同一个主域名下同一级的所有子域名,不限个数,形如 *.abc.com 。注意,通配符版只有 DVSSL 和 OVSSL 具有, EVSSL 不具有通配符版本。 - - - -### 2.5 证书在哪里 - -当你在下载并安装浏览器时,浏览器内部其实已经内嵌了全世界公认的根证书颁发机构的证书。 - -若一个网站的数字证书的证书颁发机构在浏览器中没有,则需要引导用户自行导入。 - -如果你想在 Chrome 中查看有哪些受信任的证书颁发机构,可以点击 `设置` -> `隐私设置与安全性` -> `安全` -> `管理证书` - -![](http://image.iswbm.com/20200717222206.png) - -### 2.6 证书里的信息 - -在上图的位置里,随便双击点开一个证书,就可以查看证书里的内容。 - -内容非常多,最主要的有 - -- 证书是哪个机构的? -- 证书里的公钥是什么? -- 证书有效期是什么时候? -- 采用的哪种加解密的算法? - -### 2.7 证书吊销 - -证书是有生命周期的,如果证书的私钥泄漏了那这个证书就得吊销,一般有两种吊销方式:CRL和OCSP。 - -CRL( Certificate Revocation List)是CA机构维护的一个已经被吊销的证书序列号列表,浏览器需要定时更新这个列表,浏览器在验证证书合法性的时候也会在证书吊销列表中查询是否已经被吊销,如果被吊销了那这个证书也是不可信的。可以看出,这个列表随着被吊销证书的增加而增加,列表会越来越大,浏览器还需要定时更新,实时性也比较差。 - -所以,后来就有了 OCSP (Online Certificate Status Protocol)在线证书状态协议,这个协议就是解决了 CRL 列表越来越大和实时性差的问题而生的。有了这个协议,浏览器就可以不用定期更新CRL了,在验证证书的时候直接去CA服务器实时校验一下证书有没有被吊销就可以,是解决了CRL的问题,但是每次都要去CA服务器上校验也会很慢,在网络环境较差的时候或者跨国访问的时候,体验就非常差了,OCSP虽然解决了CRL的问题但是性能却很差。 - - - -如果你想了解更多关于证书的内容,可以前往华为云的公开文档进行学习:https://support.huaweicloud.com/scm_faq/scm_01_0020.html - -## 4. 如何生成 CSR 文件 - -CSR是Certificate Signing Request的英文缩写,即证书签名请求文件。 - -当申请者申请数字证书时,CSP(加密服务提供者)生成私钥,同时也生成了CSR文件。申请者将CSR文件提交至Certificate Authority (CA)机构后,CA机构使用其根证书私钥签名,从而就生成了数字证书。 - -申请者通过CSR文件,向CA机构申请数字证书。获取证书后,就能证明申请者的网站是可信的,数据传输是加密的。 - -接下来来了解一下,CSR 文件是如何生成的? - -### 使用 OpenSSL 生成 - -假设申请的域名为 **python.iswbm.com**,公司名称为**派森时光科技**,部门是**IT部**,公司在**中国广东省深圳市**。可通过运行下方命令行生成CSR文件: - -```shell -$ openssl req -new –SHA256 -newkey rsa:2048 -nodes -keyout python.iswbm.com.key -out python.iswbm.com.csr -subj "/C=CN/ST=Guangdong/L=Shenzhen/O=派森时光科技/OU=IT/CN=python.iswbm.com" -``` - -此命令行表示: - -- `req`参数:表示证书请求request,用于生成CSR文件。 -- `SHA256`参数:表示CSR签名时用的摘要算法。 -- `newkey`参数:表示指定证书的算法。参数 2048:表示密钥对的长度。 -- `nodes`参数:表示不对私钥加密。 -- `keyout`参数:表示生成的私钥文件。名为`iswbm.key`的私钥文件,需自行保管,用于获取证书后的部署过程。 -- `out`参数:表示生成的 CSR 文件。名为`iswbm.com.csr`的CSR文件,用于提交至CA机构验证信息,从而获取证书。 -- `subj`参数:表示CSR信息,具体有哪些参数,可以继续往下看。 - -subj参数说明: - -- `C`:Country,表示国家,申请者或申请企业所在国家的英文或两位大写国家代码。如:CN -- `ST`:State/Province,表示省份,申请者或申请企业所在地的省/市/自治区英文或拼音全称。如:Guangdong -- `L`:Locality,表示城市,申请者或申请企业所在城市的英文或拼音全称。如:Shenzhen -- `O`:Organization,表示申请者的姓名或申请企业的名称。 -- `OU`:Organizational Unit,表示申请人所在部门的英文或拼音全称。如:IT -- `CN`:Common Name,表示你要为哪个域名申请证书,可是单域名(比如 python.iswbm.com),也可以是泛域名(*.iswbm.com),也可以是为多个域名申请一个证书(具体我没操作过)。 - -上一条命令执行完后,会在你的本地目录下生成俩文件 - -- python.iswbm.com.csr:用于向CA机构申请证书 - -```shell -$ cat python.iswbm.com.csr ------BEGIN CERTIFICATE REQUEST----- -MIIC0TCCAbkCAQAwgYsxCzAJBgNVBAYTAkNOMRIwEAYDVQQIDAlHdWFuZ2Rvbmcx -ETAPBgNVBAcMCFNoZW56aGVuMS0wKwYDVQQKDCTDpsK0wr7DpsKjwq7DpsKXwrbD -... -7lgB4QC1aIFz8gi9TGMJU2LqTDJCj+tgM68LDBdMLeQ8XZ33C95Nl0qt7yG+zjlZ -01jBh+T882r8x9gKdwb7nZSWFQY4/YTq+sY++YW/QuCNRcJ2vbM18U/HlIRsZ3su -x6Neh08= ------END CERTIFICATE REQUEST----- -``` - -- python.iswbm.com.key:私钥,自行保存,不要外泄 - -```shell -$ cat python.iswbm.com.key ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4OrcM9hTs9Hao -SzjsVJFX2Mmd+mToMG3u++o2Fd5yrPYq4COkT33lnL9kJNrDWqGp5TRkWqNwLaPl -... -a/lKBWLcvxE+IQ+mxNbN058kEJ3l8WAcAFCebLm5czUqmIVa3JR+cBDLvGFZVn6z -72AP5D/Evds4BOO+VzAiVLU6Ai78qhACuVExZNQCxdvJy4LxpeckUpCem9hAPiIY -LQfiTStBBU6t/+mnDyij+XreGQ== ------END PRIVATE KEY----- -``` - - - -### 使用在线生成工具 - -使用 OpenSSL 工具生成 CSR 文件的方法固然简单,但使用时,需要你的了解代码中参数的意思。 - -如果你不想花心思去记这些,推荐你使用 [CSR在线生成工具](https://myssl.com/csr_create.html)(https://myssl.com/csr_create.html) - -你只需简单地输入如下信息,再点击 `OpenSSL生成`,你就可以获得一条 OpenSSL 命令,这下你再也不用自己拼凑参数啦,真的太方便了。为什么不点击 `生成` 让其直接生成 私钥文件 和 CSR文件 呢?当然是为了安全起见啦。 - -![](http://image.iswbm.com/20200718100052.png) - - - -## 5. TLS/SSL 保证信息的安全 - -在信息安全性问题中,我们常常要做到三点才能保证信息的安全: - -1. 信息的保密性 -2. 信息的完整性 -3. 身份识别 - -将这三者结合起来,就是 TLS/SSL 做的事情 - -![img](https://img2018.cnblogs.com/blog/1169376/201910/1169376-20191008172456510-1302410435.png) - - - -1、客户端(浏览器)向服务端发出请求,服务端返回证书给客户端。 - -2、客户端拿到证书后,把证书里的签名与及明文信息分别取出来,然后会用自身携带的CA机构的公钥去解密签名,然后信息摘要1,然后再对明文信息进行HASH,得到一个信息摘要2,对比信息摘要1 和信息摘要2,如果一样,说明证书是合法的,也就是证书里的公钥是正确的。 - -以上采用的是非对称加密(CA的公钥和私钥),保证了客户端接收到服务端正确的公钥,有了服务端的公钥后,后面的信息加密都可以使用这个公钥,而用这个公钥加密过后的密文,只有服务端的私钥能解,就算黑客拿到了也没法解开。 - - - -## 参考文章 - -- [HTTPS中CA证书的签发及使用过程](https://www.cnblogs.com/xdyixia/p/11610102.html) - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c10/c10_08.rst b/source/c10/c10_08.rst deleted file mode 100644 index 6e902f9..0000000 --- a/source/c10/c10_08.rst +++ /dev/null @@ -1,424 +0,0 @@ -10.8 数字证书、签名到底是什么? -=============================== - -|image0| - -我们都知道 HTTP 协议都是明文传输内容,为了保证数据传输的安全,HTTPS -协议就应运而生了,但它其实并不是一个全新的协议,而是HTTP 协议基本之上 -再加上SSL/TLS 协议。 - -因此当你访问一个支持 https 的网站时,是需要先进行 SSL/TLS -握手建立连接的。 - -SSL/TLS 握手的目的是为了 **安全** -地协商出一份\ **对称加密**\ 的密钥,有了这个密钥之后,后续的数据全部使用这个密钥进行加密。 - -这个过程其实挺有趣的,涉及到的知识点,专业名词也很多,比如对称加密,非对称加密,信息摘要,数字签名,数字证书,公钥和私钥。本篇文章,会详细地介绍这些极易混淆的专业名词。 - -在讲解之前,我先给你提出几个摸底问题,如果你已经还不能够熟练回答,那么本篇文章会给你答案: - -1、 对称加密和非对称加密,各有什么优缺点? - -2、对称加密和非对称加密,是排他关系吗?是否可以搭配使用? - -3、摘要和加密有什么区别?有了摘要算法为什么还要有加密算法? - -4、如何在通信时同时做到保密性、高效性? - -5、如何申请数字证书?证书分为哪几种?SSL 证书如何部署? - -6、完整说下证书申请、证书签发、证书下发客户端、客户端验证证书、数据加密传输的整个流程? - -1. 对称加密与非对称加密 ------------------------ - -对称加密 -~~~~~~~~ - -``对称加密``\ 是通信双方共同拥有一把密钥。 - -这把密钥可以把明文加密(encryption)成密文,也可以把密文解密(decryption)成明文。 - -常见的对称加密算法有AES、DES、RC4,其中最常用的是AES。 - -对称加密的优点是:速度快。 - -同时也有一个缺点,就是不那么安全,一旦你的密钥被别人窃取了,所有的数据就会在网络的世界里裸奔。 - -非对称加密 -~~~~~~~~~~ - -与 对称加密相对的是 ``非对称加密``\ 。 - -通信双方持有不同的密钥。 - -服务端的密钥,称之为 ``私钥``\ (private key),客户端的密钥,称之为 -``公钥`` (public key)。 - -他们二者的区别是: - -1、私钥应仅在服务端保存,绝不可泄露。而公钥可以存在于任何的客户端,即使黑客拿到了也没有关系。 - -2、公钥加密的密文只有相对应的私钥才能解密 - -|image1| - -3、私钥加密的内容,所有与之相对应的公钥都能解密。 - -4、公钥通常用来生成签名,私钥用来验证签名。 - -|image2| - -5、公钥和私钥是相对的,两者本身并没有规定哪一个必须是公钥或私钥。这意味着,公钥只要不对外公开,那就可以做为私钥,私钥公开后也可以做为公钥。 - -典型的非对称加密算法有 RSA 。 - -非对称加密的优点,就是安全系数特别高。缺点就是速度会慢一些。 - -对称与非对称加密结合 -~~~~~~~~~~~~~~~~~~~~ - -当客户端收到的公钥是准确的时候,通信就是安全的。 - -因为用正确公钥加密过的密文,只有服务端的私钥能解。 - -那么如何保证,客户端收到正确的公钥呢? - -答案是:通过非对称加密来协商对称加密的密钥,服务端一旦把正确的公钥安全地送达到客户端后,后续的通信,为了保证高效通信,再采用对称加密来加密数据。 - -具体的过程,后面会更加详细的阐述这一过程。 - -2. 摘要、签名、证书是啥? -------------------------- - -信息摘要 -~~~~~~~~ - -一段信息,经过摘要算法得到一串哈希值,就是摘要(dijest)。 - -常见的摘要算法有MD5、SHA1、SHA256、SHA512等。 - -关于摘要,有几点需要你明白的: - -1、摘要算法,是把任意长度的信息,映射成一个定长的字符串。 - -2、摘要算法,两个不同的信息,是有可能算出同一个摘要值的。 - -3、摘要算法与加密算法不同,不存在解密的过程。 - -4、摘要算法不用于数据的保密,而是用于数据的完整性校验。 - -数字签名 -~~~~~~~~ - -摘要经过私钥的加密后,便有了一个新的名字 – ``数字签名``\ 。 - -``签名`` 是在发送方,这是一个加密的过程。 - -``验签`` 是在接收方,这是一个解密的过程。 - -那搞懂数字签名的意义是什么?只要回答下面两个问题即可。 - -**第一个问题,有了信息摘要,为何还要有数字签名?** - -答:信息摘要,虽然也不可逆,但却容易却被伪造。所以信息摘要只用于校验完整性,而要保证信息摘要的正确性,就要依靠数字签名啦。 - -数字签名的签名和验签是非对称加密,其他人除非拿到私钥,不然没法伪造。 - -**第二个问题,为什么不对内容直接加密,而是对摘要进行加密。** - -答:由上面我们知道了非对称加密的速度非常慢,如果传输的数据量非常大,那这个加密再解密的时间要远比网络传输的时间来得长,这样反而会得不偿失。 - -如果我们对传输的内容只有完整性要求,而安全性没有要求(意思是传输的内容被人知道了也没关系)。那就可以对摘要进行加密,到客户端这里解密后得到摘要明文,再用这个摘要明文与传输的数据二次计算的摘要进行比较,若一致,则说明传输的内容是完整的,没有被篡改。 - -数字证书 -~~~~~~~~ - -在数字签名那里,不知道你有没有发现一个问题? - -数字签名是非对称加密,服务端有一个私钥,客户端一个公钥,只有这两个对上了验签。 - -那假如说你(客户端)拿到的公钥并不是服务端给的呢,而是黑客塞给你的呢?而你却把这个假公钥当成真的,那么当你使用这个假公钥加密一些敏感信息时,黑客就可以截取你的这段信息,由于这信息是用黑客自己的公钥加密的,这样一来,黑客拿自己的私钥就能解密得到你的敏感信息。 - -这就是问题所在。 - -要解决这个问题,其实只要保证『公钥』是可信的。只有服务端发给你的公钥你才能拿,而坏人给你的公钥,你要懂得识别并丢弃它。 - -数字证书就应运而生了。 - -要理解数字证书,同样只要搞懂两个问题即可。 - -1. 数字证书是什么东西?其实它就是一个 ``.crt`` 文件 -2. 数字证书是谁颁发的?由权威证书认证机构颁发,一般我们简称为 CA 机构 - -3. 数字证书如何申请的?或者说如何颁发的? - -为了让你理解这一过程,我画了下面这张图: - -|image3| - -1、在自己的服务器上生成一对公钥和私钥。然后将域名、申请者、公钥(注意不是私钥,私钥是无论如何也不能泄露的)等其他信息整合在一起,生成\ ``.csr`` -文件。 - -2、将这个 ``.csr`` 文件发给 CA 机构,CA -机构收到申请后,会通过各种手段验证申请者的组织信息和个人信息,如无异常(组织存在,企业合法,确实是域名的拥有者),CA -就会使用散列算法对\ ``.csr``\ 里的明文信息先做一个HASH,得到一个信息摘要,再用 -CA 自己的私钥对这个信息摘要进行加密,生成一串密文,密文即是所说的 -**签名**\ 。签名 + ``.csr`` 明文信息,即是 **证书**\ 。CA -把这个证书返回给申请人 - -3. 数字证书(Certificate) ------------------------- - -在HTTPS的传输过程中,有一个非常关键的角色–\ ``数字证书``\ ,那什么是数字证书?又有什么作用呢? - -所谓数字证书,是一种用于电脑的身份识别机制。由数字证书颁发机构(CA)对使用私钥创建的签名请求文件做的签名(盖章),表示CA结构对证书持有者的认可。 - -3.1 数字证书拥有以下几个优点 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -- 使用数字证书能够提高用户的可信度; -- 数字证书中的公钥,能够与服务端的私钥配对使用,实现数据传输过程中的加密和解密; -- 在证认使用者身份期间,使用者的敏感个人数据并不会被传输至证书持有者的网络系统上; - -2.2. 证书类型 -~~~~~~~~~~~~~ - -x509的证书编码格式有两种: - -1. PEM(Privacy-enhanced Electronic Mail)是明文格式的,以 —–BEGIN - CERTIFICATE—–开头,已—–END - CERTIFICATE—–结尾。中间是经过base64编码的内容,apache需要的证书就是这类编码的证书.查看这类证书的信息的命令为: - ``openssl x509 -noout -text -in server.pem``\ 。其实PEM就是把DER的内容进行了一次base64编码 -2. DER是二进制格式的证书,查看这类证书的信息的命令为: - ``openssl x509 -noout -text -inform der -in server.der`` - -2.3. 扩展名 -~~~~~~~~~~~ - -- .crt证书文件,可以是DER(二进制)编码的,也可以是PEM(ASCII - (Base64))编码的),在类unix系统中比较常见; -- .cer也是证书,常见于Windows系统。编码类型同样可以是DER或者PEM的,windows下有工具可以转换crt到cer; -- .csr证书签名请求文件,一般是生成请求以后发送给CA,然后CA会给您签名并发回证书 -- .key一般公钥或者密钥都会用这种扩展名,可以是DER编码的或者是PEM编码的。查看DER编码的(公钥或者密钥)的文件的命令为: - ``openssl rsa -inform DER -noout -text -in xxx.key``\ 。查看PEM编码的(公钥或者密钥)的文件的命令为: - ``openssl rsa -inform PEM -noout -text -in xxx.key``; -- .p12证书文件,包含一个X509证书和一个被密码保护的私钥 - -2.4 证书的种类 -~~~~~~~~~~~~~~ - -安全证书主要分为DV、OV和EV三个种类,对应的安全等级为一般、较好和最高三个等级。三者的审核过程、审核标准和对应的域名数量也不同,所以价格在一两百元到几万元不等。 - -DV SSL -^^^^^^ - -DV SSL证书是只验证网站域名所有权的简易型(Class -1级)SSL证书,可10分钟快速颁发,能起到加密传输的作用,但无法向用户证明网站的真实身份。 - -目前市面上的免费证书都是这个类型的,只是提供了对数据的加密,但是对提供证书的个人和机构的身份不做验证。 - -OV SSL -^^^^^^ - -OV SSL,提供加密功能,对申请者做严格的身份审核验证,提供可信×××明。 - -和DV SSL的区别在于,OV SSL -提供了对个人或者机构的审核,能确认对方的身份,安全性更高。 - -所以这部分的证书申请是收费的~ - -EV SSL -^^^^^^ - -超安=EV=最安全、最严格 超安EV -SSL证书遵循全球统一的严格身份验证标准,是目前业界安全级别最高的顶级 -(Class 4级)SSL证书。 - -金融证券、银行、第三方支付、网上商城等,重点强调网站安全、企业可信形象的网站,涉及交易支付、客户隐私信息和账号密码的传输。 - -这部分的验证要求最高,申请费用也是最贵的。 - -选择签发机构时,最好选择行业认可的全球范围内都可以使用的ca机构签发的证书。目前我们国内的证书能够符合标准的还不是特别多,主要原因是有一些证书不能够被国外的浏览器所认可,在使用的时候需要进行一定的额外操作。 - --------------- - -根据保护域名的数量需求,SSL证书又分为: - -**单域名版:**\ 只保护一个域名,例如 -`www.abc.com `__ 或者 login.abc.com 之类的单个域名 - -**多域名版:**\ 一张证书可以保护多个域名,例如同时保护 -`www.abc.com `__ , -`www.bcd.com, `__ pay.efg.com 等 - -**通配符版:**\ 一张证书保护同一个主域名下同一级的所有子域名,不限个数,形如 -\*.abc.com 。注意,通配符版只有 DVSSL 和 OVSSL 具有, EVSSL -不具有通配符版本。 - -2.5 证书在哪里 -~~~~~~~~~~~~~~ - -当你在下载并安装浏览器时,浏览器内部其实已经内嵌了全世界公认的根证书颁发机构的证书。 - -若一个网站的数字证书的证书颁发机构在浏览器中没有,则需要引导用户自行导入。 - -如果你想在 Chrome 中查看有哪些受信任的证书颁发机构,可以点击 ``设置`` -> -``隐私设置与安全性`` -> ``安全`` -> ``管理证书`` - -|image4| - -2.6 证书里的信息 -~~~~~~~~~~~~~~~~ - -在上图的位置里,随便双击点开一个证书,就可以查看证书里的内容。 - -内容非常多,最主要的有 - -- 证书是哪个机构的? -- 证书里的公钥是什么? -- 证书有效期是什么时候? -- 采用的哪种加解密的算法? - -2.7 证书吊销 -~~~~~~~~~~~~ - -证书是有生命周期的,如果证书的私钥泄漏了那这个证书就得吊销,一般有两种吊销方式:CRL和OCSP。 - -CRL( Certificate Revocation -List)是CA机构维护的一个已经被吊销的证书序列号列表,浏览器需要定时更新这个列表,浏览器在验证证书合法性的时候也会在证书吊销列表中查询是否已经被吊销,如果被吊销了那这个证书也是不可信的。可以看出,这个列表随着被吊销证书的增加而增加,列表会越来越大,浏览器还需要定时更新,实时性也比较差。 - -所以,后来就有了 OCSP (Online Certificate Status -Protocol)在线证书状态协议,这个协议就是解决了 CRL -列表越来越大和实时性差的问题而生的。有了这个协议,浏览器就可以不用定期更新CRL了,在验证证书的时候直接去CA服务器实时校验一下证书有没有被吊销就可以,是解决了CRL的问题,但是每次都要去CA服务器上校验也会很慢,在网络环境较差的时候或者跨国访问的时候,体验就非常差了,OCSP虽然解决了CRL的问题但是性能却很差。 - -如果你想了解更多关于证书的内容,可以前往华为云的公开文档进行学习:https://support.huaweicloud.com/scm_faq/scm_01_0020.html - -4. 如何生成 CSR 文件 --------------------- - -CSR是Certificate Signing Request的英文缩写,即证书签名请求文件。 - -当申请者申请数字证书时,CSP(加密服务提供者)生成私钥,同时也生成了CSR文件。申请者将CSR文件提交至Certificate -Authority -(CA)机构后,CA机构使用其根证书私钥签名,从而就生成了数字证书。 - -申请者通过CSR文件,向CA机构申请数字证书。获取证书后,就能证明申请者的网站是可信的,数据传输是加密的。 - -接下来来了解一下,CSR 文件是如何生成的? - -使用 OpenSSL 生成 -~~~~~~~~~~~~~~~~~ - -假设申请的域名为 -**python.iswbm.com**\ ,公司名称为\ **派森时光科技**\ ,部门是\ **IT部**\ ,公司在\ **中国广东省深圳市**\ 。可通过运行下方命令行生成CSR文件: - -.. code:: shell - - $ openssl req -new –SHA256 -newkey rsa:2048 -nodes -keyout python.iswbm.com.key -out python.iswbm.com.csr -subj "/C=CN/ST=Guangdong/L=Shenzhen/O=派森时光科技/OU=IT/CN=python.iswbm.com" - -此命令行表示: - -- ``req``\ 参数:表示证书请求request,用于生成CSR文件。 -- ``SHA256``\ 参数:表示CSR签名时用的摘要算法。 -- ``newkey``\ 参数:表示指定证书的算法。参数 2048:表示密钥对的长度。 -- ``nodes``\ 参数:表示不对私钥加密。 -- ``keyout``\ 参数:表示生成的私钥文件。名为\ ``iswbm.key``\ 的私钥文件,需自行保管,用于获取证书后的部署过程。 -- ``out``\ 参数:表示生成的 CSR - 文件。名为\ ``iswbm.com.csr``\ 的CSR文件,用于提交至CA机构验证信息,从而获取证书。 -- ``subj``\ 参数:表示CSR信息,具体有哪些参数,可以继续往下看。 - -subj参数说明: - -- ``C``\ :Country,表示国家,申请者或申请企业所在国家的英文或两位大写国家代码。如:CN -- ``ST``\ :State/Province,表示省份,申请者或申请企业所在地的省/市/自治区英文或拼音全称。如:Guangdong -- ``L``\ :Locality,表示城市,申请者或申请企业所在城市的英文或拼音全称。如:Shenzhen -- ``O``\ :Organization,表示申请者的姓名或申请企业的名称。 -- ``OU``\ :Organizational - Unit,表示申请人所在部门的英文或拼音全称。如:IT -- ``CN``\ :Common Name,表示你要为哪个域名申请证书,可是单域名(比如 - python.iswbm.com),也可以是泛域名(*.iswbm.com),也可以是为多个域名申请一个证书(具体我没操作过)。 - -上一条命令执行完后,会在你的本地目录下生成俩文件 - -- python.iswbm.com.csr:用于向CA机构申请证书 - -.. code:: shell - - $ cat python.iswbm.com.csr - -----BEGIN CERTIFICATE REQUEST----- - MIIC0TCCAbkCAQAwgYsxCzAJBgNVBAYTAkNOMRIwEAYDVQQIDAlHdWFuZ2Rvbmcx - ETAPBgNVBAcMCFNoZW56aGVuMS0wKwYDVQQKDCTDpsK0wr7DpsKjwq7DpsKXwrbD - ... - 7lgB4QC1aIFz8gi9TGMJU2LqTDJCj+tgM68LDBdMLeQ8XZ33C95Nl0qt7yG+zjlZ - 01jBh+T882r8x9gKdwb7nZSWFQY4/YTq+sY++YW/QuCNRcJ2vbM18U/HlIRsZ3su - x6Neh08= - -----END CERTIFICATE REQUEST----- - -- python.iswbm.com.key:私钥,自行保存,不要外泄 - -.. code:: shell - - $ cat python.iswbm.com.key - -----BEGIN PRIVATE KEY----- - MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC4OrcM9hTs9Hao - SzjsVJFX2Mmd+mToMG3u++o2Fd5yrPYq4COkT33lnL9kJNrDWqGp5TRkWqNwLaPl - ... - a/lKBWLcvxE+IQ+mxNbN058kEJ3l8WAcAFCebLm5czUqmIVa3JR+cBDLvGFZVn6z - 72AP5D/Evds4BOO+VzAiVLU6Ai78qhACuVExZNQCxdvJy4LxpeckUpCem9hAPiIY - LQfiTStBBU6t/+mnDyij+XreGQ== - -----END PRIVATE KEY----- - -使用在线生成工具 -~~~~~~~~~~~~~~~~ - -使用 OpenSSL 工具生成 CSR -文件的方法固然简单,但使用时,需要你的了解代码中参数的意思。 - -如果你不想花心思去记这些,推荐你使用 -`CSR在线生成工具 `__\ (https://myssl.com/csr_create.html) - -你只需简单地输入如下信息,再点击 ``OpenSSL生成``\ ,你就可以获得一条 -OpenSSL 命令,这下你再也不用自己拼凑参数啦,真的太方便了。为什么不点击 -``生成`` 让其直接生成 私钥文件 和 CSR文件 呢?当然是为了安全起见啦。 - -|image5| - -5. TLS/SSL 保证信息的安全 -------------------------- - -在信息安全性问题中,我们常常要做到三点才能保证信息的安全: - -1. 信息的保密性 -2. 信息的完整性 -3. 身份识别 - -将这三者结合起来,就是 TLS/SSL 做的事情 - -.. figure:: https://img2018.cnblogs.com/blog/1169376/201910/1169376-20191008172456510-1302410435.png - :alt: img - - img - -1、客户端(浏览器)向服务端发出请求,服务端返回证书给客户端。 - -2、客户端拿到证书后,把证书里的签名与及明文信息分别取出来,然后会用自身携带的CA机构的公钥去解密签名,然后信息摘要1,然后再对明文信息进行HASH,得到一个信息摘要2,对比信息摘要1 -和信息摘要2,如果一样,说明证书是合法的,也就是证书里的公钥是正确的。 - -以上采用的是非对称加密(CA的公钥和私钥),保证了客户端接收到服务端正确的公钥,有了服务端的公钥后,后面的信息加密都可以使用这个公钥,而用这个公钥加密过后的密文,只有服务端的私钥能解,就算黑客拿到了也没法解开。 - -参考文章 --------- - -- `HTTPS中CA证书的签发及使用过程 `__ - -|image6| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/image-20200723233619429.png -.. |image2| image:: http://image.iswbm.com/image-20200724121333485.png -.. |image3| image:: http://image.iswbm.com/20200724124502.png -.. |image4| image:: http://image.iswbm.com/20200717222206.png -.. |image5| image:: http://image.iswbm.com/20200718100052.png -.. |image6| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/c10/c10_09.md b/source/c10/c10_09.md deleted file mode 100644 index e8e55b5..0000000 --- a/source/c10/c10_09.md +++ /dev/null @@ -1,276 +0,0 @@ -# 10.9 如何申请数字证书,点亮你的 HTTPS? - -![](http://image.iswbm.com/20200602135014.png) - -上一节讲解了关于 SSL 数字证书的基础知识,这一篇就来实战演示一下,如何将你的 HTTP 网站加上 HTTPS 。 - -要想使用 HTTPS ,要做的事情还是有点多的: - -1. 准备一个域名和服务器(自行准备) -2. 并将该域名解析到你的服务器ip上 -3. 生成 CSR 证书请求文件 -4. 拿着 CSR 文件去申请证书 -5. 把申请到的证书部署到你的服务器上 - -## 1. 设置 DNS 解析 - -我有一个域名 `iswbm.com`,其下有好多的子域名,比如写 Python 的在线博客 `python.iswbm.com`,还有写 Golang 的在线博客 `golang.iswbm.com`,还有我的图床 `image.iswbm.com` 等等。 - -这里新建了个子域名 `demo.iswbm.com` ,并设置其解析到 我的服务器上,如下图(服务器 ip 已码掉)。 - -![](http://image.iswbm.com/20200728233602.png) - -## 2. 搭建 HTTP 站点 - -为了方便,我这里就使用 Apache 放了一个 HTTP 的静态网页,方法很简单,大家百度即可。 - -![](http://image.iswbm.com/20200729230813.png) - - - -## 3. 申请 SSL 证书 - -SSL 数字证书怎么来的呢? - -你可以自己制作,也可以向CA权威机构申请。 - -二者的区别是: - -1. 自己颁发的证书,需要客户端验证通过,也就是需要用户手动安装证书,并将其设置为受信任的根证书。但即使如此,浏览器上( chrome, firefox)仍不认可这种自签名证书,会在地址栏前面提示连接不安全,手动安装证书后,也会提示该证书无效。若想要继续访问,并忽略该提示,可以选择继续访问。 -2. 向权威的数字证书认证机构申请,由于这些机构在网民的电脑里都有相应的根证书,且这些机构是绝对可信任的。因此你在访问网站时,不会提示连接不安全。 - -下面,我将分别向你展示这两种方法,都是如何操作的。 - - - -### 第一种:向权威CA机构申请 - -在阿里云和腾讯云都可以 进行 SSL 证书的申请,证书的申请有付费的(价格也不便宜),也有免费的,看了一圈,只有腾讯云有免费的 SSL 证书的申请渠道(阿里云听说以前也有,后来关闭了)。 - -本篇文章,仅以演示教学之用,所以只用腾讯云的免费证书的就足够啦。 - -登陆腾讯云,可以看到SSL 证书有分很多种,企业型的,企业型专业版的,增强型,增强型专业版的,还有域名型免费版。 - -![](http://image.iswbm.com/image-20200718102622663.png) - -点击选择 `域名型免费版` - -![](http://image.iswbm.com/image-20200718101358755.png) - -点击 `免费快速申请`后,填写域名和你的个人邮箱 - -![](http://image.iswbm.com/20200729232432.png) - -再点击下一步,会需要你验证域名所有权,验证方式有如下三种 - -1. 自动DNS验证 -2. 手动DNS验证 -3. 文件验证 - -但由于我的域名不是腾讯云平台解析的,因此没有 自动DNS验证的选项,只有其他两种 - -![](http://image.iswbm.com/image-20200718101652899.png) - -点击 `确认申请` 后,会提示你进入域名验证所有权的流程,这里我选择 手动DNS验证。 - -![](http://image.iswbm.com/20200729004207.png) - -审核通过后,3s 内就会给你颁发证书,你可以从控制台点击证书下载。 - -![](http://image.iswbm.com/20200729004307.png) - -下载下来的会是一个 zip 包。 - -解压一下,会有不同的服务器类型(有 Apache、IIS、Nginx、Tomcat)的文件夹。 - -![](http://image.iswbm.com/20200729004456.png) - -我使用的是 Apache ,在这个文件夹下面有三个文件: - -1. `1_root_bundle.crt`:根证书 -2. `2_demo.iswbm.com.crt`:域名证书 -3. `3_demo.iswbm.com.key`:私钥文件 - -这三个文件,下一步会部署于我的服务器上。 - -接下来讲第二种 SSL 证书申请方式。 - -### 第二种:自签名的 SSL 证书 - -没有权威的第三方 CA 机构给自己颁发证书,那就自己给自己颁发咯。 - -自签名 SSL 的证书制作过程,可以分为两步: - -1. 自己要当 CA 机构,那 CA 有的 CA 根证书、私钥 一样都不能少,因此第一步:生成 CA 的 crt 证书 和 CA 的私钥。 -2. 要申请证书,首先服务器自己要有一个密钥对(公钥和私钥) -3. 拿着上面生成的公钥,制作一个 CSR 证书申请文件 -4. 用第一步的 CA 私钥,给这个 CSR 签名,生成咱所需要的 SSL 数字证书文件。 - -步骤很多,命令很多,命令所带的参数更多,对于只想学习证书签发流程的你,把这些命令都背下来,并不是一个好的选择,毕竟这种事可能也干不了几次。 - -因此,我把这些步骤、命令,都整合成一个 shell 脚本,你只要执行这个脚本就行了。 - -```shell -$ bash create_self-signed-cert.sh --ssl-domain=demo.iswbm.com --ssl-trusted-domain=demo.iswbm.com --ssl-size=2048 --ssl-date=3650 -``` - -对应的参数的解释,在脚本中都有解释 - -![](http://image.iswbm.com/20200729235153.png) - -这个脚本过长,不好直接贴上来,我将它放在我的公众号(**Python编程时光**)后台,你可以直接回复『**证书签名**』直接获取下载。 - - 执行完成后,会在当前目录下生成好多个文件。 - -其中,只有两个文件对我们有用 - -![](http://image.iswbm.com/20200730000142.png) - - - -## 4. 部署 SSL 证书 - -根据服务器的类型不同,部署安装的方式有有所区别,腾讯云的操作文档已经非常详细了,你可以通过这个链接访问到如下的文档:https://cloud.tencent.com/document/product/400/4143 - -![](http://image.iswbm.com/20200718105347.png) - -这里我将以 CentOS 7.2 + Apache 为例,演示如何部署 SSL 证书。 - -先安装一下 mod_ssl - -```shell -$ yum install -y mod_ssl -``` - -安装完后,在 /etc/httpd/conf.d/ 目录下 会有个 ssl.conf 文件。 - -编辑修改这个文件,以下是我的配置供你参考 - -```shell - - DocumentRoot "/var/www/html" - #填写证书名称 - ServerName demo.iswbm.com - #启用 SSL 功能 - SSLEngine on - #证书文件的路径 - SSLCertificateFile /etc/pki/tls/certs/demo.iswbm.com.crt - #私钥文件的路径 - SSLCertificateKeyFile /etc/pki/tls/private/demo.iswbm.com.key - #根证书文件的路径 - SSLCACertificateFile /etc/pki/tls/certs/ca-bundle.crt - -``` - -**如果你的证书是从权威 CA 机构上申请来的。** - -比如我上面从腾讯云上申请来的,那么这三个文件就是从已经从腾讯云的控制台上下载下来的那三个文件。 - -在修改完后,务必记得把下载的这三个文件,放到相应的目录中去。 - -![](http://image.iswbm.com/20200730214826.png) - -配置完 ssl.conf,可能还需要你 check 一下 `/etc/httpd/conf/httpd.conf` 的一些配置,这些配置一般用默认的就可以,但是以防万一,还是写一下吧 - -``` -Include conf.modules.d/*.conf -``` - -写这一行的目的,就是为了 httpd 去加载 mod_ssl 这个模块 - -```shell -$ cat /etc/httpd/conf.modules.d/00-ssl.conf -LoadModule ssl_module modules/mod_ssl.so -``` - -一切配置完成后,记得重启一下 httpd 服务 - -```shell -$ systemctl restart httpd -``` - -然后使用 chrome 访问一下 `https//demo.iswbm.com` 看看,大功告成。 - -![](http://image.iswbm.com/20200730215613.png) - - - -**而如果你的证书是自签名的。** - -ssl.conf 配置文件下的应该改成这样 - -``` - - DocumentRoot "/var/www/html" - #填写证书名称 - ServerName demo.iswbm.com - #启用 SSL 功能 - SSLEngine on - #证书文件的路径 - SSLCertificateFile /etc/pki/tls/certs/tls.crt - #私钥文件的路径 - SSLCertificateKeyFile /etc/pki/tls/private/tls.key - -``` - -同时记得把这两个文件也拷贝到相应的目录下 - -```shell -$ cp tls.crt /etc/pki/tls/certs/ -$ cp tls.key /etc/pki/tls/private/ -``` - -最后还是不要忘了重启 httpd - -```shell -$ systemctl restart httpd -``` - -试着用 chrome 访问一下,可以看到 chrome 提示该连接不安全 - -![](http://image.iswbm.com/20200730220835.png) - -如果执意要访问,可以点击左下方的 `继续前往`,这样以后再访问的时候,就不会再出现这个警告页面了。 - -![](http://image.iswbm.com/20200730221745.png) - -`不安全` 三个字,让人很没有安全感,那有没有办法去掉呢? - -答案是,没有,只要是自签名的证书,在 chrome ,firefox 等主流浏览器看来都是不安全的。 - -即使你把这个根证书添加到你的受信任的证书列表中,也是徒然。 - -下面就试着来安装一下这个根证书。 - -按照下图指示,拖动证书到本地磁盘上。 - -![](http://image.iswbm.com/20200728234740.png) - -打开 Mac 上的 `钥匙串访问` - -![](http://image.iswbm.com/20200730222441.png) - -点击 `登陆`,然后再拖动这个证书到窗口中进行安装 - -![](http://image.iswbm.com/20200728235331.png) - -右键该证书,点击 `显示简介`,跳出下面的界面后,再点击 `信任`,把 IP 安全选择选为 `始终信任`。 - -![](http://image.iswbm.com/20200730222700.png) - -设置完后,再访问下 `demo.iswbm.com` ,仍然显示连接不安全,并且证书是无效的 - -![](http://image.iswbm.com/20200730222827.png) - -点击证书,显示证书,该证书确实已经放入信任列表中了。 - -![](http://image.iswbm.com/20200730222928.png) - -## 参考文档 - -- [Apache 服务器证书安装](https://cloud.tencent.com/document/product/400/35243) -- [自签名 SSL 证书](https://docs.rancher.cn/rancher2x/install-prepare/self-signed-ssl.html#_2-3-%E6%89%A9%E5%B1%95%E5%90%8D) - - - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c10/c10_09.rst b/source/c10/c10_09.rst deleted file mode 100644 index e8c0715..0000000 --- a/source/c10/c10_09.rst +++ /dev/null @@ -1,324 +0,0 @@ -10.9 如何申请数字证书,点亮你的 HTTPS? -======================================= - -|image0| - -上一节讲解了关于 SSL -数字证书的基础知识,这一篇就来实战演示一下,如何将你的 HTTP 网站加上 -HTTPS 。 - -要想使用 HTTPS ,要做的事情还是有点多的: - -1. 准备一个域名和服务器(自行准备) -2. 并将该域名解析到你的服务器ip上 -3. 生成 CSR 证书请求文件 -4. 拿着 CSR 文件去申请证书 -5. 把申请到的证书部署到你的服务器上 - -1. 设置 DNS 解析 ----------------- - -我有一个域名 ``iswbm.com``\ ,其下有好多的子域名,比如写 Python -的在线博客 ``python.iswbm.com``\ ,还有写 Golang 的在线博客 -``golang.iswbm.com``\ ,还有我的图床 ``image.iswbm.com`` 等等。 - -这里新建了个子域名 ``demo.iswbm.com`` ,并设置其解析到 -我的服务器上,如下图(服务器 ip 已码掉)。 - -|image1| - -2. 搭建 HTTP 站点 ------------------ - -为了方便,我这里就使用 Apache 放了一个 HTTP -的静态网页,方法很简单,大家百度即可。 - -|image2| - -3. 申请 SSL 证书 ----------------- - -SSL 数字证书怎么来的呢? - -你可以自己制作,也可以向CA权威机构申请。 - -二者的区别是: - -1. 自己颁发的证书,需要客户端验证通过,也就是需要用户手动安装证书,并将其设置为受信任的根证书。但即使如此,浏览器上( - chrome, - firefox)仍不认可这种自签名证书,会在地址栏前面提示连接不安全,手动安装证书后,也会提示该证书无效。若想要继续访问,并忽略该提示,可以选择继续访问。 -2. 向权威的数字证书认证机构申请,由于这些机构在网民的电脑里都有相应的根证书,且这些机构是绝对可信任的。因此你在访问网站时,不会提示连接不安全。 - -下面,我将分别向你展示这两种方法,都是如何操作的。 - -第一种:向权威CA机构申请 -~~~~~~~~~~~~~~~~~~~~~~~~ - -在阿里云和腾讯云都可以 进行 SSL -证书的申请,证书的申请有付费的(价格也不便宜),也有免费的,看了一圈,只有腾讯云有免费的 -SSL 证书的申请渠道(阿里云听说以前也有,后来关闭了)。 - -本篇文章,仅以演示教学之用,所以只用腾讯云的免费证书的就足够啦。 - -登陆腾讯云,可以看到SSL -证书有分很多种,企业型的,企业型专业版的,增强型,增强型专业版的,还有域名型免费版。 - -|image3| - -点击选择 ``域名型免费版`` - -|image4| - -点击 ``免费快速申请``\ 后,填写域名和你的个人邮箱 - -|image5| - -再点击下一步,会需要你验证域名所有权,验证方式有如下三种 - -1. 自动DNS验证 -2. 手动DNS验证 -3. 文件验证 - -但由于我的域名不是腾讯云平台解析的,因此没有 -自动DNS验证的选项,只有其他两种 - -|image6| - -点击 ``确认申请`` 后,会提示你进入域名验证所有权的流程,这里我选择 -手动DNS验证。 - -|image7| - -审核通过后,3s 内就会给你颁发证书,你可以从控制台点击证书下载。 - -|image8| - -下载下来的会是一个 zip 包。 - -解压一下,会有不同的服务器类型(有 -Apache、IIS、Nginx、Tomcat)的文件夹。 - -|image9| - -我使用的是 Apache ,在这个文件夹下面有三个文件: - -1. ``1_root_bundle.crt``\ :根证书 -2. ``2_demo.iswbm.com.crt``\ :域名证书 -3. ``3_demo.iswbm.com.key``\ :私钥文件 - -这三个文件,下一步会部署于我的服务器上。 - -接下来讲第二种 SSL 证书申请方式。 - -第二种:自签名的 SSL 证书 -~~~~~~~~~~~~~~~~~~~~~~~~~ - -没有权威的第三方 CA 机构给自己颁发证书,那就自己给自己颁发咯。 - -自签名 SSL 的证书制作过程,可以分为两步: - -1. 自己要当 CA 机构,那 CA 有的 CA 根证书、私钥 - 一样都不能少,因此第一步:生成 CA 的 crt 证书 和 CA 的私钥。 -2. 要申请证书,首先服务器自己要有一个密钥对(公钥和私钥) -3. 拿着上面生成的公钥,制作一个 CSR 证书申请文件 -4. 用第一步的 CA 私钥,给这个 CSR 签名,生成咱所需要的 SSL - 数字证书文件。 - -步骤很多,命令很多,命令所带的参数更多,对于只想学习证书签发流程的你,把这些命令都背下来,并不是一个好的选择,毕竟这种事可能也干不了几次。 - -因此,我把这些步骤、命令,都整合成一个 shell -脚本,你只要执行这个脚本就行了。 - -.. code:: shell - - $ bash create_self-signed-cert.sh --ssl-domain=demo.iswbm.com --ssl-trusted-domain=demo.iswbm.com --ssl-size=2048 --ssl-date=3650 - -对应的参数的解释,在脚本中都有解释 - -|image10| - -这个脚本过长,不好直接贴上来,我将它放在我的公众号(\ **Python编程时光**\ )后台,你可以直接回复『\ **证书签名**\ 』直接获取下载。 - -执行完成后,会在当前目录下生成好多个文件。 - -其中,只有两个文件对我们有用 - -|image11| - -4. 部署 SSL 证书 ----------------- - -根据服务器的类型不同,部署安装的方式有有所区别,腾讯云的操作文档已经非常详细了,你可以通过这个链接访问到如下的文档:https://cloud.tencent.com/document/product/400/4143 - -|image12| - -这里我将以 CentOS 7.2 + Apache 为例,演示如何部署 SSL 证书。 - -先安装一下 mod_ssl - -.. code:: shell - - $ yum install -y mod_ssl - -安装完后,在 /etc/httpd/conf.d/ 目录下 会有个 ssl.conf 文件。 - -编辑修改这个文件,以下是我的配置供你参考 - -.. code:: shell - - - DocumentRoot "/var/www/html" - #填写证书名称 - ServerName demo.iswbm.com - #启用 SSL 功能 - SSLEngine on - #证书文件的路径 - SSLCertificateFile /etc/pki/tls/certs/demo.iswbm.com.crt - #私钥文件的路径 - SSLCertificateKeyFile /etc/pki/tls/private/demo.iswbm.com.key - #根证书文件的路径 - SSLCACertificateFile /etc/pki/tls/certs/ca-bundle.crt - - -**如果你的证书是从权威 CA 机构上申请来的。** - -比如我上面从腾讯云上申请来的,那么这三个文件就是从已经从腾讯云的控制台上下载下来的那三个文件。 - -在修改完后,务必记得把下载的这三个文件,放到相应的目录中去。 - -|image13| - -配置完 ssl.conf,可能还需要你 check 一下 ``/etc/httpd/conf/httpd.conf`` -的一些配置,这些配置一般用默认的就可以,但是以防万一,还是写一下吧 - -:: - - Include conf.modules.d/*.conf - -写这一行的目的,就是为了 httpd 去加载 mod_ssl 这个模块 - -.. code:: shell - - $ cat /etc/httpd/conf.modules.d/00-ssl.conf - LoadModule ssl_module modules/mod_ssl.so - -一切配置完成后,记得重启一下 httpd 服务 - -.. code:: shell - - $ systemctl restart httpd - -然后使用 chrome 访问一下 ``https//demo.iswbm.com`` 看看,大功告成。 - -|image14| - -**而如果你的证书是自签名的。** - -ssl.conf 配置文件下的应该改成这样 - -:: - - - DocumentRoot "/var/www/html" - #填写证书名称 - ServerName demo.iswbm.com - #启用 SSL 功能 - SSLEngine on - #证书文件的路径 - SSLCertificateFile /etc/pki/tls/certs/tls.crt - #私钥文件的路径 - SSLCertificateKeyFile /etc/pki/tls/private/tls.key - - -同时记得把这两个文件也拷贝到相应的目录下 - -.. code:: shell - - $ cp tls.crt /etc/pki/tls/certs/ - $ cp tls.key /etc/pki/tls/private/ - -最后还是不要忘了重启 httpd - -.. code:: shell - - $ systemctl restart httpd - -试着用 chrome 访问一下,可以看到 chrome 提示该连接不安全 - -|image15| - -如果执意要访问,可以点击左下方的 -``继续前往``\ ,这样以后再访问的时候,就不会再出现这个警告页面了。 - -|image16| - -``不安全`` 三个字,让人很没有安全感,那有没有办法去掉呢? - -答案是,没有,只要是自签名的证书,在 chrome ,firefox -等主流浏览器看来都是不安全的。 - -即使你把这个根证书添加到你的受信任的证书列表中,也是徒然。 - -下面就试着来安装一下这个根证书。 - -按照下图指示,拖动证书到本地磁盘上。 - -|image17| - -打开 Mac 上的 ``钥匙串访问`` - -|image18| - -点击 ``登陆``\ ,然后再拖动这个证书到窗口中进行安装 - -|image19| - -右键该证书,点击 ``显示简介``\ ,跳出下面的界面后,再点击 ``信任``\ ,把 -IP 安全选择选为 ``始终信任``\ 。 - -|image20| - -设置完后,再访问下 ``demo.iswbm.com`` -,仍然显示连接不安全,并且证书是无效的 - -|image21| - -点击证书,显示证书,该证书确实已经放入信任列表中了。 - -|image22| - -参考文档 --------- - -- `Apache - 服务器证书安装 `__ -- `自签名 SSL - 证书 `__ - -|image23| - -.. |image0| image:: http://image.iswbm.com/20200602135014.png -.. |image1| image:: http://image.iswbm.com/20200728233602.png -.. |image2| image:: http://image.iswbm.com/20200729230813.png -.. |image3| image:: http://image.iswbm.com/image-20200718102622663.png -.. |image4| image:: http://image.iswbm.com/image-20200718101358755.png -.. |image5| image:: http://image.iswbm.com/20200729232432.png -.. |image6| image:: http://image.iswbm.com/image-20200718101652899.png -.. |image7| image:: http://image.iswbm.com/20200729004207.png -.. |image8| image:: http://image.iswbm.com/20200729004307.png -.. |image9| image:: http://image.iswbm.com/20200729004456.png -.. |image10| image:: http://image.iswbm.com/20200729235153.png -.. |image11| image:: http://image.iswbm.com/20200730000142.png -.. |image12| image:: http://image.iswbm.com/20200718105347.png -.. |image13| image:: http://image.iswbm.com/20200730214826.png -.. |image14| image:: http://image.iswbm.com/20200730215613.png -.. |image15| image:: http://image.iswbm.com/20200730220835.png -.. |image16| image:: http://image.iswbm.com/20200730221745.png -.. |image17| image:: http://image.iswbm.com/20200728234740.png -.. |image18| image:: http://image.iswbm.com/20200730222441.png -.. |image19| image:: http://image.iswbm.com/20200728235331.png -.. |image20| image:: http://image.iswbm.com/20200730222700.png -.. |image21| image:: http://image.iswbm.com/20200730222827.png -.. |image22| image:: http://image.iswbm.com/20200730222928.png -.. |image23| image:: http://image.iswbm.com/20200607174235.png - diff --git a/source/chapters/p01.rst b/source/chapters/p01.rst old mode 100755 new mode 100644 diff --git a/source/chapters/p02.rst b/source/chapters/p02.rst old mode 100755 new mode 100644 diff --git a/source/chapters/p03.rst b/source/chapters/p03.rst old mode 100755 new mode 100644 diff --git a/source/chapters/p04.rst b/source/chapters/p04.rst old mode 100755 new mode 100644 diff --git a/source/chapters/p05.rst b/source/chapters/p05.rst old mode 100755 new mode 100644 diff --git a/source/chapters/p06.rst b/source/chapters/p06.rst old mode 100755 new mode 100644 diff --git a/source/chapters/p07.rst b/source/chapters/p07.rst old mode 100755 new mode 100644 diff --git a/source/chapters/p08.rst b/source/chapters/p08.rst old mode 100755 new mode 100644 diff --git a/source/chapters/p10.rst b/source/chapters/p10.rst old mode 100755 new mode 100644 index b41a354..51f6755 --- a/source/chapters/p10.rst +++ b/source/chapters/p10.rst @@ -1,12 +1,8 @@ ============================= -第十章:网络基础 +第十章:好库推荐 ============================= -在找工作面试的过程中,面试官非常喜欢考察基础知识,除了数据结构与算法之外,网络知识也是一个非常重要的考察对象。 - -而网络知识,通常是很抽象,不容易理解的,有很多同学就在这里裁了跟头。 - -正好以前没在这里分享过有关网络的内容,所以打算重新梳理有关网络的一些知识,这些内容在大家面试的时候可能能用得上。 +推荐那些冷门却非常实用的模块。 本章节,会持续更新,敬请关注… diff --git a/source/conf.py b/source/conf.py old mode 100755 new mode 100644 index 6fb6b49..d15ed1c --- a/source/conf.py +++ b/source/conf.py @@ -28,7 +28,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['chinese_search','sphinx.ext.mathjax'] +extensions = ['chinese_search','sphinx.ext.mathjax', 'sphinx_sitemap', 'sphinx_multiversion'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -127,26 +127,25 @@ sys.path.append(os.path.abspath(_exts)) html_js_files = [ - 'js/readmore.js', +# 'js/readmore.js', 'js/baidutongji.js', ] -# General configuration. -with open("/home/docs/checkouts/readthedocs.org/user_builds/pythoncodingtime/envs/latest/lib/python3.7/site-packages/sphinxcontrib/disqus.py", "r") as file: - content = file.read() - content=content.replace("sphinx.application", "sphinx.errors") - -with open("/home/docs/checkouts/readthedocs.org/user_builds/pythoncodingtime/envs/latest/lib/python3.7/site-packages/sphinxcontrib/disqus.py", "w") as file: - file.write(content) author = '王炳明' -copyright = '2020, Python编程时光' +copyright = '2020-2024, Python编程时光' exclude_patterns = ['_build'] -extensions = ['sphinxcontrib.disqus'] # Add to this list. master_doc = 'index' project = 'Python编程时光' -release = '1.0' -version = '1.0' # Options for extensions. -disqus_shortname = 'iswbm' # Add this line to conf.py. +html_baseurl = 'https://magic.iswbm.com' +html_extra_path = ["robots.txt"] + +html_sidebars = { + '**': [ + 'versioning.html', + ], +} +smv_latest_version = 'master' +sitemap_url_scheme = "{link}" diff --git a/source/index.rst b/source/index.rst old mode 100755 new mode 100644 diff --git a/source/lc01/1-10.rst b/source/lc01/1-10.rst old mode 100755 new mode 100644 diff --git a/source/leetcode/leetcode.rst b/source/leetcode/leetcode.rst old mode 100755 new mode 100644 diff --git a/source/preface.rst b/source/preface.rst old mode 100755 new mode 100644 index 4651dd5..3f37edf --- a/source/preface.rst +++ b/source/preface.rst @@ -7,8 +7,6 @@ ---------------------------------- 这个博客于2018年6月29日发布完成,使用的是 Sphinx 来生成文档,使用 Github 托管文档,并使用 Read the Doc 发布文档。 -具体搭建教程请查阅 博客构建教程_ - ---------------------------------- 作者的话 ---------------------------------- @@ -32,5 +30,3 @@ ------------------------------ .. figure:: http://image.iswbm.com/20200607174235.png - -.. _博客构建教程: http://python-online.cn/zh_CN/latest/c04/c04_03.html diff --git a/source/py_mp_index.md b/source/py_mp_index.md deleted file mode 100644 index 9c4fc32..0000000 --- a/source/py_mp_index.md +++ /dev/null @@ -1,461 +0,0 @@ -# Python编程时光 - 学习索引 - -为了方便读者们能从本号真正学到一些有用的内容,我将 Python 编程时光干货文章进行了整理,方便你学习。 - -## 01. 基础系列 - -### 1.1 基础必学 - -1、[15个Pythonic的代码示例](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485036&idx=1&sn=24de1996a63bf25b0c0deec782f688cf&scene=21#wechat_redirect) - -2、[Python基础|深入闭包与变量作用域](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485039&idx=1&sn=23557ac2640819b568a426b2db4df69c&scene=21#wechat_redirect) - -3、[Python基础|类方法的强制重写与禁止重写](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485037&idx=1&sn=8f4838b5bc919631c5cb642120b010c2&scene=21#wechat_redirect) - -4、[Python基础|多继承与Mixin设计模式](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485032&idx=1&sn=35e1c7014bc3f668cc4b48d9c318f1f9&scene=21#wechat_redirect) - -5、[Python基础|理解元组存在的意义](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485038&idx=1&sn=d0c7ab3fc20b299e4a0b9f6b414e4070&scene=21#wechat_redirect) - -6、[你知道 Python里的「单分派泛函数」?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484938&idx=1&sn=061fdb00ccc499aa0cfc304852adb143&scene=21#wechat_redirect) - -7、[类型注解的福音,提高Python代码可读性](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484901&idx=1&sn=8f506cb46edb94dfb668525399f30f9e&scene=21#wechat_redirect) - -8、[写几个 Python 进阶必备函数](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484973&idx=1&sn=451d381fa3021e14b514cc15907ce0c3&scene=21#wechat_redirect) - -9、[Python 字符串连接,哪种的效率最高?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485016&idx=1&sn=88497c09c8785b656ae1fee1406827dc&scene=21#wechat_redirect) - -10、[深入理解Python中的上下文管理器](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484950&idx=1&sn=9d78e469f8f190ef0484842b0391bec9&scene=21#wechat_redirect) - -11、[秒杀市面 90% 的 Python 入门教程 (上)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484852&idx=1&sn=2362e034c2740d9f53c3cc99a6908cbd&scene=21#wechat_redirect) - -12、[秒杀市面 90% 的 Python 入门教程 (中)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484856&idx=1&sn=881ee5d8154a85479d71ca4594f90142&scene=21#wechat_redirect) - -13、[秒杀市面 90% 的 Python 入门教程 (下)](https://mp.weixin.qq.com/s?__biz=MzU4OTUwMDE1Mw==&mid=2247485231&idx=1&sn=fa7146937f51110eb7356154bc2e2282&scene=21#wechat_redirect) - -14、[检验你 Python 基本功的 17 个骚操作](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484863&idx=2&sn=ff11a17ad82938b3a4f83ee5fbc2e497&scene=21#wechat_redirect) - -25、[和import说再见,这个库教你怎么偷懒](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485325&idx=2&sn=90c6dfbdbb36f63db72d1c0af67c1115&scene=21#wechat_redirect) - -16、[如何修改 CentOS 6.x 上默认Python 版本](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484881&idx=1&sn=ecb99878d3e2fca2bedc808b1e7aae9c&scene=21#wechat_redirect) - -17、[写 Python 时你要避免的十个错误](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484885&idx=1&sn=6acba3bf872fe1309308d9ef6cde53ce&scene=21#wechat_redirect) - -18、[为何无法使用 ip 访问网站?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484976&idx=1&sn=503d75faea7a633729ec90d6f26e21a2&scene=21#wechat_redirect) - -19、[大白话解释什么是 Python Launcher?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485268&idx=1&sn=9cea766f1e1a1f6278fca4665fc33a87&scene=21#wechat_redirect) - -20、[13条Python2.x和3.x的区别,你知道几条?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485044&idx=1&sn=f38e2036317893be8388900dde3077e8&scene=21#wechat_redirect) - -21、[Python基础|新式类和经典类的区别?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485033&idx=1&sn=8b6357d5a66f9a06347bf0922ae11946&scene=21#wechat_redirect) - -22、[Python 中有 3 个不可思议的返回功能](https://mp.weixin.qq.com/s/dSky88bOCPgYDprzJhnlbg) - -23、[每天花 30 秒,就可以练习的 Python 小技巧!](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484861&idx=1&sn=e5fa97570b3c180e5a5edc753fc62669&scene=21#wechat_redirect) - -24、[常见 Python 简洁代码的样例](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484823&idx=2&sn=4459a7edc4a4989068b22b31c76f2c92&scene=21#wechat_redirect) - -25、[ 别笑!Python 新手这五大坑你躲不过](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485573&idx=1&sn=362e7c5e73c27942e8250375257620e2&chksm=e8866867dff1e17140dc043b5dd33eec0e94e1616eab7e4b415bb767883d46d58a7243fb198f#rd) - -26、[Python 3.9 发布,字典的合并操作符终于来了](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485825&idx=1&sn=04075357936423097f692fa19857c3aa&chksm=e8866963dff1e075707b605fb1352f7760fb1b41040bdda4dec6b83370af37e009424d24f65e&token=2013245174&lang=zh_CN#rd) - -### 1.2 开发技巧 - -1、[让Python中类的属性具有惰性求值的能力](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485759&idx=2&sn=e32b66c4666f96f84572738549a47a24&chksm=e88669dddff1e0cb0a4b6e6e965c9570b273236fa58a54351ae3b118d6a4bcbd52e452109a63&token=1148998814&lang=zh_CN#rd) - -2、[ 小技巧:如何在 Python 中实现延迟调用](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485689&idx=2&sn=43e35ff93918ef090480672d4685bce3&chksm=e886681bdff1e10dd0edee95c73a977da51fdeb514dcf8c3bb88f7f9aacc22ce155f45e84268#rd) - -3、[三个异常处理的好习惯](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484947&idx=1&sn=2bd31279dd9714045ed4dc5481b44208&scene=21#wechat_redirect) - -4、[超实用的 30 段 Python 案例](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484866&idx=1&sn=9c85dd1b58f319bb0339e2db577a0be6&scene=21#wechat_redirect) - -5、[删除系统 Python 引发的惨案](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485386&idx=1&sn=59eed0894159f4d0599be3fb787b5201&chksm=e8866728dff1ee3ef9fb7a0464df481306be11d9aeba944804e7ea74e660501d42e159ada7fc#rd) - -6、[ 利用 Flask 动态展示 Pyecharts 图表数据的几种方法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485658&idx=1&sn=f78e976889de52c0ce35e862bafacd8a&chksm=e8866838dff1e12e41b8f2dd1a43dc540ff39484ae64831332d233b5a4f7ca3b439d370ef1ed#rd) - -## 02. 进阶系列 - -### 2.1 进阶必学 - -1、[描述符:其实你不懂我(一)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484934&idx=1&sn=ef8b30ffe2467e5736036bd083b340ca&scene=21#wechat_redirect) - -2、[描述符:我无处不在!(二)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484927&idx=1&sn=8a3d673ee20bd418d93a249380ca8e76&scene=21#wechat_redirect) - -3、[Python静态方法其实暗藏玄机](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484911&idx=1&sn=a16846030c3589c912aec9efdf4f7f80&scene=21#wechat_redirect) - -4、[全面深入理解 Python 面向对象](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485208&idx=1&sn=61bf8d3ec81fa85b0bc9de6e5f5d6611&scene=21#wechat_redirect) - -5、[几个使用装饰器的小技巧](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484930&idx=1&sn=ed731e32b6e95e7d83d74a544f97142a&scene=21#wechat_redirect) - -6、[围观大神是如何用 Python 处理文件的?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484905&idx=1&sn=7de0eaab0a4c9f8b44cba01d28ad7254&scene=21#wechat_redirect) - -7、[Python进阶开发|元类编程](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485080&idx=1&sn=45e6d9995e4469d8b7bce2f800ac0f9b&scene=21#wechat_redirect) - -8、[Python进阶开发之网络编程](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485078&idx=1&sn=e0a3855d959c178b8c01bd196a048dce&scene=21#wechat_redirect) - -9、[Python 进阶:深入 GIL (上篇)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484875&idx=2&sn=8780a349c60522ad1b7aa20a3fe7a19c&scene=21#wechat_redirect) - -10、[没掌握好这24条,别说Python慢。](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484910&idx=1&sn=df8fb32a840a28954614b09bce730b72&scene=21#wechat_redirect) - -11、[花了两个星期,我终于把WSGI整明白了](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484919&idx=1&sn=bd7d2bc0ab8a41110d5d93e44ad20b1f&scene=21#wechat_redirect) - -12、[源码解读|Flask 上下文核心机制](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484943&idx=1&sn=ca846404d30a2ec775ab7db5df73aa8e&scene=21#wechat_redirect) - -13、[说说几个 Python 内存分配时的小秘密](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484853&idx=2&sn=edf7c3225d119291fae5d05820f55aac&scene=21#wechat_redirect) - -14、[写了三年代码,还是不懂 Python 世界的规则](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484886&idx=1&sn=f46917f6e23f8961c912606a281a354d&scene=21#wechat_redirect) - -15、[如何保护你写的 Python 代码?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484858&idx=1&sn=e18a1c89f14d4e4043833e3ff5cc8d32&scene=21#wechat_redirect) - -16、[27 个问题,告诉你 Python 为什么如此设计?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484862&idx=1&sn=89500433bc174dfc0530eebea2fb91d1&scene=21#wechat_redirect) - -17、[精心整理 30 个Python代码实现的常用功能](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485147&idx=2&sn=c5b871e1cebde6b311609f6de703f2e3&scene=21#wechat_redirect) - -18、[高手之路:从零开始打造一个Web服务器](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484891&idx=1&sn=ed12d175452c2efedeefb1635063177c&scene=21#wechat_redirect) - -19、[从0到1:全面理解 RPC 远程调用](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484915&idx=1&sn=427b9dda155e4201c2f939c3919def91&scene=21#wechat_redirect) - -20、[一篇 Python 函数式编程指南](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484834&idx=1&sn=559bac4ff85f7082bc989a6fde04d3f3&scene=21#wechat_redirect) - -21、[没看完这11 条,别说你精通 Python 装饰器](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484900&idx=1&sn=3997a2a377577e3d16a9b7f8e6a5ea53&scene=21#wechat_redirect) - -22、[程序卡住了?教你如何调试已在运行的程序](https://mp.weixin.qq.com/s/QC-Pc-0iifaVKOfsiNTYmA) - -23、[字符串在Python内部是如何省内存的](https://mp.weixin.qq.com/s/jp_I82fnr0-3cd6ioZcoGQ) - -24、[巧用 traceback 定位 Python 内存泄漏](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485751&idx=1&sn=8a054a575767c103c830a6b3ef5f73c8&chksm=e88669d5dff1e0c3ae1c7c32f35a8f46a3a2e0a637fb4b947566f417dd8f4419bec636c8a667#rd) - -25、[非常全的通俗易懂 Python 魔法方法指南(上)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485551&idx=1&sn=4c0983f22269a113bcdf83690e5e2b20&chksm=e886688ddff1e19b9ad230128a67ee1a9ee1eced0720c14b5d48f68943be10b1b85b23d8ca2d#rd) - -26、[非常全的通俗易懂 Python 魔法方法指南(下)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485555&idx=1&sn=0a218b796e651b451a17112e22790d07&chksm=e8866891dff1e18771a9392da7f509732244ebc4d1a6e2427acd39ee8b59b146e3d4961a2a62#rd) - -27、[教你如何阅读 Python 开源项目代码](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485451&idx=1&sn=e15856352c18297770c67d9df66ec3b0&chksm=e88668e9dff1e1ff0f6a39f3885f4126232213149ac09daa1fc0ec9e0de2095bd11fb727d63a#rd) - -### 2.2 包的管理 - -1、[最全的 pip 使用指南,50% 你可能没用过](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484827&idx=1&sn=df0923856c820e10baca20c9873b336b&scene=21#wechat_redirect) - -2、[全面学习 Python 包:包的构建与分发](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485349&idx=1&sn=8d6da555a2852a14506a491c4cf9a234&scene=21#wechat_redirect) - -3、[深入探讨 Python 的import机制:实现远程导入模块](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484838&idx=1&sn=1e6fbf5d7546902c6965c60383f7b639&scene=21#wechat_redirect) - -4、[盘点 Python 依赖库管理的工具:pip、pipreqs、pigar、pip-tools、pipdeptree](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485120&idx=1&sn=170ef1173eabc5bed28c724e19d6c0ac&scene=21#wechat_redirect) - -### 2.3 性能优化 - -1、[Python高效代码实践:性能、内存和可用性](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485161&idx=1&sn=07625f06db330897bc8bfee880ceac18&scene=21#wechat_redirect) - -2、[Python 代码的性能优化之道](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485004&idx=1&sn=8dc07a40bcac30c93874398b17a52831&scene=21#wechat_redirect) - -3、[7 个习惯帮你提升Python运行性能](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484957&idx=1&sn=9b3c644e6b1777b77ce7f84047c6d500&scene=21#wechat_redirect) - -4、[如何提升你的 Python 代码健壮性(上)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484871&idx=1&sn=985b79143d0cafd21e5263ee79afa777&scene=21#wechat_redirect) - -5、[如何提升你的 Python 代码健壮性(下)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484867&idx=1&sn=b8eab8416229dc74f1ac96e099a3df71&scene=21#wechat_redirect) - -6、[将 Python 运行速度提升 30 倍的神技巧](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484877&idx=1&sn=f6067875f1e807d19291e383f8421a3b&scene=21#wechat_redirect) - -7、[实战讲解:Python 性能分析与优化实践](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485404&idx=1&sn=486fcf898d6dd21b0feb990de3c1e08f&chksm=e886673edff1ee28b8c6e1d520b2732b2f66f4c4691ef2696b54743b1ad0769356e12a1e98b2#rd) - -### 2.4 并发编程 - -1、[并发编程01|从性能角度来初探并发编程](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485074&idx=1&sn=a859c6ab1d9b95c30c9f8b06f9489887&scene=21#wechat_redirect) - -2、[并发编程02|创建多线程的几种方法](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485073&idx=1&sn=854ff524645247e5020d977e57d9c0e6&scene=21#wechat_redirect) - -3、[并发编程03|谈谈线程中的“锁机制”](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485069&idx=1&sn=52370a27d4a5c4541969921ada890e0b&scene=21#wechat_redirect) - -4、[并发编程04|消息通信机制/任务协调](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485068&idx=1&sn=fc0798e84e7845cd9efee1809f932f15&scene=21#wechat_redirect) - -5、[并发编程05|线程中的信息隔离](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485066&idx=1&sn=0bb9d6c82a6d062e50d09e1eefd09427&scene=21#wechat_redirect) - -6、[并发编程06|如何创建线程池](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485065&idx=1&sn=60891b67b139806cf6bf4c05f5861f02&scene=21#wechat_redirect) - -7、[并发编程07|从生成器使用入门协程](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485064&idx=1&sn=8584bb778152a12ca335970bed9fdbdc&scene=21#wechat_redirect) - -8、[并发编程08|深入理解yield from语法](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485063&idx=1&sn=0ff7a99058320ff90a6237e7e03367fb&scene=21#wechat_redirect) - -9、[并发编程09|初识异步IO框架:asyncio](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485061&idx=1&sn=9e846df1a5cb57e0bc6254dcc953e243&scene=21#wechat_redirect) - -10、[并发编程12|学习异步IO框架:asyncio](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485058&idx=1&sn=92ef1f79ce6488670ae13e5d6a1c7908&scene=21#wechat_redirect) - -11、[并发编程11|实战异步IO框架:asyncio](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485057&idx=1&sn=8bacf0f2b42de5962fbdf32796903f27&scene=21#wechat_redirect) - -12、[百万「并发」之Python异步编程(上篇)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484896&idx=2&sn=5d8458ede3440ae501d19c5ffd5f8a99&scene=21#wechat_redirect) - -13、[百万「并发」之Python异步编程(中篇)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484889&idx=2&sn=25d11042e5b2ab17dff40535d354b8f8&scene=21#wechat_redirect) - -14、[百万「并发」之Python异步编程(下篇)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484885&idx=2&sn=b9e62a9b024358aec629e876b76e0eb5&scene=21#wechat_redirect) - -15、[如何一行 Python 代码实现并行?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484826&idx=1&sn=6c5b575b7b134642077cfde2bb8b613f&scene=21#wechat_redirect) - -16、[asyncio:从原理、源码到实现](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485245&idx=1&sn=a08826c3d28037479190fd652438bcca&scene=21#wechat_redirect) - -17、[为什么说线程是CPU调度的基本单位?](https://mp.weixin.qq.com/s/c3aZ-6UzZVD3_PvVhiDPWA) - -18、[非常适合小白的 Asyncio 教程](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485842&idx=1&sn=ab85dab95cf47046ddcc35b60e633c40&chksm=e8866970dff1e066381c0ba53fe09e34b8f1ce52bbbfe568c1ee66b9cbad87f43b442895c770&token=2013245174&lang=zh_CN#rd) - -### 2.5 实战练习 - -1、[适合 Python 新手练习的绝佳项目](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485898&idx=2&sn=36a1b1a94dece0a290652da23679cff0&chksm=e8866928dff1e03e58b95b3cd24dbc23e368b6eb2c0599b2efe814924e21ec5c01935b2e0455&token=2013245174&lang=zh_CN#rd) - -2、[4个Python实战项目,让你瞬间读懂Python!](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485852&idx=2&sn=eb3471a260cce835a755a780d6cc690d&chksm=e886697edff1e068bd0f9d456e26ee5ebe482580457951e42807e731d266c95f00c37de22fea&token=2013245174&lang=zh_CN#rd) - -## 03. 数据分析 - -### 3.1 基础库 - -1、[有关 NumPy 和数据表达的可视化介绍](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485295&idx=1&sn=6220c2ed0d73feade0c9691fbf214ba7&scene=21#wechat_redirect) - -### 3.2 数据可视化 - -1、[可视化01|一图带你入门matplotlib](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485022&idx=1&sn=76d4270a15c430217588bd9f8be8303b&chksm=e88666bcdff1efaa35c6d685f19b04e62f58e768dc9d1819bcb9f8211f65d3fd83296259d923&token=1148998814&lang=zh_CN#rd) - -2、[可视化02|详解六种可视化图表](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485020&idx=1&sn=7f82736e6a2e5442ed59c82d8e242ca4&scene=21#wechat_redirect) - -3、[可视化03|用正余弦学习matplotlib](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485019&idx=1&sn=9d44ee27fd888831e94845d6d0256deb&scene=21#wechat_redirect) - -4、[可视化04|子图与子区难点剖析](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485014&idx=1&sn=90dbdf17f049c152944a4c28642df249&scene=21#wechat_redirect) - -5、[可视化05|绘制酷炫的GIF动态图](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485007&idx=1&sn=1464dee30d006ddb5111f9827b0c4081&scene=21#wechat_redirect) - -6、[可视化06|自动生成图像视频](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485002&idx=1&sn=f09c089328eb0fe39721b73272c81214&scene=21#wechat_redirect) - -7、[可视化07|50个最有价值的图表](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484920&idx=1&sn=92eb2fd55f13a8bda75a7103a73f8d50&scene=21#wechat_redirect) - -### 3.3 工具使用 - -1、[ 整理了 50个 IPython 的实用技巧](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485613&idx=1&sn=70f89faf2573b3025edc73f525f22a0a&chksm=e886684fdff1e159509d30fd0a24854da39fb5a6ae55e0ed6e40b7971d3219968006f80aad7b#rd) - -2、[ Jupyter NoteBook 的使用指南](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485024&idx=1&sn=aac0db4b2c97c7f5c2871342b936eaa2&chksm=e8866682dff1ef947ec6474b8c03f668e462232ebd0ec0f137d42e5e63b9b66a3d9e303c1caf&token=1148998814&lang=zh_CN#rd) - -## 04. 开发工具 - -1、[代码调试|无图形调试工具 - pdb](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484969&idx=1&sn=a99fc31865edc4b439707d2be6f66654&scene=21#wechat_redirect) - -2、[代码调试|远程调试图文超详细教程](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484970&idx=1&sn=9ac9c5dfcdc8c6b48a7b4c9854825734&scene=21#wechat_redirect) - -3、[优化Python开发环境的几个技巧](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485131&idx=1&sn=f1bd7b31a3624f015c74f56a8888a695&scene=21#wechat_redirect) - -4、[让你重新爱上 Windows 的小众软件](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484964&idx=1&sn=1479536416788a6c55dddef70167eb88&scene=21#wechat_redirect) - -5、[Python 的命令行参数解析工具](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484960&idx=1&sn=3456d9a05b35588b060833ceb189db6e&scene=21#wechat_redirect) - -6、[搜索神器 EveryThing,你把它的潜力用到极致了吗?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484894&idx=1&sn=59877fe818739175d3d075458594f563&scene=21#wechat_redirect) - -7、[开发工具|盘点 Xshell 的那些奇淫技巧](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485043&idx=1&sn=8c240e569c60607cd4cea8888a6aa2e1&scene=21#wechat_redirect) - -8、[开发工具|给你的项目买份保险:Python虚拟环境](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485049&idx=1&sn=c16383d6cc91a7ed8254e344d994f101&scene=21#wechat_redirect) - -9、[学会这21条,你离 Vim 大神就不远了!](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484902&idx=1&sn=d677261fda90b67bf5eda886447a4f77&scene=21#wechat_redirect) - -10、[用 Python 做开发,做到这些才能一直爽](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484874&idx=1&sn=abffab70b1e265ede1fd3b994dedab90&scene=21#wechat_redirect) - -11、[这款神器,能把 Python 代码执行过程看地一清二楚](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484878&idx=1&sn=9bc941ff19ec38e7c7137a22df981f27&scene=21#wechat_redirect) - -12、[如何把自己的 Python 包发布到 PYPI?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484835&idx=2&sn=f24a415d47d4122c4d6b1a6ccf254a51&scene=21#wechat_redirect) - -13、[谁说 Vim 不好用?送你一个五彩斑斓的编辑器!](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484872&idx=2&sn=ce718e511b754fe936172724a824a716&scene=21#wechat_redirect) - -14、[迄今为止,我见过最好的正则入门教程(上)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484831&idx=1&sn=077d14410db6a906c3b962f0ba18b2d4&scene=21#wechat_redirect) - -15、[迄今为止,我见过最好的正则入门教程(下)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484830&idx=1&sn=4d48b1c7f2b5234992732f8f15b868df&scene=21#wechat_redirect) - -16、[Win 平台做 Python 开发的最佳组合](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484837&idx=2&sn=68935077ae9eeb6fe4f708565aa0a9ed&scene=21#wechat_redirect) - -17、[告别996,全靠这个Python补全利器](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484824&idx=2&sn=8f20af371954dfd2a5c890bb5814b4c9&scene=21#wechat_redirect) - -18、 - -### 4.1 PyCharm - -1、[受用一生的高效PyCharm使用技巧(一)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484946&idx=1&sn=12a891db18e9d1233535fa4aca9a3892&scene=21#wechat_redirect) - -2、[受用一生的高效PyCharm使用技巧(二)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484942&idx=1&sn=9dbb2cfe2a277bd3519d37414fcc608e&scene=21#wechat_redirect) - -3、[受用一生的高效PyCharm使用技巧(三)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484932&idx=1&sn=347b35ccdee94895652caa86191360c8&scene=21#wechat_redirect) - -4、[受用一生的高效PyCharm使用技巧(四)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484918&idx=1&sn=66b3fd784050e5cb85109faa3d063993&scene=21#wechat_redirect) - -5、[受用一生的高的PyCharm使用技巧(五)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484914&idx=1&sn=cd1282f94f9326647b05ad9090afd2c8&scene=21#wechat_redirect) - -6、[受用一生的高效PyCharm使用技巧(六)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484907&idx=1&sn=647a75b96884b39c57664f62ead11b89&scene=21#wechat_redirect) - -7、[受用一生的高效 PyCharm 使用技巧(七)](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485372&idx=1&sn=3ed61c4ccbb089358213304cd70bb0f7&chksm=e886675edff1ee487911e922279675264916dd08120f5f40113f37dc080b94c7bf9ad67719c4&token=1148998814&lang=zh_CN#rd) - -8、[代码调试|远程调试图文超详细教程](https://mp.weixin.qq.com/s?__biz=MzU4OTUwMDE1Mw==&mid=2247484304&idx=1&sn=7612f0c2fd6232723ca2733fb1e8f46d&scene=21#wechat_redirect) - -9、[手把手教你打造一个顔值超高的IDE](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485052&idx=1&sn=2e0bb1a3af4206fd2cf8e3650f695e6b&scene=21#wechat_redirect) - -### 4.2 VSCode - -1、[这 21 个VSCode 快捷键,能让你的代码飞起来](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484893&idx=1&sn=421b544115efad388314ccd027761b40&scene=21#wechat_redirect) - -2、[神技巧!在浏览器中也能用 VS Code](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484859&idx=2&sn=70abd19426374271a10cbef8e1645c7a&scene=21#wechat_redirect) - -### 4.3 好用的库 - -1、[使用 Python 远程登陆服务器的最佳实践](https://mp.weixin.qq.com/s/aRXYAP9D9rgil-0_Etb0SQ) - -2、[这么设置 Python 的环境变量,我还是第一次见](https://mp.weixin.qq.com/s/LcjIPZPSg0KbL9X-i6vigg) - -3、[太强了!Python中完美的日志解决方案](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485741&idx=1&sn=505c06669baa44873171a3eed81f32b4&chksm=e88669cfdff1e0d91635b75f54998667e04bfb05a1aebae096930bd23acdd9d35ea858861bb2#rd) - -4、[如何使用 Python 操作 Git 代码?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485723&idx=1&sn=35511b71347111c00a9e1633309efd13&chksm=e88669f9dff1e0ef7aec03f2bdbddb8408bf12a57a36add441b75e486dd72df13be7629625ab#rd) - -5、[用它5分钟以后,我放弃用了四年的 Flask](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485657&idx=1&sn=e21ddc5b670a9b667135df2ca97ec99c&chksm=e886683bdff1e12d9e1d64a7f5bfbc65ca2372711cd9b08ef36ce0cdd99811ce44ff162585df#rd) - -6、[整理了 34 个被吹爆了的Python开源框架](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485653&idx=1&sn=edd893dd711b3681aa0bacdf8ab384fd&chksm=e8866837dff1e1216f6fbf413118f839e6e1dc5ed7b45117df55f13e25670c5ffba34966abff#rd) - -7、[ 为了选出最合适的 http客户端,我做了个测评](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485651&idx=1&sn=647c22ac66e469ccc33478457dadf224&chksm=e8866831dff1e1279149ff1e3af84bef86d24c524b7b7c14e9c519cf7c4739fa8f10dcc4a8d0#rd) - -8、[凭什么 FastAPI 火成这样?看这篇文章就知道了](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485891&idx=1&sn=82bde3e83435ebb6beb7e3581f12f7b0&chksm=e8866921dff1e0376befb029e9f1d81f33b0fa55242182f8de20d2a6e6b0cce0b5efd02f9aef&token=2013245174&lang=zh_CN#rd) - -9、[要想 BUG 变得富有美感,这个库你一定要看](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485889&idx=1&sn=0258eb138dce2c71b533d340770d5d09&chksm=e8866923dff1e03548dee4f780b410c22b51e1c3ffde3ef814f6a9b94e49af941fc04c79a660&token=2013245174&lang=zh_CN#rd) - -10、[如何使用 Python 输出漂亮的表格?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485841&idx=2&sn=705c123abed5441a52f8640b65f3c400&chksm=e8866973dff1e0650e4b2e025d0e86b3aedeb7ea8f68ad560f37cb033f36660b6eee15fb3ce4&token=2013245174&lang=zh_CN#rd) - -## 05. 网络爬虫 - -1、[想逆向我的 js 代码?先过了我的反 debug 再说吧!](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484876&idx=1&sn=ed81400f77deb2af18311f8e42e9b11b&scene=21#wechat_redirect) - -2、[教你实现一个可视化爬虫监控系统](https://mp.weixin.qq.com/s?__biz=MzU4OTUwMDE1Mw==&mid=2247484208&idx=1&sn=d04421526cf0e12089541063b4ec448d&scene=21#wechat_redirect) - -3、[估计是讲得最清楚的「异步爬虫」指南](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484929&idx=1&sn=7055414a33d7cbadaeb5645cbd5203ec&scene=21#wechat_redirect) - -4、[10 个爬虫工程师必备的工具](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484958&idx=1&sn=3422e53de4ae701a922104ad26f8e256&scene=21#wechat_redirect) - -## 06. 实用系列 - -1、[你抢不到的火车票,我帮你!](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484972&idx=1&sn=19ac660b61f046c5d419acdae7f394a6&scene=21#wechat_redirect) - -2、[30分钟教你快速搭建一个顔值超高的博客](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485045&idx=1&sn=8b250c0c174e418e2025d86f42c695b6&scene=21#wechat_redirect) - -3、[用Python写一个表白神器让你七夕脱离单身](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485021&idx=1&sn=123b39391d11e9c7160b47a4c6a3dcb1&scene=21#wechat_redirect) - -4、[用 Sphinx 搭建博客时,如何导流到公众号?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484848&idx=1&sn=80ae18e7f53a64e62ac9c0ef0c21362e&scene=21#wechat_redirect) - -5、[10 行 Python 代码写 1 个 USB 病毒](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484832&idx=1&sn=43c353856c7dd9ea2cb1a9bbcd077fa2&scene=21#wechat_redirect) - -6、[废旧 Android 手机如何改造成 Linux 服务器](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485279&idx=1&sn=7166a2c9445438eaaf63a25b598e5427&scene=21#wechat_redirect) - -7、[如何将手机打造成 Python 开发利器?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485256&idx=1&sn=2e646e28287707c67c22fbe68112e622&scene=21#wechat_redirect) - -8、[我用 Python 做了一回黑客,批量破解了朋友的网站密码](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485156&idx=1&sn=05142ae7b4bf7e97ebe042e394aa7084&scene=21#wechat_redirect) - -9、[手把手教你安装Win+Ubuntu双系统](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485030&idx=1&sn=8383a7306381f36781957b807fa93961&scene=21#wechat_redirect) - -10、[一篇文章让你的 MacBook 进入超神状态](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484906&idx=1&sn=b2c3c969e53beae53aa7be7959227b5b&scene=21#wechat_redirect) - -11、[情人节来了,教你个用 Python 表白的技巧](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485701&idx=1&sn=ef7f0b83e60f397c1839259b275575bf&chksm=e88669e7dff1e0f1317ec429cf9e70442bb3a08ec46efe9a0d06f4ec9847574ea30b53904670&token=1148998814&lang=zh_CN#rd) - -## 07. 实用工具 - -### 7.1 Linux - -1、[值得收藏的 14 个 Linux 下 CPU 监控工具](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485356&idx=1&sn=9dc2f440299ad179e1874febdffa04d6&scene=21#wechat_redirect) - -2、[19 个没什么用,但是”特好玩“的命令](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485142&idx=2&sn=97b0f02f8f0153b1c7ba4899359d55e4&scene=21#wechat_redirect) - -3、[相见恨晚的15个 Linux 神器,你可能一个都没见过](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484887&idx=1&sn=3473df33893a23b4de1e176985a5265e&scene=21#wechat_redirect) - -### 7.2 Git - -1、[关于 Git 和 GitHub,你所不知道的十件事](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485232&idx=2&sn=c3314e79af9029bdd76ae187f7b4eb19&scene=21#wechat_redirect) - -2、[关于 Git 图谱,这一篇文章讲得很细。](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485179&idx=2&sn=a9f0a8a669d70c0dccedf71a530d1d93&scene=21#wechat_redirect) - -3、[如何巧妙处理 Git 多平台换行符问题(LF or CRLF)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485333&idx=2&sn=4e8e95e5cb12d27ea6b510aaaf15b4c0&scene=21#wechat_redirect) - -4、[这 7 个高级技巧,不会还怎么玩GitHub](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484940&idx=1&sn=9ad168943b03dff09d53e5d73e13ed46&scene=21#wechat_redirect) - -5、[ 神器!这款VSCode插件能填满Github绿色格子](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485595&idx=1&sn=65df21ab0b4042953dd8257bea8b1848&chksm=e8866879dff1e16fff97584467f2f1728f9c2da007894ae062e9201d53b82a30054036f8e81e#rd) - -6、[Git 中冷门却又非常需要的高级用法](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485890&idx=2&sn=528df28585ec16114324e391dbfebab4&chksm=e8866920dff1e036d5da7f5dfe3c944ef8b13d72f022d72b209117120c7e27ebb71036b99893&token=2013245174&lang=zh_CN#rd) - -### 7.3 MySQL - -1、[写给程序员的 MySQL 面试高频 100 问](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485321&idx=1&sn=30432df24d4e7858a475192bcf233914&scene=21#wechat_redirect) - -2、[开发人员必学的几点 SQL 优化点](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485892&idx=1&sn=e0b526002a5568391e75fb0cac60ab0b&chksm=e8866926dff1e0303f2717e95ff801c2550d97812c1a775cc232d48183af802db1be54f26105&token=2013245174&lang=zh_CN#rd) - -### 7.4 Chrome - -1、[推荐 8个 超实用的 Chrome 插件](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484925&idx=1&sn=110e4da78a20e8b5721f00bb36ba9d4c&scene=21#wechat_redirect) - -2、[没有这 42 款插件的Chrome是没有灵魂的](https://mp.weixin.qq.com/s?__biz=MzU4OTUwMDE1Mw==&mid=2247485016&idx=1&sn=ba38963413ee2926f6f5e69b1427491d&scene=21#wechat_redirect) - -### 7.5 其他工具 - -1、[整理了 11 个好用的代码质量审核和管理工具](https://mp.weixin.qq.com/s/DH_TOA3Cr3y37yZ5F4bU5A) - -2、[11 个最佳的 Python 编译器和解释器](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485682&idx=1&sn=31b1b68432ca1f1db3866fb0f670270b&chksm=e8866810dff1e106a21c63c1ee198069223047afc72482c1f14ee4f473211aca85273c57d529#rd) - -## 08. 代码优化 - -### 8.1 算法讲解 - -1、[策略模式:商场促销活动背后的代码哲学](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484937&idx=1&sn=61b81dddfb20d80060fee5f22ab28d5f&scene=21#wechat_redirect) - -2、[算法教程|八张图带你轻松理解经典排序算法(上)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485042&idx=1&sn=b9e8503905be5a960119c2893995fb6d&scene=21#wechat_redirect) - -3、[算法教程|八张图带你轻松理解经典排序算法(中)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485041&idx=1&sn=12d05e0316c8866e3876c3ed484ced15&scene=21#wechat_redirect) - -4、[一次忘记密码引发的算法思考](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484981&idx=1&sn=96f49ab2225c61b45601b68521135dd6&scene=21#wechat_redirect) - -5、[程序员走楼梯都会思考的一道题](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484993&idx=1&sn=36cc1466ed73af2fcf4a8430368a62ee&scene=21#wechat_redirect) - -6、[以为是高性能神仙算法,一看源代码才发现...](https://mp.weixin.qq.com/s/L_OvCcpIFKBLckLBvit8UQ) - -7、[ 用Python手写十大经典排序算法](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485494&idx=1&sn=1580e9fc17086bc83e30b0fa4c850bd8&chksm=e88668d4dff1e1c27a26b31fda2f0e413be83519aa9dd0e3c8d3cfd08a2478eb2596ed7ba535#rd) - -### 8.32设计模式 - -1、[单例模式告诉你只能娶一个老婆](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484921&idx=1&sn=493d0a492277ecb223ddfff9de8720ff&scene=21#wechat_redirect) - -### 8.3 代码美化 - -1、[看了同事的代码,我忍不住写了这份代码指南](https://mp.weixin.qq.com/s/goqj1YVdKV171LLpjtXruQ) - -2、[怎样才能写好一个 Python 函数](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485416&idx=1&sn=85b14857e795a92985230ba3f51b73f6&chksm=e886670adff1ee1c7f4266d4e3b931d2e3d1de4dff30dc869eae2cd174da7f54080787f60a1b#rd) - -## 09. Python 冷知识 - -1、[Python那些不为人知的冷知识(一)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485031&idx=1&sn=e5ab670ce6485a50c4b7eeaee11e6f34&scene=21#wechat_redirect) - -2、[Python那些不为人知的冷知识(二)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485029&idx=1&sn=5db54b5777348e827c274cd932a15a15&scene=21#wechat_redirect) - -3、[Python那些不为人知的冷知识(三)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485009&idx=1&sn=92e268c20c6f01278e220e11397cc2f0&scene=21#wechat_redirect) - -4、[Python那些不为人知的冷知识(四)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485001&idx=1&sn=5a02f8c912518d15a0b96319cf2da7d1&scene=21#wechat_redirect) - -5、[Python那些不为人知的冷知识(五)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484997&idx=1&sn=5b8cf44b62550e1bd67284fb2b0780dc&scene=21#wechat_redirect) - -6、[Python那些不为人知的冷知识(六)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484990&idx=1&sn=bd28368eff832ba2b24a64f637a6d0c8&scene=21#wechat_redirect) - -7、[Python那些不为人知的冷知识(七)](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484874&idx=1&sn=abffab70b1e265ede1fd3b994dedab90&scene=21#wechat_redirect) - -## 10. 云计算 - -1、[一份面向初学者的云计算通识指南](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484908&idx=1&sn=3085e048da685440408e1bdc54feb4aa&scene=21#wechat_redirect) - -2、[ 如何探测虚拟环境是物理机、虚拟机还是容器?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485401&idx=2&sn=8bfb6e1a71e0777a4d5e55f64b55ace5&chksm=e886673bdff1ee2d8acce291e402cd2ae0c7f64e455fdeea158eb53e535ee79b2aadc449935e#rd) - -3、[超详细教你如何阅读 OpenStack 源码?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485899&idx=1&sn=7a578a553c67e794ef6ec2f463864a46&chksm=e8866929dff1e03fa8152751967106e9262980db6afdff106fe9e39854d24d25494b3e8a3a4e&token=2013245174&lang=zh_CN#rd) - -## 11. 职场相关 - -1、[一个专科生的 Python 转行之路](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485012&idx=1&sn=751796e63a30d84be01486619b5fb806&scene=21#wechat_redirect) - -2、[一个机械生的Python转行自述](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484979&idx=1&sn=6513e940bc7c0677dedf7efff80aa4c6&scene=21#wechat_redirect) - -3、[自学 Python后端开发 到什么程度可以找工作?](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485010&idx=1&sn=e76d031f91633f9bfde9da36d8dcf996&scene=21#wechat_redirect) - -4、[工作不是游戏,写给编程新人的五点建议](https://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247484984&idx=1&sn=0934415cc48004ae76345dcfb8e33b94&scene=21#wechat_redirect) - -5、[Python 从业十年是种什么体验?](http://mp.weixin.qq.com/s?__biz=MzIzMzMzOTI3Nw==&mid=2247485601&idx=1&sn=0dbebc02002ab01a8cd42265822ff81c&chksm=e8866843dff1e15509f2a9d2cd551b2360607cf88e73e5d3036388716ee57949d726dcea209c#rd) - - - ---- - -![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/roadmap.rst b/source/roadmap.rst old mode 100755 new mode 100644 diff --git a/source/robots.txt b/source/robots.txt new file mode 100644 index 0000000..b10196d --- /dev/null +++ b/source/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Sitemap: http://pythontime.iswbm.com/sitemap.xml diff --git a/source/thanks.rst b/source/thanks.rst index b3f0b61..731c695 100644 --- a/source/thanks.rst +++ b/source/thanks.rst @@ -11,7 +11,7 @@ 所以在这里准备开一个页面,感谢这些朋友。另外,对文章有任何疑问的同学都可以加我微信交流。 -.. figure:: http://image.python-online.cn/20190704205721.png +.. figure:: http://image.iswbm.com/20190704205721.png :alt: 添加明哥好友