diff --git a/README.md b/README.md index 09c7213..8f1fdc2 100644 --- a/README.md +++ b/README.md @@ -1,163 +1,1153 @@ +# 从零到一的 Python 学习路线 + +我在我的个人公众号(Python编程时光)分享过非常多的 Python 干货,由于公众号是个十分封闭的生态,读过之后,就没什么人会记住它了。不像网站那样有搜索引擎会给它们持续的曝光,历久弥香。 + +我自认为在我公众号里,发布的文章质量是非常高的,为了不让这些干货沉入海底,我开了这个仓库,方便有需要的人进行索引,择需阅读。 + + + +目前目录更新内容至 2021/3/20 发的文章。 + +## 01. 基础系列 + +### 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) -这是我的个人博客( [Python编程时光](http://python.iswbm.com/) ),主要写关于Python的一些思考总结。 - -关于搭建教程,感兴趣的可以查看这边:[Sphinx 搭建博客的图文教程](http://python.iswbm.com/en/latest/c04/c04_03.html) -## 第一章:基础知识 -- 1.1 [13条Python2.x和3.x的区别?](http://python.iswbm.com/en/latest/c01/c01_01.html) -- 1.4 [什么是猴子补丁?](http://python.iswbm.com/en/latest/c01/c01_04.html) -- 1.5 [深入闭包与变量作用域](http://python.iswbm.com/en/latest/c01/c01_05.html) -- 1.6 [深入理解元组存在的意义](http://python.iswbm.com/en/latest/c01/c01_06.html) -- 1.7 [15个Pythonic的代码示例](http://python.iswbm.com/en/latest/c01/c01_07.html) -- 1.8 [新式类和经典类的区别?](http://python.iswbm.com/en/latest/c01/c01_08.html) -- 1.9 [多继承与Mixin设计模式](http://python.iswbm.com/en/latest/c01/c01_09.html) -- 1.10 [Python 黑魔法指南 50 例](http://python.iswbm.com/en/latest/c01/c01_10.html) -- 1.11 [正则表达式必知必会](http://python.iswbm.com/en/latest/c01/c01_11.html) -- 1.12 [搞懂字符编码的前世今生](http://python.iswbm.com/en/latest/c01/c01_12.html) -- 1.13 [Python几个高阶函数](http://python.iswbm.com/en/latest/c01/c01_13.html) -- 1.14 [with 与 上下文管理器](http://python.iswbm.com/en/latest/c01/c01_14.html) -- 1.15 [提升Python性能的7个习惯](http://python.iswbm.com/en/latest/c01/c01_15.html) -- 1.16 [泛型函数怎么写?](http://python.iswbm.com/en/latest/c01/c01_16.html) -- 1.17 [深入理解「描述符」](http://python.iswbm.com/en/latest/c01/c01_17.html) -- 1.18 [MySQL 使用总结](http://python.iswbm.com/en/latest/c01/c01_18.html) -- 1.20 [静态方法其实暗藏玄机](http://python.iswbm.com/en/latest/c01/c01_20.html) -- 1.21 [开发小技巧](http://python.iswbm.com/en/latest/c01/c01_21.html) -- 1.22 [如何修改 CentOS 6.x 上默认Python](http://python.iswbm.com/en/latest/c01/c01_22.html) -- 1.23 [Pythonista 学习 Js](http://python.iswbm.com/en/latest/c01/c01_23.html) -- 1.24 [深入探讨 Python 的 import 机制:实现远程导入模块](http://python.iswbm.com/en/latest/c01/c01_24.html) -- 1.25 [50% 的人不知道的Python 包与模块的知识盲区](http://python.iswbm.com/en/latest/c01/c01_25.html) -- 1.26 [C语言基础的学习](http://python.iswbm.com/en/latest/c01/c01_26.html) -- 1.27 [全面学习 Python 包:包的构建与分发](http://python.iswbm.com/en/latest/c01/c01_27.html) -- 1.27 [如何阅读 CPython源码?](http://python.iswbm.com/en/latest/c01/c01_29.html) -- 1.30 [盘点程序员学习编程的那些网站](http://python.iswbm.com/en/latest/c01/c01_30.html) -- 1.31 [学习 Pillow 笔记](http://python.iswbm.com/en/latest/c01/c01_31.html) -- 1.32 [在 CentOS 7.2 上安装 Python3.7](http://python.iswbm.com/en/latest/c01/c01_32.html) -- 1.33 [如何调试已经运行中的程序](http://python.iswbm.com/en/latest/c01/c01_33.html) -- 1.34 [每日一库:sh,最优雅的命令调用方式](http://python.iswbm.com/en/latest/c01/c01_34.html) -- 1.35 [使用 Python 远程登陆服务器的利器](http://python.iswbm.com/en/latest/c01/c01_35.html) -- 1.36 [每日一库:pretty_errors 解决bug 洁癖](http://python.iswbm.com/en/latest/c01/c01_36.html) -- 1.37 [Python 炫技操作:条件语句的七种写法](http://python.iswbm.com/en/latest/c01/c01_37.html) -- 1.38 [/usr/bin/env python 有什么用?](http://python.iswbm.com/en/latest/c01/c01_38.html) -- 1.39 [Python 炫技操作:合并字典的七种方法](http://python.iswbm.com/en/latest/c01/c01_39.html) -- 1.40 [Python 炫技操作:判断是否包含子串的七种方法](http://python.iswbm.com/en/latest/c01/c01_40.html) -- 1.41 [Python 炫技操作:连接列表的八种方法](http://python.iswbm.com/en/latest/c01/c01_41.html) -- 1.42 [Python 炫技操作:海象运算符的三种用法](http://python.iswbm.com/en/latest/c01/c01_42.html) -- 1.43 [求你了,别再使用 pprint 打印字典了](http://python.iswbm.com/en/latest/c01/c01_43.html) -- 1.44 [详解 Python 中的编码问题](http://python.iswbm.com/en/latest/c01/c01_44.html) -- 1.45 [Python炫技操作:花式导包的八种方法](http://python.iswbm.com/en/latest/c01/c01_45.html) - -## 第二章:并发编程 -- 2.1 [从性能角度初探并发编程](http://python.iswbm.com/en/latest/c02/c02_01.html) -- 2.2 [创建多线程的几种方法](http://python.iswbm.com/en/latest/c02/c02_02.html) -- 2.3 [谈谈线程中的“锁机制”](http://python.iswbm.com/en/latest/c02/c02_03.html) -- 2.4 [线程消息通信机制](http://python.iswbm.com/en/latest/c02/c02_04.html) -- 2.5 [线程中的信息隔离](http://python.iswbm.com/en/latest/c02/c02_05.html) -- 2.6 [线程池与进程池的创建](http://python.iswbm.com/en/latest/c02/c02_06.html) -- 2.7 [从生成器使用入门协程](http://python.iswbm.com/en/latest/c02/c02_07.html) -- 2.8 [深入理解yield from语法](http://python.iswbm.com/en/latest/c02/c02_08.html) -- 2.9 [初识异步IO框架:asyncio 上篇](http://python.iswbm.com/en/latest/c02/c02_09.html) -- 2.10 [深入异步IO框架:asyncio 中篇](http://python.iswbm.com/en/latest/c02/c02_10.html) -- 2.11 [实战异步IO框架:asyncio 下篇](http://python.iswbm.com/en/latest/c02/c02_11.html) -- 2.12 [生成器与协程,你分清了吗?](http://python.iswbm.com/en/latest/c02/c02_12.html) -- 2.13 [I/O多路复用:select/poll/epoll](http://python.iswbm.com/en/latest/c02/c02_13.html) -- 2.14 [浅谈线程安全那些事儿](http://python.iswbm.com/en/latest/c02/c02_14.html) - -## 第三章:高级编程 -- 3.1 [装饰器进阶用法详解](http://python.iswbm.com/en/latest/c03/c03_01.html) -- 3.2 [深入理解Python元类](http://python.iswbm.com/en/latest/c03/c03_02.html) -- 3.3 [Socket编程实现在线聊天](http://python.iswbm.com/en/latest/c03/c03_03.html) -- 3.4 [Django+Uwsgi部署网站](http://python.iswbm.com/en/latest/c03/c03_04.html) -- 3.5 [源码解读:Flask上下文与代理模式](http://python.iswbm.com/en/latest/c03/c03_05.html) -- 3.6 [Web开发者必看:理解WSGI](http://python.iswbm.com/en/latest/c03/c03_06.html) - -## 第四章:开发工具 -- 4.1 [虚拟环境:virtualenv](http://python.iswbm.com/en/latest/c04/c04_01.html) -- 4.2 [Xshell的高效使用手册](http://python.iswbm.com/en/latest/c04/c04_02.html) -- 4.3 [30分钟教你搭建一个博客](http://python.iswbm.com/en/latest/c04/c04_03.html) -- 4.4 [Jupyter NoteBook 使用指南](http://python.iswbm.com/en/latest/c04/c04_04.html) -- 4.5 [Win10+Ubuntu 双系统安装教程](http://python.iswbm.com/en/latest/c04/c04_05.html) -- 4.6 [我的 Git 使用指南](http://python.iswbm.com/en/latest/c04/c04_06.html) -- 4.7 [Hexo 搭建博客教程](http://python.iswbm.com/en/latest/c04/c04_07.html) -- 4.8 [珍惜生命,远离鼠标](http://python.iswbm.com/en/latest/c04/c04_08.html) -- 4.9 [MySQL的基本使用](http://python.iswbm.com/en/latest/c04/c04_09.html) -- 4.10 [MySQL的高级进阶](http://python.iswbm.com/en/latest/c04/c04_10.html) -- 4.11 [不能不会的远程调试技巧](http://python.iswbm.com/en/latest/c04/c04_11.html) -- 4.12 [服务器调试神器:pdb](http://python.iswbm.com/en/latest/c04/c04_12.html) -- 4.13 [命令行解析工具:argparse](http://python.iswbm.com/en/latest/c04/c04_13.html) -- 4.14 [虚拟环境:Pipenv](http://python.iswbm.com/en/latest/c04/c04_14.html) -- 4.15 [30个 PyCharm 实用技巧](http://python.iswbm.com/en/latest/c04/c04_15.html) -- 4.16 [Python 开发技巧集合](http://python.iswbm.com/en/latest/c04/c04_16.html) -- 4.17 [详解 23 种设计模式](http://python.iswbm.com/en/latest/c04/c04_17.html) -- 4.18 [详细的 Mac 使用指南](http://python.iswbm.com/en/latest/c04/c04_18.html) -- 4.19 [程序员编码必学:Vim](http://python.iswbm.com/en/latest/c04/c04_19.html) -- 4.20 [学会使用谷歌搜索引擎](http://python.iswbm.com/en/latest/c04/c04_20.html) -- 4.21 [最全的 pip 使用指南,50% 你可能没用过](http://python.iswbm.com/en/latest/c04/c04_21.html) -- 4.22 [用好 Chrome 必看](http://python.iswbm.com/en/latest/c04/c04_22.html) -- 4.23 [电脑使用技巧](http://python.iswbm.com/en/latest/c04/c04_23.html) -- 4.24 [Python “用户环境”的一次完美应用](http://python.iswbm.com/en/latest/c04/c04_24.html) - -## 第五章:算法教程 -- 5.1 [图解九大经典排序算法](http://python.iswbm.com/en/latest/c05/c05_01.html) -- 5.2 [递归算法:走楼梯会思考的题](http://python.iswbm.com/en/latest/c05/c05_02.html) -- 5.3 [哈希算法:安全方面的算法应用](http://python.iswbm.com/en/latest/c05/c05_03.html) - -## 第六章:数据分析 -- 6.1 [一张图带你入门matplotlib](http://python.iswbm.com/en/latest/c06/c06_01.html) -- 6.2 [详解六种可视化图表](http://python.iswbm.com/en/latest/c06/c06_02.html) -- 6.3 [如何绘制正余弦函数图象](http://python.iswbm.com/en/latest/c06/c06_03.html) -- 6.4 [子图与子区 难点突破](http://python.iswbm.com/en/latest/c06/c06_04.html) -- 6.5 [绘制酷炫的gif动态图](http://python.iswbm.com/en/latest/c06/c06_05.html) -- 6.6 [自动生成图像视频](http://python.iswbm.com/en/latest/c06/c06_06.html) - -## 第七章:运维人生 -- 7.1 [Linux 命令行的艺术](http://python.iswbm.com/en/latest/c07/c07_01.html) -- 7.2 [Zabbix 监控部署文档](http://python.iswbm.com/en/latest/c07/c07_02.html) -- 7.3 [Docker:Hello world](http://python.iswbm.com/en/latest/c07/c07_03.html) -- 7.4 [Docker:关于镜像](http://python.iswbm.com/en/latest/c07/c07_04.html) -- 7.5 [Docker:网络通信](http://python.iswbm.com/en/latest/c07/c07_05.html) -- 7.6 [Docker:存储与多主机](http://python.iswbm.com/en/latest/c07/c07_06.html) -- 7.7 [SaltStack 入门指南](http://python.iswbm.com/en/latest/c07/c07_07.html) -- 7.8 [Keepalived 部署文档](http://python.iswbm.com/en/latest/c07/c07_08.html) -- 7.9 [Ansible 入门指南使用手册](http://python.iswbm.com/en/latest/c07/c07_09.html) -- 7.10 [Ansible API 最全使用文档(中文)](http://python.iswbm.com/en/latest/c07/c07_10.html) -- 7.11 [K8S:基础入门](http://python.iswbm.com/en/latest/c07/c07_11.html) -- 7.12 [Linux 包管理工具:yum 和 rpm](http://python.iswbm.com/en/latest/c07/c07_12.html) -- 7.13 [基于 ansible-api 二次开发](http://python.iswbm.com/en/latest/c07/c07_13.html) -- 7.14 [Linux 如何写判断语句](http://python.iswbm.com/en/latest/c07/c07_14.html) -- 7.15 [Mariadb 与 Galera 集群总结](http://python.iswbm.com/en/latest/c07/c07_15.html) -- 7.16 [Linux 运维之路](http://python.iswbm.com/en/latest/c07/c07_16.html) -- 1.17 [ansible 自定义 Jinja2 过滤器](http://python.iswbm.com/en/latest/c07/c07_17.html) -- 7.18 [Shell中去除字符串前后空格的方法](http://python.iswbm.com/en/latest/c07/c07_18.html) -- 7.19 [Ansible 使用教程](http://python.iswbm.com/en/latest/c07/c07_19.html) -- 7.20 [使用 Shell 删除文件的多种方法](http://python.iswbm.com/en/latest/c07/c07_20.html) - -## 第八章:OpenStack -- 8.1 [OpenStack 运维命令](http://python.iswbm.com/en/latest/c08/c08_01.html) -- 8.2 [OpenStack 部署SR-IOV](http://python.iswbm.com/en/latest/c08/c08_02.html) -- 8.3 [制作 OpenStack 镜像](http://python.iswbm.com/en/latest/c08/c08_03.html) -- 8.4 [云计算与虚拟化入门通识](http://python.iswbm.com/en/latest/c08/c08_04.html) -- 8.5 [OpenStack 源码剖析与改造](http://python.iswbm.com/en/latest/c08/c08_05.html) -- 8.6 [源码解读:Cloud-Init](http://python.iswbm.com/en/latest/c08/c08_06.html) -- 8.7 [OpenStack 实现GPU直通](http://python.iswbm.com/en/latest/c08/c08_07.html) -- 8.8 [OpenStack 如何使用DHCP?](http://python.iswbm.com/en/latest/c08/c08_08.html) -- 8.9 [从0到1:全面理解RPC远程调用](http://python.iswbm.com/en/latest/c08/c08_09.html) -- 8.16 [修改 KVM 镜像文件的三种方法](http://python.iswbm.com/en/latest/c08/c08_10.html) -- 8.11 [OpenStack 问题排查](http://python.iswbm.com/en/latest/c08/c08_11.html) -- 8.12 [OpenStack之主机调度](http://python.iswbm.com/en/latest/c08/c08_12.html) -- 8.13 [网络知识必知必会](http://python.iswbm.com/en/latest/c08/c08_13.html) -- 8.14 [支持 IPv6以及多运营商](http://python.iswbm.com/en/latest/c08/c08_14.html) -- 8.15 [Neutron 源码解读](http://python.iswbm.com/en/latest/c08/c08_15.html) - -## 第九章:有趣的工具 -- 9.1 [情人节来了,教你使用 Python 来表白](http://python.iswbm.com/en/latest/c09/c09_01.html) -- 9.2 [没有这 50 个APP,我的 Mac 将只是一块铁](http://python.iswbm.com/en/latest/c09/c09_02.html) -- 9.3 [明哥的在线工具集](http://python.iswbm.com/en/latest/c09/c09_03.html) - -## 第十章:网络基础 -- 10.1 [网络知识扫盲:一篇文章搞懂 DNS](http://python.iswbm.com/en/latest/c10/c10_01.html) -- 10.2 [网络知识扫盲:如何理解 OSI七层模型](http://python.iswbm.com/en/latest/c10/c10_02.html) -- 10.3 [一篇文章理解 HTTP](http://python.iswbm.com/en/latest/c10/c10_03.html) --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) + + + +都看到这儿了,还不点个关注嘛? `*^_^*` + +![](http://image.iswbm.com/20201024132326.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 4b94018..51a7a32 100644 --- a/md2rst.py +++ b/md2rst.py @@ -24,15 +24,32 @@ # 没有文件变更 os._exit(0) -base_link = "http://python.iswbm.com/en/latest/" +base_link = "http://pythontime.iswbm.com/en/latest/" readme_header = ''' -这是我的个人博客( [Python编程时光](http://python.iswbm.com/) ),主要写关于Python的一些思考总结。 +![](http://image.iswbm.com/20200607120940.png) + +

+ Build Status + + + + +

+ +## [项目主页](http://pythontime.iswbm.com/) + +在线阅读:[Python 编程时光](http://pythontime.iswbm.com/) + +![](http://image.iswbm.com/20200607130051.png) + +## 文章结构 + +![](http://image.iswbm.com/20200607131339.png) -关于搭建教程,感兴趣的可以查看这边:[Sphinx 搭建博客的图文教程](http://python.iswbm.com/en/latest/c04/c04_03.html) ''' readme_tooter = ''' --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) ''' @@ -128,5 +145,5 @@ def main(index_info): if __name__ == '__main__': index_info = init_index_info() main(index_info) - render_index_page(index_info) + # render_index_page(index_info) print("OK") 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 5854fcd..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,5 +28,14 @@ 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 +sphinxcontrib-devhelp==1.0.1 +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 8b16f25..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 8e3f65b..b32bed2 --- a/source/aboutme.rst +++ b/source/aboutme.rst @@ -3,12 +3,12 @@ ============== * 姓名: 王炳明 -* 微信: mrbensonwon +* 微信: stromwbm * 公众号: 《Python编程时光》&《Go编程时光》 * Email: wongbingming@163.com -* GitHub: https://github.com/bingmingwong +* GitHub: https://github.com/iswbm -------------------------------------------- -.. image:: http://image.python-online.cn/image-20200320125724880.png +.. image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_01.md b/source/c01/c01_01.md index 938ffc8..d3b620f 100644 --- a/source/c01/c01_01.md +++ b/source/c01/c01_01.md @@ -1,18 +1,12 @@ # 1.1 13条Python2.x和3.x的区别? ---- +![](http://image.iswbm.com/20200602135014.png) -从今天开始,小明将和你一起过一下,那些在面试「Python开发」岗位时面试官喜欢问的问题。内容基础,但是你不一定会噢。 +## 0. 去哪里找 -这些问题全部来自个人经验,群友推荐以及网络上的帖子。如果你有好的问题,也可以随时向我提出(不要觉得简单),我会筛选后整理出来在这里,供大家学习取经,给大家在求职路上贡献一份力。 +这块的内容,你随便使用搜索引擎都可以查到。 -开篇讲些什么好呢? - -今天就来罗列一下,Python2.x和3.x到底有哪些区别吧。 - -## 去哪里找 - -你随便全使用搜索引擎都可以查到这些资料,但是大家说的都是一些普遍都知道的事儿。或者都是抄来抄去,内容相差无几。 +但是大家好像都在抄来抄去,内容相差无几。 授人以鱼,不如授人以渔。 @@ -22,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之前,只支持 ``` @@ -50,7 +44,7 @@ print("hello") print ("hello") ``` -## 1.1.2 编码方式 +## 2. 编码方式 在Python2.x中,默认使用`ASCII`编码。 @@ -74,7 +68,7 @@ print str1 所以我们可以在程序中,随意的使用中文(但并不推荐),不会报错。 -## 1.2.3 除法运算 +## 3. 除法运算 Python 2.x中除法运算,整数间运算只保留整数(向下取整)。 ```python @@ -106,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可以同时捕获多种类别的异常。 @@ -118,7 +112,7 @@ Python 2.6已经支持这两种语法。 在2.x时代,异常在代码中除了表示程序错误,还经常做一些普通控制结构应该做的事情,在3.x中可以看出,设计者让异常变的更加专一,只有在错误发生的情况才能去用异常捕获语句来处理。 -## 1.2.5 xrange +## 5. xrange 首先,要了解的是,xrange是只有在Python2.x中才有的产物。 @@ -137,7 +131,7 @@ xrange(1, 5) range(0, 10) ``` -## 1.2.6 用户输入 +## 6. 用户输入 在2.x 中,有两个函数。raw_input()和input()。 - raw_input():将所有输入作为字符串看待,返回字符串类型。 @@ -146,7 +140,7 @@ range(0, 10) 在3.x 中,对这两个函数进行整合,只留下一个`input()`,既可输入数字,也可输入字符串,返回的是字符串类型。 -## 1.2.7 数据类型 +## 7. 数据类型 Python 3.x 一个很重要的特性是,对字符串和二进制数据流做了明确的区分。 @@ -158,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中也有所不同。 @@ -193,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才是新式类。 @@ -215,7 +209,7 @@ class Cls(object): pass ``` -## 1.2.11 变量作用域 +## 11. 变量作用域 - 在2.x中无法将局部变量声明为全局变量。 - 在3.x中可以使用nonlocal语法将局部变量声明为全局变量。 @@ -234,7 +228,7 @@ foo() # 3.x输出:200 ``` -## 1.2.12 元类的使用 +## 12. 元类的使用 在2.x 中 ```python @@ -255,7 +249,7 @@ class Person(metaclass=MetaPerson): pass ``` -## 1.2.13 模块变化 +## 13. 模块变化 - 去掉了一些模块。由于不常用,这里就不列举了。 - 新增了一些模块。比如:concurrent.futures,asyncio等 @@ -263,12 +257,12 @@ class Person(metaclass=MetaPerson): ---- -大概就是这些内容,可能还有更细微的差别,这些内容要前往官网查看。但是那些对于我们普通开发者来说,并不那么重要。完全可以不去关注。 +大概就是这些内容,可能还有更细微的差别,这些内容要前往官网查看。 -实际上,当我熟悉一个版本后,基本上是可以无缝过渡到另一个版本的。这篇文章,更多的是为了科普和应对面试。 +但是那些对于我们普通开发者来说,并不那么重要,个人感觉可以不去关注。 ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c01/c01_01.rst b/source/c01/c01_01.rst old mode 100755 new mode 100644 index 21bbee0..90abe6b --- a/source/c01/c01_01.rst +++ b/source/c01/c01_01.rst @@ -1,20 +1,14 @@ 1.1 13条Python2.x和3.x的区别? ============================== --------------- - -从今天开始,小明将和你一起过一下,那些在面试「Python开发」岗位时面试官喜欢问的问题。内容基础,但是你不一定会噢。 - -这些问题全部来自个人经验,群友推荐以及网络上的帖子。如果你有好的问题,也可以随时向我提出(不要觉得简单),我会筛选后整理出来在这里,供大家学习取经,给大家在求职路上贡献一份力。 +|image0| -开篇讲些什么好呢? - -今天就来罗列一下,Python2.x和3.x到底有哪些区别吧。 +0. 去哪里找 +----------- -去哪里找 --------- +这块的内容,你随便使用搜索引擎都可以查到。 -你随便全使用搜索引擎都可以查到这些资料,但是大家说的都是一些普遍都知道的事儿。或者都是抄来抄去,内容相差无几。 +但是大家好像都在抄来抄去,内容相差无几。 授人以鱼,不如授人以渔。 @@ -23,17 +17,17 @@ 那当然是官网啦:https://www.python.org/downloads/ 这个地址里,有所有Python历史版本(2.0+)。 点击左边,Release Version栏目 -对应的版本。 |image0| +对应的版本。 |image1| 进入对应详情页后,找到如图 ``what's new in Python xx`` -就可以查看此版本的新特性。 |image1| +就可以查看此版本的新特性。 |image2| 网页是全英文的,需要你有一定的英文阅读能力。快去感觉一下吧。 接下来。和大家一起过一下,Python2.x和3.x到底有哪些区别,这不仅在你开发过程中需要考虑的,也是面试过程面试官经常会问及的。 -1.1.1 print ------------ +1. print +-------- 在Python 2.6之前,只支持 @@ -56,8 +50,8 @@ print("hello") print ("hello") -1.1.2 编码方式 --------------- +2. 编码方式 +----------- 在Python2.x中,默认使用\ ``ASCII``\ 编码。 @@ -87,8 +81,8 @@ Python 2的正确使用方法,如下 所以我们可以在程序中,随意的使用中文(但并不推荐),不会报错。 -1.2.3 除法运算 --------------- +3. 除法运算 +----------- Python 2.x中除法运算,整数间运算只保留整数(向下取整)。 @@ -124,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 @@ -139,8 +133,8 @@ Python 2.6已经支持这两种语法。 在2.x时代,异常在代码中除了表示程序错误,还经常做一些普通控制结构应该做的事情,在3.x中可以看出,设计者让异常变的更加专一,只有在错误发生的情况才能去用异常捕获语句来处理。 -1.2.5 xrange ------------- +5. xrange +--------- 首先,要了解的是,xrange是只有在Python2.x中才有的产物。 @@ -163,8 +157,8 @@ Python 2.6已经支持这两种语法。 >>> range(10) range(0, 10) -1.2.6 用户输入 --------------- +6. 用户输入 +----------- 在2.x 中,有两个函数。raw_input()和input()。 - raw_input():将所有输入作为字符串看待,返回字符串类型。 - @@ -173,8 +167,8 @@ input():只能接收“数字”的输入。 在3.x 中,对这两个函数进行整合,只留下一个\ ``input()``\ ,既可输入数字,也可输入字符串,返回的是字符串类型。 -1.2.7 数据类型 --------------- +7. 数据类型 +----------- Python 3.x 一个很重要的特性是,对字符串和二进制数据流做了明确的区分。 @@ -188,8 +182,8 @@ Python 还有一点是,3.X去除了long类型,取代它的是整型(int)。3.x的整型是没有限制大小的,可以当做long类型使用, 但实际上由于机器内存的有限,我们使用的整数是不可能无限大的。 -1.2.8 函数式编程 ----------------- +8. 函数式编程 +------------- 在Python中,我们常常使用到的map,filter,reduce,在2.x和3.x中也有所不同。 @@ -229,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才是新式类。 @@ -255,8 +249,8 @@ Python3.x 没有经典类,只有新式类,而且有三种写法 class Cls(object): pass -1.2.11 变量作用域 ------------------ +11. 变量作用域 +-------------- - 在2.x中无法将局部变量声明为全局变量。 - 在3.x中可以使用nonlocal语法将局部变量声明为全局变量。 @@ -275,8 +269,8 @@ Python3.x 没有经典类,只有新式类,而且有三种写法 # 2.x输出:100 # 3.x输出:200 -1.2.12 元类的使用 ------------------ +12. 元类的使用 +-------------- 在2.x 中 @@ -299,8 +293,8 @@ Python3.x 没有经典类,只有新式类,而且有三种写法 class Person(metaclass=MetaPerson): pass -1.2.13 模块变化 ---------------- +13. 模块变化 +------------ - 去掉了一些模块。由于不常用,这里就不列举了。 - 新增了一些模块。比如:concurrent.futures,asyncio等 @@ -308,17 +302,16 @@ Python3.x 没有经典类,只有新式类,而且有三种写法 -------------- -大概就是这些内容,可能还有更细微的差别,这些内容要前往官网查看。但是那些对于我们普通开发者来说,并不那么重要。完全可以不去关注。 +大概就是这些内容,可能还有更细微的差别,这些内容要前往官网查看。 -实际上,当我熟悉一个版本后,基本上是可以无缝过渡到另一个版本的。这篇文章,更多的是为了科普和应对面试。 +但是那些对于我们普通开发者来说,并不那么重要,个人感觉可以不去关注。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image3| -.. |image0| image:: http://image.python-online.cn/20190511165542.png -.. |image1| image:: http://image.python-online.cn/20190511165551.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 new file mode 100644 index 0000000..d2e4c2d --- /dev/null +++ b/source/c01/c01_02.md @@ -0,0 +1,279 @@ +# 1.2 Python 自省机制详解 + +自省,在我们日常生活中,通常是自我反省的意思。 + +但在计算机编程中,自省并不是这个意思,它的英文单词是 introspection,表示的是自我检查的行为或能力。 + +它的内容包括 + +1. 告诉别人,我是谁 +2. 告诉别人,我能做什么 + +(有点面试的感觉了) + +Python 是一门动态语言,有了自省,就能让程序在运行时能够获知对象的类型以及该对象下有哪些方法等。 + +## 1. 学习 Python 模块的入口 + +### help() + +在 console 模式下,输入 `help()` ,可以看到输出了一段帮助文档,教你如何使用这个 help,当你看到提示符变成了 `help>` 时,这时候就进入了 help 模式。 + +![](http://image.iswbm.com/image-20200606121047415.png) + +此时你可以键入你想要了解的模块、语法等,help 告诉你如何使用。 + +比如我输入 keywords ,就可以看到 Python 里所有的关键字。再输入 modules 就可以查看 Python 中所有的内置模块。 + +![](http://image.iswbm.com/image-20200606121544062.png) + +输入 modules + `指定包名`,就可以查看这个包下有哪些模块 + +![](http://image.iswbm.com/image-20200606121942898.png) + +如果你想学习某个包要如何使用,可以直接在 help 模式下输入 `包名`,就像下面这样,我就可以获得一份 json 的帮助文档。 + +![](http://image.iswbm.com/image-20200606122408522.png) + +如果你想学习某个关键字的用法,可以在 help 模式下直接键入 `关键字` 查询用法,比如我直接键入 `for` 。 + +![](http://image.iswbm.com/image-20200606133933401.png) + +查完后,使用 quit 就可以退出 help 模式了。 + +![](http://image.iswbm.com/image-20200606123145109.png) + +如果你觉得进入 help 模式太麻烦,可以在 console 模式下直接查询 + +```python +>>> help("json") +``` + + + +### dir() + +dir() 函数可能是 Python 自省机制中最著名的部分了。它返回传递给它的任何对象的属性名称经过排序的列表。如果不指定对象,则 dir() 返回当前作用域中的名称。让我们将 dir() 函数应用于 keyword 模块,并观察它揭示了什么: + +![](http://image.iswbm.com/image-20200606134519352.png) + + + +## 2. 应用到实际开发中 + +### type() + +type() 函数有助于我们确定对象是字符串还是整数,或是其它类型的对象。它通过返回类型对象来做到这一点,可以将这个类型对象与 types 模块中定义的类型相比较: + +```python +>>> type(42) + +>>> type([]) + +``` + +### hasattr() + +使用 dir() 函数会返回一个对象的属性列表。 + +但是,有时我们只想测试一个或多个属性是否存在。如果对象具有我们正在考虑的属性,那么通常希望只检索该属性。这个任务可以由 hasattr() 来完成. + +```python +>>> import json +>>> hasattr(json, "dumps") +True +>>> +``` + +### getattr() + +使用 hasattr 获知了对象拥有某个属性后,可以搭配 getattr() 函数来获取其属性值。 + +```python +>>> getattr(json, "__path__") +['/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json'] +>>> +``` + +使用 getattr 获取函数后,可以很方便地使用这个函数,比如下面这样,可以不再使写 json.dumps 这么字。 + +```python +>>> dumps = getattr(json, "dumps") +>>> dumps({"name": "MING"}) +'{"name": "MING"}' +>>> + +# 当然你还有更简单的方法 +>>> mydumps = json.dumps +>>> mydumps({"name": "MING"}) +'{"name": "MING"}' +``` + +### id() + +**id()** 函数返回对象的唯一标识符,标识符是一个整数。 + +```python +>>> a = "hello" +>>> b = "world" +>>> +>>> id(a) +4470767944 +>>> id(b) +4499487408 +>>> +``` + + + +### isinstance() + +使用 isinstance() 函数可以确定一个对象是否是某个特定类型或定制类的实例。 + +```python +>>> isinstance("python", str) +True +>>> isinstance(10, int) +True +>>> isinstance(False, bool) +True +``` + + + +### callable() + +使用 callable 可以确定一个对象是否是可调用的,比如函数,类这些对象都是可以调用的对象。 + +```python +>>> callable("hello") +False +>>> +>>> callable(str) +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 da2afa2..7ea82dc --- a/source/c01/c01_02.rst +++ b/source/c01/c01_02.rst @@ -1,3 +1,315 @@ -1.2 Python的自省机制 -================================ +1.2 Python 自省机制详解 +======================= + +自省,在我们日常生活中,通常是自我反省的意思。 + +但在计算机编程中,自省并不是这个意思,它的英文单词是 +introspection,表示的是自我检查的行为或能力。 + +它的内容包括 + +1. 告诉别人,我是谁 +2. 告诉别人,我能做什么 + +(有点面试的感觉了) + +Python +是一门动态语言,有了自省,就能让程序在运行时能够获知对象的类型以及该对象下有哪些方法等。 + +1. 学习 Python 模块的入口 +------------------------- + +help() +~~~~~~ + +在 console 模式下,输入 ``help()`` +,可以看到输出了一段帮助文档,教你如何使用这个 +help,当你看到提示符变成了 ``help>`` 时,这时候就进入了 help 模式。 + +|image0| + +此时你可以键入你想要了解的模块、语法等,help 告诉你如何使用。 + +比如我输入 keywords ,就可以看到 Python 里所有的关键字。再输入 modules +就可以查看 Python 中所有的内置模块。 + +|image1| + +输入 modules + ``指定包名``\ ,就可以查看这个包下有哪些模块 + +|image2| + +如果你想学习某个包要如何使用,可以直接在 help 模式下输入 +``包名``\ ,就像下面这样,我就可以获得一份 json 的帮助文档。 + +|image3| + +如果你想学习某个关键字的用法,可以在 help 模式下直接键入 ``关键字`` +查询用法,比如我直接键入 ``for`` 。 + +|image4| + +查完后,使用 quit 就可以退出 help 模式了。 + +|image5| + +如果你觉得进入 help 模式太麻烦,可以在 console 模式下直接查询 + +.. code:: python + + >>> help("json") + +dir() +~~~~~ + +dir() 函数可能是 Python +自省机制中最著名的部分了。它返回传递给它的任何对象的属性名称经过排序的列表。如果不指定对象,则 +dir() 返回当前作用域中的名称。让我们将 dir() 函数应用于 keyword +模块,并观察它揭示了什么: + +|image6| + +2. 应用到实际开发中 +------------------- + +type() +~~~~~~ + +type() +函数有助于我们确定对象是字符串还是整数,或是其它类型的对象。它通过返回类型对象来做到这一点,可以将这个类型对象与 +types 模块中定义的类型相比较: + +.. code:: python + + >>> type(42) + + >>> type([]) + + +hasattr() +~~~~~~~~~ + +使用 dir() 函数会返回一个对象的属性列表。 + +但是,有时我们只想测试一个或多个属性是否存在。如果对象具有我们正在考虑的属性,那么通常希望只检索该属性。这个任务可以由 +hasattr() 来完成. + +.. code:: python + + >>> import json + >>> hasattr(json, "dumps") + True + >>> + +getattr() +~~~~~~~~~ + +使用 hasattr 获知了对象拥有某个属性后,可以搭配 getattr() +函数来获取其属性值。 + +.. code:: python + + >>> getattr(json, "__path__") + ['/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/json'] + >>> + +使用 getattr +获取函数后,可以很方便地使用这个函数,比如下面这样,可以不再使写 +json.dumps 这么字。 + +.. code:: python + + >>> dumps = getattr(json, "dumps") + >>> dumps({"name": "MING"}) + '{"name": "MING"}' + >>> + + # 当然你还有更简单的方法 + >>> mydumps = json.dumps + >>> mydumps({"name": "MING"}) + '{"name": "MING"}' + +id() +~~~~ + +**id()** 函数返回对象的唯一标识符,标识符是一个整数。 + +.. code:: python + + >>> a = "hello" + >>> b = "world" + >>> + >>> id(a) + 4470767944 + >>> id(b) + 4499487408 + >>> + +isinstance() +~~~~~~~~~~~~ + +使用 isinstance() 函数可以确定一个对象是否是某个特定类型或定制类的实例。 + +.. code:: python + + >>> isinstance("python", str) + True + >>> isinstance(10, int) + True + >>> isinstance(False, bool) + True + +callable() +~~~~~~~~~~ + +使用 callable +可以确定一个对象是否是可调用的,比如函数,类这些对象都是可以调用的对象。 + +.. code:: python + + >>> callable("hello") + False + >>> + >>> callable(str) + 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 +.. |image3| image:: http://image.iswbm.com/image-20200606122408522.png +.. |image4| image:: http://image.iswbm.com/image-20200606133933401.png +.. |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/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 54% rename from source/c01/c01_38.md rename to source/c01/c01_03.md index a40bf3e..726d0e2 100644 --- a/source/c01/c01_38.md +++ b/source/c01/c01_03.md @@ -1,44 +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 好像没什么区别呀 @@ -48,7 +54,7 @@ 具体演示过程,你可以看下面。 -![](http://image.python-online.cn/20200331190224.png) +![](http://image.iswbm.com/20200331190224.png) 那么对于这两者,我们应该使用哪个呢? @@ -56,4 +62,4 @@ -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file 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 0cef6a9..eba6d75 100644 --- a/source/c01/c01_04.md +++ b/source/c01/c01_04.md @@ -1,5 +1,7 @@ # 1.4 什么是猴子补丁? +![](http://image.iswbm.com/20200602135014.png) + 也许你没用过猴子补丁,但是你必须知道,在Python/Ruby 这类脚本语言中,有一种用法叫 Monkey Patch 。 这名字挺好玩的哈。在网上,关于这个名称的来源,大致有如下两种说法: @@ -47,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中也有用到。 @@ -101,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 5972b03..b0a5d5b --- a/source/c01/c01_04.rst +++ b/source/c01/c01_04.rst @@ -1,6 +1,8 @@ 1.4 什么是猴子补丁? =================== +|image0| + 也许你没用过猴子补丁,但是你必须知道,在Python/Ruby 这类脚本语言中,有一种用法叫 Monkey Patch 。 @@ -62,7 +64,7 @@ 在openstack中的例子 -|image0| +|image1| 还有就是gevent中也有用到。 @@ -115,10 +117,9 @@ -------------- -.. figure:: http://image.python-online.cn/20191117142849.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image2| -.. |image0| image:: http://image.python-online.cn/20190404215330.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 febc146..21f0807 100644 --- a/source/c01/c01_05.md +++ b/source/c01/c01_05.md @@ -1,169 +1,171 @@ -# 1.5 深入闭包与变量作用域 - ---- - -## 1.5.1 作用域 - -Python的作用域可以分为四种: -- L (Local) 局部作用域 -- E (Enclosing) 闭包函数外的函数中 -- G (Global) 全局作用域 -- B (Built-in) 内建作用域 - -变量/函数 的查找顺序: -L –> E –> G –>B - -意思是,在局部找不到的,便去局部外的局部作用域找(例如 闭包),再找不到的就去全局作业域里找,再找不到就去内建作业域中找。 - -会影响 变量/函数 作用范围的有 -- 函数:def 或 lambda -- 类:class -- 关键字:global noglobal -- 文件:*py -- 推导式:[],{},()等,仅限Py3.x中,Py2.x会出现变量泄露。 - -1、赋值在前,引用在后 -```python -# ------同作用域内------ -name = "MING" -print(name) - -# ------不同作用域内------ -name = "MING" -def main(): - print(name) -``` -2、引用在前,赋值在后(同一作用域内) -```python -print(name) -name = "MING" - -# UnboundLocalError: local variable 'name' referenced before assignment -``` -3、赋值在低层,引用在高层 -```python -# L -> E -> G -> B -# 从左到右,由低层到高层 -def main(): - name = "MING" - -print(name) -# NameError: name 'name' is not defined -``` - - -## 1.5.2 闭包 - -闭包这个概念很重要噢。你一定要掌握。 ->在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。其实装饰函数,很多都是闭包。 - -好像并不难理解,为什么初学者会觉得闭包难以理解呢? - -我解释一下,你就明白了。 - -一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。 - -你可以看下面这段代码,就构成了闭包。在内函数里可以引用外函数的变量。 -```python -def deco(): - name = "MING" - def wrapper(): - print(name) - return wrapper - -deco()() -# 输出:MING -``` - -## 1.5.3 改变作用域 - -变量的作用域,与其定义(或赋值)的位置有关,但不是绝对相关。 -因为我们可以在某种程度上去改变`向上`的作用范围。 - -- 关键字:global -将 局部变量 变为全局变量 - -- 关键字:nonlocal -可以在闭包函数中,引用并使用闭包外部函数的变量(非全局的噢) - -global好理解,这里只讲下nonlocal。 - -先来看个例子 -```python -def deco(): - age = 10 - def wrapper(): - age += 1 - return wrapper - -deco()() -``` -运行一下,会报错。 -``` -# UnboundLocalError: local variable 'age' referenced before assignment -``` -但是这样就OK -``` -def deco(): - age = 10 - def wrapper(): - nonlocal age - age += 1 - return wrapper - -deco()() -# 输出:11 -``` -其实,你如果不使用 `+=`、`-=`等一类的操作,不加nonlocal也没有关系。这就展示了闭包的特性。 -``` -def deco(): - age = 10 - def wrapper(): - print(age) - return wrapper - -deco()() -# 输出:10 -``` - - -## 1.5.4 变量集合 - -在Python中,有两个内建函数,你可能用不到,但是需要掌握它们。 -- globals() :以dict的方式存储所有全局变量 -- locals():以dict的方式存储所有局部变量 - -globals() -```python -def foo(): - print("I am a func") - -def bar(): - foo="I am a string" - foo_dup = globals().get("foo") - foo_dup() - -bar() -# 输出 -# I am a func -``` - -locals() -```python -other = "test" - -def foobar(): - name = "MING" - gender = "male" - for key,value in locals().items(): - print(key, "=", value) - -foobar() -# 输出 -# name = MING -# gender = male -``` - ----- - -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +# 1.5 深入闭包与变量作用域 + +![](http://image.iswbm.com/20200602135014.png) + +--- + +## 1. 作用域 + +Python的作用域可以分为四种: +- L (Local) 局部作用域 +- E (Enclosing) 闭包函数外的函数中 +- G (Global) 全局作用域 +- B (Built-in) 内建作用域 + +变量/函数 的查找顺序: +L –> E –> G –>B + +意思是,在局部找不到的,便去局部外的局部作用域找(例如 闭包),再找不到的就去全局作业域里找,再找不到就去内建作业域中找。 + +会影响 变量/函数 作用范围的有 +- 函数:def 或 lambda +- 类:class +- 关键字:global noglobal +- 文件:*py +- 推导式:[],{},()等,仅限Py3.x中,Py2.x会出现变量泄露。 + +1、赋值在前,引用在后 +```python +# ------同作用域内------ +name = "MING" +print(name) + +# ------不同作用域内------ +name = "MING" +def main(): + print(name) +``` +2、引用在前,赋值在后(同一作用域内) +```python +print(name) +name = "MING" + +# UnboundLocalError: local variable 'name' referenced before assignment +``` +3、赋值在低层,引用在高层 +```python +# L -> E -> G -> B +# 从左到右,由低层到高层 +def main(): + name = "MING" + +print(name) +# NameError: name 'name' is not defined +``` + + +## 2. 闭包 + +闭包这个概念很重要噢。你一定要掌握。 +>在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。其实装饰函数,很多都是闭包。 + +好像并不难理解,为什么初学者会觉得闭包难以理解呢? + +我解释一下,你就明白了。 + +一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。 + +你可以看下面这段代码,就构成了闭包。在内函数里可以引用外函数的变量。 +```python +def deco(): + name = "MING" + def wrapper(): + print(name) + return wrapper + +deco()() +# 输出:MING +``` + +## 3. 改变作用域 + +变量的作用域,与其定义(或赋值)的位置有关,但不是绝对相关。 +因为我们可以在某种程度上去改变`向上`的作用范围。 + +- 关键字:global +将 局部变量 变为全局变量 + +- 关键字:nonlocal +可以在闭包函数中,引用并使用闭包外部函数的变量(非全局的噢) + +global好理解,这里只讲下nonlocal。 + +先来看个例子 +```python +def deco(): + age = 10 + def wrapper(): + age += 1 + return wrapper + +deco()() +``` +运行一下,会报错。 +``` +# UnboundLocalError: local variable 'age' referenced before assignment +``` +但是这样就OK +``` +def deco(): + age = 10 + def wrapper(): + nonlocal age + age += 1 + return wrapper + +deco()() +# 输出:11 +``` +其实,你如果不使用 `+=`、`-=`等一类的操作,不加nonlocal也没有关系。这就展示了闭包的特性。 +``` +def deco(): + age = 10 + def wrapper(): + print(age) + return wrapper + +deco()() +# 输出:10 +``` + + +## 4. 变量集合 + +在Python中,有两个内建函数,你可能用不到,但是需要掌握它们。 +- globals() :以dict的方式存储所有全局变量 +- locals():以dict的方式存储所有局部变量 + +globals() +```python +def foo(): + print("I am a func") + +def bar(): + foo="I am a string" + foo_dup = globals().get("foo") + foo_dup() + +bar() +# 输出 +# I am a func +``` + +locals() +```python +other = "test" + +def foobar(): + name = "MING" + gender = "male" + for key,value in locals().items(): + print(key, "=", value) + +foobar() +# 输出 +# name = MING +# gender = male +``` + +---- + +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c01/c01_05.rst b/source/c01/c01_05.rst old mode 100755 new mode 100644 index e3bd115..c1f7d89 --- a/source/c01/c01_05.rst +++ b/source/c01/c01_05.rst @@ -1,10 +1,12 @@ 1.5 深入闭包与变量作用域 ======================== +|image0| + -------------- -1.5.1 作用域 ------------- +1. 作用域 +--------- Python的作用域可以分为四种: - L (Local) 局部作用域 - E (Enclosing) 闭包函数外的函数中 - G (Global) 全局作用域 - B (Built-in) 内建作用域 @@ -52,8 +54,8 @@ Python的作用域可以分为四种: - L (Local) 局部作用域 - E (E print(name) # NameError: name 'name' is not defined -1.5.2 闭包 ----------- +2. 闭包 +------- 闭包这个概念很重要噢。你一定要掌握。 >在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。其实装饰函数,很多都是闭包。 @@ -77,8 +79,8 @@ Python的作用域可以分为四种: - L (Local) 局部作用域 - E (E deco()() # 输出:MING -1.5.3 改变作用域 ----------------- +3. 改变作用域 +------------- 变量的作用域,与其定义(或赋值)的位置有关,但不是绝对相关。 因为我们可以在某种程度上去改变\ ``向上``\ 的作用范围。 @@ -136,8 +138,8 @@ global好理解,这里只讲下nonlocal。 deco()() # 输出:10 -1.5.4 变量集合 --------------- +4. 变量集合 +----------- 在Python中,有两个内建函数,你可能用不到,但是需要掌握它们。 - globals() :以dict的方式存储所有全局变量 - locals():以dict的方式存储所有局部变量 @@ -177,7 +179,8 @@ locals() -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c01/c01_06.md b/source/c01/c01_06.md index 20bfbf6..ab2a7a2 100644 --- a/source/c01/c01_06.md +++ b/source/c01/c01_06.md @@ -1,102 +1,104 @@ -# 1.6 深入理解元组存在的意义 - ---- - -Python中有一个基础的数据结构,叫做元组(tuple),但是一般挺少有人会去用它的,因为在开发过程中,列表(list)基本已经能够满足我们的需求。 - -即使是这样,你也千万不要就此认为元组是多余的。不然在面试中也不会经常被人问,元组和列表有啥区别?为什么需要元组? - -以下两点,第一点是大家所熟知的,而第二点可能只有老司机才会知道,只有学习了第二点,才算真正理解了元组存在的价值和意义。 - -## 2.6.1 不可变列表 - -这是`元组`区别于`列表`最显著的特征。 - -- list:可变的序列 -- tuple:不可变的序列 - -那什么是不可变的序列呢? - -那就是在元组对象生成后,诸如列表的`插入元素`、`删除元素`、`添加元素`、`清空元素`、`修改元素`等功能,在元组中通通没有,你是无法对其进行修改的。 - -由于元组是不可变的,所以其方法也是很有限的。这里罗列一下。 - -``` -# s1和s2都是元组 -s1=(1,2,3) -s2=(4,5,6) - -# 拼接生成新元组 -s1+s2 -s1.__add__(s2) - -# 是否包含 -2 in s1 -s1.__contains__(2) - -# 统计元素包含的次数 -s1.count(2) - -# 获取元素 -s1[0] -s1.__getitem__(0) - -# 找到2第一次出现的索引 -s1.index(2) - -# 获取长度 -len(s1) - -# 重复拼接 -s1*n -``` - - -## 2.6.2 具名元组 - -这个特性,我个人认为,才是元组存在的意义所在。 - -只讲 具名元组,可能不太好理解。如果称之为 `带字段名的记录`,你可能就清楚了。 - -这里举个例子,但是实现带字段名,需要一个库(collections)的支持,你需要导入它。 -```python -from collections import namedtuple -# 生成一个City类 -City = namedtuple("City", "name country polulation coordinates") -# 实例化 -tokyo = City("Tokyo", 'JP', '36.93', ('35.68','139,69')) - -print(tokyo) -# City(name='Tokyo', country='JP', polulation='36.93', coordinates=('35.68', '139,69')) - -print(tokyo.name) -# Tokyo -``` - -看着有点像字典,是不是,但是他不是字典(获取数值的方法也与字典不同),字典是可变。元组在创建后,就无法再对其进行修改。这在某个程度上说明元组适合存放那些无需修改的数据。比如上面的,地名,国家,经纬度。 - -除了上面的用法之处,这里还要介绍一些元组自己专有的属性。 -```python -# 打印字段名 -print(City._fields) -('name', 'country', 'polulation', 'coordinates') - -# 生成新实例 -LatLong = namedtuple('LatLong', 'lat long') -Xiamen_tuple = ('Xiemen', 'China', '40,54', LatLong(24.26,118.03)) -Xiamen = City._make(Xiamen_tuple) - -print(Xiamen) -# City(name='Xiemen', country='China', polulation='40,54', coordinates=(24.26, 118.03)) - -# 将具名元组转为OrderDict -Xiamen_dict = Xiamen._asdict() -print(Xiamen_dict) -# OrderedDict([('name', 'Xiemen'), ('country', 'China'), ('polulation', '40,54'), ('coordinates', LatLong(lat=24.26, long=118.03))]) -``` - -总结一下,元组是一种很强大的可以当作记录来用的数据类型,这才是他存在的价值和意义所在。而为人所熟知的,它的第二个角色才是充当一个不可变的列表。 - --------------- - -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +# 1.6 有了列表,为什么 Python 还有元组? + +![](http://image.iswbm.com/20200602135014.png) + +--- + +Python中有一个基础的数据结构,叫做元组(tuple),但是一般挺少有人会去用它的,因为在开发过程中,列表(list)基本已经能够满足我们的需求。 + +即使是这样,你也千万不要就此认为元组是多余的。不然在面试中也不会经常被人问,元组和列表有啥区别?为什么需要元组? + +以下两点,第一点是大家所熟知的,而第二点可能只有老司机才会知道,只有学习了第二点,才算真正理解了元组存在的价值和意义。 + +## 1. 不可变列表 + +这是`元组`区别于`列表`最显著的特征。 + +- list:可变的序列 +- tuple:不可变的序列 + +那什么是不可变的序列呢? + +那就是在元组对象生成后,诸如列表的`插入元素`、`删除元素`、`添加元素`、`清空元素`、`修改元素`等功能,在元组中通通没有,你是无法对其进行修改的。 + +由于元组是不可变的,所以其方法也是很有限的。这里罗列一下。 + +``` +# s1和s2都是元组 +s1=(1,2,3) +s2=(4,5,6) + +# 拼接生成新元组 +s1+s2 +s1.__add__(s2) + +# 是否包含 +2 in s1 +s1.__contains__(2) + +# 统计元素包含的次数 +s1.count(2) + +# 获取元素 +s1[0] +s1.__getitem__(0) + +# 找到2第一次出现的索引 +s1.index(2) + +# 获取长度 +len(s1) + +# 重复拼接 +s1*n +``` + + +## 2. 具名元组 + +这个特性,我个人认为,才是元组存在的意义所在。 + +只讲 具名元组,可能不太好理解。如果称之为 `带字段名的记录`,你可能就清楚了。 + +这里举个例子,但是实现带字段名,需要一个库(collections)的支持,你需要导入它。 +```python +from collections import namedtuple +# 生成一个City类 +City = namedtuple("City", "name country polulation coordinates") +# 实例化 +tokyo = City("Tokyo", 'JP', '36.93', ('35.68','139,69')) + +print(tokyo) +# City(name='Tokyo', country='JP', polulation='36.93', coordinates=('35.68', '139,69')) + +print(tokyo.name) +# Tokyo +``` + +看着有点像字典,是不是,但是他不是字典(获取数值的方法也与字典不同),字典是可变。元组在创建后,就无法再对其进行修改。这在某个程度上说明元组适合存放那些无需修改的数据。比如上面的,地名,国家,经纬度。 + +除了上面的用法之处,这里还要介绍一些元组自己专有的属性。 +```python +# 打印字段名 +print(City._fields) +('name', 'country', 'polulation', 'coordinates') + +# 生成新实例 +LatLong = namedtuple('LatLong', 'lat long') +Xiamen_tuple = ('Xiemen', 'China', '40,54', LatLong(24.26,118.03)) +Xiamen = City._make(Xiamen_tuple) + +print(Xiamen) +# City(name='Xiemen', country='China', polulation='40,54', coordinates=(24.26, 118.03)) + +# 将具名元组转为OrderDict +Xiamen_dict = Xiamen._asdict() +print(Xiamen_dict) +# OrderedDict([('name', 'Xiemen'), ('country', 'China'), ('polulation', '40,54'), ('coordinates', LatLong(lat=24.26, long=118.03))]) +``` + +总结一下,元组是一种很强大的可以当作记录来用的数据类型,这才是他存在的价值和意义所在。而为人所熟知的,它的第二个角色才是充当一个不可变的列表。(以上都是个人看法,如有不同见解,欢迎留言讨论) + +-------------- + +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c01/c01_06.rst b/source/c01/c01_06.rst old mode 100755 new mode 100644 index a48254e..2609574 --- a/source/c01/c01_06.rst +++ b/source/c01/c01_06.rst @@ -1,5 +1,7 @@ -1.6 深入理解元组存在的意义 -========================== +1.6 有了列表,为什么 Python 还有元组? +====================================== + +|image0| -------------- @@ -9,8 +11,8 @@ Python中有一个基础的数据结构,叫做元组(tuple),但是一般 以下两点,第一点是大家所熟知的,而第二点可能只有老司机才会知道,只有学习了第二点,才算真正理解了元组存在的价值和意义。 -2.6.1 不可变列表 ----------------- +1. 不可变列表 +------------- 这是\ ``元组``\ 区别于\ ``列表``\ 最显著的特征。 @@ -53,8 +55,8 @@ Python中有一个基础的数据结构,叫做元组(tuple),但是一般 # 重复拼接 s1*n -2.6.2 具名元组 --------------- +2. 具名元组 +----------- 这个特性,我个人认为,才是元组存在的意义所在。 @@ -100,11 +102,12 @@ Python中有一个基础的数据结构,叫做元组(tuple),但是一般 print(Xiamen_dict) # OrderedDict([('name', 'Xiemen'), ('country', 'China'), ('polulation', '40,54'), ('coordinates', LatLong(lat=24.26, long=118.03))]) -总结一下,元组是一种很强大的可以当作记录来用的数据类型,这才是他存在的价值和意义所在。而为人所熟知的,它的第二个角色才是充当一个不可变的列表。 +总结一下,元组是一种很强大的可以当作记录来用的数据类型,这才是他存在的价值和意义所在。而为人所熟知的,它的第二个角色才是充当一个不可变的列表。(以上都是个人看法,如有不同见解,欢迎留言讨论) -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c01/c01_07.md b/source/c01/c01_07.md index 2c5e7f7..f7f78a1 100644 --- a/source/c01/c01_07.md +++ b/source/c01/c01_07.md @@ -1,294 +1,284 @@ -# 1.7 15个Pythonic的代码示例 - ---- - -Python由于语言的简洁性,让我们以人类思考的方式来写代码,新手更容易上手,老鸟更爱不释手。 - -要写出 Pythonic(优雅的、地道的、整洁的)代码,还要平时多观察那些大牛代码,Github 上有很多非常优秀的源代码值得阅读,比如:requests、flask、tornado,这里小明收集了一些常见的 Pythonic 写法,帮助你养成写优秀代码的习惯。 - -## 01. 变量交换 - -Bad -```python -tmp = a -a = b -b = tmp -``` -Pythonic -```python -a,b = b,a -``` - -## 02. 列表推导 - -Bad -```python -my_list = [] -for i in range(10): - my_list.append(i*2) -``` -Pythonic -```python -my_list = [i*2 for i in range(10)] -``` - -## 03. 单行表达式 - -虽然列表推导式由于其简洁性及表达性,被广受推崇。 - -但是有许多可以写成单行的表达式,并不是好的做法。 - -Bad -```python -print 'one'; print 'two' - -if x == 1: print 'one' - -if and : - # do something -``` - -Pythonic -```python -print 'one' -print 'two' - -if x == 1: - print 'one' - -cond1 = -cond2 = -if cond1 and cond2: - # do something -``` - -## 04. 带索引遍历 - -Bad -```python -for i in range(len(my_list)): - print(i, "-->", my_list[i]) -``` -Pythonic -```python -for i,item in enumerate(my_list): - print(i, "-->",item) -``` - -## 05. 序列解包 - -Pythonic -```python -a, *rest = [1, 2, 3] -# a = 1, rest = [2, 3] - -a, *middle, c = [1, 2, 3, 4] -# a = 1, middle = [2, 3], c = 4 -``` - -## 06. 字符串拼接 - -Bad -```python -letters = ['s', 'p', 'a', 'm'] -s="" -for let in letters: - s += let -``` - -Pythonic -```python -letters = ['s', 'p', 'a', 'm'] -word = ''.join(letters) -``` - -## 07. 真假判断 - -Bad -```python -if attr == True: - print 'True!' - -if attr == None: - print 'attr is None!' -``` - -Pythonic -```python -if attr: - print 'attr is truthy!' - -if not attr: - print 'attr is falsey!' - -if attr is None: - print 'attr is None!' -``` - - -## 08. 访问字典元素 - -Bad -```python -d = {'hello': 'world'} -if d.has_key('hello'): - print d['hello'] # prints 'world' -else: - print 'default_value' -``` - -Pythonic -```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'] -``` - -## 09. 操作列表 - -Bad -```python -a = [3, 4, 5] -b = [] -for i in a: - if i > 4: - b.append(i) -``` - -Pythonic -```python -a = [3, 4, 5] -b = [i for i in a if i > 4] -# Or: -b = filter(lambda x: x > 4, a) -``` - -Bad -```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) -``` - -## 10. 文件读取 -Bad -```python -f = open('file.txt') -a = f.read() -print a -f.close() -``` - -Pythonic -```python -with open('file.txt') as f: - for line in f: - 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.”""" - -from some.deep.module.inside.a.module import a_nice_function, another_nice_function, \ - yet_another_nice_function -``` - -Pythonic -```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.”" -) - -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} -``` - -## 13. 使用占位符 - -Pythonic -```python -filename = 'foobar.txt' -basename, _, ext = filename.rpartition('.') -``` - -## 14. 链式比较 - -Bad -```python -if age > 18 and age < 60: - print("young man") -``` - -Pythonic -```python -if 18 < age < 60: - print("young man") -``` - -理解了链式比较操作,那么你应该知道为什么下面这行代码输出的结果是 False -``` ->>> False == False == True -False -``` - -## 15. 三目运算 - -这个保留意见。随使用习惯就好。 - -Bad -```python -if a > 2: - b = 2 -else: - b = 1 -#b = 2 - -``` -Pythonic -```python -a = 3 - -b = 2 if a > 2 else 1 -#b = 2 -``` - -## 参考文档 - -- http://docs.python-guide.org/en/latest/writing/style/ -- https://foofish.net/idiomatic_part2.html - ----- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +# 1.7 15个Pythonic的代码示例 + +![](http://image.iswbm.com/20200602135014.png) +Python由于语言的简洁性,让我们以人类思考的方式来写代码,新手更容易上手,老鸟更爱不释手。 + +要写出 Pythonic(优雅的、地道的、整洁的)代码,还要平时多观察那些大牛代码,这里明哥收集了一些比较常见的 Pythonic 写法,帮助你养成写优秀代码的习惯。 + +## 01. 变量交换 + +交换两个变量的值,正常都会想利用一个中间临时变量来过渡。 +```python +tmp = a +a = b +b = tmp +``` +能用一行代码解决的(并且不影响可读性的),决不用三行代码。 +```python +a,b = b,a +``` + +## 02. 列表推导 + +下面是一个非常简单的 for 循环。 +```python +my_list = [] +for i in range(10): + my_list.append(i*2) +``` +在一个 for 循环中,如果逻辑比较简单,不如试用一下列表的列表推导式,虽然只有一行代码,但也逻辑清晰。 + +```python +my_list = [i*2 for i in range(10)] +``` + +## 03. 单行表达式 + +上面两个案例,都将多行代码用另一种方式写成了一行代码。 + +这并不意味着,代码行数越少,就越 Pythonic 。 + +比如下面这样写,就不推荐。 +```python +print('hello'); print('world') + +if x == 1: print('hello,world') + +if and : + # do something +``` + +建议还是按照如下的写法来 +```python +print('hello') +print('world') + +if x == 1: + print('hello,world') + +cond1 = +cond2 = +if cond1 and cond2: + # do something +``` + +## 04. 带索引遍历 + +使用 for 循环时,如何取得对应的索引,初学者习惯使用 range + len 函数 +```python +for i in range(len(my_list)): + print(i, "-->", my_list[i]) +``` +更好的做法是利用 enumerate 这个内置函数 +```python +for i,item in enumerate(my_list): + print(i, "-->",item) +``` + +## 05. 序列解包 + +使用 `*` 可以对一个列表解包 + +```python +a, *rest = [1, 2, 3] +# a = 1, rest = [2, 3] + +a, *middle, c = [1, 2, 3, 4] +# a = 1, middle = [2, 3], c = 4 +``` + +## 06. 字符串拼接 + +如果一个列表(或者可迭代对象)中的所有元素都是字符串对象,想要将他们连接起来,通常做法是 +```python +letters = ['s', 'p', 'a', 'm'] +s="" +for let in letters: + s += let +``` + +更推荐的做法是使用 join 函数 +```python +letters = ['s', 'p', 'a', 'm'] +word = ''.join(letters) +``` + +## 07. 真假判断 + +判断一个变量是否为真(假),新手习惯直接使用 `==` 与 True、False、None 进行对比 +```python +if attr == True: + print('True!') + +if attr == None: + print('attr is None!') +``` + +实际上,`""`、`[]`、`{}` 这些没有任何元素的容器都是假值,可直接使用 `if not xx` 来判断。 +```python +if attr: + print('attr is truthy!') + +if not attr: + print('attr is falsey!') +``` + + +## 08. 访问字典元素 + +当直接使用 `[]` 来访问字典里的元素时,若key不存在,是会抛异常的,所以新会可能会先判断一下是否有这个 key,有再取之。 +```python +d = {'hello': 'world'} +if d.has_key('hello'): + print(d['hello']) # prints 'world' +else: + print('default_value') +``` + +更推荐的做法是使用 `get` 来取,如果没有该 key 会默认返回 None(当然你也可以设置默认返回值) +```python +d = {'hello': 'world'} + +print(d.get('hello', 'default_value')) # prints 'world' +print(d.get('thingy', 'default_value')) # prints 'default_value' +``` + +## 09. 操作列表 + +下面这段代码,会根据条件过滤过列表中的元素 +```python +a = [3, 4, 5] +b = [] +for i in a: + if i > 4: + b.append(i) +``` +实际上可以使用列表推导或者高阶函数 filter 来实现 +```python +a = [3, 4, 5] +b = [i for i in a if i > 4] +# Or: +b = filter(lambda x: x > 4, a) +``` + +除了 filter 之外,还有 map、reduce 这两个函数也很好用 +```python +a = [3, 4, 5] +b = map(lambda i: i + 3, a) +# b: [6,7,8] +``` + +## 10. 文件读取 + +文件读取是非常常用的操作,在使用完句柄后,是需要手动调用 close 函数来关闭句柄的 +```python +fp = open('file.txt') +print(fp.read()) +fp.close() +``` +如果代码写得太长,即使你知道需要手动关闭句柄,却也会经常会漏掉。因此推荐养成习惯使用 `with open` 来读写文件,上下文管理器会自动执行关闭句柄的操作 +```python +with open('file.txt') as fp: + for line in fp.readlines(): + print(line) +``` + +## 11. 代码续行 + +将一个长度较长的字符串放在一行中,是很影响代码可读性的(下面代码可向左滑动) + +```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.”' +``` + +稍等注重代码可读性的人,会使用三个引号 `\`来续写 + +```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.”' +``` + +不过,对我来说,我更喜欢这样子写 使用括号包裹 `()` +```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.”" +) +``` + +导包的时候亦是如此 +```python +from some.deep.module.inside.a.module import ( + a_nice_function, another_nice_function, yet_another_nice_function) +``` + +## 12. 显式代码 + +有时候出于需要,我们会使用一些特殊的魔法来使代码适应更多的场景不确定性。 +```python +def make_complex(*args): + x, y = args + return dict(**locals()) +``` +但若非必要,请不要那么做。无端增加代码的不确定性,会让原先本就动态的语言写出更加动态的代码。 +```python +def make_complex(x, y): + return {'x': x, 'y': y} +``` + +## 13. 使用占位符 + +对于暂不需要,却又不得不接收的的变量,请使用占位符 +```python +filename = 'foobar.txt' +basename, _, ext = filename.rpartition('.') +``` + +## 14. 链式比较 + +对于下面这种写法 +```python +score = 85 +if score > 80 and score < 90: + print("良好") +``` +其实还有更好的写法 +```python +score = 85 +if 80 < score < 90: + print("良好") +``` + +如果你理解了上面的链式比较操作,那么你应该知道为什么下面这行代码输出的结果是 False +``` +>>> False == False == True +False +``` + +## 15. 三目运算 + +对于简单的判断并赋值 +```python +age = 20 +if age > 18: + type = "adult" +else: + type = "teenager" +``` +其实是可以使用三目运算,一行搞定。 +```python +age = 20 +b = "adult" if age > 18 else "teenager" +``` + + + +## 参考文档 + +- http://docs.python-guide.org/en/latest/writing/style/ +- https://foofish.net/idiomatic_part2.html + +---- +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c01/c01_07.rst b/source/c01/c01_07.rst old mode 100755 new mode 100644 index d9166e4..52a6391 --- a/source/c01/c01_07.rst +++ b/source/c01/c01_07.rst @@ -1,19 +1,17 @@ 1.7 15个Pythonic的代码示例 ========================== --------------- - +|image0| Python由于语言的简洁性,让我们以人类思考的方式来写代码,新手更容易上手,老鸟更爱不释手。 要写出 -Pythonic(优雅的、地道的、整洁的)代码,还要平时多观察那些大牛代码,Github -上有很多非常优秀的源代码值得阅读,比如:requests、flask、tornado,这里小明收集了一些常见的 +Pythonic(优雅的、地道的、整洁的)代码,还要平时多观察那些大牛代码,这里明哥收集了一些比较常见的 Pythonic 写法,帮助你养成写优秀代码的习惯。 01. 变量交换 ------------ -Bad +交换两个变量的值,正常都会想利用一个中间临时变量来过渡。 .. code:: python @@ -21,7 +19,7 @@ Bad a = b b = tmp -Pythonic +能用一行代码解决的(并且不影响可读性的),决不用三行代码。 .. code:: python @@ -30,7 +28,7 @@ Pythonic 02. 列表推导 ------------ -Bad +下面是一个非常简单的 for 循环。 .. code:: python @@ -38,7 +36,8 @@ Bad for i in range(10): my_list.append(i*2) -Pythonic +在一个 for +循环中,如果逻辑比较简单,不如试用一下列表的列表推导式,虽然只有一行代码,但也逻辑清晰。 .. code:: python @@ -47,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 = @@ -80,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 @@ -97,7 +96,7 @@ Pythonic 05. 序列解包 ------------ -Pythonic +使用 ``*`` 可以对一个列表解包 .. code:: python @@ -110,7 +109,7 @@ Pythonic 06. 字符串拼接 -------------- -Bad +如果一个列表(或者可迭代对象)中的所有元素都是字符串对象,想要将他们连接起来,通常做法是 .. code:: python @@ -119,7 +118,7 @@ Bad for let in letters: s += let -Pythonic +更推荐的做法是使用 join 函数 .. code:: python @@ -129,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 @@ -191,7 +188,7 @@ Bad if i > 4: b.append(i) -Pythonic +实际上可以使用列表推导或者高阶函数 filter 来实现 .. code:: python @@ -200,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.”' + +稍等注重代码可读性的人,会使用三个引号 ``\``\ 来续写 - from some.deep.module.inside.a.module import a_nice_function, another_nice_function, \ - yet_another_nice_function +.. code:: python -Pythonic + 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 - 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 @@ -275,7 +271,7 @@ Bad x, y = args return dict(**locals()) -Pythonic +但若非必要,请不要那么做。无端增加代码的不确定性,会让原先本就动态的语言写出更加动态的代码。 .. code:: python @@ -285,7 +281,7 @@ Pythonic 13. 使用占位符 -------------- -Pythonic +对于暂不需要,却又不得不接收的的变量,请使用占位符 .. code:: python @@ -295,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 :: @@ -319,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" 参考文档 -------- @@ -348,7 +343,8 @@ Pythonic -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c01/c01_08.md b/source/c01/c01_08.md index 619365f..1c1730f 100644 --- a/source/c01/c01_08.md +++ b/source/c01/c01_08.md @@ -1,8 +1,10 @@ # 1.8 新式类和经典类的区别? +![](http://image.iswbm.com/20200602135014.png) + --- -## 2.8.1 版本支持 / 写法差异 +## 1. 版本支持 / 写法差异 在Python 2.x 中 @@ -36,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的统一。 @@ -78,7 +80,7 @@ AttributeError: Kls01 instance has no attribute 'name' -## 2.8.3 MRO 查找算法的演变 +## 3. MRO 查找算法的演变 **经典类中** @@ -108,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()呢? @@ -120,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。 @@ -150,7 +152,7 @@ print inspect.getmro(D) 再来看一个复杂一点的例子。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi78odu23j20740bomxh.jpg) +![](http://image.iswbm.com/20201004123115.png) 如果只依靠上面的算法,我们来一起算下,其继承关系是怎样的。 @@ -192,7 +194,7 @@ order (MRO) for bases X, Y 例如下面这张图。 -![](https://ws1.sinaimg.cn/large/8f640247gy1fyi78xuzibj20940ayq39.jpg) +![](http://image.iswbm.com/20201004123126.png) 计算过程,会采用一种 merge算法。它的基本思想如下: @@ -250,4 +252,4 @@ A.__mro__ --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c01/c01_08.rst b/source/c01/c01_08.rst old mode 100755 new mode 100644 index 4df2492..33aaf4c --- a/source/c01/c01_08.rst +++ b/source/c01/c01_08.rst @@ -1,10 +1,12 @@ 1.8 新式类和经典类的区别? ========================== +|image0| + -------------- -2.8.1 版本支持 / 写法差异 -------------------------- +1. 版本支持 / 写法差异 +---------------------- 在Python 2.x 中 @@ -43,10 +45,10 @@ class Ming(object): pass -2.8.2 使用方法 / 独特属性 -------------------------- +2. 使用方法 / 独特属性 +---------------------- -经典类无法使用super() |image0| 经典类的类型是 classobj |image1| +经典类无法使用super() |image1| 经典类的类型是 classobj |image2| 新式类的类型是 type,保持class与type的统一。 @@ -83,8 +85,8 @@ kls01.name AttributeError: Kls01 instance has no attribute 'name' -2.8.3 MRO 查找算法的演变 ------------------------- +3. MRO 查找算法的演变 +--------------------- **经典类中** @@ -114,7 +116,7 @@ 非常好理解,但是在菱形继承时,方法的调用会出现问题。 -|image2| +|image3| 假设 d 是 D 的一个实例,那么执行 d.show()是调用 A.show() 呢 还是调用 C.show()呢? @@ -132,7 +134,7 @@ A.show(),这显然是不合理的。所以这才有了后来的一步一步优 Python 2.2 的新式类 MRO 计算方式和经典类 MRO 的计算方式非常相似:它仍然采用从左至右的深度优先遍历,但是如果遍历中出现重复的类,只保留最后一个。重新考虑上面「菱形继承」的例子: -|image3| +|image4| 同样地,我们也来验证一下。另说明,在新式类中,除用inspect外,可以直接通过__mro__属性获取类的 MRO。 @@ -163,7 +165,7 @@ MRO。 再来看一个复杂一点的例子。 -|image4| +|image5| 如果只依靠上面的算法,我们来一起算下,其继承关系是怎样的。 @@ -209,7 +211,7 @@ C 搜索顺序中 X 和 Y 互换仍然不能解决问题,这时候它又会和 例如下面这张图。 -|image5| +|image6| 计算过程,会采用一种 merge算法。它的基本思想如下: @@ -269,15 +271,14 @@ C 搜索顺序中 X 和 Y 互换仍然不能解决问题,这时候它又会和 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image7| -.. |image0| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi765tblqj20cy05cwfx.jpg -.. |image1| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi76mgwcbj20b708cmyo.jpg -.. |image2| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi77urc3lj206108n74e.jpg -.. |image3| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi78drp24j20680bjaaa.jpg -.. |image4| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi78odu23j20740bomxh.jpg -.. |image5| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyi78xuzibj20940ayq39.jpg +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |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 b9d825b..697f7e0 100644 --- a/source/c01/c01_09.md +++ b/source/c01/c01_09.md @@ -1,12 +1,14 @@ # 1.9 多继承与Mixin设计模式 +![](http://image.iswbm.com/20200602135014.png) + --- 类的单继承,是我们再熟悉不过的,写起来也毫不费力。而多继承呢,见得很多,写得很少。在很多的项目代码里,你还会见到一种很奇怪的类,他们有一个命名上的共同点,就是在类名的结尾,都喜欢用 Mixin。 -## 1.9.1 认识Mixin模式 +## 1. 认识Mixin模式 那我们今天就来讲讲这个 Mixin,对于这个Mixin,如何理解?它其实是一种设计模式,如果开发者之间没有产生这样一种设计模式的共识,那么设计模式将不复存在。 @@ -42,7 +44,7 @@ class Airplane(Vehicle, PlaneMixin): -## 1.9.2 不使用Mixin的弊端 +## 2. 不使用Mixin的弊端 你肯定会问,不使用 Mixin 行吗? @@ -66,5 +68,5 @@ class Airplane(Vehicle, PlaneMixin): --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c01/c01_09.rst b/source/c01/c01_09.rst old mode 100755 new mode 100644 index 32fe3bd..ddc0e8e --- a/source/c01/c01_09.rst +++ b/source/c01/c01_09.rst @@ -1,13 +1,15 @@ 1.9 多继承与Mixin设计模式 ========================= +|image0| + -------------- 类的单继承,是我们再熟悉不过的,写起来也毫不费力。而多继承呢,见得很多,写得很少。在很多的项目代码里,你还会见到一种很奇怪的类,他们有一个命名上的共同点,就是在类名的结尾,都喜欢用 Mixin。 -1.9.1 认识Mixin模式 -------------------- +1. 认识Mixin模式 +---------------- 那我们今天就来讲讲这个 Mixin,对于这个Mixin,如何理解?它其实是一种设计模式,如果开发者之间没有产生这样一种设计模式的共识,那么设计模式将不复存在。 @@ -46,8 +48,8 @@ Mixin 类,一般都要求开发者遵循规范,在类名末尾加上 Mixin - 功能单一:若有多个功能,那就写多个Mixin类; - 绝对独立:不能依赖于子类的实现;子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。 -1.9.2 不使用Mixin的弊端 ------------------------ +2. 不使用Mixin的弊端 +-------------------- 你肯定会问,不使用 Mixin 行吗? @@ -73,7 +75,8 @@ C3 算法,如果你还不清楚,可以点击我的另一篇文章 ,了解 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c01/c01_10.md b/source/c01/c01_10.md index 3c5fc47..1538e31 100644 --- a/source/c01/c01_10.md +++ b/source/c01/c01_10.md @@ -1,2191 +1,193 @@ -# 1.10 Python 黑魔法指南 50 例 +# 1.10 如何修改 CentOS 6.x 上默认Python ---- +![](http://image.iswbm.com/20200602135014.png) -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 +最近在工作中遇到一个问题,就是有一个功能希望在各种服务器上实现,而服务器上的系统版本可能都不一样,有的是 CentOS 6.x,有的是 CentOS 7.x 。 +需要说明的一点是,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 中你可以直接写 `...` 来得到它 -```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 - -```shell -$ cat demo.py -def func01(): - ... - -def func02(): - pass - -func01() -func02() - -print("ok") - -$ python3 demo.py -ok -``` - - - -## 02. 使用 end 来结束代码块 - -有不少编程语言,循环、判断代码块需要用 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 -``` - -## 03. 可直接运行的 zip 包 - -我们可以经常看到有 Python 包,居然可以以 zip 包进行发布,并且可以不用解压直接使用。 - -这与大多数人的认识的 Python 包格式不一样,正常人认为 Python 包的格式要嘛 是 egg,要嘛是whl 格式。 - -那么这个zip 是如何制作的呢,请看下面的示例。 - -```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 ~]# -``` - -## 04. 反斜杠的倔强: 不写最后 - -`\` 在 Python 中的用法主要有两种 - -**1、在行尾时,用做续行符** - - ```python -[root@localhost ~]$ cat demo.py -print("hello "\ - "world") -[root@localhost ~]$ -[root@localhost ~]$ python demo.py -hello world - ``` - - - -**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 -``` - - - -**是不是傻了?iter 还有这种用法?这为啥是个死循环?** - -关于这个问题,你如果看中文网站,可能找不到相关资料。 - -还好你可以通过 IDE 看py源码里的注释内容,介绍了很详细的使用方法。 - -原来iter有两种使用方法。 - -- 通常我们的认知是第一种,将一个列表转化为一个迭代器。 - -- 而第二种方法,他接收一个 callable对象,和一个sentinel 参数。第一个对象会一直运行,直到它返回 sentinel 值才结束。 - -那`int` 呢? - -这又是一个知识点,int 是一个内建方法。通过看注释,可以看出它是有默认值0的。你可以在console 模式下输入 `int()` 看看是不是返回0。 - -由于int() 永远返回0,永远返回不了1,所以这个 for 循环会没有终点。一直运行下去。 - -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -## 06. 懒人必备技能:使用 “_” - -对于 `_` ,大家对于他的印象都是用于 **占位符**,省得为一个不需要用到的变量,绞尽脑汁的想变量名。 - -今天要介绍的是他的第二种用法,就是在 console 模式下的应用。 - -示例如下: - -```python ->>> 3 + 4 -7 ->>> _ -7 ->>> name='公众号: Python编程时光' ->>> name -'公众号: Python编程时光' ->>> _ -'公众号: Python编程时光' -``` - -它可以返回上一次的运行结果。 - -但是,如果是print函数打印出来的就不行了。 - -```python ->>> 3 + 4 -7 ->>> _ -7 ->>> print("公众号: Python编程时光") -ming ->>> _ -7 -``` - -我自己写了个例子,验证了下,用`__repr__`输出的内容可以被获取到的。 -首先,在我们的目录下,写一个文件 demo.py。内容如下 - -```python -# demo.py -class mytest(): - def __str__(self): - return "hello" - - def __repr__(self): - return "world" -``` - -然后在这个目录下进入交互式环境。 - -```python ->>> import demo ->>> mt=demo.mytest() ->>> mt -world ->>> print(mt) -hello ->>> _ -world -``` - -知道这两个魔法方法的人,一看就明白了,这里不再解释啦。 - -## 07. 最快查看包搜索路径的方式 - -当你使用 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'] ->>> -``` - -那有没有更快的方式呢? - -我这有一种连 console 模式都不用进入的方法,一行命令即可解决 - -```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 会选择第二个值。 - -示例如下: - -```python ->>>(2 or 3) * (5 and 7) -14 # 2*7 -``` - -## 09. 如何修改解释器提示符 - -这个当做今天的一个小彩蛋吧。应该算是比较冷门的,估计知道的人很少了吧。 - -正常情况下,我们在 终端下 执行Python 命令是这样的。 -```python ->>> for i in range(2): -... print (i) -... -0 -1 -``` - -你是否想过 `>>>` 和 `...` 这两个提示符也是可以修改的呢? -```python ->>> import sys ->>> sys.ps1 -'>>> ' ->>> sys.ps2 -'... ' ->>> ->>> sys.ps2 = '---------------- ' ->>> sys.ps1 = 'Python编程时光>>>' -Python编程时光>>>for i in range(2): ----------------- print (i) ----------------- -0 -1 -``` - -## 10. 逗号也有它独特的用法 - -逗号,虽然是个很不起眼的符号,但在 Python 中也有他的用武之地。 - -**第一个用法** - -元组的转化 - -```shell -[root@localhost ~]# cat demo.py -def func(): - return "ok", - -print(func()) -[root@localhost ~]# python3 demo.py -('ok',) -``` - -**第二个用法** - -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. -``` - -那有哪些情况下,是不需要写续行符的呢? - -经过总结,在这些符号中间的代码换行可以省略掉续行符:`[]`,`()`,`{}` - -``` ->>> 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") - -``` -在往下看之前,你可以思考一下,什么情况下才会走 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...") -``` - -同样来几个例子。当不传参数时,就抛出异常。 -```python -test_try_else() -# Exception occurred... - -test_try_else("ming") -# No Exception occurred... -``` - -可以看出,没有 try 里面的代码块没有抛出异常的,会正常走else。 - -总结一下,for else 和 try else 相同,只要代码正常走下去不被 break,不抛出异常,就可以走else。 - - -## 24. 字符串里的缝隙是什么? - -在Python中求一个字符串里,某子字符(串)出现的次数。 - -大家都懂得使用 count() 函数,比如下面几个常规例子: - -```python ->>> "aabb".count("a") -2 ->>> "aabb".count("b") -2 ->>> "aabb".count("ab") -1 -``` - -但是如果我想计算空字符串的个数呢? -```python ->>> "aabb".count("") -5 -``` - -**奇怪了吧?** - -不是应该返回 0 吗?怎么会返回 5? - -实际上,在 Python 看来,两个字符之间都是一个空字符,通俗的说就是缝隙。 - -因此 对于 `aabb` 这个字符串在 Python 来看应该是这样的 - -![](http://image.iswbm.com/20200509172331.png) - -理解了这个“**缝隙**” 的概念后,以下这些就好理解了。 - -```python ->>> (" " * 10).count("") -11 ->>> ->>> "" in "" -True ->>> ->>> "" in "M" -True -``` - - - -## 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'} -``` +这个历史问题一直遗留到现在,由于这次的功能影响到的代码较多,如果要对两个版本的框架分别进行定制的话,需要花不少的时间,为了不维护两套版本,避免浪费多余的精力去做适配,我决定将 CentOS 6.x 上默认的 Python2.6 升级成 Python2.7。 +下面是整个升级过程,别看步骤简单,这些精简步骤的背后可是有不少的坑,被我踩过后,你可以直接使用了。 -**6. 最酷炫的字典解析式** -Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 -那就是列表解析式,集合解析式和字典解析式,通常是 Python 发烧友的最爱,那么今天的主题:字典合并,字典解析式还能否胜任呢? +1. 首先确认下你机器上的默认的 Python 版本 -当然可以,具体示例代码如下: +```shell +$ python -V +Python 2.6.6 -```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'} +$ 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 ``` + +2. 由于我们将使用编译安装的方式,所以要安装下 gcc,及一些工具包。 -**7. Python 3.9 新特性** +注意一定要全部安装,不然后面会发现有不少 python 的工具用不了。 -在 2 月份发布的 Python 3.9.04a 版本中,新增了一个抓眼球的新操作符操作符: `|`, PEP584 将它称之为合并操作符(Union Operator),用它可以很直观地合并多个字典。 +比如不安装 zlib 会无法安装 setuptools,不装 openssl 和 openssl-devel,会无法使用 pip 工具等 -```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'} +$ yum install gcc -y +$ yum groupinstall "Development tools" +$ yum install zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel -y ``` + 如果你这里未按照我的步骤来安装,你后面使用的时候出现了各种各样的问题,不要慌,只要再回来这里,把没安装的包装上,安装完成后,你需要进入第四步重新编译安装Python。 -看到这里,有没有涨姿势了,学了这么久的 Python ,没想到合并字典还有这么多的方法。本篇文章的主旨,并不在于让你全部掌握这 7 种合并字典的方法,实际在工作中,你只要选用一种最顺手的方式即可,但是在协同工作中,或者在阅读他人代码时,你不可避免地会碰到各式各样的写法,这时候你能下意识的知道这是在做合并字典的操作,那这篇文章就是有意义的。 - - - -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -## 45. 条件语句的七种写法 -**第一种:原代码** +3. 下载最新的 Python2.7.x 安装包,解压并进入指定目录 -这是一段非常简单的通过年龄判断一个人是否成年的代码,由于代码行数过多,有些人就不太愿意这样写,因为这体现不出自己多年的 Python 功力。 - -```python -if age > 18: - return "已成年" -else: - return "未成年" ``` - -下面我列举了六种这段代码的变异写法,一个比一个还 6 ,单独拿出来比较好理解,放在工程代码里,没用过这些学法的人,一定会看得一脸懵逼,理解了之后,又不经意大呼:**卧槽,还可以这样写?**,而后就要开始骂街了:**这是给人看的代码?** (除了第一种之外) - -**第二种** - -语法: - -```python - if else +$ 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 ->>> age1 = 20 ->>> age2 = 17 ->>> ->>> ->>> msg1 = "已成年" if age1 > 18 else "未成年" ->>> print msg1 -已成年 ->>> ->>> msg2 = "已成年" if age2 > 18 else "未成年" ->>> print msg2 -未成年 ->>> -``` -**第三种** -语法 +4. 配置,编译,安装 -```python - and or +```shell +# --prefix 指定 python 安装的路径 +$ ./configure --prefix=/usr/local/python/python2.7 +$ make +$ make install ``` -例子 +`./configure` 命令执行完毕之后创建一个文件creating Makefile,供下面的make命令使用 执行 `make install` 之后就会把程序安装到我们指定的目录中去。 -```python ->>> msg1 = age1 > 18 and "已成年" or "未成年" ->>> msg2 = "已成年" if age2 > 18 else "未成年" ->>> ->>> print(msg1) -已成年 ->>> ->>> print(msg2) -未成年 -``` +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 -(, )[condition] -``` + -例子 +5. 查看系统的 Python 版本 -```python ->>> msg1 = ("未成年", "已成年")[age1 > 18] ->>> print(msg1) -已成年 ->>> ->>> ->>> msg2 = ("未成年", "已成年")[age2 > 18] ->>> print(msg2) -未成年 +```shell +$ python -V +Python 2.6.6 ``` -**第五种** + 如果你查看还是 Python 2.6.6 版本,请继续看第六步。 -语法 -```python -(lambda: , lambda:)[]() -``` -例子 +6. 修改系统默认的 Python 版本 -```python ->>> msg1 = (lambda:"未成年", lambda:"已成年")[age1 > 18]() ->>> print(msg1) -已成年 ->>> ->>> msg2 = (lambda:"未成年", lambda:"已成年")[age2 > 18]() ->>> print(msg2) -未成年 -``` +查看新安装的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 -语法: +# 这是系统默认 Python +$ /usr/bin/python -V +Python 2.6.6 -```python -{True: , False: }[] -``` +# 备份原来的 Python 文件 +$ mv /usr/bin/python /usr/bin/python.bak -例子: +# 建立软链接,将我们刚安装的 python2.7 做为系统默认版本 +ln -s /usr/local/bin/python2.7 /usr/bin/python -```python ->>> msg1 = {True: "已成年", False: "未成年"}[age1 > 18] ->>> print(msg1) -已成年 ->>> ->>> msg2 = {True: "已成年", False: "未成年"}[age2 > 18] ->>> print(msg2) -未成年 +# 再次查看 Python 版本,已经成功切换过来 +$ python -V +Python 2.7.14 ``` -**第七种** - -语法 +7. 重新指定 yum 的Python版本 -```python -(() and (,) or (,))[0] -``` +上面我们改了系统的默认 Python 版本,由于CentOS 6.x 的 yum 是基于Python2.6 的,为了不影响 yum 的使用,需单独将yum指向python2.6版本。 -例子 +编辑: vim /usr/bin/yum ,将` /usr/bin/python` 改成 `/usr/bin/python2.6` ```python ->>> msg1 = ((age1 > 18) and ("已成年",) or ("未成年",))[0] ->>> print(msg1) -已成年 ->>> ->>> msg2 = ((age2 > 18) and ("已成年",) or ("未成年",))[0] ->>> print(msg2) -未成年 +#!/usr/bin/python2.6 ``` -以上代码,都比较简单,仔细看都能看懂,我就不做解释了。 -看到这里,有没有涨姿势了,学了这么久的 Python ,这么多骚操作,还真是活久见。。这六种写法里,我最推荐使用的是第一种,自己也经常在用,简洁直白,代码行还少。而其他的写法虽然能写,但是不会用,也不希望在我余生里碰到会在公共代码里用这些写法的同事。 -> 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 +8. 安装 setuptools 及 pip -## 46. /usr/bin/env python 有什么用? - -我们经常会在别人的脚本或者项目的入口文件里看到第一行是下面这样 +pip是python的安装工具,很多python的常用工具,都可以通过pip进行安装。要安装pip,首先要安装setuptools。从这个链接,你可以得到相关信息:https://pypi.python.org/pypi/setuptools ```shell -#!/usr/bin/python -``` - -或者这样 - -```sh e llsh el -#!/usr/bin/env python +# 下载 setuptools +$ wget https://pypi.python.org/packages/ff/d4/209f4939c49e31f5524fa0027bf1c8ec3107abaf7c61fdaad704a648c281/setuptools-21.0.0.tar.gz#md5=81964fdb89534118707742e6d1a1ddb4 ``` -这两者有什么区别呢? - -稍微接触过 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") -} +$ tar vxf setuptools-21.0.0.tar.gz +$ cd setuptools-21.0.0 +$ python setup.py install ``` -输出如下,myfunc 的调用会在函数返回前一步完成,即使你将 myfunc 的调用写在函数的第一行,这就是延迟调用。 +安装完成后,下载pip。其信息在如下网站:https://pypi.python.org/pypi/pip -``` -A -B -``` - -那么在 Python 中否有这种机制呢? - -当然也有,只不过并没有 Golang 这种简便。 - -在 Python 可以使用 **上下文管理器** 达到这种效果 - -```python -import contextlib - -def callback(): - print('B') - -with contextlib.ExitStack() as stack: - stack.callback(callback) - print('A') +```shell +# 下载 pip +wget https://pypi.python.org/packages/41/27/9a8d24e1b55bd8c85e4d022da2922cb206f183e2d18fee4e320c9547e751/pip-8.1.1.tar.gz#md5=6b86f11841e89c8241d689956ba99ed7 ``` -输出如下 +同样的,进行安装 -``` -A -B +```shell +$ tar vxf pip-8.1.1.tar.gz +$ cd pip-8.1.1 +$ python setup.py install ``` -## 49. 自带的缓存机制不用白不用 +安装完成后,执行 `pip list` 查看一下安装的包,若无异常,则一切顺利。或者你也可以试着安装一下第三方包 `pip install requests` 。 -缓存是一种将定量数据加以保存,以备迎合后续获取需求的处理方式,旨在加快数据获取的速度。 -数据的生成过程可能需要经过计算,规整,远程获取等操作,如果是同一份数据需要多次使用,每次都重新生成会大大浪费时间。所以,如果将计算或者远程请求等操作获得的数据缓存下来,会加快后续的数据获取需求。 -为了实现这个需求,Python 3.2 + 中给我们提供了一个机制,可以很方便的实现,而不需要你去写这样的逻辑代码。 - -这个机制实现于 functool 模块中的 lru_cache 装饰器。 - -```python -@functools.lru_cache(maxsize=None, typed=False) -``` +8. 转移cloudinit -参数解读: +上面说的项目,其实就是 cloudinit。接下来就要将 centos 7.2 上的cloudinit 的目录整体拷贝到 centos 6.5 的/usr/local/lib/python2.7/site-packages/ 目录下 -- maxsize:最多可以缓存多少个此函数的调用结果,如果为None,则无限制,设置为 2 的幂时,性能最佳 -- typed:若为 True,则不同参数类型的调用将分别缓存。 +![](http://image.iswbm.com/20190831160317.png) -举个例子 +然后安装一些 cloudinit 的依赖包。 -```python -from functools import lru_cache +```shell +$ pip install six requests prettytable jsonpatch configobj -@lru_cache(None) -def add(x, y): - print("calculating: %s + %s" % (x, y)) - return x + y +# 默认还是安装在 python2.6 下 +$ yum install PyYAML -y -print(add(1, 2)) -print(add(1, 2)) -print(add(2, 3)) +# 将这些文件拷贝到 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 -calculating: 1 + 2 -3 -3 -calculating: 2 + 3 -5 +$ cloud-init init -l +$ cloud-init init ``` -## 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.python-online.cn/image-20200320125724880.png) +![](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 cf3d866..7b9f5a7 --- a/source/c01/c01_10.rst +++ b/source/c01/c01_10.rst @@ -1,2339 +1,207 @@ -1.10 Python 黑魔法指南 50 例 -============================ +1.10 如何修改 CentOS 6.x 上默认Python +===================================== --------------- - - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 - -01. 默默无闻的省略号很好用 --------------------------- - -在Python中,一切皆对象,省略号也不例外。 - -在 Python 3 中你可以直接写 ``...`` 来得到它 - -.. code:: python - - >>> ... - Ellipsis - >>> type(...) - - -而在 Python 2 中没有\ ``...`` 这个语法,只能直接写Ellipsis来获取。 - -.. code:: python - - >>> Ellipsis - Ellipsis - >>> type(Ellipsis) - - >>> - -它转为布尔值时为真 +|image0| -.. code:: python +最近在工作中遇到一个问题,就是有一个功能希望在各种服务器上实现,而服务器上的系统版本可能都不一样,有的是 +CentOS 6.x,有的是 CentOS 7.x 。 - >>> bool(...) - True +需要说明的一点是,CentOS 6.x 上的 Python 版本是 2.6.x 的,而 CentOS 7.x +上的 Python 版本是 2.7.x +的,这意味着我要实现的功能要适配这两种版本的系统。 -最后,这东西是一个单例。 +你可能会说,这有什么的,自己写的时候,注意一下就好了。 -.. code:: python +事情其实没有那么容易,我要实现的功能是基于一个框架进行定制,需要修改不少的框架代码。这个框架在不同的 +Linux 版本上,是有不同的版本的,而且差异巨大,曾经想过在 CentOS 6.x 和 +CentOS 7.x +将这个框架安装成同一个版本,最后还是失败了,无法安装,原因就是高版本需要 +Python2.7,而 CentOS 6.x 上只有 Python2.6。 - >>> id(...) - 4362672336 - >>> id(...) - 4362672336 +这个历史问题一直遗留到现在,由于这次的功能影响到的代码较多,如果要对两个版本的框架分别进行定制的话,需要花不少的时间,为了不维护两套版本,避免浪费多余的精力去做适配,我决定将 +CentOS 6.x 上默认的 Python2.6 升级成 Python2.7。 -那这东西有啥用呢? +下面是整个升级过程,别看步骤简单,这些精简步骤的背后可是有不少的坑,被我踩过后,你可以直接使用了。 -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. 默认参数最好不为可变对象 ----------------------------- - -函数的参数分三种 - 可变参数 - 默认参数 - 关键字参数 - -当你在传递默认参数时,有新手很容易踩雷的一个坑。 - -先来看一个示例 - -.. code:: 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 -会与这个内存地址绑定。在后面的函数调用中,如果调用方指定了新的默认值,就会将原来的默认值覆盖。如果调用方没有指定新的默认值,那就会使用原来的默认值。 - -|image0| - -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() + $ tar vxf setuptools-21.0.0.tar.gz + $ cd setuptools-21.0.0 + $ python setup.py install - # 调用公有方法,没问题 - ins.public() +安装完成后,下载pip。其信息在如下网站:https://pypi.python.org/pypi/pip - # 直接调用私有方法,不行 - ins.__private() - - # 但你可以通过内部公有方法,进行代理 - ins.call_private() - -既然都是方法,那我们真的没有方法可以直接调用吗? - -当然有啦,只是建议你千万不要这样弄,这里只是普及,让你了解一下。 - -.. code:: python - - # 调用私有方法,以下两种等价 - ins._Kls__private() - ins.call_private() - -13. 时有时无的切片异常 ----------------------- - -这是个简单例子,alist 只有5 个元素,当你取第 6 -个元素时,会抛出索引异常。这与我们的认知一致。 - -.. 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 +.. code:: shell - def my_reverse(str): - if str == "": - return str - else: - return my_reverse(str[1:]) + str[0] + # 下载 pip + wget https://pypi.python.org/packages/41/27/9a8d24e1b55bd8c85e4d022da2922cb206f183e2d18fee4e320c9547e751/pip-8.1.1.tar.gz#md5=6b86f11841e89c8241d689956ba99ed7 -在这里,介绍一种最优雅的反转方式,使用切片,不管你是字符串,还是列表,简直通杀。 +同样的,进行安装 -.. code:: python +.. code:: shell - >>> mstr = 'abc' - >>> ml = [1,2,3] - >>> mstr[::-1] - 'cba' - >>> ml[::-1] - [3, 2, 1] + $ tar vxf pip-8.1.1.tar.gz + $ cd pip-8.1.1 + $ python setup.py install -.. +安装完成后,执行 ``pip list`` +查看一下安装的包,若无异常,则一切顺利。或者你也可以试着安装一下第三方包 +``pip install requests`` 。 - 作者:王炳明,微信公众号《Python编程时光》。版权归个人所有,欢迎分享,仅用于学习交流,但勿用作商业用途,违者必究。 +8. 转移cloudinit -21. 改变默认递归次数限制 ------------------------- +上面说的项目,其实就是 cloudinit。接下来就要将 centos 7.2 上的cloudinit +的目录整体拷贝到 centos 6.5 的/usr/local/lib/python2.7/site-packages/ +目录下 -上面才提到递归,大家都知道使用递归是有风险的,递归深度过深容易导致堆栈的溢出。如果你这字符串太长啦,使用递归方式反转,就会出现问题。 +|image1| -那到底,默认递归次数限制是多少呢? +然后安装一些 cloudinit 的依赖包。 -.. code:: python +.. code:: shell - >>> import sys - >>> sys.getrecursionlimit() - 1000 + $ pip install six requests prettytable jsonpatch configobj -可以查,当然也可以自定义修改次数,退出即失效。 + # 默认还是安装在 python2.6 下 + $ yum install PyYAML -y -.. code:: python + # 将这些文件拷贝到 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/ - >>> sys.setrecursionlimit(2000) - >>> sys.getrecursionlimit() - 2000 +执行一下 cloudinit 的几个命令,没有问题,任务就完成了。 -22. 一行代码实现FTP服务器 -------------------------- +.. code:: shell -搭建FTP,或者是搭建网络文件系统,这些方法都能够实现Linux的目录共享。但是FTP和网络文件系统的功能都过于强大,因此它们都有一些不够方便的地方。比如你想快速共享Linux系统的某个目录给整个项目团队,还想在一分钟内做到,怎么办?很简单,使用Python中的SimpleHTTPServer。 + $ cloud-init init -l + $ cloud-init init -SimpleHTTPServer是Python -2自带的一个模块,是Python的Web服务器。它在Python -3已经合并到http.server模块中。具体例子如下,如不指定端口,则默认是8000端口。 +**参考文章** -.. code:: python - - # python2 - python -m SimpleHTTPServer 8888 - - # python3 - python3 -m http.server 8888 - -|image1| - -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 来看应该是这样的 +- https://www.cnblogs.com/stonehe/p/7944366.html |image2| -理解了这个“**缝隙**” 的概念后,以下这些就好理解了。 - -.. 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 - -就会自动打开一个网页。 |image3| - -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 - -什么?没有截图你不信? - -|image4| - -如果你在自己的电脑上尝试一下,结果可能是这样的 - -|image5| - -**怎么又好了呢?** - -如果你想复现的话,请复制我这边给出的代码:\ ``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 这个内置模块,可以很方便的注册退出函数。 - -不管你在哪个地方导致程序崩溃,都会执行那些你注册过的函数。 - -示例如下 - -|image6| - -如果\ ``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`` - -|image7| - -而当你在可执行文件头里使用 ``#!`` + ``/usr/bin/python`` -,意思就是说你得用哪个软件 (python)来执行这个文件。 - -那么加和不加有什么区别呢? - -不加的话,你每次执行这个脚本时,都得这样: ``python xx.py`` , - -|image8| - -有没有一种方式?可以省去每次都加 ``python`` 呢? - -当然有,你可以文件头里加上\ ``#!/usr/bin/python`` -,那么当这个文件有可执行权限 时,只直接写这个脚本文件,就像下面这样。 - -|image9| - -明白了这个后,再来看看 ``!/usr/bin/env python`` 这个 又是什么意思 ? - -当我执行 ``env python`` 时,自动进入了 python console 的模式。 - -|image10| - -这是为什么?和 直接执行 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`` -了。 - -具体演示过程,你可以看下面。 - -|image11| - -那么对于这两者,我们应该使用哪个呢? - -个人感觉应该优先使用 ``#!/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() - --------------- - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190511165650.png -.. |image1| image:: http://image.python-online.cn/20190511165716.png -.. |image2| image:: http://image.iswbm.com/20200509172331.png -.. |image3| image:: http://image.python-online.cn/20190511165735.png -.. |image4| image:: http://image.iswbm.com/20200509122954.png -.. |image5| image:: http://image.iswbm.com/20200509123107.png -.. |image6| image:: http://image.iswbm.com/20200510112133.png -.. |image7| image:: http://image.python-online.cn/20200331184021.png -.. |image8| image:: http://image.python-online.cn/20200331185034.png -.. |image9| image:: http://image.python-online.cn/20200331184755.png -.. |image10| image:: http://image.python-online.cn/20200331185741.png -.. |image11| image:: http://image.python-online.cn/20200331190224.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 57d427d..9d06f86 100644 --- a/source/c01/c01_11.md +++ b/source/c01/c01_11.md @@ -1,8 +1,10 @@ # 1.11 正则表达式必知必会 +![](http://image.iswbm.com/20200602135014.png) + --- -## 一、正则表达式先导 +## 1. 正则表达式先导 ### 1.1 正则基础知识 @@ -70,7 +72,7 @@ re_match.group(2) `{n,m}?` :重复n到m次,但尽可能少重复 `{n,}?`: 重复n次以上,但尽可能少重复 -## 二、Python中的正则 +## 2. Python中的正则 在Python中,自带了re模块,这是专门用来做正则表达式的匹配的。 @@ -174,7 +176,7 @@ match.span() (0, 3) ``` -## 三、检验表达式 +## 3. 检验表达式 ### 3.1 校验数字 ``` 1. 数字:^[0-9]*$ @@ -322,4 +324,4 @@ match.span() --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c01/c01_11.rst b/source/c01/c01_11.rst old mode 100755 new mode 100644 index ca3cb4c..7113dfd --- a/source/c01/c01_11.rst +++ b/source/c01/c01_11.rst @@ -1,10 +1,12 @@ 1.11 正则表达式必知必会 ======================= +|image0| + -------------- -一、正则表达式先导 ------------------- +1. 正则表达式先导 +----------------- 1.1 正则基础知识 ~~~~~~~~~~~~~~~~ @@ -75,8 +77,8 @@ | ``{n,m}?`` :重复n到m次,但尽可能少重复 | ``{n,}?``\ : 重复n次以上,但尽可能少重复 -二、Python中的正则 ------------------- +2. Python中的正则 +----------------- 在Python中,自带了re模块,这是专门用来做正则表达式的匹配的。 @@ -188,8 +190,8 @@ start(),end(),end() match.span() (0, 3) -三、检验表达式 --------------- +3. 检验表达式 +------------- 3.1 校验数字 ~~~~~~~~~~~~ @@ -344,7 +346,8 @@ start(),end(),end() -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c01/c01_12.md b/source/c01/c01_12.md index 0ed9f6f..9d0c0af 100644 --- a/source/c01/c01_12.md +++ b/source/c01/c01_12.md @@ -1,10 +1,12 @@ # 1.12 搞懂字符编码的前世今生 +![](http://image.iswbm.com/20200602135014.png) + --- 初学计算机的人,肯定对众多字符编码感到头疼。为什么会那么多字符串编码? 这些内容是在去年整理的,现在重新整理下,发布在博客,搞懂字符串编码,这一篇文章足矣 -## 1.12.1 前言必知 +## 1. 前言必知 初学计算机的人,肯定对众多字符编码感到头疼。为什么会那么多字符串编码? @@ -13,7 +15,7 @@ bit,位,一个bit可以表示两个数字,0和1 byte,字节,一个byte由8个bit表示,一个byte表示的数字区间[0,255] -## 1.12.2 ASCII编码 +## 2. ASCII编码 ### 原始ASCII编码 @@ -29,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里所有字符)。 @@ -46,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来解码呢?无论用哪个解码都会出现乱码的情况。 @@ -59,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编码 到目前为止,世界各国人民,都能愉快的无语言障碍的使用计算机了。 但是随着信息化时代的来临,人们越来越追求资源的传输速度和硬盘的存储效率。 @@ -83,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还没出现。 @@ -111,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 @@ -134,4 +136,4 @@ GB 18030 与 GB 2312-1980 和 GBK 兼容,共收录汉字70244个。 --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c01/c01_12.rst b/source/c01/c01_12.rst old mode 100755 new mode 100644 index b9736b4..695d245 --- a/source/c01/c01_12.rst +++ b/source/c01/c01_12.rst @@ -1,13 +1,15 @@ 1.12 搞懂字符编码的前世今生 =========================== +|image0| + -------------- 初学计算机的人,肯定对众多字符编码感到头疼。为什么会那么多字符串编码? 这些内容是在去年整理的,现在重新整理下,发布在博客,搞懂字符串编码,这一篇文章足矣 -1.12.1 前言必知 ---------------- +1. 前言必知 +----------- 初学计算机的人,肯定对众多字符编码感到头疼。为什么会那么多字符串编码? @@ -16,8 +18,8 @@ bit,位,一个bit可以表示两个数字,0和1 byte,字节,一个byte由8个bit表示,一个byte表示的数字区间[0,255] -1.12.2 ASCII编码 ----------------- +2. ASCII编码 +------------ 原始ASCII编码 ~~~~~~~~~~~~~ @@ -42,8 +44,8 @@ byte,字节,一个byte由8个bit表示,一个byte表示的数字区间[0,2 原始ASCII编码 -1.12.3 ANSI标准 ---------------- +3. ANSI标准 +----------- ANSI:美国国家标准学会(AMERICAN NATIONAL STANDARDS INSTITUTE) @@ -61,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来解码呢?无论用哪个解码都会出现乱码的情况。 @@ -76,8 +78,8 @@ Consortium),一个统筹统一码(Unicode)发展的非营利机构,其 Unicode至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为2017年6月20日公布的10.0.0,已经收入超过十万个字符(第十万个字符在2005年获采纳)。\ `Unicode-维基百科 `__ -1.12.5 UTF-8编码 ----------------- +5. UTF-8编码 +------------ | 到目前为止,世界各国人民,都能愉快的无语言障碍的使用计算机了。 | 但是随着信息化时代的来临,人们越来越追求资源的传输速度和硬盘的存储效率。 @@ -95,14 +97,14 @@ TransferFormat,即把Unicode转做某种格式的意思)应运而生 **扩展问题** - UTF-8有用一个字节表示,有用两个字节表示,读取数据,如何识别是几个字节表示一个字符? - |image0| + |image1| - ‘联通’显示乱码 当在txt输入输入’联通’,保存再次打开就乱码,输入’你好联通’就不会出现这个情况。 - |image1| + |image2| -1.12.6 编码之于Python ---------------------- +6. 编码之于Python +----------------- **Python2** Python2默认是使用ASCII编码,这也是出现编码问题的罪魁祸首。但这也不怪Python,在Python诞生的时候,Unicode还没出现。 @@ -130,8 +132,8 @@ Python2默认是使用ASCII编码,这也是出现编码问题的罪魁祸首 在Python3中,已经默认使用Unicode编码了。解决了很多编码的问题。 如果py文件中,含有中文还是得在文件头出加入 ``# coding=utf-8`` -1.12.7 扩展阅读 ---------------- +7. 扩展阅读 +----------- 中文编码的发展 GB2312-> GBK -> GB18030 @@ -154,11 +156,10 @@ Python2默认是使用ASCII编码,这也是出现编码问题的罪魁祸首 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image3| -.. |image0| image:: https://i.loli.net/2017/08/02/598168fe2b016.png -.. |image1| image:: https://i.loli.net/2017/08/02/59816d652aeb9.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: https://i.loli.net/2017/08/02/598168fe2b016.png +.. |image2| image:: https://i.loli.net/2017/08/02/59816d652aeb9.png +.. |image3| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_13.md b/source/c01/c01_13.md index aab1c8b..8cb56c2 100644 --- a/source/c01/c01_13.md +++ b/source/c01/c01_13.md @@ -1,127 +1,353 @@ -# 1.13 Python几个高阶函数 +# 1.13 别再使用 pprint 打印字典了 ---- +![](http://image.iswbm.com/20200602135014.png) -## 1.13.1 lambda 表达式 +## 1. 吐槽问题 -匿名函数(英语:anonymous function)是指一类无需定义标识符(函数名)的函数。通俗来说呢,就是它可以让我们的函数,可以不需要函数名。 +pprint 你应该很熟悉了吧? -正常情况下,我们定义一个函数,使用的是 `def` 关键字,而当你学会使用匿名函数后,替代 `def` 的是 `lambda`。 +随便在搜索引擎上搜索如何打印漂亮的字典或者格式化字符串时,大部分人都会推荐你使用这货 。 -这边使用`def` 和 `lambda` 分别举个例子,你很快就能理解。 -``` -def mySum(x, y): - return x+y -mySum(2, 3) -# 5 +比如这下面这个 json 字符串或者说字典(我随便在网上找的),如果不格式化美化一下,根本无法阅读。 -(lambda x, y: x+y)(2, 4) -# 6 +```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 中是不一样的效果)。 -带 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 +```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}] ``` -从以上示例来看,lambda 表达式和常规的函数相比,写法比较怪异,可读性相对较差。除了可以直接运行之外,好像并没有其他较为突出的功能,为什么在今天我们要介绍它呢? +好像有点效果,真的是 “神器”呀。 -首先我们要知道 lambda 是一个表达式,而不是一个语句。正因为这个特点,我们可以在一些特殊的场景中去使用它。具体是什么场景呢?接下来我们会介绍到几个非常好用的内置函数。 +但是你告诉我, **\xe4\xbd\xa0\xe7\x9a** 这些是什么玩意?本来想提高可读性的,现在变成完全不可读了。 -## 1.13.2 map 函数 +好在我懂点 Python 2 的编码,知道 Python 2 中默认(不带u)的字符串格式都是 str 类型,也是 bytes 类型,它是以 byte 存储的。 -map 函数,它接收两个参数,第一个参数是一个函数对象(当然也可以是一个lambda表达式),第二个参数是一个序列。 +行吧,好像是我错了,我改了下,使用 unicode 类型来定义中文字符串吧。 -它可以实现怎样的功能呢,我举个例子你就明白了。 -``` ->>> map(lambda x: x*2, [1,2,3,4,5]) -[2, 4, 6, 8, 10] +```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}] ``` -可以很清楚地看到,它可以将后面序列中的每一个元素做为参数传入lambda中。 -当我们不使用 map 函数时,你也许会这样子写。 +确实是有好点了,但是看到下面这些,我崩溃了,我哪里知道这是什么鬼,难道是我太菜了吗?当我是计算机呀? + ``` -mylist=[] -for i in [1,2,3,4,5]: - mylist.append(i*2) +u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u6700\u597d\u7684\u9e21\u513f' ``` -## 1.13.3 filter 函数 +除此之外,我们知道 json 的严格要求必须使用 **双引号**,而我定义字典时,也使用了双引号了,为什么打印出来的为什么是 **单引号**?我也太难了吧,我连自己的代码都无法控制了吗? -filter 函数,和 map 函数相似。同样也是接收两个参数,一个lambda 表达式,一个序列。它会遍历后面序列中每一个元素,并将其做为参数传入lambda表达式中,当表达式返回 True,则元素会被保留下来,当表达式返回 False ,则元素会被丢弃。 +到这里,我们知道了 pprint 带来的两个问题: -下面这个例子,将过滤出一个列表中小于0的元素。 -``` ->>>filter(lambda x: x < 0, range(-5, 5)) -[-5, -4, -3, -2, -1] -``` +1. 没法在 Python 2 下正常打印中文 +2. 没法输出 JSON 标准格式的格式化内容(双引号) -## 1.13.4 reduce 函数 +## 2. 解决问题 -reduce 函数,也是类似的。它的作用是先对序列中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 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}] +>>> -![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 + +但是很多时候(在公司的一些服务器)你无法选择自己使用哪个版本的 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) ``` -## 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.python-online.cn/image-20200320125724880.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 2060038..99db502 --- a/source/c01/c01_13.rst +++ b/source/c01/c01_13.rst @@ -1,166 +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 表达式 --------------------- + u'\u6597\u9c7c271934\u8d70\u8fc7\u8def\u8fc7\u4e0d\u8981\u9519\u8fc7\uff0c\u8fd9\u91cc\u6709\u6700\u597d\u7684\u9e21\u513f' -匿名函数(英语:anonymous -function)是指一类无需定义标识符(函数名)的函数。通俗来说呢,就是它可以让我们的函数,可以不需要函数名。 +除此之外,我们知道 json 的严格要求必须使用 +**双引号**\ ,而我定义字典时,也使用了双引号了,为什么打印出来的为什么是 +**单引号**\ ?我也太难了吧,我连自己的代码都无法控制了吗? -正常情况下,我们定义一个函数,使用的是 ``def`` -关键字,而当你学会使用匿名函数后,替代 ``def`` 的是 ``lambda``\ 。 +到这里,我们知道了 pprint 带来的两个问题: -这边使用\ ``def`` 和 ``lambda`` 分别举个例子,你很快就能理解。 +1. 没法在 Python 2 下正常打印中文 +2. 没法输出 JSON 标准格式的格式化内容(双引号) -:: +2. 解决问题 +----------- - def mySum(x, y): - return x+y - mySum(2, 3) - # 5 +打印中文 +~~~~~~~~ - (lambda x, y: x+y)(2, 4) - # 6 +如果你是在 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}] + >>> -带 if/else +但是很多时候(在公司的一些服务器)你无法选择自己使用哪个版本的 +Python,本来我可以选择不用的,因为有更好的替代方案(\ **这个后面会讲**\ )。 -:: +但是我出于猎奇,正好前两天不是写过一篇关于 编码 +的文章吗,我自认为自己对于 +编码还是掌握比较熟练的,就想着来解决一下这个问题。 - >>>( lambda x, y: x if x < y else y )( 1, 2 ) - 1 +索性就来看下 pprint +的源代码,还真被我找到了解决方法,如果你也想挑战一下,不防在这里停住,自己研究一下如何实现,我相信对你阅读源码会有帮助。 -嵌套函数 +**以下是我的解决方案,供你参考**\ : -:: +写一个自己的 printer 对象,继承自 PrettyPrinter (pprint 使用的printer) - >>>( lambda x: ( lambda y: ( lambda z: x + y + z )( 1 ) )( 2 ) )( 3 ) - 6 +并且复写 format 方法,判断传进来的字符串对象是否 str 类型,如果不是 str +类型,而是 unicode 类型,就用 uft8 编码成 str 类型。 -递归函数 +.. code:: python -:: + # coding: utf-8 + from pprint import PrettyPrinter - >>> func = lambda n:1 if n == 0 else n * func(n-1) - >>> func(5) - 120 + # 继承 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) - >>> f = lambda func, n: 1 if n == 0 else n * func( func, n - 1 ) - >>> f(f,4) - 24 +输出如下,已经解决了中文的显示问题: -从以上示例来看,lambda -表达式和常规的函数相比,写法比较怪异,可读性相对较差。除了可以直接运行之外,好像并没有其他较为突出的功能,为什么在今天我们要介绍它呢? +|image1| -首先我们要知道 lambda -是一个表达式,而不是一个语句。正因为这个特点,我们可以在一些特殊的场景中去使用它。具体是什么场景呢?接下来我们会介绍到几个非常好用的内置函数。 +打印双引号 +~~~~~~~~~~ -1.13.2 map 函数 ---------------- +解决了中文问题后,再来看看如何让 pprint 打印双引号。 -map -函数,它接收两个参数,第一个参数是一个函数对象(当然也可以是一个lambda表达式),第二个参数是一个序列。 +在实例化 PrettyPrinter 对象的时候,可以接收一个 stream +对象,它表示你要将内容输出到哪里,默认是使用 sys.stdout 这个 +stream,也就是标准输出。 -它可以实现怎样的功能呢,我举个例子你就明白了。 +现在我们要修改输出的内容,也就是将输出的单引号替换成双引号。 -:: +那我们完全可以自己定义一个 stream +类型的对象,该对象不需要继承任何父类,只要你实现 write 方法就可以。 - >>> map(lambda x: x*2, [1,2,3,4,5]) - [2, 4, 6, 8, 10] +有了思路,就可以开始写代码了,如下: -可以很清楚地看到,它可以将后面序列中的每一个元素做为参数传入lambda中。 +.. code:: python -当我们不使用 map 函数时,你也许会这样子写。 + # 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 + } + ] - mylist=[] - for i in [1,2,3,4,5]: - mylist.append(i*2) +经过一番研究,才知道是因为 print 函数默认会将打印的内容后面加个 +**换行符**\ 。 -1.13.3 filter 函数 ------------------- +那如何将使 print 函数打印的内容,不进行换行呢? -filter 函数,和 map 函数相似。同样也是接收两个参数,一个lambda -表达式,一个序列。它会遍历后面序列中每一个元素,并将其做为参数传入lambda表达式中,当表达式返回 -True,则元素会被保留下来,当表达式返回 False ,则元素会被丢弃。 +方法很简单,但是我相信很多人都不知道,只要在 print 的内容后加一个 +**逗号** 就行。 -下面这个例子,将过滤出一个列表中小于0的元素。 +就像下面这样。 -:: +|image2| - >>>filter(lambda x: x < 0, range(-5, 5)) - [-5, -4, -3, -2, -1] +知道了问题所在,再修改下代码 -1.13.4 reduce 函数 ------------------- +.. code:: python -reduce 函数,也是类似的。它的作用是先对序列中的第 1、2 -个元素进行操作,得到的结果再与第三个数据用 lambda -函数运算,将其得到的结果再与第四个元素进行运算,以此类推下去直到后面没有元素了。 + # coding: utf-8 + from pprint import PrettyPrinter -|reduce 逻辑演示| 这边举个例子你也就明白了。 + 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('\'', '"'), - >>>reduce(lambda x,y: x+y, [1,2,3,4,5]) - 15 + 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 +|image3| -1.13.5 注意点 -------------- +3. 何必折腾 +----------- -以上几个函数,熟练的掌握它们的写法,可以让我们的代码看起来更加的 -Pythonic ,在某一程度上代码看起来更加的简洁。 +通过上面的一番折腾,我终于实现了我 **梦寐以求** 的需求。 -如果你是新手呢,你需要注意的是,以上示例是在 Python2.x -环境下演示的。而在 Python3.x 中,却有所不同,你可以自己尝试一下。 +代价就是我整整花费了两个小时,才得以实现,而对于小白来说,可能没有信心,也没有耐心去做这样的事情。 -这里总结一下: +**所以我想说的是,Python 2 下的 pprint ,真的不要再用了**\ 。 -第一点,map 和 filter -函数返回的都不再是一个列表,而是一个迭代器对象。这里以map为例 +为什么我要用这么 说,因为明明有更好的替代品,人生苦短,既然用了 Python +,当然是怎么简单怎么来咯,何必为难自己呢,一行代码可以解决的事情,偏偏要去写两个类,那不是自讨苦吃吗?(我这是在骂自己吗? -:: +如果你愿意抛弃 pprint ,那我推荐你用 json.dumps ,我保证你再也不想用 +pprint 了。 - >>> 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] +.. _打印中文-1: -第二点,reduce 不可以直接调用,而是要先导入才能使用, +打印中文 +~~~~~~~~ -:: +其实无法打印中文,是 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 +的源码(各种处理其实还是挺复杂的),不过好在最后也能有所收获。 - from functools import reduce +本文的分享就到这里,阅读本文,我认为你可以获取到三个知识点 --------------- +1. 核心观点:Python2 下不要再使用 pprint +2. 若真要使用,且有和一样的改造需求,可以参考我的实现 +3. Python 2 中的 print 语句后居然可以加 逗号 -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +以上。希望此文能对你有帮助。 - 关注公众号,获取最新干货! +|image4| -.. |reduce 逻辑演示| image:: https://ws1.sinaimg.cn/large/8f640247gy1fyx6i8q3anj208c04u3yu.jpg +.. |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_14.md b/source/c01/c01_14.md index cf90646..d47444a 100644 --- a/source/c01/c01_14.md +++ b/source/c01/c01_14.md @@ -1,6 +1,6 @@ # 1.14 with 与 上下文管理器 -> 提示:前面的内容较为基础,重点知识在后半段。 +![](http://image.iswbm.com/20200602135014.png) `with` 这个关键字,对于每一学习Python的人,都不会陌生。 @@ -11,7 +11,7 @@ with open('test.txt') as f: print f.readlines() ``` -## 1.14.1 what context manager? +## 1. what context manager? **基本语法** @@ -28,7 +28,7 @@ with EXPR as VAR: 3. f 不是上下文管理器,应该是资源对象。 ``` -## 1.14.2 how context manager? +## 2. how context manager? 要自己实现这样一个上下文管理,要先知道上下文管理协议。 @@ -61,7 +61,7 @@ with Resource() as res: 从这个示例可以很明显的看出,在编写代码时,可以将资源的连接或者获取放在`__enter__`中,而将资源的关闭写在`__exit__` 中。 -## 1.14.3 why context manager? +## 3. why context manager? 学习时多问自己几个为什么,养成对一些细节的思考,有助于加深对知识点的理解。 @@ -111,7 +111,7 @@ with Resource() as res: 当主逻辑代码没有报异常时,这三个参数将都为None。 -## 1.14.4 how contextlib? +## 4. how contextlib? 在上面的例子中,我们只是为了构建一个上下文管理器,却写了一个类。如果只是要实现一个简单的功能,写一个类未免有点过于繁杂。这时候,我们就想,如果只写一个函数就可以实现上下文管理器就好了。 @@ -185,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) 总结起来,使用上下文管理器有三个好处: @@ -195,4 +195,4 @@ with open_func('/Users/MING/mytest.txt') as file_in: ------ -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c01/c01_14.rst b/source/c01/c01_14.rst index 263231c..bb99084 100644 --- a/source/c01/c01_14.rst +++ b/source/c01/c01_14.rst @@ -1,7 +1,7 @@ 1.14 with 与 上下文管理器 ========================= - 提示:前面的内容较为基础,重点知识在后半段。 +|image0| ``with`` 这个关键字,对于每一学习Python的人,都不会陌生。 @@ -13,8 +13,8 @@ with open('test.txt') as f: print f.readlines() -1.14.1 what context manager? ------------------------------ +1. what context manager? +------------------------- **基本语法** @@ -31,8 +31,8 @@ 2. 上下文管理器:open('test.txt') 3. f 不是上下文管理器,应该是资源对象。 -1.14.2 how context manager? ----------------------------- +2. how context manager? +------------------------ 要自己实现这样一个上下文管理,要先知道上下文管理协议。 @@ -66,8 +66,8 @@ 从这个示例可以很明显的看出,在编写代码时,可以将资源的连接或者获取放在\ ``__enter__``\ 中,而将资源的关闭写在\ ``__exit__`` 中。 -1.14.3 why context manager? ----------------------------- +3. why context manager? +------------------------ 学习时多问自己几个为什么,养成对一些细节的思考,有助于加深对知识点的理解。 @@ -122,8 +122,8 @@ Python解释器,这个异常我们已经捕获了,不需要再往外抛了 当主逻辑代码没有报异常时,这三个参数将都为None。 -1.14.4 how contextlib? ----------------------- +4. how contextlib? +------------------ 在上面的例子中,我们只是为了构建一个上下文管理器,却写了一个类。如果只是要实现一个简单的功能,写一个类未免有点过于繁杂。这时候,我们就想,如果只写一个函数就可以实现上下文管理器就好了。 @@ -199,7 +199,7 @@ open)的上下文管理器。 代码是这样的 -|image0| +|image1| 总结起来,使用上下文管理器有三个好处: @@ -209,10 +209,9 @@ open)的上下文管理器。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image2| -.. |image0| image:: http://image.python-online.cn/20190310172800.png +.. |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/c01/c01_15.md b/source/c01/c01_15.md index fa232e8..d6561cd 100644 --- a/source/c01/c01_15.md +++ b/source/c01/c01_15.md @@ -1,71 +1,49 @@ -# 1.15 提升Python性能的7个习惯 +# 1.21 Python 开发的几个小 Tips ---- +## 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.python-online.cn/image-20200320125724880.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 d4a43ca..97bab2d 100644 --- a/source/c01/c01_15.rst +++ b/source/c01/c01_15.rst @@ -1,84 +1,53 @@ -1.15 提升Python性能的7个习惯 -============================ +1.21 Python 开发的几个小 Tips +============================= --------------- - - 转载自: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)) #调用一次函数 + import contextlib -不要在重复操作的内容作为参数放到循环条件中,避免重复运算。 + def unshelve_task(): + pass -:: - - #每次循环都需要重新执行len(a)while i < len(a): statement#len(a)仅执行一次m = len(a)while i < m: statement - -如需使用模块X中的某个函数或对象Y,应直接使用from X import -Y,而不是import X; -X.Y。这样在使用Y时,可以减少一次查询(解释器不必首先查找到X模块,然后在X模块的字典中查找Y)。 + @contextlib.contextmanager + def close_stdout(): + raw_stdout = sys.stdout + file = open(log_file, 'a+') + sys.stdout = file -1.15.3 采用映射替代条件查找 ---------------------------- + yield -映射(比如dict等)的搜索速度远快于条件语句(如if等)。Python中也没有select-case语句。 - -:: + 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 采用生成器表达式替代列表解析 ------------------------------------ +如何使用netaddr库将ipv4子网掩码转换为cidr表示法? 示例:255.255.255.0到/ +24 -列表解析(list -comprehension),会产生整个列表,对大量数据的迭代会产生负面效应。 - -而生成器表达式则不会,其不会真正创建列表,而是返回一个生成器,在需要时产生一个值(延迟计算),对内存更加友好。 +使用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()) - -1.15.6 先编译后调用 -------------------- - -使用eval()、exec()函数执行代码时,最好调用代码对象(提前通过compile()函数编译成字节码),而不是直接调用str,可以避免多次执行重复编译过程,提高程序性能。 + >>> from netaddr import IPAddress + >>> IPAddress("255.255.255.0").netmask_bits() + 24 -正则表达式模式匹配也类似,也最好先将正则表达式模式编译成regex对象(通过re.complie()函数),然后再执行比较和匹配。 +您也可以在不使用任何库的情况下执行此操作,只需在网络掩码的二进制表示中计算1位: -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 --------------- +|image0| -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +.. |image0| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c01/c01_16.md b/source/c01/c01_16.md index 576eedf..11847b4 100644 --- a/source/c01/c01_16.md +++ b/source/c01/c01_16.md @@ -1,5 +1,7 @@ # 1.16 泛型函数怎么写? +![](http://image.iswbm.com/20200602135014.png) + 泛型,如果你尝过java,应该对他不陌生吧。但你可能不知道在 Python 中(3.4+ ),也可以实现 简单的泛型函数。 在Python中只能实现基于单个(第一个)参数的数据类型来选择具体的实现方式,官方名称 是 `single-dispatch`。你或许听不懂,说人话,就是可以实现第一个参数的数据类型不同,其调用的函数也就不同。 @@ -159,4 +161,4 @@ hello, world ------ -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c01/c01_16.rst b/source/c01/c01_16.rst index ab6c40b..50c0dcd 100644 --- a/source/c01/c01_16.rst +++ b/source/c01/c01_16.rst @@ -1,6 +1,8 @@ 1.16 泛型函数怎么写? ===================== +|image0| + 泛型,如果你尝过java,应该对他不陌生吧。但你可能不知道在 Python 中(3.4+ ),也可以实现 简单的泛型函数。 @@ -166,7 +168,8 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c01/c01_17.md b/source/c01/c01_17.md index 84c494e..9e13038 100644 --- a/source/c01/c01_17.md +++ b/source/c01/c01_17.md @@ -1,528 +1,274 @@ -# 1.17 深入理解「描述符」 +# 1.17 详解 Python 中的编码问题 -学习 Python 这么久了,说起 Python 的优雅之处,能让我脱口而出的, Descriptor(描述符)特性可以排得上号。 +![](http://image.iswbm.com/20200602135014.png) -描述符 是Python 语言独有的特性,它不仅在应用层使用,在语言的基础设施中也有涉及。 +Python 中编码问题,一直是很多 Python 开发者的噩梦,尽管你是工作多年的 Python 开发者,也肯定会经常遇到令人神烦的编码问题,好不容易花了半天搞明白了。 -我可以大胆地猜测,你对于描述符的了解是始于诸如 Django ORM 和 SQLAlchemy 中的字段对象,是的,它们都是描述符。你的它的认识,可能也止步于此,如果你没有去深究,它为何要如此设计?也就加体会不到 Python 给我们带来的便利与优雅。 +一段时间后,又全都忘光光了,一脸懵逼的你又开始你找各种博客、帖子,从头搞清楚什么是编码?什么是 unicode?它和 ASCII 有什么区别?为什么 decode encode 老是报错?python2 里和 python3 的字符串类型怎么都不一样,怎么对应起来?如何检测编码格式? -由于 描述符的内容较多,长篇大论,容易让你倦怠,所以我打算分几篇来讲。 +反反复复,这个过程真是太痛苦了。 -## 1.17.1 为什么要使用描述符? +今天我把大家在 Python 上会遇到的一些编码问题都讲清楚了,以后你可以不用再 Google,收藏这篇文章就行。 -假想你正在给学校写一个成绩管理系统,并没有太多编码经验的你,可能会这样子写。 -```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 -```python ->>> std1 = Student('小明', 76, 87, 68) ->>> std1 - -``` +在 Python3中,字符串有两种类型 ,str 和 bytes。 + +今天就来说一说这二者的区别: -但是程序并不像人那么智能,不会自动根据使用场景判断数据的合法性,如果老师在录入成绩的时候,不小心录入了将成绩录成了负数,或者超过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' +>>> ``` -程序还是一样的人工智能,非常好。 - -![](http://image.python-online.cn/20190425221322.png) +## 2. Python 2 中 str 与 unicode -你以为你写的代码,已经非常优秀,无懈可击了。 +而在 Python2 中,字符串的类型又与 Python3 不一样,需要仔细区分。 -没想到,人外有天,你的主管看了你的代码后,深深地叹了口气:类里的三个属性,math、chinese、english,都使用了 Property 对属性的合法性进行了有效控制。功能上,没有问题,但就是太啰嗦了,三个变量的合法性逻辑都是一样的,只要大于0,小于100 就可以,代码重复率太高了,这里三个成绩还好,但假设还有地理、生物、历史、化学等十几门的成绩呢,这代码简直没法忍。去了解一下 Python 的描述符吧。 +在 Python2 里,字符串也只有两种类型,unicode 和 str 。 -经过主管的指点,你知道了「描述符」这个东西。怀着一颗敬畏之心,你去搜索了下关于 描述符的用法。 +只有 unicode object 和 非unicode object(其实应该叫 str object) 的区别: -其实也很简单,一个实现了 `描述符协议` 的类就是一个描述符。 +- `unicode string(unicode类型)`:以 Unicode code points 形式存储,**人类认识的形式** +- `byte string(str 类型)`:以 byte 形式存储,**机器认识的形式** -什么描述符协议:实现了 `__get__()`、`__set__()`、`__delete__()` 其中至少一个方法的类,就是一个描述符。 +当我们直接使用双引号或单引号包含字符的方式来定义字符串时,就是 str 字符串对象,比如这样 -- `__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.python-online.cn/image-20200320125724880.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 f8bf4ac..b8f6202 100644 --- a/source/c01/c01_17.rst +++ b/source/c01/c01_17.rst @@ -1,589 +1,295 @@ -1.17 深入理解「描述符」 -======================= - -学习 Python 这么久了,说起 Python 的优雅之处,能让我脱口而出的, -Descriptor(描述符)特性可以排得上号。 - -描述符 是Python -语言独有的特性,它不仅在应用层使用,在语言的基础设施中也有涉及。 - -我可以大胆地猜测,你对于描述符的了解是始于诸如 Django ORM 和 SQLAlchemy -中的字段对象,是的,它们都是描述符。你的它的认识,可能也止步于此,如果你没有去深究,它为何要如此设计?也就加体会不到 -Python 给我们带来的便利与优雅。 - -由于 描述符的内容较多,长篇大论,容易让你倦怠,所以我打算分几篇来讲。 - -1.17.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 - ) - -这下程序稍微有点人工智能了,能够自己明辨是非了。 +1.17 详解 Python 中的编码问题 +============================= |image0| -程序是智能了,但在\ ``__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 - ) - -程序还是一样的人工智能,非常好。 - -|image1| +Python 中编码问题,一直是很多 Python 开发者的噩梦,尽管你是工作多年的 +Python +开发者,也肯定会经常遇到令人神烦的编码问题,好不容易花了半天搞明白了。 -你以为你写的代码,已经非常优秀,无懈可击了。 +一段时间后,又全都忘光光了,一脸懵逼的你又开始你找各种博客、帖子,从头搞清楚什么是编码?什么是 +unicode?它和 ASCII 有什么区别?为什么 decode encode 老是报错?python2 +里和 python3 的字符串类型怎么都不一样,怎么对应起来?如何检测编码格式? -没想到,人外有天,你的主管看了你的代码后,深深地叹了口气:类里的三个属性,math、chinese、english,都使用了 -Property -对属性的合法性进行了有效控制。功能上,没有问题,但就是太啰嗦了,三个变量的合法性逻辑都是一样的,只要大于0,小于100 -就可以,代码重复率太高了,这里三个成绩还好,但假设还有地理、生物、历史、化学等十几门的成绩呢,这代码简直没法忍。去了解一下 -Python 的描述符吧。 +反反复复,这个过程真是太痛苦了。 -经过主管的指点,你知道了「描述符」这个东西。怀着一颗敬畏之心,你去搜索了下关于 -描述符的用法。 +今天我把大家在 Python 上会遇到的一些编码问题都讲清楚了,以后你可以不用再 +Google,收藏这篇文章就行。 -其实也很简单,一个实现了 ``描述符协议`` 的类就是一个描述符。 +1. Python 3 中 str 与 bytes +--------------------------- -什么描述符协议:实现了 -``__get__()``\ 、\ ``__set__()``\ 、\ ``__delete__()`` -其中至少一个方法的类,就是一个描述符。 +在 Python3中,字符串有两种类型 ,str 和 bytes。 -- ``__get__``\ : - 用于访问属性。它返回属性的值,若属性不存在、不合法等都可以抛出对应的异常。 -- ``__set__``\ :将在属性分配操作中调用。不会返回任何内容。 -- ``__delete__``\ :控制删除操作。不会返回内容。 +今天就来说一说这二者的区别: -对描述符有了大概的了解后,你开始重写上面的方法。 +- ``unicode string(str 类型)``\ :以 Unicode code points + 形式存储,\ **人类认识的形式** +- ``byte string(bytes 类型)``\ :以 byte + 形式存储,\ **机器认识的形式** -如前所述,Score 类是一个描述器,当从 Student 的实例访问 -math、chinese、english这三个属性的时候,都会经过 Score -类里的三个特殊的方法。这里的 Score 避免了 使用Property -出现大量的代码无法复用的尴尬。 +在 Python 3 中你定义的所有字符串,都是 unicode string类型,使用 ``type`` +和 ``isinstance`` 可以判别 .. 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 - ) - -实现的效果和前面的一样,可以对数据的合法性进行有效控制(字段类型、数值区间等) - -|image2| - -以上,我举了下具体的实例,从最原始的编码风格到 Property -,最后引出描述符。由浅入深,一步一步带你感受到描述符的优雅之处。 - -通过此文,你需要记住的只有一点,就是描述符给我们带来的编码上的便利,它在实现 -``保护属性不受修改``\ 、\ ``属性类型检查`` -的基本功能,同时有大大提高代码的复用率。 + # python3 --------------- - -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 + # python2 - @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`` 特性。 + >>> 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 + # python2 - 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) + >>> 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 呢? -|image3| +**有两种方法** -调用这个方法可以知道,每调用一次,它都会经过描述符类的 ``__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 + '中文' + >>> + +5. 如何设置文件编码 +------------------- - def __get__(self, instance, owner=None): - print("in classmethod __get__") - - def newfunc(*args): - return self.f(owner, *args) - return newfunc +在 Python 2 中,默认使用的是 ASCII 编码来读取的,因此,我们在使用 Python +2 的时候,如果你的 python 文件里有中文,运行是会报错的。 - class Test: - def myfunc(cls): - print("hello") - - # 重点:这就是描述符的体现 - myfunc = classmethod(myfunc) +:: -验证结果如下 + SyntaxError: Non-ASCII character '\xe4' in file demo.py -.. code:: python +原因就是 ASCII 编码表太小,无法解释中文。 - >>> Test.myfunc() - in classmethod __get__ - hello - >>> Test().myfunc() - in classmethod __get__ - hello +而在 Python 3 中,默认使用的是 uft-8 来读取,所以省了不少的事。 -讲完了 ``property``\ 、\ ``staticmethod``\ 和\ ``classmethod`` 与 -描述符的关系。我想你应该对描述符在 Python 中的应用有了更深的理解。对于 -super 的实现原理,就交由你来自己完成。 +对于这个问题,通常解决方法有两种: -1.17.5 所有实例共享描述符 -------------------------- +**第一种方法** -若要合理使用描述符,利用描述符给我们带来的编码上的便利。有一个坑,需要注意,比如下面这个Student我们没有定义构造函数 +在 python2 中,可以使用在头部指定 -.. code:: python +可以这样写,虽然很好看 - class Score: - def __init__(self, default=0): - self._value = default +:: - def __get__(self, instance, owner): - return self._value + # -*- 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) + # coding:utf-8 + # coding=utf-8 - def __repr__(self): - return "".format(self.math, self.chinese, self.english) +**第二种方法** -看一下会出现什么样的问题,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 `__ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image3| -.. |image0| image:: http://image.python-online.cn/20190425221322.png -.. |image1| image:: http://image.python-online.cn/20190425221322.png -.. |image2| image:: http://image.python-online.cn/20190425221233.png -.. |image3| image:: http://image.python-online.cn/20190519001930.png +.. |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_18.md b/source/c01/c01_18.md index 1a63ec8..0a20d4b 100644 --- a/source/c01/c01_18.md +++ b/source/c01/c01_18.md @@ -1,6 +1,8 @@ # 1.18 MySQL 使用总结 -## 1.18.1 安装MySQL-python +![](http://image.iswbm.com/20200602135014.png) + +## 1. 安装MySQL-python MySQL-python 这玩意实在是太难装了,为了以防后面再踩坑,这里还是记录一下吧。 @@ -48,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 成功后,又陷入了一个坑。。 @@ -98,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登陆(安全起见我选不允许)。 @@ -110,7 +112,7 @@ mysql -uroot -p -## 1.18.3 Win上忘记密码 +## 3. Win上忘记密码 ```shell @@ -138,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 0ba5e6b..add8a60 100644 --- a/source/c01/c01_18.rst +++ b/source/c01/c01_18.rst @@ -1,8 +1,10 @@ 1.18 MySQL 使用总结 =================== -1.18.1 安装MySQL-python ------------------------ +|image0| + +1. 安装MySQL-python +------------------- MySQL-python 这玩意实在是太难装了,为了以防后面再踩坑,这里还是记录一下吧。 @@ -58,31 +60,31 @@ mysql,这时也请将其卸载再重新安装吧。 经过漫长的等待后,mysql 终于安装成功 -|image0| +|image1| 这时候,再 执行 pip install MySQL-python,发现还是报错。 -|image1| +|image2| 有经验的我,立马知道了 ``mysql_config`` 这个文件的路径可能没有在环境变量中。 -|image2| +|image3| 然后,我又重新执行 ``pip install MySQL-python`` ,发现还是报错。 -|image3| +|image4| 但是这个错误相对比较明显,明眼人一看就知道是权限不足。 那我就以 root 权限去安装好了。 -|image4| +|image5| 终于安装成功,折腾了两个晚上(主要是网速慢)。 -1.18.2 Mac 启动MySQL服务 ------------------------- +2. Mac 启动MySQL服务 +-------------------- 使用 brew 安装 mysql 成功后,又陷入了一个坑。。 @@ -111,7 +113,7 @@ mysql,这时也请将其卸载再重新安装吧。 选择密码强度,视情况而写,我这边选最强的,长度大于8,有数字,有大小写,有特殊字符。 -|image5| +|image6| 接下来还会问你,是否删除其他匿名用户,是否删除 test 数据库,是否允许远程使用root登陆(安全起见我选不允许)。 @@ -122,8 +124,8 @@ mysql,这时也请将其卸载再重新安装吧。 mysql -uroot -p -1.18.3 Win上忘记密码 --------------------- +3. Win上忘记密码 +---------------- .. code:: shell @@ -149,16 +151,17 @@ mysql,这时也请将其卸载再重新安装吧。 # 再重新登陆,用新的密码登陆,发现可以生效 mysql -uroot -p -1.18.4 命令行使用技巧 ---------------------- +4. 命令行使用技巧 +----------------- -|image6| +|image7| -.. |image0| image:: http://image.python-online.cn/20190615001340.png -.. |image1| image:: http://image.python-online.cn/20190615001414.png -.. |image2| image:: http://image.python-online.cn/20190615001633.png -.. |image3| image:: http://image.python-online.cn/20190615001706.png -.. |image4| image:: http://image.python-online.cn/20190615001908.png -.. |image5| image:: http://image.python-online.cn/20190615112422.png -.. |image6| image:: http://image.python-online.cn/20190705225651.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 87% rename from source/c01/c01_33.md rename to source/c01/c01_19.md index e6f4756..8d3745b 100644 --- a/source/c01/c01_33.md +++ b/source/c01/c01_19.md @@ -1,4 +1,6 @@ -# 1.33 如何调试已经运行中的程序 +# 1.19 如何调试已经运行中的程序 + +![](http://image.iswbm.com/20200602135014.png) 官方原始wiki:https://wiki.python.org/moin/DebuggingWithGdb @@ -46,7 +48,7 @@ sudo yum install gdb python-debuginfo -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c01/c01_33.rst b/source/c01/c01_19.rst similarity index 84% rename from source/c01/c01_33.rst rename to source/c01/c01_19.rst index bfe6e11..65fc111 100644 --- a/source/c01/c01_33.rst +++ b/source/c01/c01_19.rst @@ -1,6 +1,8 @@ -1.33 如何调试已经运行中的程序 +1.19 如何调试已经运行中的程序 ============================= +|image0| + 官方原始wiki:https://wiki.python.org/moin/DebuggingWithGdb 在CentOS 下,安装包过程,官方给的不够详细。这里记录一下 @@ -44,7 +46,8 @@ sudo yum install gdb python-debuginfo -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c01/c01_20.md b/source/c01/c01_20.md index f70e790..1e9fde1 100644 --- a/source/c01/c01_20.md +++ b/source/c01/c01_20.md @@ -1,100 +1,39 @@ -# 1.20 静态方法其实暗藏玄机 +# 1.20 在 CentOS 7.2 上安装 Python3.7 -这个标题「**静态方法其实暗藏玄机**」其实只是该文章的一个知识点。或许有些标题党,但没有关系,我相信有不少人对此并没有深入研究他们,不信我问你三个问题,你看能否答上来。 +![](http://image.iswbm.com/20200602135014.png) -1、Python2.x和3.x中,函数和方法的区分有什么不同? +首先下载 python3.7的源码包,然后解压 -2、有了类/实例方法和普通函数,为什么还会有静态方法? +```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 +``` -3、Python3.x 中,静态方法有几种写法? +安装 一些依赖包 -带着这三个问题,你可以尝试在下文中寻找答案。 +```shell +$ yum install gcc zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel libffi-devel python3-devel -y +``` ---- +编译安装 -在 Python 2 中的函数和方法的区别,十分清晰,很好分辨。但在 Python3中,我却发现完全又是另一套准则。 +```shell +$ ./configure +$ make +$ sudo make install +``` -首先先来 Python2 的(以下在 Python2.7中测试通过) +至此,你已经成功安装 了 Python3, pip3,setuptools -![](http://image.python-online.cn/20190630111243.png) +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 +``` -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.python-online.cn/image-20200320125724880.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 08c279b..843d931 100644 --- a/source/c01/c01_20.rst +++ b/source/c01/c01_20.rst @@ -1,117 +1,40 @@ -1.20 静态方法其实暗藏玄机 -========================= - -这个标题「\ **静态方法其实暗藏玄机**\ 」其实只是该文章的一个知识点。或许有些标题党,但没有关系,我相信有不少人对此并没有深入研究他们,不信我问你三个问题,你看能否答上来。 - -1、Python2.x和3.x中,函数和方法的区分有什么不同? - -2、有了类/实例方法和普通函数,为什么还会有静态方法? - -3、Python3.x 中,静态方法有几种写法? - -带着这三个问题,你可以尝试在下文中寻找答案。 - --------------- - -在 Python 2 中的函数和方法的区别,十分清晰,很好分辨。但在 -Python3中,我却发现完全又是另一套准则。 - -首先先来 Python2 的(以下在 Python2.7中测试通过) +1.20 在 CentOS 7.2 上安装 Python3.7 +=================================== |image0| -可以得出结论: - -1、普通函数(未定位在类里),都是函数。 +首先下载 python3.7的源码包,然后解压 -2、静态方法(@staticmethod),都是函数。 +.. code:: shell -3、类方法(@classmethod),都是方法。 + $ 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 -4、实例方法(首参数为self且非静态、非类方法的),都是方法。 +安装 一些依赖包 -你一定想说,类方法和实例方法,是方法没错呀,毕竟名字本身就有方法,普通函数是函数,我也理解呀。那静态方法,为什么不是方法而是函数呢? +.. code:: shell -名字只是一个外在的表面称呼,你能说「赵铁男」就一定是汉子吗? + $ yum install gcc zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel libffi-devel python3-devel -y -我的理解是:方法是一种和对象(实例或者类)绑定后的函数。 +编译安装 -类方法的首参是\ ``cls``\ ,调用时,无需显示传入。实例方法首参是self,调用时,也无需显示传入。 +.. code:: shell -而静态方法,其实和普通函数没啥区别,唯一的区别就是,他定义的位置被放在了类里,做为类或者实例的一个函数。 + $ ./configure + $ make + $ sudo make install -那你肯定又要问了,既然静态方法和普通函数都是一样的,为什么要刻意将它放在类里呢? +至此,你已经成功安装 了 Python3, pip3,setuptools -我上面说了,放在类里定义,就可以让它成为类的一个工具函数,这就像你身上随身携带了一把刀,这把刀与你本人并没有什么直接关系,唯一的联系就是这把刀是你的,而你把它带在身上,无论你去到哪里,只要需要,你就可以直接拿出来用上。对比将函数放在类外面,缺点是什么呢?就是当你出门在外(在别的模块里)发现你要用刀的时候,还要特地跑一趟去商店买一把刀(import -这个函数)。 +requests.get(“https://www.baidu.com”) -另外,我觉得静态方法在业务和设计上的意义,会更多一些。 +:: -一般静态方法是做为类或者实例的一个工具函数,比如对变量的做一个合法性的检验,对数据进行序列化反序列化操作等等。 - -说完了 Python2 ,再来说说Python3. - -以前我觉得 Python2 对于方法和函数的界线更加清晰,但接触了 -Python3,我反而觉得Python3里方法和函数的区分似乎更加合理。 - -还是刚刚那段代码,我更改了解释器为Python3.6(以下在 -Python3.6中测试通过) + python3 -m pip install --user requests aiohttp cryptography pymysql prettytable sh Fabric paramiko apscheduler bashplotlib httpie PathPicker -i https://pypi.douban.com/simple |image1| -和Python2的唯一区别是,\ ``People.jump`` 在Python3 中变成了函数。 - -这一下颠覆了你刚刚才建立起来的知识体系有木有? - -先别急,我再做个试验,也许你就知道了。 - -**在 Python2中** - -执行People.jump(‘hello’),会报错说,jump的首参必须为People的实例对象,这可以理解,毕竟jump定义时,第一个参数为self。 - -|image2| - -**在 Python3中** - -你可以发现,这里的jump的首参不再要求是 People -的一个实例,而可以是任意的对象,比如我使用字符串对象,也没有报错。 - -|image3| - -也就是说,当你往jump中传入的首参为People的实例时,jump -就是方法,而当你传入的首参不是People的实例对象时,jump就是函数。 - -你看,多么灵活呀。 - -再总结一下,在Python3中: - -1、普通函数(未定位在类里),都是函数。 - -2、静态方法(@staticmethod),都是函数。 - -3、类方法(@classmethod),都是方法。 - -4、方法和函数区分没有那么明确,而是更加灵活了,一个函数有可能是方法也有可能是函数。 - -你肯定又要问了,那这是不是就意味着,Python3 -中静态方法,可以不用再使用@staticmethod -装饰了呢,反正Python3都可以识别。 - -这是个好问题,是的,可以不用指定,但是最好指定,如果你不指定,你调用这个方法只能通过People.jump,而不能通过 -self.jump了,因为首参不是 self,而如果使用@staticmethod -就可以使用self.jump。 - -所以说这是一个规范,就像类的私有方法,规范要求外部最好不要调用,但这不是强制要求,不是说外部就不能调用。 - -写这篇文章的起源,是前两天有位读者在交流里问到了相关的问题,正好没什么主题可以写,就拿过来做为素材整理一下,也正好没有写过静态方法、类方法的内容,没想到简单的东西,也能写出这么多的内容出来。 - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190630111243.png -.. |image1| image:: http://image.python-online.cn/20190630104956.png -.. |image2| image:: http://image.python-online.cn/20190630105735.png -.. |image3| image:: http://image.python-online.cn/20190630105600.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_21.md b/source/c01/c01_21.md index 38d4e7c..cf1190c 100644 --- a/source/c01/c01_21.md +++ b/source/c01/c01_21.md @@ -1,86 +1,216 @@ -# 1.21 开发小技巧 +# 1.21 Python 炫技操作:连接列表的八种方法 -## 1. 解决网页鼠标限制 +![](http://image.iswbm.com/20200602135014.png) -``` -解决网页不能选中,在console中输入:document.onselectstart=true -解决网页不能复制,在console中输入:document.oncopy=true -解决网页不能右键,在console中输入:document.oncontextmenu=true -``` +Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 -## 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.python-online.cn/image-20200320125724880.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 aab9498..8302f52 100644 --- a/source/c01/c01_21.rst +++ b/source/c01/c01_21.rst @@ -1,95 +1,233 @@ -1.21 开发小技巧 -=============== +1.21 Python 炫技操作:连接列表的八种方法 +======================================== -1. 解决网页鼠标限制 -------------------- +|image0| -:: +Python 语言里有许多(而且是越来越多)的高级特性,是 Python +发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 - 解决网页不能选中,在console中输入:document.onselectstart=true - 解决网页不能复制,在console中输入:document.oncopy=true - 解决网页不能右键,在console中输入:document.oncontextmenu=true +但你要知道,在团队合作里,炫技是大忌。 -2. 在 linux 上看 json 文件 --------------------------- +为什么这么说呢?我说下自己的看法: -:: +1. 越简洁的代码,越清晰的逻辑,就越不容易出错; +2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 +3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) - cat test.json | python -m json.tool +该篇是「\ **炫技系列**\ 」的第三篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 +Python +发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 -3. 在 Mac 上多开微信 --------------------- +1. 最直观的相加 +--------------- -:: +使用 ``+`` 对多个列表进行相加,你应该懂,不多说了。 - open -n /Applications/WeChat.app +.. code:: python -3. 微信测试是否被删除 ---------------------- + >>> 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 +----------------- - 1. 转账(不是好友不能点) - 2. 拉群(不说话不会提示) +itertools 在 Python +里有一个非常强大的内置模块,它专门用于操作可迭代对象。 -4. Mac 启动台 app 大小调整 --------------------------- +在前面的文章中也介绍过,使用 ``itertools.chain()`` +函数先可迭代对象(在这里指的是列表)串联起来,组成一个更大的可迭代对象。 -:: +最后你再利用 list 将其转化为 列表。 - 1.终端运行命令:10代表一行显示10个图标,几个可以自定义 - defaults write com.apple.dock springboard-columns -int 10 +.. code:: python - 2.设置完需要重新启动一下 启动台 - killall Dock + >>> 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] + >>> -5. 重定向标准输出到文件 ------------------------ +3. 使用 \* 解包 +--------------- -:: +在 `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 +示例如下: - yield +.. code:: python - sys.stdout = raw_stdout - file.close() - - with close_stdout(): - unshelve_task() + >>> list01 = [1,2,3] + >>> list02 = [4,5,6] + >>> + >>> [*list01, *list02] + [1, 2, 3, 4, 5, 6] + >>> -6. 将子网掩码转换为cidr ------------------------ +4. 使用 extend +-------------- -如何使用netaddr库将ipv4子网掩码转换为cidr表示法? 示例:255.255.255.0到/ -24 +在字典中,使用 update 可实现原地更新,而在列表中,使用 extend +可实现列表的自我扩展。 -使用netaddr: +.. 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 - >>> from netaddr import IPAddress - >>> IPAddress("255.255.255.0").netmask_bits() - 24 + >>> 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] + >>> -您也可以在不使用任何库的情况下执行此操作,只需在网络掩码的二进制表示中计算1位: +它的效果等价于下面这行代码: + +.. code:: python + + sorted(itertools.chain(*iterables)) + +如果你希望得到一个始终有序的列表,那请第一时间想到 +heapq.merge,因为它采用堆排序,效率非常高。但若你不希望得到一个排过序的列表,就不要使用它了。 + +7. 借助魔法方法 +--------------- + +在之前的文章里,把魔法方法介绍得很全。 + +`非常全的通俗易懂 Python +魔法方法指南(上) `__ + +`非常全的通俗易懂 Python +魔法方法指南(下) `__ + +其中有一个魔法方法是 ``__add__``\ ,实际 上当我们使用第一种方法 list01 + +list02 的时候,内部实际上是作用在 ``__add__`` 这个魔法方法上的. + +所以以下两种方法其实是等价的 :: - >>> 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| -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c01/c01_22.md b/source/c01/c01_22.md index 448951a..39cd84d 100644 --- a/source/c01/c01_22.md +++ b/source/c01/c01_22.md @@ -1,191 +1,175 @@ -# 1.22 如何修改 CentOS 6.x 上默认Python +# 1.22 Python 炫技操作:海象运算符的三种用法 -最近在工作中遇到一个问题,就是有一个功能希望在各种服务器上实现,而服务器上的系统版本可能都不一样,有的是 CentOS 6.x,有的是 CentOS 7.x 。 +![](http://image.iswbm.com/20200602135014.png) -需要说明的一点是,CentOS 6.x 上的 Python 版本是 2.6.x 的,而 CentOS 7.x 上的 Python 版本是 2.7.x 的,这意味着我要实现的功能要适配这两种版本的系统。 +Python 版本发展非常快,如今最新的版本已经是 Pyhton 3.9,即便如此,有很多人甚至还停留在 3.6 或者 3.7,连 3.8 还没用上。 -你可能会说,这有什么的,自己写的时候,注意一下就好了。 +很多 Python 3.8 的特性还没来得及了解,就已经成为旧知识了,比如今天要说的海象运算符。 -事情其实没有那么容易,我要实现的功能是基于一个框架进行定制,需要修改不少的框架代码。这个框架在不同的 Linux 版本上,是有不同的版本的,而且差异巨大,曾经想过在 CentOS 6.x 和 CentOS 7.x 将这个框架安装成同一个版本,最后还是失败了,无法安装,原因就是高版本需要 Python2.7,而 CentOS 6.x 上只有 Python2.6。 +海象运算符是在 PEP 572 被提出的,直到 3.8 版本合入发布。 -这个历史问题一直遗留到现在,由于这次的功能影响到的代码较多,如果要对两个版本的框架分别进行定制的话,需要花不少的时间,为了不维护两套版本,避免浪费多余的精力去做适配,我决定将 CentOS 6.x 上默认的 Python2.6 升级成 Python2.7。 +它的英文原名叫 `Assignment Expressions`,翻译过来也就是 `赋值表达式`,不过现在大家更普遍地称之为海象运算符,就是因为它长得真的太像海象了。 -下面是整个升级过程,别看步骤简单,这些精简步骤的背后可是有不少的坑,被我踩过后,你可以直接使用了。 +![](http://image.iswbm.com/image-20200418122739417.png) +## 1. 第一个用法:if/else +可能有朋友是第一次接触这个新特性,所以还是简单的介绍一下这个海象运算符有什么用? +在 Golang 中的条件语句可以直接在 if 中运算变量的获取后直接对这个变量进行判断,可以让你少写一行代码 -1. 首先确认下你机器上的默认的 Python 版本 +```go +import "fmt" -```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 循环来读取文件的时候,你也许会这么写 +```python +file = open("demo.txt", "r") +while True: + line = file.readline() + if not line: + break + print(line.strip()) ``` -$ 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. 配置,编译,安装 - -```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.python-online.cn/image-20200320125724880.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 80b32d3..177b431 100644 --- a/source/c01/c01_22.rst +++ b/source/c01/c01_22.rst @@ -1,206 +1,183 @@ -1.22 如何修改 CentOS 6.x 上默认Python -===================================== +1.22 Python 炫技操作:海象运算符的三种用法 +========================================== -最近在工作中遇到一个问题,就是有一个功能希望在各种服务器上实现,而服务器上的系统版本可能都不一样,有的是 -CentOS 6.x,有的是 CentOS 7.x 。 - -需要说明的一点是,CentOS 6.x 上的 Python 版本是 2.6.x 的,而 CentOS 7.x -上的 Python 版本是 2.7.x -的,这意味着我要实现的功能要适配这两种版本的系统。 - -你可能会说,这有什么的,自己写的时候,注意一下就好了。 - -事情其实没有那么容易,我要实现的功能是基于一个框架进行定制,需要修改不少的框架代码。这个框架在不同的 -Linux 版本上,是有不同的版本的,而且差异巨大,曾经想过在 CentOS 6.x 和 -CentOS 7.x -将这个框架安装成同一个版本,最后还是失败了,无法安装,原因就是高版本需要 -Python2.7,而 CentOS 6.x 上只有 Python2.6。 - -这个历史问题一直遗留到现在,由于这次的功能影响到的代码较多,如果要对两个版本的框架分别进行定制的话,需要花不少的时间,为了不维护两套版本,避免浪费多余的精力去做适配,我决定将 -CentOS 6.x 上默认的 Python2.6 升级成 Python2.7。 +|image0| -下面是整个升级过程,别看步骤简单,这些精简步骤的背后可是有不少的坑,被我踩过后,你可以直接使用了。 +Python 版本发展非常快,如今最新的版本已经是 Pyhton +3.9,即便如此,有很多人甚至还停留在 3.6 或者 3.7,连 3.8 还没用上。 -1. 首先确认下你机器上的默认的 Python 版本 +很多 Python 3.8 +的特性还没来得及了解,就已经成为旧知识了,比如今天要说的海象运算符。 -.. code:: shell +海象运算符是在 PEP 572 被提出的,直到 3.8 版本合入发布。 - $ python -V - Python 2.6.6 +它的英文原名叫 ``Assignment Expressions``\ ,翻译过来也就是 +``赋值表达式``\ ,不过现在大家更普遍地称之为海象运算符,就是因为它长得真的太像海象了。 - $ 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 +|image1| -2. 由于我们将使用编译安装的方式,所以要安装下 gcc,及一些工具包。 +1. 第一个用法:if/else +---------------------- -注意一定要全部安装,不然后面会发现有不少 python 的工具用不了。 +可能有朋友是第一次接触这个新特性,所以还是简单的介绍一下这个海象运算符有什么用? -比如不安装 zlib 会无法安装 setuptools,不装 openssl 和 -openssl-devel,会无法使用 pip 工具等 +在 Golang 中的条件语句可以直接在 if +中运算变量的获取后直接对这个变量进行判断,可以让你少写一行代码 -:: +.. code:: go - $ yum install gcc -y - $ yum groupinstall "Development tools" - $ yum install zlib-devel bzip2-devel openssl openssl-devel ncurses-devel sqlite-devel -y + import "fmt" -如果你这里未按照我的步骤来安装,你后面使用的时候出现了各种各样的问题,不要慌,只要再回来这里,把没安装的包装上,安装完成后,你需要进入第四步重新编译安装Python。 + func main() { + if age := 20;age > 18 { + fmt.Println("已经成年了") + } + } -3. 下载最新的 Python2.7.x 安装包,解压并进入指定目录 +若在 Python 3.8 之前,Python 必须得这样子写 -:: +.. 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 - - # 下载 setuptools - $ wget https://pypi.python.org/packages/ff/d4/209f4939c49e31f5524fa0027bf1c8ec3107abaf7c61fdaad704a648c281/setuptools-21.0.0.tar.gz#md5=81964fdb89534118707742e6d1a1ddb4 + 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 -.. code:: shell + def get_bmi(info): + global count + count += 1 - $ tar vxf setuptools-21.0.0.tar.gz - $ cd setuptools-21.0.0 - $ python setup.py install + print(f"执行了 {count} 次") -安装完成后,下载pip。其信息在如下网站:https://pypi.python.org/pypi/pip + height = info["height"] + weight = info["weight"] -.. code:: shell + return weight / (height**2) - # 下载 pip - wget https://pypi.python.org/packages/41/27/9a8d24e1b55bd8c85e4d022da2922cb206f183e2d18fee4e320c9547e751/pip-8.1.1.tar.gz#md5=6b86f11841e89c8241d689956ba99ed7 + # 查出所有会员中过于肥胖的人的 bmi 指数 + fat_bmis = [get_bmi(m) for m in members if get_bmi(m) > 24] -同样的,进行安装 + print(fat_bmis) -.. code:: shell +输出如下 - $ tar vxf pip-8.1.1.tar.gz - $ cd pip-8.1.1 - $ python setup.py install +:: -安装完成后,执行 ``pip list`` -查看一下安装的包,若无异常,则一切顺利。或者你也可以试着安装一下第三方包 -``pip install requests`` 。 + 执行了 1 次 + 执行了 2 次 + 执行了 3 次 + 执行了 4 次 + [25.88057063502083] -8. 转移cloudinit +可以看到,会员数只有 3 个,但是 get_bmi 函数却执行了 4 +次,原因是在判断时执行了 3 次,而在构造新的列表时又重复执行了一遍。 -上面说的项目,其实就是 cloudinit。接下来就要将 centos 7.2 上的cloudinit -的目录整体拷贝到 centos 6.5 的/usr/local/lib/python2.7/site-packages/ -目录下 +如果所有会员都是过于肥胖的,那最终将执行 6 +次,这种在大量的数据下是比较浪费性能的,因此对于这种结构,我通常会使用传统的for +循环 + if 判断。 -|image0| +.. code:: python -然后安装一些 cloudinit 的依赖包。 + fat_bmis = [] -.. code:: shell + # 查出所有会员中过于肥胖的人的 bmi 指数 + for m in members: + bmi = get_bmi(m) + if bmi > 24: + fat_bmis.append(bmi) - $ pip install six requests prettytable jsonpatch configobj +在有了海象运算符之后,你就可以不用在这种场景下做出妥协。 - # 默认还是安装在 python2.6 下 - $ yum install PyYAML -y +.. code:: python - # 将这些文件拷贝到 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/ + # 查出所有会员中过于肥胖的人的 bmi 指数 + fat_bmis = [bmi for m in members if (bmi := get_bmi(m)) > 24] -执行一下 cloudinit 的几个命令,没有问题,任务就完成了。 +最终从输出结果可以看出,只执行了 3 次 -.. code:: shell +:: - $ cloud-init init -l - $ cloud-init init + 执行了 1 次 + 执行了 2 次 + 执行了 3 次 + [25.88057063502083] -**参考文章** +这里仅介绍了列表推导式,但在字典推导式和集合推导式中同样适用。不再演示。 -- https://www.cnblogs.com/stonehe/p/7944366.html +海象运算符,是一个新奇的特性,有不少人觉得这样这种特性会破坏代码的可读性。确实在一个新鲜事物刚出来时是会这样,但我相信经过时间的沉淀后,越来越多的人使用它并享受它带来的便利时,这种争议也会慢慢消失在历史的长河中。 -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +-------------- - 关注公众号,获取最新干货! +|image2| -.. |image0| image:: http://image.python-online.cn/20190831160317.png +.. |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_23.md b/source/c01/c01_23.md index 82e6a09..7142677 100644 --- a/source/c01/c01_23.md +++ b/source/c01/c01_23.md @@ -1,126 +1,142 @@ -# 1.23 Pythonista 学习 Js - -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.python-online.cn/image-20200320125724880.png) \ No newline at end of file +# 1.23 Python 炫技操作:模块重载的五种方法 + +![](http://image.iswbm.com/20200602135014.png) + +## 环境准备 + +新建一个 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 19f5b35..c2f08a9 100644 --- a/source/c01/c01_23.rst +++ b/source/c01/c01_23.rst @@ -1,124 +1,156 @@ -1.23 Pythonista 学习 Js -======================= +1.23 Python 炫技操作:模块重载的五种方法 +======================================== -1. JavaScript的设计者希望用\ ``null``\ 表示一个空的值,而\ ``undefined``\ 表示值未定义。事实证明,这并没有什么卵用,区分两者的意义不大。大多数情况下,我们都应该用\ ``null``\ 。\ ``undefined``\ 仅仅在判断函数参数是否传递的情况下有用。 +|image0| -2. 在JavaScript中,使用等号\ ``=``\ 对变量进行赋值。可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量,但是要注意只能用\ ``var``\ 申明一次。 +环境准备 +-------- -3. var 申明的变量不是全局变量,如果不使用 var - 申明变量,就会变成全局变量,多个js文件会共享这个变量,互相影响。这个语言设计弊端,在 - ECMA 推出 strict 模式后得到解决,不使用 var 申明变量的会运行出错。 +新建一个 foo 文件夹,其下包含一个 bar.py 文件 -4. 直接操作数组的长度,或者对超出数组长度的索引赋值,可以扩容和压缩数组的大小。 +:: -5. 对象之间的比较和Python差异巨大,\ ``=`` 表示值的比较,\ ``===`` - 表示对象类型的比较,主要分为下面三种情况: + $ tree foo + foo + └── bar.py - .. code:: javascript + 0 directories, 1 file - // 1. 对于string,number等【基础类型】,==和===是有区别的。 - // 如果是不同类型,== 会将两边转化成同一类型再看值是否相等 - // 如果是相同类型,直接对值进行比较。 - alert('1'==1);//结果是true - alert('1'===1);//结果是false +bar.py 的内容非常简单,只写了个 print 语句 +:: - // 2. 如果是 Array,Object等高级类型,==和===是没有区别的。 - // 直接比较指针地址。,跟 Python 里的 is 等同。 + print("successful to be imported") +只要 bar.py 被导入一次,就被执行一次 print - // 3. 基础类型与高级类型,==和===是有区别的。 - // 对于==,将高级转化为基础类型,进行“值”比较。 - // 因为类型不同,===结果为false。 - var a = new String('1');//定义一个string的高级类型 - var b = '1';//定一个基础类型字符串 - alert(b==a);//为true - alert(b===a);//为false +禁止重复导入 +------------ -6. 定义一个函数,其参数个数可以少于传入的参数个数,基于可以不定义参数,还可以传入参数。那传入的多余的参数如何获取呢?可以使用 - arguments 这个变量取得所有的参数,但这个变量仅在参数内起作用。 +‘由于有 sys.modules +的存在,当你导入一个已导入的模块时,实际上是没有效果的。’ - .. code:: python +:: - function foo(a, b, ...rest) { - console.log('a = ' + a); - console.log('b = ' + b); - console.log(rest); - } + >>> from foo import bar + successful to be imported + >>> from foo import bar + >>> -7. JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部,但是并不会提前为变量赋值。 +重复导入方法一 +-------------- - .. code:: javascript +如果你使用的 python2(记得前面在 foo 文件夹下加一个 +``__init__.py``\ ),有一个 reload 的方法可以直接使用 - 'use strict'; +:: - function foo() { - var x = 'Hello, ' + y; - console.log(x); - var y = 'Bob'; - } + >>> from foo import bar + successful to be imported + >>> from foo import bar + >>> + >>> reload(bar) + successful to be imported + - foo(); +如果你使用的 python3 那方法就多了,详细请看下面 - // 对于 javascript 引擎,看到代码相当于 - function foo() { - var y; // 提升变量y的申明,此时y为undefined - var x = 'Hello, ' + y; - console.log(x); - y = 'Bob'; - } +重复导入方法二 +-------------- -8. This有个巨坑,需要新手注意。 +如果你使用 Python3.0 -> 3.3,那么可以使用 imp.reload 方法 - 如下,当没有 ``var that = this;`` - 这句时,函数内部的函数里的this指向的是 window 对象。 +:: - .. code:: javascript + >>> from foo import bar + successful to be imported + >>> from foo import bar + >>> + >>> import imp + >>> imp.reload(bar) + successful to be imported + - 'use strict'; +但是这个方法在 Python 3.4+,就不推荐使用了 - 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 + :1: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses - 或者,可以用 apply 的方法来解决这个问题,apply - 的第一个参数是人该函数要绑定的对象,第二个参数是一个列表,装的是要传递给这个函数据参数。 +重复导入方法三 +-------------- - .. code:: javascript +如果你使用的 Python 3.4+,请使用 importlib.reload 方法 - function getAge() { - var y = new Date().getFullYear(); - return y - this.birth; - } +:: - var xiaoming = { - name: '小明', - birth: 1990, - age: getAge - }; + >>> from foo import bar + successful to be imported + >>> from foo import bar + >>> + >>> import importlib + >>> importlib.reload(bar) + successful to be imported + - xiaoming.age(); // 29 - getAge.apply(xiaoming, []); // 29, this指向xiaoming, 参数为空 +重复导入方法四 +-------------- - 假如,不想绑定给任何对象,第一个参数可以用 null。如 +如果你对包的加载器有所了解(详细可以翻阅我以前写的文章:https://iswbm.com/84.html) - .. code:: javascript +还可以使用下面的方法 - Math.max.apply(null, [3, 5, 4]); // 5 - Math.max.call(null, 3, 5, 4); // 5 +:: -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! + >>> 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 + >>> + +.. |image0| image:: http://image.iswbm.com/20200602135014.png - 关注公众号,获取最新干货! diff --git a/source/c01/c01_24.md b/source/c01/c01_24.md index dc08028..ab429b8 100644 --- a/source/c01/c01_24.md +++ b/source/c01/c01_24.md @@ -1,723 +1,148 @@ -# 1.24 深入探讨 Python 的 import 机制:实现远程导入模块 +# 1.24 Python 炫技操作:条件语句的七种写法 -所谓的模块导入( `import` ),是指在一个模块中使用另一个模块的代码的操作,它有利于代码的复用。 +![](http://image.iswbm.com/20200602135014.png) -在 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 功力。 ```python -# 使用 import -import os - -# 使用 __import__ -os = __import__('os') -``` - -通过举一反三,下面两种方法同样也是等价的。 - -```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.python-online.cn/image-20200320125724880.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 eca008a..4fa41c6 100644 --- a/source/c01/c01_24.rst +++ b/source/c01/c01_24.rst @@ -1,802 +1,165 @@ -1.24 深入探讨 Python 的 import 机制:实现远程导入模块 -===================================================== - -所谓的模块导入( ``import`` -),是指在一个模块中使用另一个模块的代码的操作,它有利于代码的复用。 - -在 Python 中使用 import 关键字来实现这个操作,但不是唯一的方法,还有 -``importlib.import_module()`` 和 ``__import__()`` 等。 - -也许你看到这个标题,会说我怎么会发这么基础的文章? - -与此相反。恰恰我觉得这篇文章的内容可以算是 Python -的进阶技能,会深入地探讨并以真实案例讲解 Python import Hook 的知识点。 - -当然为了使文章更系统、全面,前面会有小篇幅讲解基础知识点,但请你有耐心的往后读下去,因为后面才是本篇文章的精华所在,希望你不要错过。 +1.24 Python 炫技操作:条件语句的七种写法 +======================================== |image0| -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 + if age > 18: + return "已成年" + else: + return "未成年" - # bad - from ..bar import Bar +下面我列举了六种这段代码的变异写法,一个比一个还 6 +,单独拿出来比较好理解,放在工程代码里,没用过这些学法的人,一定会看得一脸懵逼,理解了之后,又不经意大呼:\ **卧槽,还可以这样写?**\ ,而后就要开始骂街了:\ **这是给人看的代码?** +(除了第一种之外) - # 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__`` -的使用会更加灵活,常常用于框架中,对于插件的动态加载。 + if else -实际上,当我们调用 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 + >>> 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`` -里是否已经载入这个模块了,如果已经载入,则不会再次导入,如果不存在,才会去检索导入这个模块。 - -来实验一下,在 ``my_mod02`` 这个模块里,我 import 两次 ``my_mod01`` -这个模块,按逻辑每一次 import 会一次 ``my_mod01`` 里的代码(即打印 -``in mod01``\ ),但是验证结果是,只打印了一次。 + and or -.. 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 + (, )[condition] - 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 - [] + >>> 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' +第四种 +------ -另一个是: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异常。 - -因此要实现远程导入模块,有两种思路。 + (lambda: , lambda:)[]() -- 一种是实现自己的元路径导入器; -- 另一种是编写一个钩子,添加到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 +以上代码,都比较简单,仔细看都能看懂,我就不做解释了。 -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +看到这里,有没有涨姿势了,学了这么久的 Python +,这么多骚操作,还真是活久见。。这六种写法里,我最推荐使用的是第一种,自己也经常在用,简洁直白,代码行还少。而其他的写法虽然能写,但是不会用,也不希望在我余生里碰到会在公共代码里用这些写法的同事。 - 关注公众号,获取最新干货! +|image1| -.. |image0| image:: http://image.python-online.cn/20191027192949.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c01/c01_25.md b/source/c01/c01_25.md index dddcda6..428535f 100644 --- a/source/c01/c01_25.md +++ b/source/c01/c01_25.md @@ -1,109 +1,290 @@ -# 1.25 50% 的人不知道的Python 包与模块的知识盲区 +# 1.25 Python炫技操作:花式导包的八种方法 -## 1. 使用 \__all__ 控制可被导入的变量 +![](http://image.iswbm.com/20200602135014.png) -使用 `from module import *` 默认情况下会导入 module 里的所有变量,若你只想从模块中导入其中几个变量,可以在 module 中使用 `__all__` 来控制想要被其他模块导入的变量。 -```python -# profile.py -name='wangbm' -age=27 -gender='male' -__all__=['name'] +## 1. 直接 import + +人尽皆知的方法,直接导入即可 + +```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 importlib +>>> myos=importlib.import_module("os") +>>> myos.getcwd() +'/home/wangbm' +``` + +如果要实现 `import xx as yy`效果,可以这样 ```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' ``` -## 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 是非常有必要的。 -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_25.rst b/source/c01/c01_25.rst index 846efa9..90d9581 100644 --- a/source/c01/c01_25.rst +++ b/source/c01/c01_25.rst @@ -1,111 +1,309 @@ -1.25 50% 的人不知道的Python 包与模块的知识盲区 -============================================== +1.25 Python炫技操作:花式导包的八种方法 +======================================= -1. 使用 \__all_\_ 控制可被导入的变量 ------------------------------------- +|image0| -使用 ``from module import *`` 默认情况下会导入 module -里的所有变量,若你只想从模块中导入其中几个变量,可以在 module 中使用 -``__all__`` 来控制想要被其他模块导入的变量。 +1. 直接 import +-------------- + +人尽皆知的方法,直接导入即可 .. code:: python - # profile.py - name='wangbm' - age=27 - gender='male' + >>> 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 - __all__=['name'] + >>> os = __import__('os') + >>> os.getcwd() + '/home/wangbm' -打开 python console 验证一下 +如果要实现 ``import xx as yy`` 的效果,只要修改左值即可 + +如下示例,等价于 ``import os as myos``\ : + +.. code:: python + + >>> myos = __import__('os') + >>> myos.getcwd() + '/home/wangbm' + +上面说过的 ``__import__`` +是一个内建函数,既然是内建函数的话,那么这个内建函数必将存在于 +``__buildins__`` 中,因此我们还可以这样导入 os 的模块: .. 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 + >>> __builtins__.__dict__['__import__']('os').getcwd() + '/home/wangbm' -``__all__`` 仅对于使用\ ``from module import *`` 这种情况适用。 +3. 使用 importlib 模块 +---------------------- -它经常在一个包的 ``__init__.py`` 中出现。 +importlib 是 Python 中的一个标准库,importlib 能提供的功能非常全面。 -2. 命名空间包的神奇之处 ------------------------ +它的简单示例: -命名空间包,一个陌生的名字。 +.. code:: python + + >>> import importlib + >>> myos=importlib.import_module("os") + >>> myos.getcwd() + '/home/wangbm' + +如果要实现 ``import xx as yy``\ 效果,可以这样 + +.. code:: python -与我们熟悉的常规包不同的是,它没有 ``__init__.py`` 文件。 + >>> 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 函数,利用它可以用来执行一个文件。 + +语法如下: :: - foo-package/ - spam/ - blah.py + execfile(filename[, globals[, locals]]) - bar-package/ - spam/ - grok.py +参数有这么几个: -在这2个目录里,都有着共同的命名空间spam。在任何一个目录里都没有__init__.py文件。 +- filename:文件名。 +- globals:变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。 +- locals:变量作用域,局部命名空间,如果被提供,可以是任何映射对象。 -让我们看看,如果将foo-package和bar-package都加到python模块路径并尝试导入会发生什么? +.. code:: python + + >>> execfile("/usr/lib64/python2.7/os.py") + >>> + >>> getcwd() + '/home/wangbm' + +6. 使用 exec 执行 +----------------- + +``execfile`` 只能在 Python2 中使用,Python 3.x 里已经删除了这个函数。 + +但是原理值得借鉴,你可以使用 open … read 读取文件内容,然后再用 exec +去执行模块。 + +示例如下: .. code:: python - >>> import sys - >>> sys.path.extend(['foo-package', 'bar-package']) - >>> import spam.blah - >>> import spam.grok + >>> 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__': } >>> -当一个包为命名空间包时,他就不再和常规包一样具有 ``__file_`` -属性,取而代之的是 ``__path__`` +看了 +import_from_github_com的源码后,你会注意到它并没有使用importlib。实际上,它的原理就是使用 +pip +来安装那些没有安装的包,然后使用Python的\ ``__import__()``\ 函数来引入新安装的模块。 + +8. 远程导入模块 +--------------- + +我在这篇文章里(\ `深入探讨 Python 的 import +机制:实现远程导入模块 `__\ ),深入剖析了导入模块的内部原理,并在最后手动实现了从远程服务器上读取模块内容,并在本地成功将模块导入的导入器。 + +具体内容非常的多,你可以点击这个\ `链接 `__\ 进行深入学习。 + +示例代码如下: .. 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__' + # 新建一个 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() -3. 包重载就是一个坑 -------------------- + 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>> import spam - >>> import imp - >>> imp.reload(spam) - - >>> + >>> from my_importer import install_meta + >>> install_meta('http://localhost:12800/') # 往 sys.meta_path 注册 finder + >>> import my_info # 打印ok,说明导入成功 + ok + >>> my_info.name # 验证可以取得到变量 + 'wangbm' -由于这种重载方法,只对 ``import module`` 有效,而使用 -``from module import arg`` 导入的 arg 并不会刷新。 +好了,8 种方法都给大家介绍完毕,对于普通开发者来说,其实只要掌握 import +这种方法足够了,而对于那些想要自己开发框架的人来说,深入学习\ ``__import__``\ 以及 +importlib 是非常有必要的。 -因此,在生产环境中可能需要避免重新加载模块。而在调试模式中,它会提供一定的便利,但你要知道这个重载的弊端,以免掉入坑里。 +|image1| -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c01/c01_26.md b/source/c01/c01_26.md index c94c65c..d75731e 100644 --- a/source/c01/c01_26.md +++ b/source/c01/c01_26.md @@ -1,262 +1,220 @@ -# 1.26 C语言基础的学习 +# 1.26 Python 炫技操作:合并字典的七种方法 -## 1. 安装编译器 +![](http://image.iswbm.com/20200602135014.png) -C 语言编译器用于把源代码编译成最终的可执行程序。这里假设您已经对编程语言编译器有基本的了解了。 +Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 -最常用的免费可用的编译器是 GNU 的 C/C++ 编译器。 +但你要知道,在团队合作里,炫技是大忌。 -### 1.1 windows +为什么这么说呢?我说下自己的看法: -MinGw 是 Minimal GNU on Windows 的缩写,允许在 GNU/Linux 和 Windows 平台生成本地的 Windows 程序而不需要第三方运行时库。本文主要介绍 MinGw 的安装和使用。 +1. 越简洁的代码,越清晰的逻辑,就越不容易出错; +2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 +3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) -**安装** +该篇是「**炫技系列**」的第二篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 Python 发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 -1.下载 [min-gw](https://osdn.net/projects/mingw/downloads/68260/mingw-get-setup.exe/) 安装程序,下载 mingw-get-setup.exe (86.5 kB) +## 1. 最简单的原地更新 -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 就安装成功了。 - -如果想安装 g++,gdb,只要输入命令 **mingw-get install g++** 和 **mingw-get install gdb** - -**使用** - -在 cmd 的当前工作目录写 C 程序 hello.c: +字典对象内置了一个 update 方法,用于把另一个字典更新到自己身上。 +```python +>>> profile = {"name": "xiaoming", "age": 27} +>>> ext_info = {"gender": "male"} +>>> +>>> profile.update(ext_info) +>>> print(profile) +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ``` -# include -int main() -{ - printf("%s\n","hello world"); - return 0; -} -``` - -在 cmd 中输入命令 -```shell -$ gcc hello.c +如果想使用 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} ``` -在当前目录下会生成 a.exe 的可执行文件,在 cmd 中输入 a.exe 就可以执行程序了。 -如果想调试程序,可以输入 gdb a.exe -进入 gdb 的功能,使用 gdb 常用的命令就可以调试程序了。 +## 2. 先解包再合并字典 -### Mac OSX 及 Linux +使用 `**` 可以解包字典,解包完后再使用 dict 或者 `{}` 就可以合并。 -从苹果的网站上下载 [Xcode 开发环境](https://developer.apple.com/xcode/),并按照安装说明进行安装。一旦安装上 Xcode,您就能使用 GNU 编译器。 - -**使用** +```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'} +``` -打开终端,使用 vim 编辑文件 hello.c +若你不知道 `dict(**profile, **ext_info)` 做了啥,你可以将它等价于 -``` -# include -int main() -{ - printf("%s\n","hello world"); - return 0; -} +```python +>>> dict((("name", "xiaoming"), ("age", 27), ("gender", "male"))) +{'name': 'xiaoming', 'age': 27, 'gender': 'male'} ``` -使用 gcc 工具进行编译完后,就会在当前目录下生成一个名为 a.out 的可执行文件,手动运行它即可 -```shell -$ gcc hello.c -$ ./a.out -``` +## 3. 借助 itertools +在 Python 里有一个非常强大的内置模块,它专门用于操作可迭代对象。 -### 使用 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 -} -``` +正好我们字典也是可迭代对象,自然就可以想到,可以使用 `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 -```shell -%d   有符号10进制整数(%ld 长整型,%hd短整型 ) -%hu   无符号短整形(%u无符号整形,%lu无符号长整形) -%i   有符号10进制整数 (%i 和%d 没有区别,%i 是老式写法,都是整型格式) +如果可以引入一个辅助包,那我就再提一个, `ChainMap` 也可以达到和 `itertools` 同样的效果。 -%o   无符号8进制整数 -%u   无符号10进制整数 -%x   无符号的16进制数字,并以小写abcdef表示 -%X   无符号的16进制数字,并以大写ABCDEF表示 +```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'} +``` -%f   输入输出为浮点型 (%lf双精度浮点型) -%E/e 用科学表示格式的浮点数 +使用 ChainMap 有一点需要注意,当字典间有重复的键时,只会取第一个值,排在后面的键值并不会更新掉前面的(使用 itertools 就不会有这个问题)。 -%c 输入输出为单个字符 -%s 输入输出为字符串 +```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. 最酷炫的字典解析式 -getchar() & putchar() +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'} +``` -记住 `*` 有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 新特性 -```c -// 如下是错误的,指针不能接受一个值 -int a =5; int* ptr=a; +在 2 月份发布的 Python 3.9.04a 版本中,新增了一个抓眼球的新操作符操作符: `|`, PEP584 将它称之为合并操作符(Union Operator),用它可以很直观地合并多个字典。 -// 如下是正确的,a的地址给指针ptr -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} +>>> +>>> ``` +除了 `|` 操作符之外,还有另外一个操作符 `|=`,类似于原地更新。 + +```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 种合并字典的方法,实际在工作中,你只要选用一种最顺手的方式即可,但是在协同工作中,或者在阅读他人代码时,你不可避免地会碰到各式各样的写法,这时候你能下意识的知道这是在做合并字典的操作,那这篇文章就是有意义的。 + +--- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_26.rst b/source/c01/c01_26.rst index 4f32dcb..cb9ad4c 100644 --- a/source/c01/c01_26.rst +++ b/source/c01/c01_26.rst @@ -1,288 +1,231 @@ -1.26 C语言基础的学习 -==================== +1.26 Python 炫技操作:合并字典的七种方法 +======================================== -1. 安装编译器 -------------- +|image0| -C -语言编译器用于把源代码编译成最终的可执行程序。这里假设您已经对编程语言编译器有基本的了解了。 +Python 语言里有许多(而且是越来越多)的高级特性,是 Python +发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 -最常用的免费可用的编译器是 GNU 的 C/C++ 编译器。 +但你要知道,在团队合作里,炫技是大忌。 -1.1 windows -~~~~~~~~~~~ +为什么这么说呢?我说下自己的看法: -MinGw 是 Minimal GNU on Windows 的缩写,允许在 GNU/Linux 和 Windows -平台生成本地的 Windows 程序而不需要第三方运行时库。本文主要介绍 MinGw -的安装和使用。 +1. 越简洁的代码,越清晰的逻辑,就越不容易出错; +2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 +3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) -**安装** +该篇是「\ **炫技系列**\ 」的第二篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 +Python +发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 -1.下载 -`min-gw `__ -安装程序,下载 mingw-get-setup.exe (86.5 kB) +1. 最简单的原地更新 +------------------- -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 就安装成功了。 +字典对象内置了一个 update 方法,用于把另一个字典更新到自己身上。 -如果想安装 g++,gdb,只要输入命令 **mingw-get install g++** 和 **mingw-get -install gdb** +.. code:: python -**使用** + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> profile.update(ext_info) + >>> print(profile) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} -在 cmd 的当前工作目录写 C 程序 hello.c: +如果想使用 update +这种最简单、最地道原生的方法,但又不想更新到自己身上,而是生成一个新的对象,那请使用深拷贝。 -:: +.. code:: python - # include - int main() - { - printf("%s\n","hello world"); - return 0; - } + >>> 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 中输入命令 +2. 先解包再合并字典 +------------------- -.. code:: shell +使用 ``**`` 可以解包字典,解包完后再使用 dict 或者 ``{}`` 就可以合并。 - $ gcc hello.c +.. code:: python -在当前目录下会生成 a.exe 的可执行文件,在 cmd 中输入 a.exe -就可以执行程序了。 + >>> 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'} -如果想调试程序,可以输入 gdb a.exe +若你不知道 ``dict(**profile, **ext_info)`` 做了啥,你可以将它等价于 -进入 gdb 的功能,使用 gdb 常用的命令就可以调试程序了。 +.. code:: python -Mac OSX 及 Linux -~~~~~~~~~~~~~~~~ + >>> dict((("name", "xiaoming"), ("age", 27), ("gender", "male"))) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} -从苹果的网站上下载 `Xcode -开发环境 `__\ ,并按照安装说明进行安装。一旦安装上 -Xcode,您就能使用 GNU 编译器。 +3. 借助 itertools +----------------- -**使用** +在 Python 里有一个非常强大的内置模块,它专门用于操作可迭代对象。 -打开终端,使用 vim 编辑文件 hello.c +正好我们字典也是可迭代对象,自然就可以想到,可以使用 +``itertools.chain()`` +函数先将多个字典(可迭代对象)串联起来,组成一个更大的可迭代对象,然后再使用 +dict 转成字典。 -:: +.. code:: python - # include - int main() - { - printf("%s\n","hello world"); - return 0; - } + >>> 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 -的可执行文件,手动运行它即可 +4. 借助 ChainMap +---------------- -.. code:: shell +如果可以引入一个辅助包,那我就再提一个, ``ChainMap`` 也可以达到和 +``itertools`` 同样的效果。 - $ gcc hello.c - $ ./a.out +.. code:: python -使用 VS code -~~~~~~~~~~~~ + >>> from collections import ChainMap + >>> + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> dict(ChainMap(profile, ext_info)) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} -在 vscode 中引入标准库的头文件时,会出现波浪线,提示找不到头文件。 +使用 ChainMap +有一点需要注意,当字典间有重复的键时,只会取第一个值,排在后面的键值并不会更新掉前面的(使用 +itertools 就不会有这个问题)。 -解决方法是在当前项目下的 .vscode/c_cpp_properties.json 中的 includePath -列表中添加包含标准头文件的路径,这个路径哪里来呢?你可以使用 everything -搜索一下,哪个路径下有此 stdio.h 文件。 +.. code:: python -比如我的是这样,注意路径不要使用 ``\`` 和 ``\\`` ,一定要使用 ``\`` + >>> from collections import ChainMap + >>> + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info={"age": 30} + >>> dict(ChainMap(profile, ext_info)) + {'name': 'xiaoming', 'age': 27} -.. code:: json +5. 使用dict.items() 合并 +------------------------ - { - "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 3.9 之前,其实就已经有 ``|`` +操作符了,只不过它通常用于对集合(set)取并集。 -字符串学习 ----------- +利用这一点,也可以将它用于字典的合并,只不过得绕个弯子,有点不好理解。 -格式式字符串 -~~~~~~~~~~~~ +你得先利用 ``items`` 方法将 dict 转成 dict_items,再对这两个 dict_items +取并集,最后利用 dict 函数,转成字典。 -.. code:: shell +.. code:: python - %d   有符号10进制整数(%ld 长整型,%hd短整型 ) - %hu   无符号短整形(%u无符号整形,%lu无符号长整形) - %i   有符号10进制整数 (%i 和%d 没有区别,%i 是老式写法,都是整型格式) + >>> 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'} - %o   无符号8进制整数 - %u   无符号10进制整数 - %x   无符号的16进制数字,并以小写abcdef表示 - %X   无符号的16进制数字,并以大写ABCDEF表示 +当然了,你如果嫌这样太麻烦,也可以简单点,直接使用 list +函数再合并(示例为 Python 3.x ) - %f   输入输出为浮点型 (%lf双精度浮点型) - %E/e 用科学表示格式的浮点数 +.. code:: python - %c 输入输出为单个字符 - %s 输入输出为字符串 + >>> 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 函数。 -字符串声明使用 ``char`` +.. code:: python -.. code:: c + >>> profile = {"name": "xiaoming", "age": 27} + >>> ext_info = {"gender": "male"} + >>> + >>> dict(profile.items() + ext_info.items()) + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} - #include +6. 最酷炫的字典解析式 +--------------------- - // 定义 name ,不设置大小 - char name[] = "wangbm"; +Python 里对于生成列表、集合、字典,有一套非常 Pythonnic 的写法。 - // 定义 gender ,设置大小为7个字节 - char gender[7] = "female"; +那就是列表解析式,集合解析式和字典解析式,通常是 Python +发烧友的最爱,那么今天的主题:字典合并,字典解析式还能否胜任呢? - int main() - { - printf("name is %s\n", name); - printf("size of name: %lu\n", sizeof(name)); - printf("len of name: %lu\n", strlen(name)); +当然可以,具体示例代码如下: - 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"} + >>> + >>> {k:v for d in [profile, ext_info] for k,v in d.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。 +7. Python 3.9 新特性 +-------------------- -字符的输入 -~~~~~~~~~~ +在 2 月份发布的 Python 3.9.04a +版本中,新增了一个抓眼球的新操作符操作符: ``|``\ , PEP584 +将它称之为合并操作符(Union Operator),用它可以很直观地合并多个字典。 -使用 ``printf`` 和 ``scanf`` 函数 +.. code:: 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 + >>> 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[]) - { - char name[10]; - printf("Enter your name: "); - fgets(name, 10, stdin); - printf("Your name is: "); - fputs(name, stdout); - return 0; - } +除了 ``|`` 操作符之外,还有另外一个操作符 ``|=``\ ,类似于原地更新。 -getchar() & putchar() +.. code:: python -指针相关的两个符号 -~~~~~~~~~~~~~~~~~~ + >>> ext_info |= profile + >>> ext_info + {'gender': 'male', 'name': 'xiaoming', 'age': 27} + >>> + >>> + >>> profile |= ext_info + >>> profile + {'name': 'xiaoming', 'age': 27, 'gender': 'male'} -记住 ``*`` 有3个用途: +看到这里,有没有涨姿势了,学了这么久的 Python +,没想到合并字典还有这么多的方法。本篇文章的主旨,并不在于让你全部掌握这 +7 +种合并字典的方法,实际在工作中,你只要选用一种最顺手的方式即可,但是在协同工作中,或者在阅读他人代码时,你不可避免地会碰到各式各样的写法,这时候你能下意识的知道这是在做合并字典的操作,那这篇文章就是有意义的。 -1. 乘号(Multiply): 2*3 就是6 -2. 声明指针(Pointer Statement): int a =5; int\* - ptr=&a;就是声明变量a是5,把a的地址附到指针ptr上 -3. 解引用 (Dereference): \*ptr 单独拿出来就是找出 - ptr指针指向的值,按照第二点的说法就是5. +-------------- -``&``\ 叫做取地址符号,一般指针只能接受一个内存地址而不能接受一个值 +|image1| -.. code:: c +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - // 如下是错误的,指针不能接受一个值 - int a =5; int* ptr=a; - - // 如下是正确的,a的地址给指针ptr - int a =5; int* ptr=&a; - -可变参数的获取 -~~~~~~~~~~~~~~ - -以下写了一个函数 ``get_sum`` -来求得输入的所有参数的和(除了第一个参数外,第一个参数表示,对后面几个可变参数求和,在示例中是作为结束条件存在)。 - -.. code:: 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; - } - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! diff --git a/source/c01/c01_27.md b/source/c01/c01_27.md index f440996..e8b2220 100644 --- a/source/c01/c01_27.md +++ b/source/c01/c01_27.md @@ -1,643 +1,143 @@ -# 1.27 全面学习 Python 包:包的构建与分发 +# 1.27 Python 炫技操作:判断是否包含子串的七种方法 -> 首发于公众号:Python编程时光 +![](http://image.iswbm.com/20200602135014.png) +Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 +但你要知道,在团队合作里,炫技是大忌。 -## 1. 为什么需要对项目分发打包? +为什么这么说呢?我说下自己的看法: -平常我们习惯了使用 pip 来安装一些第三方模块,这个安装过程之所以简单,是因为模块开发者为我们默默地为我们做了所有繁杂的工作,而这个过程就是 `打包`。 +1. 越简洁的代码,越清晰的逻辑,就越不容易出错; +2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 +3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) -打包,就是将你的源代码进一步封装,并且将所有的项目部署工作都事先安排好,这样使用者拿到后即装即用,不用再操心如何部署的问题(如果你不想对照着一堆部署文档手工操作的话)。 +该篇是「**炫技系列**」的第三篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 Python 发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 -不管你是在工作中,还是业余准备自己写一个可以上传到 PyPI 的项目,你都要学会如何打包你的项目。 +## 1. 使用 in 和 not in -Python 发展了这么些年了,项目打包工具也已经很成熟了。他们都有哪些呢? +`in` 和 `not in` 在 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 +## 2. 使用 find 方法 -示例: +使用 字符串 对象的 find 方法,如果有找到子串,就可以返回指定子串在字符串中的出现位置,如果没有找到,就返回 `-1` ```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', - ] -) +>>> "hello, python".find("llo") != -1 +True +>>> "hello, python".find("lol") != -1 +False +>> ``` -**关于文件的分发** +## 3. 使用 index 方法 -```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 会自动读取该文件。 - - - -**关于依赖包下载安装** +字符串对象有一个 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 方法 -有些库并不是在所以的 Python 版本中都适用的,若一个库安装在一个未兼容的 Python 环境中,理论上不应该在使用时才报错,而应该在安装过程就使其失败,提示禁止安装。 +利用和 index 这种曲线救国的思路,同样我们可以使用 count 的方法来判断。 -这样的功能,可以使用 `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()]) - ] -) +>>> "hello, python".__contains__("llo") +True +>>> +>>> "hello, python".__contains__("lol") +False +>>> ``` -详细了解可参考:https://docs.python.org/3.6/distutils/setupscript.html#preprocessor-options - - - -setup.py 的参数非常多,能够不借助文档写好一个setup.py好像没那么简单。为了备忘,我整理了 setup 函数常用的一些参数: - -![](http://image.python-online.cn/20191218203255.png) - -更多参数可见:https://setuptools.readthedocs.io/en/latest/setuptools.html - -## 8. 打包辅助神器PBR 是什么? +这个用法与使用 in 和 not in 没有区别,但不排除有人会特意写成这样来增加代码的理解难度。 -`pbr` 是 setuptools 的辅助工具,最初是为 OpenStack 开发(https://launchpad.net/pbr),基于`d2to1`。 +## 6. 借助 operator +operator模块是python中内置的操作符函数接口,它定义了一些算术和比较内置操作的函数。operator模块是用c实现的,所以执行速度比 python 代码快。 +在 operator 中有一个方法 `contains` 可以很方便地判断子串是否在字符串中。 -`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 -``` - -若你喜欢使用 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 +```python +>>> import operator +>>> +>>> operator.contains("hello, python", "llo") +True +>>> operator.contains("hello, python", "lol") +False +>>> ``` -## 11. 如何发布包到 PyPi? +## 7. 使用正则匹配 -通过上面的学习,你一定已经学会了如何打包自己的项目,若你觉得自己开发的模块非常不错,想要 share 给其他人使用,你可以将其上传到 PyPi (Python Package Index)上,它是 Python 官方维护的第三方包仓库,用于统一存储和管理开发者发布的 Python 包。 +说到查找功能,那正则绝对可以说是专业的工具,多复杂的查找规则,都能满足你。 +对于判断字符串是否存在于另一个字符串中的这个需求,使用正则简直就是大材小用。 +```python +import re -如果要发布自己的包,需要先到 pypi 上注册账号。然后创建 `~/.pypirc` 文件,此文件中配置 PyPI 访问地址和账号。如的.pypirc文件内容请根据自己的账号来修改。 - -典型的 .pypirc 文件 - -```ini -[distutils] -index-servers = pypi - -[pypi] -username:xxx -password:xxx -``` - -然后使用这条命令进行信息注册,完成后,你可以在 PyPi 上看到项目信息。 - -```shell -$ python setup.py register -``` - -注册完了后,你还要上传源码包,别人才使用下载安装 +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/ +--- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_27.rst b/source/c01/c01_27.rst index 3e69eb0..f65a018 100644 --- a/source/c01/c01_27.rst +++ b/source/c01/c01_27.rst @@ -1,695 +1,154 @@ -1.27 全面学习 Python 包:包的构建与分发 -======================================= - - 首发于公众号:Python编程时光 - -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. 以源码包的方式发布 - -源码包安装的过程,是先解压,再编译,最后才安装,所以它是跨平台的,由于每次安装都要进行编译,相对二进包安装方式来说安装速度较慢。 - -源码包的本质是一个压缩包,其常见的格式有: +1.27 Python 炫技操作:判断是否包含子串的七种方法 +================================================ |image0| -2. 以二进制包形式发布 - -二进制包的安装过程省去了编译的过程,直接进行解压安装,所以安装速度较源码包来说更快。 - -由于不同平台的编译出来的包无法通用,所以在发布时,需事先编译好多个平台的包。 - -二进制包的常见格式有: - -|image1| - -6. eggs 与 wheels 有什么区别? ------------------------------- - -Egg 格式是由 setuptools 在 2004 年引入,而 Wheel 格式是由 PEP427 在 2012 -年定义。Wheel 的出现是为了替代 Egg,它的本质是一个zip包,其现在被认为是 -Python 的二进制包的标准格式。 +Python 语言里有许多(而且是越来越多)的高级特性,是 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 -的命令。 +1. 越简洁的代码,越清晰的逻辑,就越不容易出错; +2. 在团队合作中,你的代码不只有你在维护,降低别人的阅读/理解代码逻辑的成本是一个良好的品德 +3. 简单的代码,只会用到最基本的语法糖,复杂的高级特性,会有更多的依赖(如语言的版本) -.. code:: shell - - $ pip install wheel - $ pip wheel --wheel-dir=/local/wheels pkg +该篇是「\ **炫技系列**\ 」的第三篇内容,在这个系列里,我将总结盘点一下,我所见过的那些炫技操作。在这里,如果你是 +Python +发烧友,你可以学到一些写出超酷的代码书写技巧。同时,看了这些内容,对你在阅读别人的代码时,也许会有些帮助。 -7. 超详细讲解 setup.py 的编写? -------------------------------- +1. 使用 in 和 not in +-------------------- -打包分发最关键的一步是编写 ``setup.py`` 文件。 +``in`` 和 ``not in`` 在 Python 中是很常用的关键字,我们将它们归类为 +``成员运算符``\ 。 -以下是一个 setup.py 简单的使用示例 +使用这两个成员运算符,可以很让我们很直观清晰的判断一个对象是否在另一个对象中,示例如下: .. code:: python - from setuptools import setup, find_packages + >>> "llo" in "hello, python" + True + >>> + >>> "lol" in "hello, python" + False - 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() - ) +2. 使用 find 方法 +----------------- -接下来,我将慢慢扩充这个setup函数,增加更多的参数,以便你能理解setup函数能做哪些事情。 - -**程序分类信息** - -``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', - - # 目标 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', - ] - ) - -**关于文件的分发** + >>> "hello, python".find("llo") != -1 + True + >>> "hello, python".find("lol") != -1 + False + >> -.. code:: python +3. 使用 index 方法 +------------------ - 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 -会自动读取该文件。 - -**关于依赖包下载安装** +字符串对象有一个 index +方法,可以返回指定子串在该字符串中第一次出现的索引,如果没有找到会抛出异常,因此使用时需要注意捕获。 .. 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 + def is_in(full_str, sub_str): + try: + full_str.index(sub_str) + return True + except ValueError: + return False - setup( - ... - python_requires='>=2.7, <=3', - ) + print(is_in("hello, python", "llo")) # True + print(is_in("hello, python", "lol")) # False -**生成可执行文件的分发** +4. 使用 count 方法 +------------------ -.. code:: python +利用和 index 这种曲线救国的思路,同样我们可以使用 count 的方法来判断。 - 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 - -setup.py -的参数非常多,能够不借助文档写好一个setup.py好像没那么简单。为了备忘,我整理了 -setup 函数常用的一些参数: - -|image2| - -更多参数可见: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 + >>> "hello, python".__contains__("llo") + True + >>> + >>> "hello, python".__contains__("lol") + False + >>> - $ python setup.py sdist --formats=gztar,zip +这个用法与使用 in 和 not in +没有区别,但不排除有人会特意写成这样来增加代码的理解难度。 -你可以指定的格式有哪些呢? +6. 借助 operator +---------------- -创建一个压缩的tarball和一个zip文件。可用格式为: +operator模块是python中内置的操作符函数接口,它定义了一些算术和比较内置操作的函数。operator模块是用c实现的,所以执行速度比 +python 代码快。 -|image3| +在 operator 中有一个方法 ``contains`` +可以很方便地判断子串是否在字符串中。 -对以上的格式,有几点需要注意一下: - -- 在版本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:: python -.. code:: shell + >>> import operator + >>> + >>> operator.contains("hello, python", "llo") + True + >>> operator.contains("hello, python", "lol") + False + >>> - $ python setup.py register +7. 使用正则匹配 +--------------- -注册完了后,你还要上传源码包,别人才使用下载安装 +说到查找功能,那正则绝对可以说是专业的工具,多复杂的查找规则,都能满足你。 -.. code:: shell +对于判断字符串是否存在于另一个字符串中的这个需求,使用正则简直就是大材小用。 - $ python setup.py upload +.. code:: python -或者也可以使用 ``twine`` 工具注册上传,它是一个专门用于与 pypi -进行交互的工具,详情可以参考官网:https://www.ctolib.com/twine.html,这里不详细讲了。 + import re -参考文章 --------- + def is_in(full_str, sub_str): + if re.findall(sub_str, full_str): + return True + else: + return False -- http://blog.konghy.cn/2018/04/29/setup-dot-py/ -- https://note.qidong.name/2018/01/python-setup-requires/ + print(is_in("hello, python", "llo")) # True + print(is_in("hello, python", "lol")) # False -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +-------------- - 关注公众号,获取最新干货! +|image1| -.. |image0| image:: http://image.python-online.cn/20191218202833.png -.. |image1| image:: http://image.python-online.cn/20191218203005.png -.. |image2| image:: http://image.python-online.cn/20191218203255.png -.. |image3| image:: http://image.python-online.cn/20191218203517.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 97% rename from source/c01/c01_41.rst rename to source/c01/c01_28.rst index 03af012..82b79a2 100644 --- a/source/c01/c01_41.rst +++ b/source/c01/c01_28.rst @@ -1,6 +1,8 @@ 1.41 Python 炫技操作:连接列表的八种方法 ======================================== +|image0| + Python 语言里有许多(而且是越来越多)的高级特性,是 Python 发烧友们非常喜欢的。在这些人的眼里,能够写出那些一般开发者看不懂的高级特性,就是高手,就是大神。 @@ -224,7 +226,8 @@ yield from 意义及使用方法。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c01/c01_29.md b/source/c01/c01_29.md deleted file mode 100644 index 2657d2a..0000000 --- a/source/c01/c01_29.md +++ /dev/null @@ -1,11 +0,0 @@ -# 1.27 如何阅读 CPython源码? - - - -参考学习地址:https://realpython.com/cpython-source-code-guide/ - -基于 Python 3.6 的源码分析:https://he11olx.com/ - - - -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file diff --git a/source/c01/c01_29.rst b/source/c01/c01_29.rst index 9004d6b..9df2a34 100644 --- a/source/c01/c01_29.rst +++ b/source/c01/c01_29.rst @@ -1,11 +1,183 @@ -1.27 如何阅读 CPython源码? -=========================== +1.42 Python 炫技操作:海象运算符的三种用法 +========================================== -参考学习地址:https://realpython.com/cpython-source-code-guide/ +|image0| -基于 Python 3.6 的源码分析:https://he11olx.com/ +Python 版本发展非常快,如今最新的版本已经是 Pyhton +3.9,即便如此,有很多人甚至还停留在 3.6 或者 3.7,连 3.8 还没用上。 -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +很多 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_30.md b/source/c01/c01_30.md deleted file mode 100644 index f319646..0000000 --- a/source/c01/c01_30.md +++ /dev/null @@ -1,35 +0,0 @@ -# 1.30 盘点程序员学习编程的那些网站 - -## 书栈网 - -**网站链接**:https://www.bookstack.cn/rank?tab=popular - -![](http://image.python-online.cn/20200104144109.png) - -## 魔法学院 - - **网站链接**:http://www.nowamagic.net/academy/ - -![](http://image.python-online.cn/20200112210558.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) \ No newline at end of file diff --git a/source/c01/c01_30.rst b/source/c01/c01_30.rst deleted file mode 100644 index 0e34aaf..0000000 --- a/source/c01/c01_30.rst +++ /dev/null @@ -1,43 +0,0 @@ -1.30 盘点程序员学习编程的那些网站 -================================= - -书栈网 ------- - -**网站链接**\ :https://www.bookstack.cn/rank?tab=popular - -|image0| - -魔法学院 --------- - -**网站链接**\ :http://www.nowamagic.net/academy/ - -|image1| - -Python 3 标准库实例教程 ------------------------ - -**网站链接**\ :https://learnku.com/docs/pymotw - -|image2| - -Django Web 框架 ---------------- - -网站链接:https://developer.mozilla.org/zh-CN/docs/learn/Server-side/Django - -该网站可以让你从0开始学习Web,包括前端(HTML,CSS,JS)、后端(Django) - -|image3| - -在服务端网页编程里,重点介绍了 Django - -|image4| - -.. |image0| image:: http://image.python-online.cn/20200104144109.png -.. |image1| image:: http://image.python-online.cn/20200112210558.png -.. |image2| image:: http://image.iswbm.com/20200508201333.png -.. |image3| image:: http://image.iswbm.com/20200525080531.png -.. |image4| image:: http://image.iswbm.com/20200525080715.png - diff --git a/source/c01/c01_31.md b/source/c01/c01_31.md deleted file mode 100644 index 71656f0..0000000 --- a/source/c01/c01_31.md +++ /dev/null @@ -1,23 +0,0 @@ -# 1.31 学习 Pillow 笔记 - - - -## 1. 安装 pillow - -``` -pip install pillow -``` - - - -## 2. 使用 - -ARGB 是一种色彩模式,也就是RGB色彩模式附加上Alpha(透明度)通道,常见于32位位图的存储结构。 - -RGB 色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。 - - - - - -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.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 5b12101..0000000 --- a/source/c01/c01_31.rst +++ /dev/null @@ -1,23 +0,0 @@ -1.31 学习 Pillow 笔记 -===================== - -1. 安装 pillow --------------- - -:: - - pip install pillow - -2. 使用 -------- - -ARGB -是一种色彩模式,也就是RGB色彩模式附加上Alpha(透明度)通道,常见于32位位图的存储结构。 - -RGB -色彩模式是工业界的一种颜色标准,是通过对红(R)、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。 - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! diff --git a/source/c01/c01_32.md b/source/c01/c01_32.md deleted file mode 100644 index 77da3e4..0000000 --- a/source/c01/c01_32.md +++ /dev/null @@ -1,37 +0,0 @@ -# 1.32 在 CentOS 7.2 上安装 Python3.7 - -首先下载 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.python-online.cn/image-20200320125724880.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 4cebca3..0000000 --- a/source/c01/c01_32.rst +++ /dev/null @@ -1,37 +0,0 @@ -1.32 在 CentOS 7.2 上安装 Python3.7 -=================================== - -首先下载 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 - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! diff --git a/source/c01/c01_35.md b/source/c01/c01_35.md deleted file mode 100644 index 594e430..0000000 --- a/source/c01/c01_35.md +++ /dev/null @@ -1,382 +0,0 @@ -# 1.35 使用 Python 远程登陆服务器的利器 - -在使用 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.python-online.cn/image-20200320125724880.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 0ed5503..0000000 --- a/source/c01/c01_35.rst +++ /dev/null @@ -1,404 +0,0 @@ -1.35 使用 Python 远程登陆服务器的利器 -===================================== - -在使用 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`` 的人并不多,于是我又“追问”了下,期望能得到回复。 - -|image0| - -以上这个问题,只有在需要输入密码才会出现,如果设置了机器互信是没有问题的。 - -为了感受 ``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 `__\ ,已经 “年久失修”,没人维护了。 - -|image1| - -至此,我离 “卒”,就差最后一根稻草了。 - -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 -解决,但是我建议你直接放弃,坑太深了。 - -|image2| - -注意事项 -~~~~~~~~ - -使用 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/ - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20200228085749.png -.. |image1| image:: http://image.python-online.cn/20200228093627.png -.. |image2| image:: http://image.python-online.cn/20200228111654.png - diff --git a/source/c01/c01_36.md b/source/c01/c01_36.md deleted file mode 100644 index 6b4f513..0000000 --- a/source/c01/c01_36.md +++ /dev/null @@ -1,251 +0,0 @@ -# 1.36 每日一库:pretty_errors 解决bug 洁癖 - -当我们写的一个脚本或程序发生各种不可预知的异常时,如果我们没有进行捕获处理的时候,通常都会致使程序崩溃退出,并且会在终端打印出一堆 **密密麻麻** 的 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.python-online.cn/image-20200320125724880.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 f137980..0000000 --- a/source/c01/c01_36.rst +++ /dev/null @@ -1,285 +0,0 @@ -1.36 每日一库:pretty_errors 解决bug 洁癖 -========================================= - -当我们写的一个脚本或程序发生各种不可预知的异常时,如果我们没有进行捕获处理的时候,通常都会致使程序崩溃退出,并且会在终端打印出一堆 -**密密麻麻** 的 traceback 堆栈信息来告诉我们,是哪个地方出了问题。 - -就像这样子,天呐,密集恐惧症要犯了都 - -|image0| - -上面这段 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 ,并且报错了的程序,是这样子的。 - -|image1| - -而使用了 pretty_errors 后,报错信息被美化成这样了。 - -|image2| - -是不是感觉清楚了不少,那种密密麻麻带来的焦虑感是不是都消失了呢? - -当然这段代码少,你可能还没感受到,那就来看下 该项目在 -Github上的一张效果对比图吧 - -|image3| - -3. 配置全局可用 ---------------- - -可以看到使用了 pretty_errors -后,无非就是把过滤掉了一些干扰我们视线的无用信息,然后把有用的关键信息给我们高亮显示。 - -既然既然这样,那 pretty_errors -应该也能支持我们如何自定义我们选用什么样的颜色,怎么排版吧? - -答案是显而易见的。 - -pretty_errors -和其他库不太一样,在一定程度上(如果你使用全局配置的话),它并不是开箱即用的,你在使用它之前可能需要做一下配置。 - -使用这一条命令,会让你进行配置,可以让你在该环境中运行其他脚本时的 -traceback 输出都自动美化。 - -.. code:: shell - - $ python3 -m pretty_errors - -|image4| - -配置完成后,你再运行任何脚本,traceback 都会自动美化了。 - -不仅是在我的 iTerm 终端下 - -|image5| - -在 PyCharm 中也会 - -|image6| - -唯一的缺点就是,原先在 PyCharm 中的 traceback 可以直接点击 ``文件路径`` -直接跳转到对应错误文件代码行,而你如果是在 VSCode 可以使用 -下面自定义配置的方案解决这个问题(下面会讲到,参数是:\ ``display_link``\ )。 - -|image7| - -因此,有些情况下,你并不想设置 ``pretty_errors`` 全局可用。 - -那怎么取消之前的配置呢? - -只需要再次输出 ``python -m pretty_errors``\ ,输出入 ``C`` 即可清除。 - -|image8| - -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`` -进行配置时,抛出的的异常信息就变成这样了。 - -|image9| - -当然了,\ ``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`` 用于设置背景色,举个例子如下。 - -|image10| - -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`` -会是一个不错的解决方案,明哥把它推荐给你。 - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/image-20200307210853246.png -.. |image1| image:: http://image.python-online.cn/image-20200307212823345.png -.. |image2| image:: http://image.python-online.cn/image-20200307213534278.png -.. |image3| image:: https://warehouse-camo.cmh1.psfhosted.org/31399c5a034c3989b9e99b35249e8f2f0d40e102/68747470733a2f2f692e696d6775722e636f6d2f306a7045716f622e706e67 -.. |image4| image:: http://image.python-online.cn/image-20200307214742135.png -.. |image5| image:: http://image.python-online.cn/image-20200307213534278.png -.. |image6| image:: http://image.python-online.cn/image-20200307215530270.png -.. |image7| image:: http://image.python-online.cn/image-20200307215834623.png -.. |image8| image:: http://image.python-online.cn/image-20200307214600749.png -.. |image9| image:: http://image.python-online.cn/image-20200308121949011.png -.. |image10| image:: http://image.python-online.cn/image-20200308125431779.png - diff --git a/source/c01/c01_37.md b/source/c01/c01_37.md deleted file mode 100644 index ebdc0c0..0000000 --- a/source/c01/c01_37.md +++ /dev/null @@ -1,159 +0,0 @@ -# 1.37 Python 炫技操作:条件语句的七种写法 - -有的人说 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.python-online.cn/image-20200320125724880.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 af11db0..0000000 --- a/source/c01/c01_37.rst +++ /dev/null @@ -1,179 +0,0 @@ -1.37 Python 炫技操作:条件语句的七种写法 -======================================== - -有的人说 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 -,这么多骚操作,还真是活久见。。这六种写法里,我最推荐使用的是第一种,自己也经常在用,简洁直白,代码行还少。而其他的写法虽然能写,但是不会用,也不希望在我余生里碰到会在公共代码里用这些写法的同事。 - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! diff --git a/source/c01/c01_38.rst b/source/c01/c01_38.rst deleted file mode 100644 index c0d5d1e..0000000 --- a/source/c01/c01_38.rst +++ /dev/null @@ -1,75 +0,0 @@ -1.38 /usr/bin/env python 有什么用? -=================================== - -我们经常会在别人的脚本或者项目的入口文件里看到第一行是下面这样 - -.. code:: json - - #!/usr/bin/python - -或者这样 - -.. code:: json - - #!/usr/bin/env python - -这两者有什么区别呢? - -稍微接触过 linux 的人都知道 ``/usr/bin/python`` 就是我们执行 ``python`` -进入console 模式里的 ``python`` - -|image0| - -而当你在可执行文件头里使用 ``#!`` + ``/usr/bin/python`` -,意思就是说你得用哪个软件 (python)来执行这个文件。 - -那么加和不加有什么区别呢? - -不加的话,你每次执行这个脚本时,都得这样: ``python xx.py`` , - -|image1| - -有没有一种方式?可以省去每次都加 ``python`` 呢? - -当然有,你可以文件头里加上\ ``#!/usr/bin/python`` -,那么当这个文件有可执行权限 时,只直接写这个脚本文件,就像下面这样。 - -|image2| - -明白了这个后,再来看看 ``!/usr/bin/env python`` 这个 又是什么意思 ? - -当我执行 ``env python`` 时,自动进入了 python console 的模式。 - -|image3| - -这是为什么?和 直接执行 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`` -了。 - -具体演示过程,你可以看下面。 - -|image4| - -那么对于这两者,我们应该使用哪个呢? - -个人感觉应该优先使用 ``#!/usr/bin/env python``\ ,因为不是所有的机器的 -python 解释器都是 ``/usr/bin/python`` 。 - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20200331184021.png -.. |image1| image:: http://image.python-online.cn/20200331185034.png -.. |image2| image:: http://image.python-online.cn/20200331184755.png -.. |image3| image:: http://image.python-online.cn/20200331185741.png -.. |image4| image:: http://image.python-online.cn/20200331190224.png - diff --git a/source/c01/c01_39.md b/source/c01/c01_39.md deleted file mode 100644 index e4eaccc..0000000 --- a/source/c01/c01_39.md +++ /dev/null @@ -1,218 +0,0 @@ -# 1.39 Python 炫技操作:合并字典的七种方法 - -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.python-online.cn/image-20200320125724880.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 3037283..0000000 --- a/source/c01/c01_39.rst +++ /dev/null @@ -1,228 +0,0 @@ -1.39 Python 炫技操作:合并字典的七种方法 -======================================== - -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 -种合并字典的方法,实际在工作中,你只要选用一种最顺手的方式即可,但是在协同工作中,或者在阅读他人代码时,你不可避免地会碰到各式各样的写法,这时候你能下意识的知道这是在做合并字典的操作,那这篇文章就是有意义的。 - --------------- - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! diff --git a/source/c01/c01_40.md b/source/c01/c01_40.md deleted file mode 100644 index a70130c..0000000 --- a/source/c01/c01_40.md +++ /dev/null @@ -1,141 +0,0 @@ -# 1.40 Python 炫技操作:判断是否包含子串的七种方法 - -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.python-online.cn/image-20200320125724880.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 b662c06..0000000 --- a/source/c01/c01_40.rst +++ /dev/null @@ -1,151 +0,0 @@ -1.40 Python 炫技操作:判断是否包含子串的七种方法 -================================================ - -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 - --------------- - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! diff --git a/source/c01/c01_41.md b/source/c01/c01_41.md deleted file mode 100644 index 211fb5c..0000000 --- a/source/c01/c01_41.md +++ /dev/null @@ -1,214 +0,0 @@ -# 1.41 Python 炫技操作:连接列表的八种方法 - -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.python-online.cn/image-20200320125724880.png) - - diff --git a/source/c01/c01_42.md b/source/c01/c01_42.md deleted file mode 100644 index fbde99b..0000000 --- a/source/c01/c01_42.md +++ /dev/null @@ -1,173 +0,0 @@ -# 1.42 Python 炫技操作:海象运算符的三种用法 - -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.python-online.cn/image-20200320125724880.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 a92b178..0000000 --- a/source/c01/c01_42.rst +++ /dev/null @@ -1,182 +0,0 @@ -1.42 Python 炫技操作:海象运算符的三种用法 -========================================== - -Python 版本发展非常快,如今最新的版本已经是 Pyhton -3.9,即便如此,有很多人甚至还停留在 3.6 或者 3.7,连 3.8 还没用上。 - -很多 Python 3.8 -的特性还没来得及了解,就已经成为旧知识了,比如今天要说的海象运算符。 - -海象运算符是在 PEP 572 被提出的,直到 3.8 版本合入发布。 - -它的英文原名叫 ``Assignment Expressions``\ ,翻译过来也就是 -``赋值表达式``\ ,不过现在大家更普遍地称之为海象运算符,就是因为它长得真的太像海象了。 - -|image0| - -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] - -这里仅介绍了列表推导式,但在字典推导式和集合推导式中同样适用。不再演示。 - -海象运算符,是一个新奇的特性,有不少人觉得这样这种特性会破坏代码的可读性。确实在一个新鲜事物刚出来时是会这样,但我相信经过时间的沉淀后,越来越多的人使用它并享受它带来的便利时,这种争议也会慢慢消失在历史的长河中。 - --------------- - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.iswbm.com/image-20200418122739417.png - diff --git a/source/c01/c01_43.md b/source/c01/c01_43.md deleted file mode 100644 index 8b8a5ed..0000000 --- a/source/c01/c01_43.md +++ /dev/null @@ -1,351 +0,0 @@ -# 1.43 求你了,别再使用 pprint 打印字典了 - -## 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.python-online.cn/image-20200320125724880.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 dd26fab..0000000 --- a/source/c01/c01_43.rst +++ /dev/null @@ -1,390 +0,0 @@ -1.43 求你了,别再使用 pprint 打印字典了 -======================================= - -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) - -输出如下,已经解决了中文的显示问题: - -|image0| - -打印双引号 -~~~~~~~~~~ - -解决了中文问题后,再来看看如何让 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 的内容后加一个 -**逗号** 就行。 - -就像下面这样。 - -|image1| - -知道了问题所在,再修改下代码 - -.. 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) - -终于成功了,太不容易了吧。 - -|image2| - -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 语句后居然可以加 逗号 - -以上。希望此文能对你有帮助。 - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.iswbm.com/20200507171451.png -.. |image1| image:: http://image.iswbm.com/20200507174459.png -.. |image2| image:: http://image.iswbm.com/20200507174802.png - diff --git a/source/c01/c01_44.md b/source/c01/c01_44.md deleted file mode 100644 index a576798..0000000 --- a/source/c01/c01_44.md +++ /dev/null @@ -1,272 +0,0 @@ -# 1.44 详解 Python 中的编码问题 - -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.python-online.cn/image-20200320125724880.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 cac3270..0000000 --- a/source/c01/c01_44.rst +++ /dev/null @@ -1,294 +0,0 @@ -1.44 详解 Python 中的编码问题 -============================= - -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) - -|image0| - -4. 编码与解码的区别 -------------------- - -编码和解码,其实就是 str 与 bytes 的相互转化的过程(Python 2 -已经远去,这里以及后面都只用 Python 3 举例) - -- **编码**\ :encode 方法,把字符串对象转化为二进制字节序列 - -- **解码**\ :decode 方法,把二进制字节序列转化为字符串对象 - -|image1| - -那么假如我们真知道了其编码格式,如何来转成 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 `__ - --------------- - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.iswbm.com/20200423185819.png -.. |image1| image:: http://image.iswbm.com/20200423190331.png - diff --git a/source/c01/c01_45.md b/source/c01/c01_45.md deleted file mode 100644 index 1ae10d4..0000000 --- a/source/c01/c01_45.md +++ /dev/null @@ -1,288 +0,0 @@ -# 1.45 Python炫技操作:花式导包的八种方法 - - - -## 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.python-online.cn/image-20200320125724880.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 79ea785..0000000 --- a/source/c01/c01_45.rst +++ /dev/null @@ -1,306 +0,0 @@ -1.45 Python炫技操作:花式导包的八种方法 -======================================= - -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 是非常有必要的。 - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! diff --git a/source/c02/c02_01.md b/source/c02/c02_01.md index 3320f33..0024d85 100644 --- a/source/c02/c02_01.md +++ b/source/c02/c02_01.md @@ -1,24 +1,9 @@ # 2.1 从性能角度初探并发编程 ---- - -作为进阶系列的一个分支「`并发编程`」,我觉得这是每个程序员都应该会的。 - -`并发编程` 这个系列,我准备了将近一个星期,从知识点梳理,到思考要举哪些例子才能更加让人容易吃透这些知识点。希望呈现出来的效果真能如想象中的那样,对小白也一样的友好。 - -昨天大致整理了下,这个系列我大概会讲如下内容(后期可能调整): -![课程大纲](https://i.loli.net/2018/05/27/5b0a1523a0730.png) - - -对于并发编程,Python 的实现,总结了一下,大致有如下三种方法: -- 多线程 -- 多进程 -- 协程(生成器) - -在之后的章节里,将陆陆续续地给大家介绍到这三个知识点。 +![](http://image.iswbm.com/20200602135014.png) -## 2.1.1 基本概念 +## 1. 基本概念 在开始讲解理论知识之前,先过一下几个基本概念。虽然咱是进阶教程,但我也希望写得更小白,更通俗易懂。 @@ -42,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) **注意** 以下代码,若要理解,对小白有如下知识点要求: @@ -213,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) 我们来分析下这个表格。 @@ -235,4 +220,4 @@ multi_process(io_simulation, type="模拟IO密集型") - 多进程虽然总是最快的,但是不一定是最优的选择,因为它需要CPU资源支持下才能体现优势 ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c02/c02_01.rst b/source/c02/c02_01.rst old mode 100755 new mode 100644 index b9b9ce0..3b42f98 --- a/source/c02/c02_01.rst +++ b/source/c02/c02_01.rst @@ -1,23 +1,10 @@ 2.1 从性能角度初探并发编程 ========================== --------------- - -作为进阶系列的一个分支「\ ``并发编程``\ 」,我觉得这是每个程序员都应该会的。 - -``并发编程`` -这个系列,我准备了将近一个星期,从知识点梳理,到思考要举哪些例子才能更加让人容易吃透这些知识点。希望呈现出来的效果真能如想象中的那样,对小白也一样的友好。 - -昨天大致整理了下,这个系列我大概会讲如下内容(后期可能调整): -|课程大纲| +|image0| -对于并发编程,Python 的实现,总结了一下,大致有如下三种方法: - 多线程 - -多进程 - 协程(生成器) - -在之后的章节里,将陆陆续续地给大家介绍到这三个知识点。 - -2.1.1 基本概念 --------------- +1. 基本概念 +----------- 在开始讲解理论知识之前,先过一下几个基本概念。虽然咱是进阶教程,但我也希望写得更小白,更通俗易懂。 @@ -45,8 +32,8 @@ .\ |image2| -2.1.2 单线程VS多线程VS多进程 ----------------------------- +2. 单线程VS多线程VS多进程 +------------------------- 文字总是苍白无力的,千言万语不如几行代码来得孔武有力。 @@ -225,8 +212,8 @@ 【多进程】-网络IO密集型花费时间:0.13074755668640137秒 【多进程】-模拟IO密集型花费时间:2.0076842308044434秒 -2.1.3 性能对比成果总结 ----------------------- +3. 性能对比成果总结 +------------------- 将结果汇总一下,制成表格。 @@ -248,14 +235,12 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image5| -.. |课程大纲| image:: https://i.loli.net/2018/05/27/5b0a1523a0730.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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.python-online.cn/20190112205155.png -.. |image4| image:: http://image.python-online.cn/20190112204930.png +.. |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 56586f4..78af0fb 100644 --- a/source/c02/c02_02.md +++ b/source/c02/c02_02.md @@ -1,5 +1,7 @@ # 2.2 创建多线程的几种方法 +![](http://image.iswbm.com/20200602135014.png) + --- @@ -8,7 +10,7 @@ 经过总结,Python创建多线程主要有如下两种方法: -- - 函数 +- 函数 - 类 接下来,我们就来揭开多线程的神秘面纱。 @@ -128,7 +130,7 @@ t.daemon = False t.name = "My-Thread" ``` -至此,Python线程基础知识,我们大概都介绍完了。 + ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](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 eac8902..2c9b6e2 --- a/source/c02/c02_02.rst +++ b/source/c02/c02_02.rst @@ -1,16 +1,15 @@ 2.2 创建多线程的几种方法 ======================== +|image0| + -------------- 今天的内容会比较基础,主要是为了让新手也能无障碍地阅读,所以还是要再巩固下基础。学完了基础,你们也就能很顺畅地跟着我的思路理解以后的文章。 经过总结,Python创建多线程主要有如下两种方法: -- - - - 函数 - +- 函数 - 类 接下来,我们就来揭开多线程的神秘面纱。 @@ -141,11 +140,10 @@ # 设置线程名 t.name = "My-Thread" -至此,Python线程基础知识,我们大概都介绍完了。 - -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c02/c02_03.md b/source/c02/c02_03.md index 5414f41..519ff92 100644 --- a/source/c02/c02_03.md +++ b/source/c02/c02_03.md @@ -1,5 +1,7 @@ # 2.3 谈谈线程中的“锁机制” +![](http://image.iswbm.com/20200602135014.png) + --- ## 1. 什么是锁? @@ -305,7 +307,7 @@ t2.start() ## 6. 饱受争议的GIL(全局锁) -在第一章的时候,我就和大家介绍到,多线程和多进程是不一样的。 +在第一节的时候,我就和大家介绍到,多线程和多进程是不一样的。 多进程是真正的并行,而多线程是伪并行,实际上他只是交替执行。 @@ -324,4 +326,4 @@ t2.start() ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c02/c02_03.rst b/source/c02/c02_03.rst old mode 100755 new mode 100644 index 6b7165b..eea5c95 --- a/source/c02/c02_03.rst +++ b/source/c02/c02_03.rst @@ -1,6 +1,8 @@ 2.3 谈谈线程中的“锁机制” ======================== +|image0| + -------------- 1. 什么是锁? @@ -333,7 +335,7 @@ lock.release()必须成对出现。否则就有可能造成死锁。 6. 饱受争议的GIL(全局锁) -------------------------- -在第一章的时候,我就和大家介绍到,多线程和多进程是不一样的。 +在第一节的时候,我就和大家介绍到,多线程和多进程是不一样的。 多进程是真正的并行,而多线程是伪并行,实际上他只是交替执行。 @@ -352,7 +354,8 @@ CPython,所以也就默许了Python具有GIL锁这个事。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c02/c02_04.md b/source/c02/c02_04.md index 5b90ec1..c6f70dc 100644 --- a/source/c02/c02_04.md +++ b/source/c02/c02_04.md @@ -1,5 +1,7 @@ # 2.4 线程消息通信机制 +![](http://image.iswbm.com/20200602135014.png) + --- 前面我已经向大家介绍了,如何使用创建线程,启动线程。相信大家都会有这样一个想法,线程无非就是创建一下,然后再`start()`下,实在是太简单了。 @@ -19,7 +21,7 @@ --- -## 2.4.1 Event事件 +## 1. Event事件 Python提供了非常简单的通信机制 `Threading.Event`,通用的条件变量。多个线程可以`等待某个事件的发生`,在事件发生后,`所有的线程`都会被`激活`。 @@ -91,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 是类似的,并没有多大区别。 @@ -152,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来通信,阻塞自己,并使对方执行。从而,达到有顺序的执行。 看下结果 @@ -165,11 +167,11 @@ hider: 我赢了 seeker: 被你找到了,哎~~~ ``` -## 2.4.3 Queue队列 +## 3. Queue队列 最后一个,队列,它是本节的重点,因为它是我们日常开发中最使用频率最高的。 -从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 Queue 对象,这些线程通过使用`put()` 和 `get()` 操作来向队列中添加或者删除元素。 +从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 Queue 对象,这些线程通过使用`put()` 和 `get()` 操作来向队列中发送和获取元素。 同样,对于Queue,我们也只需要掌握几个函数即可。 ```python @@ -178,73 +180,119 @@ from queue import Queue # 一旦>0,而消息数又达到限制,q.put()也将阻塞 q = Queue(maxsize=0) -# 阻塞程序,等待队列消息。 -q.get() - -# 获取消息,设置超时时间 -q.get(timeout=5.0) +# 默认阻塞程序,等待队列消息,可设置超时时间 +q.get(block=True, timeout=None) -# 发送消息 -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 @@ -253,6 +301,7 @@ teacher.call('小亮') 小明:到! 老师:小亮来了没? 小亮:到! +点名结束,开始上课.. ``` 其实 queue 还有一个很重要的方法,Queue.task_done() @@ -269,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` 的指令,所以只能适用于某些简单的场景中。 @@ -279,4 +438,4 @@ teacher.call('小亮') ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c02/c02_04.rst b/source/c02/c02_04.rst old mode 100755 new mode 100644 index 3649ea0..408c43a --- a/source/c02/c02_04.rst +++ b/source/c02/c02_04.rst @@ -1,6 +1,8 @@ 2.4 线程消息通信机制 ==================== +|image0| + -------------- 前面我已经向大家介绍了,如何使用创建线程,启动线程。相信大家都会有这样一个想法,线程无非就是创建一下,然后再\ ``start()``\ 下,实在是太简单了。 @@ -18,8 +20,8 @@ threading.Condition - queue.Queue -------------- -2.4.1 Event事件 ---------------- +1. Event事件 +------------ Python提供了非常简单的通信机制 ``Threading.Event``\ ,通用的条件变量。多个线程可以\ ``等待某个事件的发生``\ ,在事件发生后,\ ``所有的线程``\ 都会被\ ``激活``\ 。 @@ -97,8 +99,8 @@ Python提供了非常简单的通信机制 可见在所有线程都启动(\ ``start()``\ )后,并不会执行完,而是都在\ ``self.event.wait()``\ 止住了,需要我们通过\ ``event.set()``\ 来给所有线程发送执行指令才能往下执行。 -2.4.2 Condition ---------------- +2. Condition +------------ Condition和Event 是类似的,并没有多大区别。 @@ -163,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来通信,阻塞自己,并使对方执行。从而,达到有顺序的执行。 看下结果 @@ -177,15 +179,15 @@ Condition和Event 是类似的,并没有多大区别。 hider: 我赢了 seeker: 被你找到了,哎~~~ -2.4.3 Queue队列 ---------------- +3. Queue队列 +------------ 最后一个,队列,它是本节的重点,因为它是我们日常开发中最使用频率最高的。 从一个线程向另一个线程发送数据最安全的方式可能就是使用 queue 库中的队列了。创建一个被多个线程共享的 Queue 对象,这些线程通过使用\ ``put()`` 和 ``get()`` -操作来向队列中添加或者删除元素。 +操作来向队列中发送和获取元素。 同样,对于Queue,我们也只需要掌握几个函数即可。 @@ -196,24 +198,27 @@ Condition和Event 是类似的,并没有多大区别。 # 一旦>0,而消息数又达到限制,q.put()也将阻塞 q = Queue(maxsize=0) - # 阻塞程序,等待队列消息。 - q.get() - - # 获取消息,设置超时时间 - q.get(timeout=5.0) + # 默认阻塞程序,等待队列消息,可设置超时时间 + q.get(block=True, timeout=None) - # 发送消息 - 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() # 检测队列里消息是否已满 @@ -225,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") 运行结果如下 @@ -275,6 +320,7 @@ Condition和Event 是类似的,并没有多大区别。 小明:到! 老师:小亮来了没? 小亮:到! + 点名结束,开始上课.. 其实 queue 还有一个很重要的方法,Queue.task_done() @@ -293,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`` 和 @@ -307,7 +465,8 @@ Queue.task_done(),说明队列这个任务已经结束了。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c02/c02_05.md b/source/c02/c02_05.md index 4c753e6..52d86d8 100644 --- a/source/c02/c02_05.md +++ b/source/c02/c02_05.md @@ -1,14 +1,13 @@ # 2.5 线程中的信息隔离 +![](http://image.iswbm.com/20200602135014.png) + --- 上一篇我们说,线程与线程之间要通过消息通信来控制程序的执行。 讲完了消息通信,今天就来探讨下线程里的`信息隔离`是如何做到的。 ->**大家注意**: ->`信息隔离`,这并不是官方命名的名词,也不是网上广为流传的名词。是我为了方便理解而自创的,大家知道就好咯。 - -## 2.5.1 初步认识信息隔离 +## 1. 初步认识信息隔离 什么是`信息隔离`? @@ -74,7 +73,7 @@ if __name__ == '__main__': 所以如果想在当前线程保存一个全局值,并且各自线程(包括主线程)互不干扰,使用local类吧。 -## 2.5.2 信息隔离的意义何在 +## 2. 信息隔离的意义何在 细心的你,一定已经发现了,上面那个例子,即使我们不用`threading.local`来做信息隔离,两个线程`self.getName()`本身就是隔离的,没有任何关系的。因为这两个线程是由一个class实例出的两个不同的实例对象。自然是可以不用做隔离,因为其本身就是隔离的。 @@ -137,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.python-online.cn/image-20200320125724880.png) +![](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 daa59c4..e9038f3 --- a/source/c02/c02_05.rst +++ b/source/c02/c02_05.rst @@ -1,16 +1,14 @@ 2.5 线程中的信息隔离 ==================== +|image0| + -------------- 上一篇我们说,线程与线程之间要通过消息通信来控制程序的执行。 -讲完了消息通信,今天就来探讨下线程里的\ ``信息隔离``\ 是如何做到的。 ->\ **大家注意**\ : ->\ ``信息隔离``\ ,这并不是官方命名的名词,也不是网上广为流传的名词。是我为了方便理解而自创的,大家知道就好咯。 - -2.5.1 初步认识信息隔离 ----------------------- +讲完了消息通信,今天就来探讨下线程里的\ ``信息隔离``\ 是如何做到的。 ## +1. 初步认识信息隔离 什么是\ ``信息隔离``\ ? @@ -80,8 +78,8 @@ 所以如果想在当前线程保存一个全局值,并且各自线程(包括主线程)互不干扰,使用local类吧。 -2.5.2 信息隔离的意义何在 ------------------------- +2. 信息隔离的意义何在 +--------------------- 细心的你,一定已经发现了,上面那个例子,即使我们不用\ ``threading.local``\ 来做信息隔离,两个线程\ ``self.getName()``\ 本身就是隔离的,没有任何关系的。因为这两个线程是由一个class实例出的两个不同的实例对象。自然是可以不用做隔离,因为其本身就是隔离的。 @@ -146,120 +144,8 @@ 如果是在这种场景下,要做到线程之间的状态信息的隔离,就肯定要借助\ ``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| -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c02/c02_06.md b/source/c02/c02_06.md index 4083c47..95e51f5 100644 --- a/source/c02/c02_06.md +++ b/source/c02/c02_06.md @@ -1,9 +1,6 @@ -# 2.6 线程池与进程池的创建 +# 2.6 线程池创建的几种方法 ---- - ->**友情提醒**: ->本系列所有的代码均在Python3下编写。Python2中可能有所差异。 +![](http://image.iswbm.com/20200602135014.png) ## 1. 线程池的创建 @@ -142,9 +139,5 @@ running thread-123145485651968:1 -## 2. 进程池的创建 - - - ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](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 6d314fc..78be5e5 --- a/source/c02/c02_06.rst +++ b/source/c02/c02_06.rst @@ -1,10 +1,7 @@ -2.6 线程池与进程池的创建 +2.6 线程池创建的几种方法 ======================== --------------- - - **友情提醒**\ : - 本系列所有的代码均在Python3下编写。Python2中可能有所差异。 +|image0| 1. 线程池的创建 --------------- @@ -149,12 +146,10 @@ 构建线程池的方法,是可以很灵活的,大家有空可以自己多研究。但是建议只要掌握一种自己熟悉的,能快速上手的就好了。 -2. 进程池的创建 ---------------- - -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c02/c02_07.md b/source/c02/c02_07.md index c61bc4d..a8af238 100644 --- a/source/c02/c02_07.md +++ b/source/c02/c02_07.md @@ -1,5 +1,7 @@ # 2.7 从生成器使用入门协程 +![](http://image.iswbm.com/20200602135014.png) + --- 从今天开始,我们将开始进入Python的难点,那就是`协程`。 @@ -13,7 +15,7 @@ >**友情提醒**: >本系列所有的代码均在Python3下编写。Python2中可能有所差异。 -## 2.7.1 可迭代、迭代器、生成器 +## 1. 可迭代、迭代器、生成器 初学Python的时候,对于这三货真的是傻傻分不清。甚至还认为他们是等价的。 @@ -87,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) @@ -221,7 +223,7 @@ if __name__ == '__main__': 可迭代对象和迭代器,是将所有的值都生成存放在内存中,而`生成器`则是需要元素才临时生成,节省时间,节省空间。 -## 2.7.2 如何运行/激活生成器 +## 2. 如何运行/激活生成器 由于生成器并不是一次生成所有元素,而是一次一次的执行返回,那么如何刺激生成器执行(或者说激活)呢? @@ -254,7 +256,7 @@ if __name__ == '__main__': 3 ``` -## 2.7.3 生成器的执行状态 +## 3. 生成器的执行状态 生成器在其生命周期中,会有如下四个状态 >`GEN_CREATED` # 等待开始执行 @@ -293,7 +295,7 @@ GEN_SUSPENDED GEN_CLOSED ``` -## 2.7.4 生成器的异常处理 +## 4. 生成器的异常处理 在生成器工作过程中,若生成器不满足生成元素的条件,就`会`/`应该` 抛出异常(`StopIteration`)。 @@ -317,7 +319,7 @@ if __name__ == '__main__': next(gen) ``` -## 2.7.5 从生成器过渡到协程:yield +## 5. 从生成器过渡到协程:yield 通过上面的介绍,我们知道生成器为我们引入了暂停函数执行(`yield`)的功能。当有了暂停的功能之后,人们就想能不能在生成器暂停的时候向其发送一点东西(其实上面也有提及:`send(None)`)。这种向暂停的生成器发送信息的功能通过 `PEP 342` 进入 `Python 2.5` 中,并催生了 `Python` 中`协程`的诞生。根据 `wikipedia` 中的定义 >协程是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序。 @@ -364,4 +366,4 @@ if __name__ == '__main__': 下一章,我将讲一个Python3.5新引入的语法:`yield from`。篇幅也比较多,所以就单独拿出来讲。 ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c02/c02_07.rst b/source/c02/c02_07.rst old mode 100755 new mode 100644 index a98ee67..93f6a58 --- a/source/c02/c02_07.rst +++ b/source/c02/c02_07.rst @@ -1,6 +1,8 @@ 2.7 从生成器使用入门协程 ======================== +|image0| + -------------- 从今天开始,我们将开始进入Python的难点,那就是\ ``协程``\ 。 @@ -14,8 +16,8 @@ **友情提醒**\ : 本系列所有的代码均在Python3下编写。Python2中可能有所差异。 -2.7.1 可迭代、迭代器、生成器 ----------------------------- +1. 可迭代、迭代器、生成器 +------------------------- 初学Python的时候,对于这三货真的是傻傻分不清。甚至还认为他们是等价的。 @@ -96,7 +98,7 @@ 3. 所以,最好的判断方法应该是通过 ``for循环``\ 或者\ ``iter()`` 去真实运行。 -|image0| +|image1| 接下来是,\ ``迭代器``\ 。 对比可迭代对象,\ ``迭代器``\ 其实就只是多了一个函数而已。就是\ ``__next__()``\ ,我们可以不再使用\ ``for``\ 循环来间断获取元素值。而可以直接使用next()方法来实现。 @@ -232,8 +234,8 @@ 可迭代对象和迭代器,是将所有的值都生成存放在内存中,而\ ``生成器``\ 则是需要元素才临时生成,节省时间,节省空间。 -2.7.2 如何运行/激活生成器 -------------------------- +2. 如何运行/激活生成器 +---------------------- 由于生成器并不是一次生成所有元素,而是一次一次的执行返回,那么如何刺激生成器执行(或者说激活)呢? @@ -267,8 +269,8 @@ 2 3 -2.7.3 生成器的执行状态 ----------------------- +3. 生成器的执行状态 +------------------- 生成器在其生命周期中,会有如下四个状态 >\ ``GEN_CREATED`` # 等待开始执行 >\ ``GEN_RUNNING`` # @@ -308,14 +310,14 @@ 1 GEN_CLOSED -2.7.4 生成器的异常处理 ----------------------- +4. 生成器的异常处理 +------------------- 在生成器工作过程中,若生成器不满足生成元素的条件,就\ ``会``/``应该`` 抛出异常(\ ``StopIteration``\ )。 通过列表生成式构建的生成器,其内部已经自动帮我们实现了抛出异常这一步。不信我们来看一下。 -|image1| +|image2| 所以我们在自己定义一个生成器的时候,我们也应该在不满足生成元素条件的时候,抛出异常。 拿上面的代码来修改一下。 @@ -335,8 +337,8 @@ next(gen) next(gen) -2.7.5 从生成器过渡到协程:yield -------------------------------- +5. 从生成器过渡到协程:yield +---------------------------- 通过上面的介绍,我们知道生成器为我们引入了暂停函数执行(\ ``yield``\ )的功能。当有了暂停的功能之后,人们就想能不能在生成器暂停的时候向其发送一点东西(其实上面也有提及:\ ``send(None)``\ )。这种向暂停的生成器发送信息的功能通过 ``PEP 342`` 进入 ``Python 2.5`` 中,并催生了 ``Python`` @@ -390,11 +392,10 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image3| -.. |image0| image:: http://image.python-online.cn/20190527123516.png -.. |image1| image:: https://i.loli.net/2018/05/19/5affd48c34e3f.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 fd3ba5d..144925f 100644 --- a/source/c02/c02_08.md +++ b/source/c02/c02_08.md @@ -1,5 +1,7 @@ # 2.8 深入理解yield from语法 +![](http://image.iswbm.com/20200602135014.png) + --- 直到上一篇,我们终于迎来了Python并发编程中,最高级、最重要、当然也是最难的知识点--`协程`。 @@ -9,7 +11,7 @@ >**友情提醒**: >本系列所有的代码均在Python3下编写。Python2中可能有所差异。 -## 2.8.1 为什么要使用协程 +## 1. 为什么要使用协程 在上一篇中,我们从生成器的基本认识与使用,成功过渡到了协程。 @@ -49,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`的例子来对比看下。 @@ -102,7 +104,7 @@ print(list(new_list)) 由上面两种方式对比,可以看出,yield from后面加上可迭代对象,他可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰。 -### 复杂应用:生成器的嵌套 +### 2.2 复杂应用:生成器的嵌套 如果你认为只是 `yield from` 仅仅只有上述的功能的话,那你就太小瞧了它,它的更强大的功能还在后面。 @@ -213,7 +215,7 @@ if __name__ == '__main__': ``` -## 2.8.3 为什么要使用yield from +## 3. 为什么要使用yield from 学到这里,我相信你肯定要问,既然委托生成器,起到的只是一个双向通道的作用,我还需要委托生成器做什么?我调用方直接调用子生成器不就好啦? @@ -221,7 +223,7 @@ if __name__ == '__main__': 下面我们来一起探讨一下,到底yield from 有什么过人之处,让我们非要用它不可。 -### 因为它可以帮我们处理异常 +### 3.1 因为它可以帮我们处理异常 如果我们去掉委托生成器,而直接调用子生成器。那我们就需要把代码改成像下面这样,我们需要自己捕获异常并处理。而不像使`yield from`那样省心。 @@ -333,4 +335,4 @@ RESULT = _r --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c02/c02_08.rst b/source/c02/c02_08.rst old mode 100755 new mode 100644 index 248cda8..2c142be --- a/source/c02/c02_08.rst +++ b/source/c02/c02_08.rst @@ -1,6 +1,8 @@ 2.8 深入理解yield from语法 ========================== +|image0| + -------------- 直到上一篇,我们终于迎来了Python并发编程中,最高级、最重要、当然也是最难的知识点–\ ``协程``\ 。 @@ -10,8 +12,8 @@ **友情提醒**\ : 本系列所有的代码均在Python3下编写。Python2中可能有所差异。 -2.8.1 为什么要使用协程 ----------------------- +1. 为什么要使用协程 +------------------- 在上一篇中,我们从生成器的基本认识与使用,成功过渡到了协程。 @@ -52,8 +54,8 @@ 协程是在单线程里实现任务的切换的 2. 利用同步的方式去实现异步 3. 不再需要锁,提高了并发性能 -2.8.2 yield from的用法详解 --------------------------- +2. yield from的用法详解 +----------------------- ``yield from`` 是在Python3.3才出现的语法。所以这个特性在Python2中是没有的。 @@ -61,8 +63,8 @@ ``yield from`` 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。 -简单应用:拼接可迭代对象 -~~~~~~~~~~~~~~~~~~~~~~~~ +2.1 简单应用:拼接可迭代对象 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 我们可以用一个使用\ ``yield``\ 和一个使用\ ``yield from``\ 的例子来对比看下。 @@ -112,8 +114,8 @@ 由上面两种方式对比,可以看出,yield from后面加上可迭代对象,他可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰。 -复杂应用:生成器的嵌套 -~~~~~~~~~~~~~~~~~~~~~~ +2.2 复杂应用:生成器的嵌套 +~~~~~~~~~~~~~~~~~~~~~~~~~~ 如果你认为只是 ``yield from`` 仅仅只有上述的功能的话,那你就太小瞧了它,它的更强大的功能还在后面。 @@ -229,8 +231,8 @@ from后面加上可迭代对象,他可以把可迭代对象里的每个元素 计算完毕!! 总共传入 3 个数值, 总和:60,平均数:20.0 -2.8.3 为什么要使用yield from ----------------------------- +3. 为什么要使用yield from +------------------------- 学到这里,我相信你肯定要问,既然委托生成器,起到的只是一个双向通道的作用,我还需要委托生成器做什么?我调用方直接调用子生成器不就好啦? @@ -239,8 +241,8 @@ from后面加上可迭代对象,他可以把可迭代对象里的每个元素 下面我们来一起探讨一下,到底yield from 有什么过人之处,让我们非要用它不可。 -因为它可以帮我们处理异常 -~~~~~~~~~~~~~~~~~~~~~~~~ +3.1 因为它可以帮我们处理异常 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 如果我们去掉委托生成器,而直接调用子生成器。那我们就需要把代码改成像下面这样,我们需要自己捕获异常并处理。而不像使\ ``yield from``\ 那样省心。 @@ -359,7 +361,8 @@ from后面加上可迭代对象,他可以把可迭代对象里的每个元素 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c02/c02_09.md b/source/c02/c02_09.md index 00c4519..bde67f4 100644 --- a/source/c02/c02_09.md +++ b/source/c02/c02_09.md @@ -1,5 +1,7 @@ # 2.9 初识异步IO框架:asyncio 上篇 +![](http://image.iswbm.com/20200602135014.png) + --- 通过前两节的铺垫(关于协程的使用),今天我们终于可以来介绍我们整个系列的重点 -- `asyncio`。 @@ -17,7 +19,7 @@ 那是因为,我们现在还缺少一个成熟的框架,帮助你完成那些复杂的动作。这个时候,`ayncio`就这么应运而生了。 -## 2.9.1 如何定义/创建协程 +## 1. 如何定义/创建协程 还记得在前两章节的时候,我们创建了生成器,是如何去检验我们创建的是不是生成器对象吗? @@ -65,7 +67,7 @@ if __name__ == '__main__': print(isinstance(coroutine, Coroutine)) # False ``` -## 2.9.2 asyncio的几个概念 +## 2. asyncio的几个概念 在了解`asyncio`的使用方法前,首先有必要先介绍一下,这几个贯穿始终的概念。 @@ -77,7 +79,7 @@ if __name__ == '__main__': 这几个概念,干看可能很难以理解,没事,往下看实例,然后再回来,我相信你一定能够理解。 -## 2.9.3 学习协程是如何工作的 +## 3. 学习协程是如何工作的 协程完整的工作流程是这样的 - 定义/创建协程对象 @@ -110,7 +112,7 @@ loop.run_until_complete(task) Hello, World ``` -## 2.9.4 await与yield对比 +## 4. await与yield对比 前面我们说,`await`用于挂起阻塞的异步调用接口。其作用在`一定程度上`类似于yield。 @@ -160,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的返回值的,这就要用到回调了。 @@ -220,4 +222,4 @@ emmm,和上面的结果是一样的。nice --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c02/c02_09.rst b/source/c02/c02_09.rst old mode 100755 new mode 100644 index 2709d13..7edb981 --- a/source/c02/c02_09.rst +++ b/source/c02/c02_09.rst @@ -1,6 +1,8 @@ 2.9 初识异步IO框架:asyncio 上篇 ================================ +|image0| + -------------- 通过前两节的铺垫(关于协程的使用),今天我们终于可以来介绍我们整个系列的重点 @@ -20,8 +22,8 @@ 那是因为,我们现在还缺少一个成熟的框架,帮助你完成那些复杂的动作。这个时候,\ ``ayncio``\ 就这么应运而生了。 -2.9.1 如何定义/创建协程 ------------------------ +1. 如何定义/创建协程 +-------------------- 还记得在前两章节的时候,我们创建了生成器,是如何去检验我们创建的是不是生成器对象吗? @@ -73,8 +75,8 @@ print(isinstance(coroutine, Generator)) # True print(isinstance(coroutine, Coroutine)) # False -2.9.2 asyncio的几个概念 ------------------------ +2. asyncio的几个概念 +-------------------- 在了解\ ``asyncio``\ 的使用方法前,首先有必要先介绍一下,这几个贯穿始终的概念。 @@ -90,8 +92,8 @@ 这几个概念,干看可能很难以理解,没事,往下看实例,然后再回来,我相信你一定能够理解。 -2.9.3 学习协程是如何工作的 --------------------------- +3. 学习协程是如何工作的 +----------------------- 协程完整的工作流程是这样的 - 定义/创建协程对象 - 将协程转为task任务 - 定义事件循环对象容器 - 将task任务扔进事件循环对象中触发 @@ -124,8 +126,8 @@ Hello, World -2.9.4 await与yield对比 ----------------------- +4. await与yield对比 +------------------- 前面我们说,\ ``await``\ 用于挂起阻塞的异步调用接口。其作用在\ ``一定程度上``\ 类似于yield。 @@ -177,8 +179,8 @@ 好了,接下来,开始验证。 |验证通过| -2.9.5 绑定回调函数 ------------------- +5. 绑定回调函数 +--------------- 异步IO的实现原理,就是在IO高的地方挂起,等IO结束后,再继续执行。在绝大部分时候,我们后续的代码的执行是需要依赖IO的返回值的,这就要用到回调了。 @@ -244,12 +246,11 @@ emmm,和上面的结果是一样的。nice -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image4| +.. |image0| image:: http://image.iswbm.com/20200602135014.png .. |普通函数中 不能使用 await| image:: https://i.loli.net/2018/05/26/5b09794f45340.png .. |async 中 不能使用yield| image:: https://i.loli.net/2018/05/26/5b0978b646230.png .. |验证通过| image:: https://i.loli.net/2018/05/26/5b09814dc4714.png +.. |image4| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c02/c02_10.md b/source/c02/c02_10.md index d015315..e219c04 100644 --- a/source/c02/c02_10.md +++ b/source/c02/c02_10.md @@ -1,5 +1,7 @@ # 2.10 深入异步IO框架:asyncio 中篇 +![](http://image.iswbm.com/20200602135014.png) + --- 今天的内容其实还挺多的,我准备了三天,到今天才整理完毕。希望大家看完,有所收获的,能给小明一个赞。这就是对小明最大的鼓励了。 @@ -25,7 +27,7 @@ 那么这一节,我们就来看下,协程中的`多任务`。 -## 2.10.1 协程中的并发 +## 1. 协程中的并发 协程的并发,和线程一样。举个例子来说,就好像 一个人同时吃三个馒头,咬了第一个馒头一口,就得等这口咽下去,才能去啃第其他两个馒头。就这样交替换着吃。 @@ -112,7 +114,7 @@ Task ret: Done after 2s Task ret: Done after 4s ``` -## 2.10.2 协程中的嵌套 +## 2. 协程中的嵌套 使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来。 @@ -234,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任务)有哪些状态。 @@ -287,7 +289,7 @@ if __name__ == '__main__': 假如,执行后 立马按下 Ctrl+C,则会触发task取消,就会打印 `Pending` -> `Cancelling` -> `Cancelling` 的状态变化。 -## 2.10.4 gather与wait +## 4. gather与wait 还记得上面我说,把多个协程注册进一个事件循环中有两种方法吗? - 使用`asyncio.wait()` @@ -318,7 +320,7 @@ async def factorial(name, number): print("Task %s: factorial(%s) = %s" % (name, number, f)) ``` -## 2.10.5 接收参数方式 +## 5. 接收参数方式 ### asyncio.wait @@ -386,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 @@ -412,7 +414,7 @@ for result in results: print('Task ret: ', result) ``` -## 2.10.7wait有控制功能 +## 7. wait有控制功能 ```python import asyncio @@ -458,4 +460,4 @@ loop.close() ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c02/c02_10.rst b/source/c02/c02_10.rst old mode 100755 new mode 100644 index e7d699a..0451b3a --- a/source/c02/c02_10.rst +++ b/source/c02/c02_10.rst @@ -1,6 +1,8 @@ 2.10 深入异步IO框架:asyncio 中篇 ================================= +|image0| + -------------- 今天的内容其实还挺多的,我准备了三天,到今天才整理完毕。希望大家看完,有所收获的,能给小明一个赞。这就是对小明最大的鼓励了。 @@ -23,8 +25,8 @@ 那么这一节,我们就来看下,协程中的\ ``多任务``\ 。 -2.10.1 协程中的并发 -------------------- +1. 协程中的并发 +--------------- 协程的并发,和线程一样。举个例子来说,就好像 一个人同时吃三个馒头,咬了第一个馒头一口,就得等这口咽下去,才能去啃第其他两个馒头。就这样交替换着吃。 @@ -119,8 +121,8 @@ Task ret: Done after 2s Task ret: Done after 4s -2.10.2 协程中的嵌套 -------------------- +2. 协程中的嵌套 +--------------- 使用async可以定义协程,协程用于耗时的io操作,我们也可以封装更多的io操作过程,这样就实现了嵌套的协程,即一个协程中await了另外一个协程,如此连接起来。 @@ -252,8 +254,8 @@ # 【重点】:await一个内部协程 return await _wait(fs, timeout, return_when, loop) -2.10.3协程中的状态 ------------------- +3. 协程中的状态 +--------------- 还记得我们在讲生成器的时候,有提及过生成器的状态。同样,在协程这里,我们也了解一下协程(准确的说,应该是Future对象,或者Task任务)有哪些状态。 @@ -308,8 +310,8 @@ 假如,执行后 立马按下 Ctrl+C,则会触发task取消,就会打印 ``Pending`` -> ``Cancelling`` -> ``Cancelling`` 的状态变化。 -2.10.4 gather与wait -------------------- +4. gather与wait +--------------- 还记得上面我说,把多个协程注册进一个事件循环中有两种方法吗? - 使用\ ``asyncio.wait()`` @@ -344,8 +346,8 @@ f *= i print("Task %s: factorial(%s) = %s" % (name, number, f)) -2.10.5 接收参数方式 -------------------- +5. 接收参数方式 +--------------- asyncio.wait ~~~~~~~~~~~~ @@ -424,8 +426,8 @@ asyncio.gather loop.run_until_complete(asyncio.gather(group1, group2, group3)) -2.10.6 返回结果不同 -------------------- +6. 返回结果不同 +--------------- .. _asyncio.wait-1: @@ -458,8 +460,8 @@ asyncio.gather for result in results: print('Task ret: ', result) -2.10.7wait有控制功能 --------------------- +7. wait有控制功能 +----------------- .. code:: python @@ -507,7 +509,8 @@ asyncio.gather -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c02/c02_11.md b/source/c02/c02_11.md index f4f9298..34dba9b 100644 --- a/source/c02/c02_11.md +++ b/source/c02/c02_11.md @@ -1,5 +1,7 @@ # 2.11 实战异步IO框架:asyncio 下篇 +![](http://image.iswbm.com/20200602135014.png) + --- 前面两节,我们讲了协程中的单任务和多任务 @@ -14,7 +16,7 @@ - Redis的基本使用 - asyncio的使用 -## 2.11.1 动态添加协程 +## 1. 动态添加协程 在实战之前,我们要先了解下在asyncio中如何将协程态添加到事件循环中的。这是前提。 如何实现呢,有两种方法: @@ -112,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的队列。主线程中有一个是无限循环,用户消费队列。 @@ -211,4 +213,4 @@ Thu May 31 23:42:48 2018 ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c02/c02_11.rst b/source/c02/c02_11.rst old mode 100755 new mode 100644 index 842b33a..27271cf --- a/source/c02/c02_11.rst +++ b/source/c02/c02_11.rst @@ -1,6 +1,8 @@ 2.11 实战异步IO框架:asyncio 下篇 ================================= +|image0| + -------------- 前面两节,我们讲了协程中的单任务和多任务 @@ -12,8 +14,8 @@ 在实战中,将会用到以下知识点: - 多线程的基本使用 - Queue消息队列的使用 - Redis的基本使用 - asyncio的使用 -2.11.1 动态添加协程 -------------------- +1. 动态添加协程 +--------------- 在实战之前,我们要先了解下在asyncio中如何将协程态添加到事件循环中的。这是前提。 @@ -117,19 +119,19 @@ 第一个 协程运行完.. Thu May 31 22:23:41 2018 -2.11.2 利用redis实现动态添加任务 --------------------------------- +2. 利用redis实现动态添加任务 +---------------------------- 对于并发任务,通常是用生成消费模型,对队列的处理可以使用类似master-worker的方式,master主要用户获取队列的msg,worker用户处理消息。 为了简单起见,并且协程更适合单线程的方式,我们的主线程用来监听队列,子线程用于处理队列。这里使用redis的队列。主线程中有一个是无限循环,用户消费队列。 先安装Redis 到 https://github.com/MicrosoftArchive/redis/releases 下载 -|image0| 解压到你的路径。 |image1| +|image1| 解压到你的路径。 |image2| -然后,在当前路径运行cmd,运行redis的服务端。 |image2| +然后,在当前路径运行cmd,运行redis的服务端。 |image3| 服务开启后,我们就可以运行我们的客户端了。 -并依次输入key=queue,value=5,3,1的消息。 |image3| +并依次输入key=queue,value=5,3,1的消息。 |image4| 一切准备就绪之后,我们就可以运行我们的代码了。 @@ -216,13 +218,12 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image5| -.. |image0| image:: https://i.loli.net/2018/06/03/5b13ba8525bcf.png -.. |image1| image:: https://i.loli.net/2018/06/03/5b13ba9f66baa.png -.. |image2| image:: https://i.loli.net/2018/06/03/5b13bab682a32.png -.. |image3| image:: https://i.loli.net/2018/06/03/5b13bad79f5ce.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: https://i.loli.net/2018/06/03/5b13ba8525bcf.png +.. |image2| image:: https://i.loli.net/2018/06/03/5b13ba9f66baa.png +.. |image3| image:: https://i.loli.net/2018/06/03/5b13bab682a32.png +.. |image4| image:: https://i.loli.net/2018/06/03/5b13bad79f5ce.png +.. |image5| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c02/c02_12.md b/source/c02/c02_12.md index 40ecd8f..45c34a7 100644 --- a/source/c02/c02_12.md +++ b/source/c02/c02_12.md @@ -1,5 +1,7 @@ # 2.12 生成器与协程,你分清了吗? +![](http://image.iswbm.com/20200602135014.png) + 之前写的 `并发编程` 系列文章,是我迄今务止,得到的反馈最好的一个系列,也是因为它我才能认识这么多优秀的朋友。 @@ -108,4 +110,4 @@ MING.send('香肠') --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c02/c02_12.rst b/source/c02/c02_12.rst index e7812e2..1efdb19 100644 --- a/source/c02/c02_12.rst +++ b/source/c02/c02_12.rst @@ -1,6 +1,8 @@ 2.12 生成器与协程,你分清了吗? =============================== +|image0| + 之前写的 ``并发编程`` 系列文章,是我迄今务止,得到的反馈最好的一个系列,也是因为它我才能认识这么多优秀的朋友。 @@ -107,7 +109,8 @@ yield -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c02/c02_13.md b/source/c02/c02_13.md index 17c3df8..7a3f71a 100644 --- a/source/c02/c02_13.md +++ b/source/c02/c02_13.md @@ -1,5 +1,7 @@ # 2.13 I/O多路复用:select/poll/epoll +![](http://image.iswbm.com/20200602135014.png) + 在讲解 select/poll/epoll 之前 ,先来了解一下什么是 `I/O多路复用` `I/O多路复用` ,英文全称为 `I/O multiplexing`,这个中文翻译和把 socket 翻译成 套接字一样,影响了我们对其概念的理解。 diff --git a/source/c02/c02_13.rst b/source/c02/c02_13.rst index 3c53390..3656b5d 100644 --- a/source/c02/c02_13.rst +++ b/source/c02/c02_13.rst @@ -1,6 +1,8 @@ 2.13 I/O多路复用:select/poll/epoll =================================== +|image0| + 在讲解 select/poll/epoll 之前 ,先来了解一下什么是 ``I/O多路复用`` ``I/O多路复用`` ,英文全称为 ``I/O multiplexing``\ ,这个中文翻译和把 @@ -12,3 +14,6 @@ socket 翻译成 套接字一样,影响了我们对其概念的理解。 ---------------------- 举个现实生活中的例子。 + +.. |image0| image:: http://image.iswbm.com/20200602135014.png + diff --git a/source/c02/c02_14.md b/source/c02/c02_14.md index 6a07cae..2c2c526 100644 --- a/source/c02/c02_14.md +++ b/source/c02/c02_14.md @@ -1,5 +1,7 @@ # 2.14 浅谈线程安全那些事儿 +![](http://image.iswbm.com/20200602135014.png) + 在并发编程时,如果多个线程访问同一资源,我们需要保证访问的时候不会产生冲突,数据修改不会发生错误,这就是我们常说的 **线程安全** 。 那什么情况下,访问数据时是安全的?什么情况下,访问数据是不安全的?如何知道你的代码是否线程安全?要如何访问数据才能保证数据的安全? @@ -178,4 +180,4 @@ https://juejin.im/post/5b129a1be51d45068a6c91d4#comment -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c02/c02_14.rst b/source/c02/c02_14.rst index a92ab36..3be3f8f 100644 --- a/source/c02/c02_14.rst +++ b/source/c02/c02_14.rst @@ -1,6 +1,8 @@ 2.14 浅谈线程安全那些事儿 ========================= +|image0| + 在并发编程时,如果多个线程访问同一资源,我们需要保证访问的时候不会产生冲突,数据修改不会发生错误,这就是我们常说的 **线程安全** 。 @@ -97,7 +99,7 @@ operation**\ ),指不会被线程调度机制打断的操作,这种操作 当我们还是无法确定我们的代码是否具有原子性的时候,可以尝试通过 ``dis`` 模块里的 dis 函数来查看 -|image0| +|image1| 当我们执行这段代码时,可以看到 ``number += 1`` 这一行代码,由两条字节码实现。 @@ -113,7 +115,7 @@ operation**\ ),指不会被线程调度机制打断的操作,这种操作 这里我拿字典的 update 操作举例,代码和执行过程如下图 -|image1| +|image2| 从截图里可以看到,\ ``info.update(new)`` 虽然也分为好几个操作 @@ -191,11 +193,10 @@ https://zhuanlan.zhihu.com/p/34150765 https://juejin.im/post/5b129a1be51d45068a6c91d4#comment -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image3| -.. |image0| image:: http://image.iswbm.com/20200506080445.png -.. |image1| image:: http://image.iswbm.com/20200506081541.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200506080445.png +.. |image2| image:: http://image.iswbm.com/20200506081541.png +.. |image3| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c03/c03_01.md b/source/c03/c03_01.md index 542646b..5c3562e 100644 --- a/source/c03/c03_01.md +++ b/source/c03/c03_01.md @@ -1,5 +1,7 @@ # 3.1 装饰器进阶用法详解 +![](http://image.iswbm.com/20200602135014.png) + --- 对于每一个学习 Python 的同学,想必对 `@` 符号一定不陌生了,正如你所知, @ 符号是装饰器的语法糖,@符号后面的函数就是我们本文的主角:**装饰器**。 @@ -16,9 +18,9 @@ 当时带着这两个问题,我就开始系统的学习装饰器的所有内容。这些一直整理在自己的博客中,今天对其进行了大量的补充和勘误,发表在这里分享给大家。希望对刚入门以及进阶的朋友可以提供一些参考。 -![](http://image.python-online.cn/20190811100737.png) +![](http://image.iswbm.com/20190811100737.png) -## 3.1.1 Hello,装饰器 +## 1. Hello,装饰器 装饰器的使用方法很固定 - 先定义一个装饰器(帽子) @@ -45,7 +47,7 @@ def function(): 接下来,我将以实例讲解,如何编写出各种简单及复杂的装饰器。 -## 3.1.2 入门:日志打印器 +## 2. 入门:日志打印器 首先是**日志打印器**。 实现的功能: @@ -81,7 +83,7 @@ add(200, 50) 主人,我执行完啦。 ``` -## 3.1.3 入门:时间计时器 +## 3. 入门:时间计时器 再来看看 **时间计时器** 实现功能:顾名思义,就是计算一个函数的执行时长。 @@ -116,7 +118,7 @@ want_sleep(10) ``` -## 3.1.4 进阶:带参数的函数装饰器 +## 4. 进阶:带参数的函数装饰器 通过上面两个简单的入门示例,你应该能体会到装饰器的工作原理了。 @@ -186,7 +188,7 @@ jack() ------------ hello. ``` -## 3.1.5 高阶:不带参数的类装饰器 +## 5. 高阶:不带参数的类装饰器 以上都是基于函数实现的装饰器,在阅读别人代码时,还可以时常发现还有基于类实现的装饰器。 @@ -218,7 +220,7 @@ say("hello") say hello! ``` -## 3.1.6 高阶:带参数的类装饰器 +## 6. 高阶:带参数的类装饰器 上面不带参数的例子,你发现没有,只能打印`INFO`级别的日志,正常情况下,我们还需要打印`DEBUG` `WARNING`等级别的日志。 这就需要给类装饰器传入参数,给这个函数指定级别了。 @@ -251,7 +253,7 @@ say("hello") say hello! ``` -## 3.1.7 使用偏函数与类实现装饰器 +## 7. 使用偏函数与类实现装饰器 绝大多数装饰器都是基于函数和闭包实现的,但这并非制造装饰器的唯一方式。 @@ -317,7 +319,7 @@ Wait for 2 seconds... ``` -## 3.1.8 如何写能装饰类的装饰器? +## 8. 如何写能装饰类的装饰器? 用 Python 写单例模式的时候,常用的有三种写法。其中一种,是用装饰器来实现的。 @@ -350,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 装饰器,你应该也经常见过,那他有啥用呢? 先来看一个例子 @@ -442,7 +444,7 @@ def wrapped(): print(wrapped.__name__) ``` -## 3.1.10 内置装饰器:property +## 10. 内置装饰器:property 以上,我们介绍的都是自定义的装饰器。 @@ -664,7 +666,7 @@ in __get__ 如对上面代码的运行原理,有疑问的同学,请务必结合上面两点说明加以理解,那两点相当关键。 -## 3.1.11 其他装饰器:装饰器实战 +## 11. 其他装饰器:装饰器实战 读完并理解了上面的内容,你可以说是Python高手了。别怀疑,自信点,因为很多人都不知道装饰器有这么多用法呢。 @@ -704,4 +706,4 @@ def timeout_limit(timeout_time): 我的文章更新频率是远低于其他 Python 技术号,但我仍然坚持自己,坚持原创,每周虽然只有一篇,但我能保证我的每一篇文章都是诚意之作。希望那些对你有帮助的文章能够多多帮忙转发分享。这也是我更新的一大动力。非常感谢。 -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c03/c03_01.rst b/source/c03/c03_01.rst old mode 100755 new mode 100644 index aa4839a..0fcf09e --- a/source/c03/c03_01.rst +++ b/source/c03/c03_01.rst @@ -1,6 +1,8 @@ 3.1 装饰器进阶用法详解 ====================== +|image0| + -------------- 对于每一个学习 Python 的同学,想必对 ``@`` @@ -20,10 +22,10 @@ 当时带着这两个问题,我就开始系统的学习装饰器的所有内容。这些一直整理在自己的博客中,今天对其进行了大量的补充和勘误,发表在这里分享给大家。希望对刚入门以及进阶的朋友可以提供一些参考。 -|image0| +|image1| -3.1.1 Hello,装饰器 -------------------- +1. Hello,装饰器 +---------------- 装饰器的使用方法很固定 - 先定义一个装饰器(帽子) - 再定义你的业务函数或者类(人) - @@ -49,8 +51,8 @@ 接下来,我将以实例讲解,如何编写出各种简单及复杂的装饰器。 -3.1.2 入门:日志打印器 ----------------------- +2. 入门:日志打印器 +------------------- 首先是\ **日志打印器**\ 。 实现的功能: - 在函数执行前,先打印一行日志告知一下主人,我要执行函数了。 - @@ -91,8 +93,8 @@ 200 + 50 = 250 主人,我执行完啦。 -3.1.3 入门:时间计时器 ----------------------- +3. 入门:时间计时器 +------------------- 再来看看 **时间计时器** 实现功能:顾名思义,就是计算一个函数的执行时长。 @@ -129,8 +131,8 @@ 花费时间:10.0073800086975098秒 -3.1.4 进阶:带参数的函数装饰器 ------------------------------- +4. 进阶:带参数的函数装饰器 +--------------------------- 通过上面两个简单的入门示例,你应该能体会到装饰器的工作原理了。 @@ -207,8 +209,8 @@ periodic_task ------------ hello. -3.1.5 高阶:不带参数的类装饰器 ------------------------------- +5. 高阶:不带参数的类装饰器 +--------------------------- 以上都是基于函数实现的装饰器,在阅读别人代码时,还可以时常发现还有基于类实现的装饰器。 @@ -242,8 +244,8 @@ periodic_task [INFO]: the function say() is running... say hello! -3.1.6 高阶:带参数的类装饰器 ----------------------------- +6. 高阶:带参数的类装饰器 +------------------------- 上面不带参数的例子,你发现没有,只能打印\ ``INFO``\ 级别的日志,正常情况下,我们还需要打印\ ``DEBUG`` ``WARNING``\ 等级别的日志。 @@ -280,8 +282,8 @@ periodic_task [WARNING]: the function say() is running... say hello! -3.1.7 使用偏函数与类实现装饰器 ------------------------------- +7. 使用偏函数与类实现装饰器 +--------------------------- 绝大多数装饰器都是基于函数和闭包实现的,但这并非制造装饰器的唯一方式。 @@ -352,8 +354,8 @@ Python工匠:使用装饰器的小技巧) >>> add.func # 实现实例方法 -3.1.8 如何写能装饰类的装饰器? ------------------------------- +8. 如何写能装饰类的装饰器? +--------------------------- 用 Python 写单例模式的时候,常用的有三种写法。其中一种,是用装饰器来实现的。 @@ -388,10 +390,10 @@ Python工匠:使用装饰器的小技巧) 其实例化的过程,你可以参考我这里的调试过程,加以理解。 -|image1| +|image2| -3.1.9 wraps 装饰器有啥用? --------------------------- +9. wraps 装饰器有啥用? +----------------------- 在 functools 标准库中有提供一个 wraps 装饰器,你应该也经常见过,那他有啥用呢? @@ -486,8 +488,8 @@ wraps的情况下,也可以让 ``wrapped.__name__`` 打印出 wrapped,代码 print(wrapped.__name__) -3.1.10 内置装饰器:property ---------------------------- +10. 内置装饰器:property +------------------------ 以上,我们介绍的都是自定义的装饰器。 @@ -730,8 +732,8 @@ property 如对上面代码的运行原理,有疑问的同学,请务必结合上面两点说明加以理解,那两点相当关键。 -3.1.11 其他装饰器:装饰器实战 ------------------------------ +11. 其他装饰器:装饰器实战 +-------------------------- 读完并理解了上面的内容,你可以说是Python高手了。别怀疑,自信点,因为很多人都不知道装饰器有这么多用法呢。 @@ -773,11 +775,10 @@ property 我的文章更新频率是远低于其他 Python 技术号,但我仍然坚持自己,坚持原创,每周虽然只有一篇,但我能保证我的每一篇文章都是诚意之作。希望那些对你有帮助的文章能够多多帮忙转发分享。这也是我更新的一大动力。非常感谢。 -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image3| -.. |image0| image:: http://image.python-online.cn/20190811100737.png -.. |image1| image:: http://image.python-online.cn/20190512113917.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 a5ef80c..45edc25 100644 --- a/source/c03/c03_02.md +++ b/source/c03/c03_02.md @@ -1,8 +1,10 @@ # 3.2 深入理解Python元类 +![](http://image.iswbm.com/20200602135014.png) + --- -## 3.2.1 类是如何产生的 +## 1. 类是如何产生的 类是如何产生? @@ -13,7 +15,7 @@ type?这不是判断对象类型的函数吗? 是的,type通常用法就是用来判断对象的类型。但除此之外,他最大的用途是用来动态创建类。当Python扫描到class的语法的时候,就会调用type函数进行类的创建。 -## 3.2.2 如何使用type创建类 +## 2. 如何使用type创建类 首先,type()需要接收三个参数 ``` @@ -37,7 +39,7 @@ def say(self): User = type("User", (BaseClass, ), {"name":"user", "say":say}) ``` -## 3.2.3 理解什么是元类 +## 3. 理解什么是元类 什么是类?可能谁都知道,类就是用来创建对象的「模板」。 @@ -155,7 +157,7 @@ True -## 3.3.4 使用元类的意义 +## 4. 使用元类的意义 正常情况下,我们都不会使用到元类。但是这并不意味着,它不重要。假如某一天,我们需要写一个框架,很有可能就需要你对元类要有进一步的研究。 @@ -171,7 +173,7 @@ True 但是,这样说,你一定不会服气,到底元类用来干什么?其实元类的作用就是`创建API`,一个最典型的应用是 `Django ORM`。 -## 3.3.5 元类实战:ORM +## 5. 元类实战:ORM 使用过Django ORM的人都知道,有了ORM,使得我们操作数据库,变得异常简单。 @@ -305,7 +307,7 @@ class ModelMetaClass(type): return super().__new__(cls, name, bases, attrs) ``` -## 3.2.6 \__new__ 有什么用? +## 6. \__new__ 有什么用? 在没有元类的情况下,每次创建实例,在先进入 `__init__` 之前都会先进入 ` __new__`。 @@ -344,4 +346,4 @@ in User ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c03/c03_02.rst b/source/c03/c03_02.rst old mode 100755 new mode 100644 index 8be7e21..38fa73b --- a/source/c03/c03_02.rst +++ b/source/c03/c03_02.rst @@ -1,10 +1,12 @@ 3.2 深入理解Python元类 ====================== +|image0| + -------------- -3.2.1 类是如何产生的 --------------------- +1. 类是如何产生的 +----------------- 类是如何产生? @@ -14,8 +16,8 @@ type?这不是判断对象类型的函数吗? 是的,type通常用法就是用来判断对象的类型。但除此之外,他最大的用途是用来动态创建类。当Python扫描到class的语法的时候,就会调用type函数进行类的创建。 -3.2.2 如何使用type创建类 ------------------------- +2. 如何使用type创建类 +--------------------- 首先,type()需要接收三个参数 @@ -41,8 +43,8 @@ type?这不是判断对象类型的函数吗? # 使用type来创建User类 User = type("User", (BaseClass, ), {"name":"user", "say":say}) -3.2.3 理解什么是元类 --------------------- +3. 理解什么是元类 +----------------- 什么是类?可能谁都知道,类就是用来创建对象的「模板」。 @@ -168,8 +170,8 @@ type是Python在背后用来创建所有类的元类,我们熟知的类的始 >>> u1 is u2 True -3.3.4 使用元类的意义 --------------------- +4. 使用元类的意义 +----------------- 正常情况下,我们都不会使用到元类。但是这并不意味着,它不重要。假如某一天,我们需要写一个框架,很有可能就需要你对元类要有进一步的研究。 @@ -186,8 +188,8 @@ type是Python在背后用来创建所有类的元类,我们熟知的类的始 但是,这样说,你一定不会服气,到底元类用来干什么?其实元类的作用就是\ ``创建API``\ ,一个最典型的应用是 ``Django ORM``\ 。 -3.3.5 元类实战:ORM -------------------- +5. 元类实战:ORM +---------------- 使用过Django ORM的人都知道,有了ORM,使得我们操作数据库,变得异常简单。 @@ -328,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__``\ 。 @@ -374,7 +376,8 @@ ORM的一个类(User),就对应数据库中的一张表。id,name,email,passwo -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c03/c03_03.md b/source/c03/c03_03.md index c3f5ceb..a6347d5 100644 --- a/source/c03/c03_03.md +++ b/source/c03/c03_03.md @@ -1,8 +1,10 @@ # 3.3 Socket编程实现在线聊天 +![](http://image.iswbm.com/20200602135014.png) + --- -## 3.3.1 什么是socket? +## 1. 什么是socket? 说到网络编程,难免要提到socket? @@ -12,7 +14,7 @@ 不管是不同主机,还是同一主机。既然是通信,必定有一个发送方,一个接收方。对应一个客户端,和一个服务端。 -## 3.3.2 创建客户端 +## 2. 创建客户端 - 创建socket,建立连接 ```python @@ -45,7 +47,7 @@ while True: data = ''.join(buffer) ``` -## 3.3.3 创建服务端 +## 3. 创建服务端 - 创建socket ```python import socket @@ -82,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数据,返回值:发送的字节当量 @@ -143,7 +145,7 @@ sk.setsockopt(level,optname,value) sk.getsockopt(level,optname) ``` -## 3.3.6 搭建在线聊天机器人 +## 6. 搭建在线聊天机器人 通过上面的学习,我们知道,同主机下或不同主机下的两个进程要进行通信(TCP/UDP,不管是消息传输还是文件传输),必定要借助socket这个桥梁。 @@ -257,4 +259,4 @@ if __name__ == '__main__': ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c03/c03_03.rst b/source/c03/c03_03.rst old mode 100755 new mode 100644 index d8ecd86..00502ce --- a/source/c03/c03_03.rst +++ b/source/c03/c03_03.rst @@ -1,10 +1,12 @@ 3.3 Socket编程实现在线聊天 ========================== +|image0| + -------------- -3.3.1 什么是socket? --------------------- +1. 什么是socket? +----------------- 说到网络编程,难免要提到socket? @@ -14,8 +16,8 @@ 不管是不同主机,还是同一主机。既然是通信,必定有一个发送方,一个接收方。对应一个客户端,和一个服务端。 -3.3.2 创建客户端 ----------------- +2. 创建客户端 +------------- - 创建socket,建立连接 @@ -53,8 +55,8 @@ break data = ''.join(buffer) -3.3.3 创建服务端 ----------------- +3. 创建服务端 +------------- - 创建socket @@ -101,13 +103,13 @@ sock.send('Hello, %s!' % data) sock.close() -3.3.4 socket工作流程 --------------------- +4. socket工作流程 +----------------- -|image0| +|image1| -3.3.5 socket公共函数汇总 ------------------------- +5. socket公共函数汇总 +--------------------- - 发送数据 @@ -174,8 +176,8 @@ sk.setsockopt(level,optname,value) sk.getsockopt(level,optname) -3.3.6 搭建在线聊天机器人 ------------------------- +6. 搭建在线聊天机器人 +--------------------- 通过上面的学习,我们知道,同主机下或不同主机下的两个进程要进行通信(TCP/UDP,不管是消息传输还是文件传输),必定要借助socket这个桥梁。 @@ -288,15 +290,14 @@ cs = ChatServer(port=9999) cs.main() -将服务端程序跑起来,然后运行客户端,看下效果。 |image1| +将服务端程序跑起来,然后运行客户端,看下效果。 |image2| -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image3| -.. |image0| image:: https://i.loli.net/2018/04/30/5ae6c303c870c.png -.. |image1| image:: https://i.loli.net/2018/04/30/5ae6c31b2d1c8.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: https://i.loli.net/2018/04/30/5ae6c303c870c.png +.. |image2| image:: https://i.loli.net/2018/04/30/5ae6c31b2d1c8.png +.. |image3| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c03/c03_04.md b/source/c03/c03_04.md index 19cf25f..6437148 100644 --- a/source/c03/c03_04.md +++ b/source/c03/c03_04.md @@ -1,5 +1,7 @@ # 3.4 Django+Uwsgi部署网站 +![](http://image.iswbm.com/20200602135014.png) + 本文是先前自己使用django写过一个个人博客时的部署过程记录。 涉及的工具有:Django+Nginx+Uwsgi+Supervisor @@ -451,4 +453,4 @@ vim /etc/nginx/nginx.conf --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c03/c03_04.rst b/source/c03/c03_04.rst old mode 100755 new mode 100644 index 280a3a6..c36d5ed --- a/source/c03/c03_04.rst +++ b/source/c03/c03_04.rst @@ -1,6 +1,8 @@ 3.4 Django+Uwsgi部署网站 ======================== +|image0| + 本文是先前自己使用django写过一个个人博客时的部署过程记录。 涉及的工具有:Django+Nginx+Uwsgi+Supervisor @@ -27,7 +29,7 @@ sudo apt-get update sudo apt-get upgrade -更新时,会让你选择是否安装新版软件,输入Y安装 |image0| +更新时,会让你选择是否安装新版软件,输入Y安装 |image1| 更新安装完成后,系统就会给我们预装了Python2.7.12和Python3.5.2 @@ -149,17 +151,17 @@ **外网连接** 使用navicat进行测试连接 -|image1| +|image2| 连接上后就可以新建数据库 -|image2| +|image3| 如果有备份数据要还原,可以使用数据传输。 数据传输功能,不能确保一次就能将数据传输完整,要多传输几次。 实在无法完整传输,就要导出导入的方式。 -|image3| +|image4| 三、项目上传 ------------ @@ -391,7 +393,7 @@ vim /etc/supervisord.conf -直接跳到最后,填入红框内容 |image4| +直接跳到最后,填入红框内容 |image5| 5.3 启动项目 ~~~~~~~~~~~~ @@ -406,7 +408,7 @@ myblog: stopped myblog: updated process group -通过\ ``supervisorctl status``\ 查看启动状态 |image5| +通过\ ``supervisorctl status``\ 查看启动状态 |image6| 5.4 supervisor其他命令 ~~~~~~~~~~~~~~~~~~~~~~ @@ -510,15 +512,14 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image7| -.. |image0| image:: https://i.loli.net/2017/08/20/599982f513b7e.png -.. |image1| image:: https://i.loli.net/2017/08/20/59998b33265d9.png -.. |image2| image:: https://i.loli.net/2017/08/20/59998b91d16dd.png -.. |image3| image:: https://i.loli.net/2017/08/20/59998bdf36c79.png -.. |image4| image:: https://i.loli.net/2017/08/25/59a0236def497.png -.. |image5| image:: https://i.loli.net/2017/08/25/59a0244d239f0.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: https://i.loli.net/2017/08/20/599982f513b7e.png +.. |image2| image:: https://i.loli.net/2017/08/20/59998b33265d9.png +.. |image3| image:: https://i.loli.net/2017/08/20/59998b91d16dd.png +.. |image4| image:: https://i.loli.net/2017/08/20/59998bdf36c79.png +.. |image5| image:: https://i.loli.net/2017/08/25/59a0236def497.png +.. |image6| image:: https://i.loli.net/2017/08/25/59a0244d239f0.png +.. |image7| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c03/c03_05.md b/source/c03/c03_05.md index 9da86be..06afe19 100644 --- a/source/c03/c03_05.md +++ b/source/c03/c03_05.md @@ -1,5 +1,7 @@ # 3.5 源码解读:Flask上下文与代理模式 +![](http://image.iswbm.com/20200602135014.png) + 在上一节中,我跟大家一起深入了解了一下Python的「上下文管理器 」。而今天呢,还是和上下文有关的话题。只不过这里的上下文和上一节的内容有点不一样,上下文管理器是管理代码块级别的上下文,而今天要讲的上下文是工程项目中的上下文。 可能你现在对**上下文**这个概念,还是不太清楚。这里再简单说明一下 @@ -29,7 +31,7 @@ 这三个对象都是 `werkzeug` 里提供的,定义的 `local.py` 里,所以它们并不是Flask 中特有的, 这就意味着我们可以直接在自己的项目中使用它们,而不用依托于 Flask 的环境。 -## 3.5.1 Local +## 1. Local 首先是 `Local` ,记得在以前的「并发编程系列」的第五篇线程的 `信息隔离` 的时候,提过了 `threading.local` ,它是专门用来存储当前线程的变量,从而实现对象的线程隔离。 @@ -122,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__ ` 进行数据清理。是如何做到的呢,看下面这段代码。 @@ -187,7 +189,7 @@ class Local(object): -## 3.5.2 LocalStack +## 2. LocalStack 通过对 Local 的介绍,可以知道 Local 其实是通过封装了字典的,以此实现了线程隔离。 @@ -195,7 +197,7 @@ class Local(object): 同样用一张图来表示 -![](http://image.python-online.cn/FimULzWaeZWS2KJx_EQLAK_yRZ4A) +![](http://image.iswbm.com/FimULzWaeZWS2KJx_EQLAK_yRZ4A) 栈结构的特性,无非就是后进先出。这里就不说了,这里的重点是线程隔离的特性如何体现,还是以上面的例子,稍微做了下修改。 @@ -265,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` 来进行的有没有? @@ -357,7 +359,7 @@ def _get_current_object(self): 这样就能实现每次对栈顶元素的操作,都是面对最新元素执行的。 -## 3.5.4 经典错误 +## 4. 经典错误 在 Flask 中经常会遇到的一个错误是: @@ -457,4 +459,4 @@ with app.app_context(): --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c03/c03_05.rst b/source/c03/c03_05.rst index ce07cd8..72c8fca 100644 --- a/source/c03/c03_05.rst +++ b/source/c03/c03_05.rst @@ -1,6 +1,8 @@ 3.5 源码解读:Flask上下文与代理模式 =================================== +|image0| + 在上一节中,我跟大家一起深入了解了一下Python的「上下文管理器 」。而今天呢,还是和上下文有关的话题。只不过这里的上下文和上一节的内容有点不一样,上下文管理器是管理代码块级别的上下文,而今天要讲的上下文是工程项目中的上下文。 @@ -38,8 +40,8 @@ request。这是它们之间的对应关系。 这就意味着我们可以直接在自己的项目中使用它们,而不用依托于 Flask 的环境。 -3.5.1 Local ------------ +1. Local +-------- 首先是 ``Local`` ,记得在以前的「并发编程系列」的第五篇线程的 ``信息隔离`` 的时候,提过了 ``threading.local`` @@ -139,11 +141,11 @@ wangbm,然后开始一个线程,做的事就是将这个 name 属性改为 w 如果要用图来表示,最开始的Local对象就是一个空盒子 -|image0| +|image1| 当有不同的线程往里写数据时,Local 对象为每个线程分配了一个 micro-box。 -|image1| +|image2| local 是需要被 ``localmanager`` 管理的,在请求结束后,会调用 ``localmanager.cleanup()`` 函数,其实是调用 ``local.__release_local__`` @@ -204,8 +206,8 @@ local 是需要被 ``localmanager`` 管理的,在请求结束后,会调用 except KeyError: raise AttributeError(name) -3.5.2 LocalStack ----------------- +2. LocalStack +------------- 通过对 Local 的介绍,可以知道 Local 其实是通过封装了字典的,以此实现了线程隔离。 @@ -216,7 +218,7 @@ local 是需要被 ``localmanager`` 管理的,在请求结束后,会调用 同样用一张图来表示 -|image2| +|image3| 栈结构的特性,无非就是后进先出。这里就不说了,这里的重点是线程隔离的特性如何体现,还是以上面的例子,稍微做了下修改。 @@ -293,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`` 来进行的有没有? @@ -392,8 +394,8 @@ local 是需要被 ``localmanager`` 管理的,在请求结束后,会调用 这样就能实现每次对栈顶元素的操作,都是面对最新元素执行的。 -3.5.4 经典错误 --------------- +4. 经典错误 +----------- 在 Flask 中经常会遇到的一个错误是: @@ -498,12 +500,11 @@ app 的上下文信息是否已经 push 进去了,如果没有的话,就会 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image4| -.. |image0| image:: http://image.python-online.cn/Fuhww2CZdUv4mGqx-N0YqAuXUWlX -.. |image1| image:: http://image.python-online.cn/FgI6y-_Ka-S20VCjyufsCIczKjup -.. |image2| image:: http://image.python-online.cn/FimULzWaeZWS2KJx_EQLAK_yRZ4A +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |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 7f1f097..820bbdf 100644 --- a/source/c03/c03_06.md +++ b/source/c03/c03_06.md @@ -1,5 +1,7 @@ # 3.6 Web开发者必看:理解WSGI +![](http://image.iswbm.com/20200602135014.png) + 在 三百六十行,行行转 IT 的现状下,很多来自各行各业的同学,都选择 Python 这门胶水语言做为踏入互联网大门的第一块敲门砖,在这些人里,又有相当大比例的同学选择了 Web 开发这个方向(包括我)。而从事 web 开发,绕不过一个知识点,就是 WSGI。 不管你是否是这些如上同学中的一员,都应该好好地学习一下这个知识点。 @@ -18,7 +20,7 @@ 一个HTTP请求的过程可以分为两个阶段,第一阶段是从客户端到WSGI Server,第二阶段是从WSGI Server 到WSGI Application -![](http://image.python-online.cn/20190607131728.png) +![](http://image.iswbm.com/20190607131728.png) 今天主要是讲第二阶段,主要内容有以下几点: @@ -32,7 +34,7 @@ 8. webob.dec.wsgify 装饰器 9. 第二次路由:中间件 routes 路由 -## 3.6.1 WSGI 是什么,因何而生? +## 1. WSGI 是什么,因何而生? WSGI是 Web Server Gateway Interface 的缩写。 @@ -56,7 +58,7 @@ WSGI 对于 application 对象有如下三点要求 -## 3.6.2 为什么要有 WSGI? +## 2. 为什么要有 WSGI? 这是来自我的知乎专栏一个朋友在评论区的一个问题,我觉得问得很好,所以来答一下,更新在这里。 @@ -80,7 +82,7 @@ web 服务器 和 web 框架,分工不同,职责不同(web 服务器专注 最好的情况应该是,由专业的团队去开发专业的web服务器,而开发出来的web服务器需要具备框架通用性,Django可以用,Flask也可以用,开发者也可以自由选择用哪个web 服务器软件,用哪个web 框架,灵活组合。 -## 3.6.3 HTTP请求是如何到应用程序的? +## 3. HTTP请求是如何到应用程序的? 当客户端发出一个 HTTP 请求后,是如何转到我们的应用程序处理并返回的呢? @@ -88,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得到相应,之后相应给客户端。 @@ -102,7 +104,7 @@ web 服务器 和 web 框架,分工不同,职责不同(web 服务器专注 nginx可以做负载均衡(前提是有多个服务器),保护了实际的web服务器(客户端是和nginx交互而不是uWSGI) -## 3.6.4 实现一个简单的 WSGI Server +## 4. 实现一个简单的 WSGI Server 在上面的架构图里,不知道你发现没有,有个库叫做 `wsgiref` ,它是 Python 自带的一个 wsgi 服务器模块。 @@ -123,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 的二次开发,对它比较熟悉。 @@ -141,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 开启了线程池,那线程池里的每个线程应该都是一个服务器吧?它是如何接收请求的? @@ -192,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 的项目。 @@ -227,7 +229,7 @@ PasteDeploy 到底是做什么的呢? 具体可以,看下nova的实现。 -![](http://image.python-online.cn/20190530221101.png) +![](http://image.iswbm.com/20190530221101.png) 通过打印的 DEBUG 内容得知 config_url 和 app name 的值 @@ -251,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,所以这里结合网上博客做了下总结。对你入门会有帮助。 @@ -387,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)。内容如下 @@ -447,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) 如果像上图一样一切正常,那么打开浏览器 @@ -458,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 的规范 @@ -477,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。 @@ -491,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 请求粗略简单地划分为两个过程。但事实上,整个过程远比这个过程要复杂得多。 @@ -511,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) 这个中间件的原理,看起来是挺简单的。并没有很复杂的逻辑。 @@ -519,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 到是什么东西? @@ -543,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 对象有多么的多而杂 @@ -721,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打印出来 @@ -734,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(函数名) 取得处理函数对象。 @@ -745,11 +747,11 @@ meth : + print(1 / 0) +ZeroDivisionError: division by zero + +During handling of the above exception, another exception occurred: + +Traceback (most recent call last): + File "demo.py", line 4, in + raise RuntimeError("Something bad happened") +RuntimeError: Something bad happened +``` + +如果在异常处理程序或 finally 块中引发异常,默认情况下,异常机制会隐式工作会将先前的异常附加为新异常的 `__context__`属性。这就是 Python 默认开启的自动关联异常上下文。 + +如果你想自己控制这个上下文,可以加个 from 关键字(`from` 语法会有个限制,就是第二个表达式必须是另一个异常类或实例。),来表明你的新异常是直接由哪个异常引起的。 + +```python +try: + print(1 / 0) +except Exception as exc: + raise RuntimeError("Something bad happened") from exc +``` + +输出如下 + +```python +Traceback (most recent call last): + File "demo.py", line 2, in + print(1 / 0) +ZeroDivisionError: division by zero + +The above exception was the direct cause of the following exception: + +Traceback (most recent call last): + File "demo.py", line 4, in + raise RuntimeError("Something bad happened") from exc +RuntimeError: Something bad happened +``` + + +当然,你也可以通过`with_traceback()`方法为异常设置上下文`__context__`属性,这也能在`traceback`更好的显示异常信息。 + +```python +try: + print(1 / 0) +except Exception as exc: + raise RuntimeError("bad thing").with_traceback(exc) +``` + + +最后,如果我想彻底关闭这个自动关联异常上下文的机制?有什么办法呢? + +可以使用 `raise...from None`,从下面的例子上看,已经没有了原始异常 + +```python +$ cat demo.py +try: + print(1 / 0) +except Exception as exc: + raise RuntimeError("Something bad happened") from None +$ +$ python demo.py +Traceback (most recent call last): + File "demo.py", line 4, in + raise RuntimeError("Something bad happened") from None +RuntimeError: Something bad happened +(PythonCodingTime) +``` + + +## 3. 最快查看包搜索路径的方式 + +当你使用 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'] +>>> +``` + +那有没有更快的方式呢? + +我这有一种连 console 模式都不用进入的方法呢? + +你可能会想到这种,但这本质上与上面并无区别 + +```python +[wangbm@localhost ~]$ python -c "print('\n'.join(__import__('sys').path))" + +/usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg +/usr/lib/python2.7/site-packages/redis-3.0.1-py2.7.egg +/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 +``` + +这里我要介绍的是比上面两种都方便的多的方法,一行命令即可解决 + +```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 更全,它包含了用户环境的目录。 + + +## 4. 将嵌套 for 循环写成单行 + +我们经常会如下这种嵌套的 for 循环代码 + +```python +list1 = range(1,3) +list2 = range(4,6) +list3 = range(7,9) +for item1 in list1: + for item2 in list2: + for item3 in list3: + print(item1+item2+item3) +``` + +这里仅仅是三个 for 循环,在实际编码中,有可能会有更层。 + +这样的代码,可读性非常的差,很多人不想这么写,可又没有更好的写法。 + +这里介绍一种我常用的写法,使用 itertools 这个库来实现更优雅易读的代码。 + +```python +from itertools import product +list1 = range(1,3) +list2 = range(4,6) +list3 = range(7,9) +for item1,item2,item3 in product(list1, list2, list3): + print(item1+item2+item3) +``` + +输出如下 + +```shell +$ python demo.py +12 +13 +13 +14 +13 +14 +14 +15 +``` + +## 5. 如何使用 print 输出日志 + +初学者喜欢使用 print 来调试代码,并记录程序运行过程。 + +但是 print 只会将内容输出到终端上,不能持久化到日志文件中,并不利于问题的排查。 + +如果你热衷于使用 print 来调试代码(虽然这并不是最佳做法),记录程序运行过程,那么下面介绍的这个 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 +``` + +## 6. 如何快速计算函数运行时间 + +计算一个函数的运行时间,你可能会这样子做 + +```python +import time + +start = time.time() + +# run the function + +end = time.time() +print(end-start) +``` + +你看看你为了计算函数运行时间,写了几行代码了。 + +有没有一种方法可以更方便的计算这个运行时间呢? + +有。 + +有一个内置模块叫 timeit + +使用它,只用一行代码即可 +```python +import time +import timeit + +def run_sleep(second): + print(second) + time.sleep(second) + +# 只用这一行 +print(timeit.timeit(lambda :run_sleep(2), number=5)) +``` +运行结果如下 +```python +2 +2 +2 +2 +2 +10.020059824 +``` + + + +## 7. 利用自带的缓存机制提高效率 + +缓存是一种将定量数据加以保存,以备迎合后续获取需求的处理方式,旨在加快数据获取的速度。 + +数据的生成过程可能需要经过计算,规整,远程获取等操作,如果是同一份数据需要多次使用,每次都重新生成会大大浪费时间。所以,如果将计算或者远程请求等操作获得的数据缓存下来,会加快后续的数据获取需求。 + +为了实现这个需求,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 +``` + +下面这个是经典的斐波那契数列,当你指定的 n 较大时,会存在大量的重复计算 + +```python +def fib(n): + if n < 2: + return n + return fib(n - 2) + fib(n - 1) +``` + +第六点介绍的 timeit,现在可以用它来测试一下到底可以提高多少的效率。 + +不使用 lru_cache 的情况下,运行时间 31 秒 +```python +import timeit + +def fib(n): + if n < 2: + return n + return fib(n - 2) + fib(n - 1) + + + +print(timeit.timeit(lambda :fib(40), number=1)) +# output: 31.2725698948 +``` +由于使用了 lru_cache 后,运行速度实在太快了,所以我将 n 值由 30 调到 500,可即使是这样,运行时间也才 0.0004 秒。提高速度非常显著。 +```python +import timeit +from functools import lru_cache + +@lru_cache(None) +def fib(n): + if n < 2: + return n + return fib(n - 2) + fib(n - 1) + +print(timeit.timeit(lambda :fib(500), number=1)) +# output: 0.0004921059880871326 +``` + +## 8. 在程序退出前执行代码的技巧 + +使用 atexit 这个内置模块,可以很方便的注册退出函数。 + +不管你在哪个地方导致程序崩溃,都会执行那些你注册过的函数。 + +示例如下 + +![](http://image.iswbm.com/20200510112133.png) + +如果`clean()`函数有参数,那么你可以不用装饰器,而是直接调用`atexit.register(clean_1, 参数1, 参数2, 参数3='xxx')`。 + +可能你有其他方法可以处理这种需求,但肯定比上不使用 atexit 来得优雅,来得方便,并且它很容易扩展。 + +但是使用 atexit 仍然有一些局限性,比如: + +- 如果程序是被你没有处理过的系统信号杀死的,那么注册的函数无法正常执行。 +- 如果发生了严重的 Python 内部错误,你注册的函数无法正常执行。 +- 如果你手动调用了`os._exit()`,你注册的函数无法正常执行。 + + + + +## 9. 实现类似 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 +``` + +## 10. 如何流式读取数G超大文件 + +使用 with...open... 可以从一个文件中读取数据,这是所有 Python 开发者都非常熟悉的操作。 + +但是如果你使用不当,也会带来很大的麻烦。 + +比如当你使用了 read 函数,其实 Python 会将文件的内容一次性的全部载入内存中,如果文件有 10 个G甚至更多,那么你的电脑就要消耗的内存非常巨大。 + +```python +# 一次性读取 +with open("big_file.txt", "r") as fp: + content = fp.read() +``` + +对于这个问题,你也许会想到使用 readline 去做一个生成器来逐行返回。 + +```python +def read_from_file(filename): + with open(filename, "r") as fp: + yield fp.readline() +``` + +可如果这个文件内容就一行呢,一行就 10个G,其实你还是会一次性读取全部内容。 + +最优雅的解决方法是,在使用 read 方法时,指定每次只读取固定大小的内容,比如下面的代码中,每次只读取 8kb 返回。 + +```python +def read_from_file(filename, block_size = 1024 * 8): + with open(filename, "r") as fp: + while True: + chunk = fp.read(block_size) + if not chunk: + break + + yield chunk +``` + +上面的代码,功能上已经没有问题了,但是代码看起来代码还是有些臃肿。 + + 借助偏函数 和 iter 函数可以优化一下代码 + +```python +from functools import partial + +def read_from_file(filename, block_size = 1024 * 8): + with open(filename, "r") as fp: + for chunk in iter(partial(fp.read, block_size), ""): + yield chunk +``` \ No newline at end of file diff --git a/source/c03/c03_07.rst b/source/c03/c03_07.rst new file mode 100644 index 0000000..cfffaaf --- /dev/null +++ b/source/c03/c03_07.rst @@ -0,0 +1,546 @@ +3.7 超实用10 条 Python使用技巧 +============================== + +|image0| + +今天给大家分享 10 个我平时整理非常实用的 Python +开发小技巧,内容目录如下: + +|image1| + +值得一提的是,这 10 个技巧全部收录在我自己写的 《Python黑魔法指南》里。 + +|image2| + +1. 如何在运行状态查看源代码? +----------------------------- + +查看函数的源代码,我们通常会使用 IDE 来完成。 + +比如在 PyCharm 中,你可以 Ctrl + 鼠标点击 进入函数的源代码。 + +那如果没有 IDE 呢? + +当我们想使用一个函数时,如何知道这个函数需要接收哪些参数呢? + +当我们在使用函数时出现问题的时候,如何通过阅读源代码来排查问题所在呢? + +这时候,我们可以使用 inspect 来代替 IDE 帮助你完成这些事 + +.. code:: python + + # demo.py + import inspect + + + def add(x, y): + return x + y + + print("===================") + print(inspect.getsource(add)) + +运行结果如下 + +.. code:: shell + + $ python demo.py + =================== + def add(x, y): + return x + y + +2. 如何关闭异常自动关联上下文? +------------------------------- + +当你在处理异常时,由于处理不当或者其他问题,再次抛出另一个异常时,往外抛出的异常也会携带原始的异常信息。 + +就像这样子。 + +.. code:: python + + try: + print(1 / 0) + except Exception as exc: + raise RuntimeError("Something bad happened") + +从输出可以看到两个异常信息 + +.. code:: python + + Traceback (most recent call last): + File "demo.py", line 2, in + print(1 / 0) + ZeroDivisionError: division by zero + + During handling of the above exception, another exception occurred: + + Traceback (most recent call last): + File "demo.py", line 4, in + raise RuntimeError("Something bad happened") + RuntimeError: Something bad happened + +如果在异常处理程序或 finally +块中引发异常,默认情况下,异常机制会隐式工作会将先前的异常附加为新异常的 +``__context__``\ 属性。这就是 Python 默认开启的自动关联异常上下文。 + +如果你想自己控制这个上下文,可以加个 from 关键字(\ ``from`` +语法会有个限制,就是第二个表达式必须是另一个异常类或实例。),来表明你的新异常是直接由哪个异常引起的。 + +.. code:: python + + try: + print(1 / 0) + except Exception as exc: + raise RuntimeError("Something bad happened") from exc + +输出如下 + +.. code:: python + + Traceback (most recent call last): + File "demo.py", line 2, in + print(1 / 0) + ZeroDivisionError: division by zero + + The above exception was the direct cause of the following exception: + + Traceback (most recent call last): + File "demo.py", line 4, in + raise RuntimeError("Something bad happened") from exc + RuntimeError: Something bad happened + +当然,你也可以通过\ ``with_traceback()``\ 方法为异常设置上下文\ ``__context__``\ 属性,这也能在\ ``traceback``\ 更好的显示异常信息。 + +.. code:: python + + try: + print(1 / 0) + except Exception as exc: + raise RuntimeError("bad thing").with_traceback(exc) + +最后,如果我想彻底关闭这个自动关联异常上下文的机制?有什么办法呢? + +可以使用 ``raise...from None``\ ,从下面的例子上看,已经没有了原始异常 + +.. code:: python + + $ cat demo.py + try: + print(1 / 0) + except Exception as exc: + raise RuntimeError("Something bad happened") from None + $ + $ python demo.py + Traceback (most recent call last): + File "demo.py", line 4, in + raise RuntimeError("Something bad happened") from None + RuntimeError: Something bad happened + (PythonCodingTime) + +3. 最快查看包搜索路径的方式 +--------------------------- + +当你使用 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 模式都不用进入的方法呢? + +你可能会想到这种,但这本质上与上面并无区别 + +.. code:: python + + [wangbm@localhost ~]$ python -c "print('\n'.join(__import__('sys').path))" + + /usr/lib/python2.7/site-packages/pip-18.1-py2.7.egg + /usr/lib/python2.7/site-packages/redis-3.0.1-py2.7.egg + /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 + +这里我要介绍的是比上面两种都方便的多的方法,一行命令即可解决 + +.. 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 +更全,它包含了用户环境的目录。 + +4. 将嵌套 for 循环写成单行 +-------------------------- + +我们经常会如下这种嵌套的 for 循环代码 + +.. code:: python + + list1 = range(1,3) + list2 = range(4,6) + list3 = range(7,9) + for item1 in list1: + for item2 in list2: + for item3 in list3: + print(item1+item2+item3) + +这里仅仅是三个 for 循环,在实际编码中,有可能会有更层。 + +这样的代码,可读性非常的差,很多人不想这么写,可又没有更好的写法。 + +这里介绍一种我常用的写法,使用 itertools 这个库来实现更优雅易读的代码。 + +.. code:: python + + from itertools import product + list1 = range(1,3) + list2 = range(4,6) + list3 = range(7,9) + for item1,item2,item3 in product(list1, list2, list3): + print(item1+item2+item3) + +输出如下 + +.. code:: shell + + $ python demo.py + 12 + 13 + 13 + 14 + 13 + 14 + 14 + 15 + +5. 如何使用 print 输出日志 +-------------------------- + +初学者喜欢使用 print 来调试代码,并记录程序运行过程。 + +但是 print +只会将内容输出到终端上,不能持久化到日志文件中,并不利于问题的排查。 + +如果你热衷于使用 print +来调试代码(虽然这并不是最佳做法),记录程序运行过程,那么下面介绍的这个 +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 + +6. 如何快速计算函数运行时间 +--------------------------- + +计算一个函数的运行时间,你可能会这样子做 + +.. code:: python + + import time + + start = time.time() + + # run the function + + end = time.time() + print(end-start) + +你看看你为了计算函数运行时间,写了几行代码了。 + +有没有一种方法可以更方便的计算这个运行时间呢? + +有。 + +有一个内置模块叫 timeit + +使用它,只用一行代码即可 + +.. code:: python + + import time + import timeit + + def run_sleep(second): + print(second) + time.sleep(second) + + # 只用这一行 + print(timeit.timeit(lambda :run_sleep(2), number=5)) + +运行结果如下 + +.. code:: python + + 2 + 2 + 2 + 2 + 2 + 10.020059824 + +7. 利用自带的缓存机制提高效率 +----------------------------- + +缓存是一种将定量数据加以保存,以备迎合后续获取需求的处理方式,旨在加快数据获取的速度。 + +数据的生成过程可能需要经过计算,规整,远程获取等操作,如果是同一份数据需要多次使用,每次都重新生成会大大浪费时间。所以,如果将计算或者远程请求等操作获得的数据缓存下来,会加快后续的数据获取需求。 + +为了实现这个需求,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 + +下面这个是经典的斐波那契数列,当你指定的 n 较大时,会存在大量的重复计算 + +.. code:: python + + def fib(n): + if n < 2: + return n + return fib(n - 2) + fib(n - 1) + +第六点介绍的 timeit,现在可以用它来测试一下到底可以提高多少的效率。 + +不使用 lru_cache 的情况下,运行时间 31 秒 + +.. code:: python + + import timeit + + def fib(n): + if n < 2: + return n + return fib(n - 2) + fib(n - 1) + + + + print(timeit.timeit(lambda :fib(40), number=1)) + # output: 31.2725698948 + +由于使用了 lru_cache 后,运行速度实在太快了,所以我将 n 值由 30 调到 +500,可即使是这样,运行时间也才 0.0004 秒。提高速度非常显著。 + +.. code:: python + + import timeit + from functools import lru_cache + + @lru_cache(None) + def fib(n): + if n < 2: + return n + return fib(n - 2) + fib(n - 1) + + print(timeit.timeit(lambda :fib(500), number=1)) + # output: 0.0004921059880871326 + +8. 在程序退出前执行代码的技巧 +----------------------------- + +使用 atexit 这个内置模块,可以很方便的注册退出函数。 + +不管你在哪个地方导致程序崩溃,都会执行那些你注册过的函数。 + +示例如下 + +|image3| + +如果\ ``clean()``\ 函数有参数,那么你可以不用装饰器,而是直接调用\ ``atexit.register(clean_1, 参数1, 参数2, 参数3='xxx')``\ 。 + +可能你有其他方法可以处理这种需求,但肯定比上不使用 atexit +来得优雅,来得方便,并且它很容易扩展。 + +但是使用 atexit 仍然有一些局限性,比如: + +- 如果程序是被你没有处理过的系统信号杀死的,那么注册的函数无法正常执行。 +- 如果发生了严重的 Python 内部错误,你注册的函数无法正常执行。 +- 如果你手动调用了\ ``os._exit()``\ ,你注册的函数无法正常执行。 + +9. 实现类似 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 + +10. 如何流式读取数G超大文件 +--------------------------- + +使用 with…open… 可以从一个文件中读取数据,这是所有 Python +开发者都非常熟悉的操作。 + +但是如果你使用不当,也会带来很大的麻烦。 + +比如当你使用了 read 函数,其实 Python +会将文件的内容一次性的全部载入内存中,如果文件有 10 +个G甚至更多,那么你的电脑就要消耗的内存非常巨大。 + +.. code:: python + + # 一次性读取 + with open("big_file.txt", "r") as fp: + content = fp.read() + +对于这个问题,你也许会想到使用 readline 去做一个生成器来逐行返回。 + +.. code:: python + + def read_from_file(filename): + with open(filename, "r") as fp: + yield fp.readline() + +可如果这个文件内容就一行呢,一行就 +10个G,其实你还是会一次性读取全部内容。 + +最优雅的解决方法是,在使用 read +方法时,指定每次只读取固定大小的内容,比如下面的代码中,每次只读取 8kb +返回。 + +.. code:: python + + def read_from_file(filename, block_size = 1024 * 8): + with open(filename, "r") as fp: + while True: + chunk = fp.read(block_size) + if not chunk: + break + + yield chunk + +上面的代码,功能上已经没有问题了,但是代码看起来代码还是有些臃肿。 + +借助偏函数 和 iter 函数可以优化一下代码 + +.. code:: python + + from functools import partial + + def read_from_file(filename, block_size = 1024 * 8): + with open(filename, "r") as fp: + for chunk in iter(partial(fp.read, block_size), ""): + yield chunk + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200617084208.png +.. |image2| image:: http://image.iswbm.com/20200617085313.png +.. |image3| image:: http://image.iswbm.com/20200510112133.png + diff --git a/source/c03/c03_08.md b/source/c03/c03_08.md new file mode 100644 index 0000000..29b2687 --- /dev/null +++ b/source/c03/c03_08.md @@ -0,0 +1,597 @@ +# 3.8 深入理解 Python 中的描述符 + +![](http://image.iswbm.com/20200602135014.png) + +学习 Python 这么久了,说起 Python 的优雅之处,能让我脱口而出的, Descriptor(描述符)特性可以排得上号。 + +描述符 是Python 语言独有的特性,它不仅在应用层使用,在语言语法糖的实现上也有使用到(在下面的文章会一一介绍)。 + +当你点进这篇文章时 + +- 你也许没学过描述符,甚至没听过描述符。 +- 或者你对描述符只是一知半解 + +无论你是哪种,本篇都将带你全面的学习描述符,一起来感受 Python 语言的优雅。 + +### 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 + ) +``` + +看起来一切都很合理 + +```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 d99c596..0bf4881 100644 --- a/source/c04/c04_01.md +++ b/source/c04/c04_01.md @@ -1,9 +1,11 @@ # 4.1 虚拟环境:virtualenv +![](http://image.iswbm.com/20200602135014.png) + --- -## 1.0 什么是虚拟环境? +## 1. 什么是虚拟环境? 虚拟环境的意义,就如同 虚拟机 一样,它可以实现不同环境中Python依赖包相互独立,互不干扰。 @@ -25,20 +27,11 @@ 工具很多,但个人认为最好用的,当属 `virtualenvwrapper`,推荐大家也使用。 -## 2.0 virtualenv +## 2. virtualenv 由于 virtualenvwrapper 是 virtualenv 的一组扩展,所以如果要使用 virtualenvwrapper,就必须先安装 virtualenv。 - -**安装** -```bash -$ pip install virtualenv - -# 检查版本 -$ virtualenv --version -``` - -**基本使用** +**安装***基本使用** 由于virtualenv创建虚拟环境是在当前环境下创建的。所以我们要准备一个专门存放虚拟环境的目录。(以下操作在Linux在完成,windows相对简单,请自行完成,有不明白的请微信与我联系。) @@ -92,7 +85,7 @@ $ pip freeze > requirements.txt $ pip install -r requirements.txt ``` -## 3.0 virtualenvwrapper +## 3. virtualenvwrapper virtualenv 虽然已经相当好用了,可是功能还是不够完善。 @@ -135,7 +128,7 @@ source /usr/bin/virtualenvwrapper.sh 若是 windows 则新增环境变量:`WORKON_HOME` -![](http://image.python-online.cn/20200209161935.png) +![](http://image.iswbm.com/20200209161935.png) @@ -193,7 +186,7 @@ $ lssitepackages 更多内容,可查看 官方文档 https://virtualenvwrapper.readthedocs.io/en/latest/command_ref.html -## 4.0 实战演示 +## 4. 实战演示 以上内容,是一份使用指南。接下来,一起来看看,如何在项目中使用虚拟环境。 @@ -247,4 +240,4 @@ $ ./ming.py ![](https://i.loli.net/2018/06/11/5b1e812db603f.png) ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_01.rst b/source/c04/c04_01.rst old mode 100755 new mode 100644 index ac4b062..129c3ac --- a/source/c04/c04_01.rst +++ b/source/c04/c04_01.rst @@ -1,10 +1,12 @@ 4.1 虚拟环境:virtualenv ======================== +|image0| + -------------- -1.0 什么是虚拟环境? --------------------- +1. 什么是虚拟环境? +------------------- 虚拟环境的意义,就如同 虚拟机 一样,它可以实现不同环境中Python依赖包相互独立,互不干扰。 @@ -27,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相对简单,请自行完成,有不明白的请微信与我联系。) @@ -101,8 +94,8 @@ virtualenvwrapper,就必须先安装 virtualenv。 # 安装依赖包 $ pip install -r requirements.txt -3.0 virtualenvwrapper ---------------------- +3. virtualenvwrapper +-------------------- virtualenv 虽然已经相当好用了,可是功能还是不够完善。 @@ -146,7 +139,7 @@ virtualenv 虽然已经相当好用了,可是功能还是不够完善。 若是 windows 则新增环境变量:\ ``WORKON_HOME`` -|image0| +|image1| **基本语法**\ : @@ -204,8 +197,8 @@ mkvirtualenv [-a project_path] [-i package] [-r requirements_file] 更多内容,可查看 官方文档 https://virtualenvwrapper.readthedocs.io/en/latest/command_ref.html -4.0 实战演示 ------------- +4. 实战演示 +----------- 以上内容,是一份使用指南。接下来,一起来看看,如何在项目中使用虚拟环境。 @@ -221,7 +214,7 @@ https://virtualenvwrapper.readthedocs.io/en/latest/command_ref.html 先对比下,全局环境和虚拟环境的区别,全局环境中有requests包,而虚拟环境中并未安装。 当我们敲入 ``workon my_env01``\ ,前面有\ ``my_env01``\ 的标识,说明我们已经处在虚拟环境中。后面所有的操作,都将在虚拟环境下执行。 -|image1| +|image2| 4.2 工程项目中 ~~~~~~~~~~~~~~ @@ -254,25 +247,24 @@ https://virtualenvwrapper.readthedocs.io/en/latest/command_ref.html $ ./ming.py -发现和预期一样,真的报错了。说明我们指定的虚拟环境有效果。 |image2| +发现和预期一样,真的报错了。说明我们指定的虚拟环境有效果。 |image3| 4.3 PyCharm中 ~~~~~~~~~~~~~ -点击 File - Settings - Project - Interpreter |image3| +点击 File - Settings - Project - Interpreter |image4| 点击小齿轮。如图点击添加,按提示添加一个虚拟环境。然后点 OK -就可以使用这个虚拟环境,之后的项目都会在这个虚拟环境下运行。 |image4| +就可以使用这个虚拟环境,之后的项目都会在这个虚拟环境下运行。 |image5| -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image6| -.. |image0| image:: http://image.python-online.cn/20200209161935.png -.. |image1| image:: https://i.loli.net/2018/06/11/5b1e7d36ce8ad.png -.. |image2| image:: https://i.loli.net/2018/06/11/5b1e7f140be6a.png -.. |image3| image:: https://i.loli.net/2018/06/11/5b1e805c996c8.png -.. |image4| image:: https://i.loli.net/2018/06/11/5b1e812db603f.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 +.. |image5| image:: https://i.loli.net/2018/06/11/5b1e812db603f.png +.. |image6| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_02.md b/source/c04/c04_02.md index bf0a8b8..607c775 100644 --- a/source/c04/c04_02.md +++ b/source/c04/c04_02.md @@ -1,159 +1,162 @@ -# 4.2 Xshell的高效使用手册 +# 4.2 虚拟环境:Pipenv ---- - -![](http://image.python-online.cn/20190511162815.png) +![](http://image.iswbm.com/20200602135014.png) -做为一名开发人员,我们难免都会与服务器打交道。 +以前一直使用pip+virtualenv+virtualwrapper管理模块和环境, 但是virtualwrapper在windows上使用不太方便,而且包和环境分开管理确实经常不记得哪个是哪个了。 -有时候是公司的线上生产环境,你需要上去部署公司的项目。 -有时候是在阿里上买的云主机,想自己搭个博客来写写文章。 -就像我,是从事云计算相关的,每天远程登陆的服务器都有几十台。 +为什么 会推荐 pipenv 呢? -这时候,掌握一些远程登陆工具的使用技巧是相当有必要的,会大大增加你使用的便利性和愉悦性。 +- 它是 `virtualenv` 和 `pip` 的合体,可以合起来使用; +- 使用`Pipfile` 和 `Pipfile.lock`替代`requirements.txt` +- 可以使用 `pipenv graph`很方便的看出包的依赖关系。 +- 通过加载`.env`文件简化开发工作流程 -优秀的远程工具有很多,Xshell,SecureCRT,PuTTY等等。 +## 1. 安装pipenv -由于小明是个很注重工具顔值的人,所以一开始就选择了Xshell。本篇也只会介绍Xshell的使用技巧,但是快捷键在其他工具上部分是通用的。 +如果你的电脑上没有安装 pipenv,可以使用如下方法安装 -## 快捷入口 +```shell +# mac +$ brew install pipenv -**快速命令** +# windows +pip install [--user] pipenv +``` -查看 -> 快速命令 +如果你的电脑是 windows 的。 -双击底部自定义快速命令。 -![](http://image.python-online.cn/20190511162524.png) +![](http://image.iswbm.com/Fk6WZ2xbqg2DM3AvnYCpsiKQ4xOn) -**使用收藏栏** +需要将如标示路径,加入到 环境变量 PATH 中。 -点击最左侧按按钮添加收藏。 -![](http://image.python-online.cn/20190511162607.png) +![](http://image.iswbm.com/FjuJ8yZsgjkzVuBRZHxK1ZnnzaEX) -**快捷设置** +然后需要重启一下,CMD 终端才能够刷新环境变量。 -工具 -> 选项 -> 键盘和鼠标 +## 2. 创建虚拟环境 -① 右键粘贴 -② 双击分隔符 -③ 选中即复制 +DjangoWebBlog 是我们的项目目录,进入这个目录下创建虚拟环境 -![](http://image.python-online.cn/20190511162716.png) +```shell +$ mkdir DjangoWebBlog && cd DjangoWebBlog -**设置Meta键** +# 在当前目录下创建一个虚拟环境(默认的Python版本) +$ pipenv install +``` -文件 -> 属性 -> 键盘 +你也可以指定版本创建 -一定要打钩,这是后面诸多快捷使用的前提。 -![](http://image.python-online.cn/20190511162730.png) +```shell +$ pipenv --two # 相当于 pipenv --python /usr/bin/python2 +$ pipenv --three # 相当于 pipenv --python /usr/bin/python3 -## 移动光标 +$ pipenv --python 3.7 # 也可以指定具体的版本 +pipenv install --python 2 ``` -Ctrl+f 向后移动一个字符 -Ctrl+b 向前移动一个字符 -Ctrl+a 将光标移至输入行头,相当于Home键 -Ctrl+e 将光标移至输入行末,相当于End键 +这边以安装 python2 版本的虚拟环境为例说明。 -Alt+f 以单词为单位,向前移动 -Alt+b 以单词为单位,向前移动 +![](http://image.iswbm.com/20190612211330.png) -Shift+PgUp 将终端显示向上滚动 -Shift+PgDn 将终端显示向下滚动 -``` +如果你原项目使用的是 requirements.txt 这个管理包的方式,这时候执行 `pipenv --tow` 创建一个虚拟环境后,会找到 requirements.txt ,并根据这里面的依赖包生成 Pipfile文件。 -## 删除元素 -``` -Ctrl+u 删除到行首 -Ctrl+k 删除到行末 +![](http://image.iswbm.com/20190612213015.png) -Ctrl+y 粘贴上次Ctrl+u/k的字符 -Ctrl+d 删除当前字符,等同于Del +## 3. 查询虚拟环境 -Alt+Backspace 向前删除一个单词,和 Ctrl+w 一样 -``` +```shell +# 返回项目的路径 +$ pipenv --where -## 切换标签 -``` -Alt+n n为1-9数字,快速切换标签页 -Ctrl+Tab 向右切换标签 +# 返回虚拟环境路径 +$ pipenv --venv -Ctrl+Shift+Tab 向右切换标签 -Shift+Tab 两个窗口来回切换 +# 返回该虚拟环境的解释器 +$ pipenv --py ``` -## 屏幕模式 -``` -Alt+s 切换到简单版模式 -Alt+Enter 切换至全屏 -``` +演示如下: -## 快速操作 -```shell -Alt+. 取得上次命令的参数,并粘贴 -Ctrl+r 在历史命令中查找,回车执行 -Ctrl+o 放在命令后执行,可重复执行 +![](http://image.iswbm.com/20190612213950.png) +## 4. 操作虚拟环境 -Alt+u 以单词为单位,将光标处到单词结尾的字符转化为大写 -Alt+l 以单词为单位,将光标处到单词结尾的字符转化为小写 +```shell +# 进入这个虚拟环境 +$ pipenv shell + +# 退出这个虚拟环境 +$ exit +$ deactivate -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`文件,用来存放一些环境变量。 --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_02.rst b/source/c04/c04_02.rst old mode 100755 new mode 100644 index f312ac8..5508abc --- a/source/c04/c04_02.rst +++ b/source/c04/c04_02.rst @@ -1,179 +1,185 @@ -4.2 Xshell的高效使用手册 -======================== - --------------- +4.2 虚拟环境:Pipenv +==================== |image0| -做为一名开发人员,我们难免都会与服务器打交道。 +以前一直使用pip+virtualenv+virtualwrapper管理模块和环境, +但是virtualwrapper在windows上使用不太方便,而且包和环境分开管理确实经常不记得哪个是哪个了。 -有时候是公司的线上生产环境,你需要上去部署公司的项目。 -有时候是在阿里上买的云主机,想自己搭个博客来写写文章。 +为什么 会推荐 pipenv 呢? -就像我,是从事云计算相关的,每天远程登陆的服务器都有几十台。 +- 它是 ``virtualenv`` 和 ``pip`` 的合体,可以合起来使用; +- 使用\ ``Pipfile`` 和 ``Pipfile.lock``\ 替代\ ``requirements.txt`` +- 可以使用 ``pipenv graph``\ 很方便的看出包的依赖关系。 +- 通过加载\ ``.env``\ 文件简化开发工作流程 -这时候,掌握一些远程登陆工具的使用技巧是相当有必要的,会大大增加你使用的便利性和愉悦性。 +1. 安装pipenv +------------- -优秀的远程工具有很多,Xshell,SecureCRT,PuTTY等等。 +如果你的电脑上没有安装 pipenv,可以使用如下方法安装 -由于小明是个很注重工具顔值的人,所以一开始就选择了Xshell。本篇也只会介绍Xshell的使用技巧,但是快捷键在其他工具上部分是通用的。 +.. code:: shell -快捷入口 --------- + # mac + $ brew install pipenv -**快速命令** + # windows + pip install [--user] pipenv -查看 -> 快速命令 +如果你的电脑是 windows 的。 -双击底部自定义快速命令。 |image1| +|image1| -**使用收藏栏** +需要将如标示路径,加入到 环境变量 PATH 中。 -点击最左侧按按钮添加收藏。 |image2| +|image2| -**快捷设置** +然后需要重启一下,CMD 终端才能够刷新环境变量。 -工具 -> 选项 -> 键盘和鼠标 +2. 创建虚拟环境 +--------------- -① 右键粘贴 ② 双击分隔符 ③ 选中即复制 +DjangoWebBlog 是我们的项目目录,进入这个目录下创建虚拟环境 -|image3| +.. code:: shell -**设置Meta键** + $ mkdir DjangoWebBlog && cd DjangoWebBlog -文件 -> 属性 -> 键盘 + # 在当前目录下创建一个虚拟环境(默认的Python版本) + $ pipenv install -一定要打钩,这是后面诸多快捷使用的前提。 |image4| +你也可以指定版本创建 -移动光标 --------- +.. code:: shell -:: + $ pipenv --two # 相当于 pipenv --python /usr/bin/python2 + $ pipenv --three # 相当于 pipenv --python /usr/bin/python3 - Ctrl+f 向后移动一个字符 - Ctrl+b 向前移动一个字符 + $ pipenv --python 3.7 # 也可以指定具体的版本 + pipenv install --python 2 - Ctrl+a 将光标移至输入行头,相当于Home键 - Ctrl+e 将光标移至输入行末,相当于End键 +这边以安装 python2 版本的虚拟环境为例说明。 - Alt+f 以单词为单位,向前移动 - Alt+b 以单词为单位,向前移动 +|image3| - Shift+PgUp 将终端显示向上滚动 - Shift+PgDn 将终端显示向下滚动 +如果你原项目使用的是 requirements.txt 这个管理包的方式,这时候执行 +``pipenv --tow`` 创建一个虚拟环境后,会找到 requirements.txt +,并根据这里面的依赖包生成 Pipfile文件。 -删除元素 --------- +|image4| -:: +3. 查询虚拟环境 +--------------- - Ctrl+u 删除到行首 - Ctrl+k 删除到行末 +.. code:: shell - Ctrl+y 粘贴上次Ctrl+u/k的字符 - Ctrl+d 删除当前字符,等同于Del + # 返回项目的路径 + $ pipenv --where - Alt+Backspace 向前删除一个单词,和 Ctrl+w 一样 + # 返回虚拟环境路径 + $ pipenv --venv -切换标签 --------- + # 返回该虚拟环境的解释器 + $ pipenv --py -:: +演示如下: - Alt+n n为1-9数字,快速切换标签页 - Ctrl+Tab 向右切换标签 +|image5| - Ctrl+Shift+Tab 向右切换标签 - Shift+Tab 两个窗口来回切换 +4. 操作虚拟环境 +--------------- -屏幕模式 --------- +.. code:: shell -:: + # 进入这个虚拟环境 + $ pipenv shell - Alt+s 切换到简单版模式 - Alt+Enter 切换至全屏 + # 退出这个虚拟环境 + $ exit + $ deactivate -快速操作 --------- + # 移除当前目录的虚拟环境 + $ pipenv --rm -.. code:: shell +执行 ``pipenv shell`` +就可以进入这个虚拟环境,在头部会有虚拟环境的标识名称。有这个标识,说明已经进入虚拟环境。 - Alt+. 取得上次命令的参数,并粘贴 - Ctrl+r 在历史命令中查找,回车执行 - Ctrl+o 放在命令后执行,可重复执行 +|image6| +.. code:: python - Alt+u 以单词为单位,将光标处到单词结尾的字符转化为大写 - Alt+l 以单词为单位,将光标处到单词结尾的字符转化为小写 + # 在当前虚拟环境中运行 + $ pipenv run python # 进入交互式,跟直接执行 python 一样 + $ pipenv run python 文件名 # 运行文件 + $ pipenv run pip ... # 运行pip - Alt+Shift+# 注释当前命令,相当于ctrl-a,#,enter +5. 虚拟环境包管理 +----------------- -辅助命令 --------- +.. code:: shell -:: + # 安装一个本地包(setup.py)到虚拟环境(Pipfile) + $ pipenv install -e . - Ctrl+s 锁住终端,可用来停留在当前屏 - Ctrl+q 解锁终端,恢复刷屏 - Ctrl+d 键盘输入结束或退出终端 + # 安装、卸载模块 + $ pipenv install requests + $ pipenv uninstall requests + $ pipenv uninstall --all # 卸载全部包 + $ pipenv install -r path/to/requirements.txt - Ctrl+s 暂停当前程序,暂停后按下任意键恢复运行 - Ctrl+z 将当前程序放到后台运行,恢复到前台为命令fg + # 安装所有依赖 + $ pipenv install --dev - Ctrl+Shift+r 重新连接 + # 更新包 + $ pipenv update # 更新所有包 + $ pipenv update --outdated # 打印所有要更新的包 + $ pipenv update <包名> # 更新指定的包 - Ctrl+Insert 复制 - Shift+Insert 粘贴 + # 将Pipfile和Pipfile.lock文件里面的包导出为requirements.txt文件 + $ pipenv run pip freeze # 相当于pipenv run pip freeze >requirements.txt -以我日常使用到的,暂时就这么些了,以后有用更多的使用技巧和快捷键再来补充。 + $ pipenv lock -r > requirements.txt + $ pipenv lock -r --dev # 若只想导出开发用的包 -配色方案 --------- +6. 其他命令 +----------- -新建一个文件\ ``ubuntu.xcs`` +.. code:: shell -:: - [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 + # 创建一个包含预发布的锁文件: + $ pipenv lock --pre -然后在xshell配色方案中导入即可 + # 打印所有包的依赖关系图 + $ pipenv graph -附上一个更全的帖子:\ `Xshell快捷键汇总 `__ + # 检查安全漏洞 + $ pipenv check --------------- +打印该虚拟环境下所有包的依赖关系图 -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image7| - 关注公众号,获取最新干货! +有的python第三方包旧版本会有安全漏洞,使用 pipenv check +可以检查安全漏洞。 + +|image8| + +.env`文件,用来存放一些环境变量。 + +-------------- -.. |image0| image:: http://image.python-online.cn/20190511162815.png -.. |image1| image:: http://image.python-online.cn/20190511162524.png -.. |image2| image:: http://image.python-online.cn/20190511162607.png -.. |image3| image:: http://image.python-online.cn/20190511162716.png -.. |image4| image:: http://image.python-online.cn/20190511162730.png +|image9| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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 5113a94..ec5afbd 100644 --- a/source/c04/c04_03.md +++ b/source/c04/c04_03.md @@ -1,6 +1,6 @@ # 4.3 30分钟教你搭建一个博客 ---- +![](http://image.iswbm.com/20200602135014.png) 10个优秀的程序员里,有9个人都有写博客的习惯。这是非常好的习惯,值得每个程序员,投入时间和精力去坚持做下去。 @@ -23,32 +23,35 @@ - ReadtheDocs:发布网页 -## 4.3.1 成品展示 -以我的博客(`python-online.cn`)为例,先给大家展示一下。 +## 1. 成品展示 + +以我的博客(`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好像有些坑,你需要踩一下。 安装Python工具包 + ``` $ pip install sphinx sphinx-autobuild sphinx_rtd_theme -i https://pypi.douban.com/simple/ ``` @@ -100,7 +103,7 @@ F:. - Makefile:编译文件。完全不用管。 - make.bat:bat脚本。你也不用管。 -## 4.3.3 配置及扩展 +## 3. 配置及扩展 Sphinx 的配置文件是 source\conifg.py @@ -126,7 +129,7 @@ Sphinx 的配置文件是 source\conifg.py pip install -r requirements.txt -i https://pypi.douban.com/simple/ ``` -## 4.3.4 撰写文章 +## 4. 撰写文章 万事俱备,接下来要写文档了。 @@ -205,14 +208,15 @@ 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) + +## 5. 托管项目 -## 4.3.5 托管项目 看到网页的那一刻是不是相当激动。 不过别激动,这只是本地的,我们需要将其发布在线上。 @@ -231,26 +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 这个神器转换一下。 @@ -268,9 +273,9 @@ pandoc -V mainfont="SimSun" -f markdown -t rst hello.md -o hello.rst -## 4.3.7 自定义插件 +## 7. 自定义插件 -之前有不少同学看过我的个人博客(http://python-online.cn),也根据我写的搭建教程,完成了自己的个人站点。 +之前有不少同学看过我的个人博客(http://pythontime.iswbm.com),也根据我写的搭建教程,完成了自己的个人站点。 使用这个方法搭建的站点,一直有一个痛点,就是无法自定义页面,自由度非常的低(和 WordPress 真的是没法比,因为这两种产品定位本身就不一样。) @@ -284,7 +289,7 @@ pandoc -V mainfont="SimSun" -f markdown -t rst hello.md -o hello.rst 方法就是引入两个 JavaSript 插件实现。 -## 第一个插件:导流工具 +### 7.1 第一个插件:导流工具 **作用**:用于将自己博客上流量引导到自己的公众号上。 @@ -292,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) 如想要浏览完整内容,需要点击 “阅读全文” 进行解锁: @@ -302,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) 思路有了,那么如何实现呢? @@ -378,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) 同时你如果之前是看过我写的教程,使用过我的中文检索插件,那你要注意了。 @@ -390,7 +395,7 @@ html_js_files = [ -## 第二个插件:百度统计 +### 3.2 第二个插件:百度统计 **作用**:用于收集个人网站的访问数据。 @@ -408,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 代码。 @@ -441,15 +446,29 @@ 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 帐号进行注册 + +![](http://image.iswbm.com/image-20200704154427375.png) + +然后根据指引填写好资料 + +![](http://image.iswbm.com/image-20200704154846176.png) + +选择基础版 + +![](http://image.iswbm.com/image-20200704155335679.png) +![](http://image.iswbm.com/image-20200704155410411.png) ## 附录:参考文档 @@ -461,4 +480,4 @@ html_js_files = [ --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c04/c04_03.rst b/source/c04/c04_03.rst old mode 100755 new mode 100644 index 8698ed8..33ddd0d --- a/source/c04/c04_03.rst +++ b/source/c04/c04_03.rst @@ -1,7 +1,7 @@ 4.3 30分钟教你搭建一个博客 ========================== --------------- +|image0| 10个优秀的程序员里,有9个人都有写博客的习惯。这是非常好的习惯,值得每个程序员,投入时间和精力去坚持做下去。 @@ -23,27 +23,27 @@ - GitHub:托管项目 - ReadtheDocs:发布网页 -4.3.1 成品展示 --------------- +1. 成品展示 +----------- -以我的博客(\ ``python-online.cn``)为例,先给大家展示一下。 +以我的博客(\ ``pythontime.iswbm.com``)为例,先给大家展示一下。 -这是首页。显示了你所有的文章索引。 |image0| +这是首页。显示了你所有的文章索引。 |image1| -这是我的导航栏。是不是结构很清晰,很方便索引。 |image1| +这是我的导航栏。是不是结构很清晰,很方便索引。 |image2| -点击文章后,还可以很方便查看标题,跳转。 |image2| +点击文章后,还可以很方便查看标题,跳转。 |image3| 体验下搜索功能,速度很快。 -|image3| +|image4| 看完这些你是不是也很想拥有这样一个博客呢? 只要你认真往下看,30分钟搭建这样一个博客不在话下。 -4.3.2 安装Sphinx ----------------- +2. 安装Sphinx +------------- 安装之前,请确认下Python版本。我这里使用的是Python 2.7.14,其他版本请自行尝试噢,Python3.6好像有些坑,你需要踩一下。 @@ -102,8 +102,8 @@ - Makefile:编译文件。完全不用管。 - make.bat:bat脚本。你也不用管。 -4.3.3 配置及扩展 ----------------- +3. 配置及扩展 +------------- Sphinx 的配置文件是 source:raw-latex:`\conifg`.py @@ -131,8 +131,8 @@ exts pip install -r requirements.txt -i https://pypi.douban.com/simple/ -4.3.4 撰写文章 --------------- +4. 撰写文章 +----------- 万事俱备,接下来要写文档了。 @@ -213,12 +213,12 @@ source:raw-latex:`\index`.rst,千万要注意中间的空行不可忽略。 | 我们点进去 build:raw-latex:`\html` 目录,使用浏览器打开index.html文件。 -| |image4| +| |image5| -真棒,已经完成了一半了。点击 我们刚写的 暴富指南。 |image5| +真棒,已经完成了一半了。点击 我们刚写的 暴富指南。 |image6| -4.3.5 托管项目 --------------- +5. 托管项目 +----------- 看到网页的那一刻是不是相当激动。 @@ -236,24 +236,24 @@ source:raw-latex:`\index`.rst,千万要注意中间的空行不可忽略。 接下来,在你的GitHub上新建一个仓库。然后把mkdocs这个目录下的所有文件都提交上去。步骤很简单,这里就不细讲了。 -4.3.6 发布上线 --------------- +6. 发布上线 +----------- 托管完成后,我们要发布它,让别人也可以使用公网访问。 你需要先去 Read the Docs 注册下帐号。 -关联一下GitHub |image6| +关联一下GitHub |image7| -|image7| +|image8| -导入代码库。填好与你对应的信息。 |image8| +导入代码库。填好与你对应的信息。 |image9| -|image9| +|image10| 构建网页后。右下方,你可以看见你的在线地址。 -|image10| +|image11| 这里要提醒一下的是,Sphinx的文档格式,默认是 rst 格式,如果你习惯了使用Markdown来写文章,可以使用 Pandoc @@ -271,10 +271,10 @@ source:raw-latex:`\index`.rst,千万要注意中间的空行不可忽略。 到这里,属于你的个人博客就搭建好了,快去试一下吧。 最后,整个项目的源码和模块包我都放在公众号(\ ``Python编程时光``\ )后台,请关注后,回复「\ ``Sphinx``\ 」领取。 -4.3.7 自定义插件 ----------------- +7. 自定义插件 +------------- -之前有不少同学看过我的个人博客(http://python-online.cn),也根据我写的搭建教程,完成了自己的个人站点。 +之前有不少同学看过我的个人博客(http://pythontime.iswbm.com),也根据我写的搭建教程,完成了自己的个人站点。 使用这个方法搭建的站点,一直有一个痛点,就是无法自定义页面,自由度非常的低(和 WordPress 真的是没法比,因为这两种产品定位本身就不一样。) @@ -289,8 +289,8 @@ WordPress 真的是没法比,因为这两种产品定位本身就不一样。 方法就是引入两个 JavaSript 插件实现。 -第一个插件:导流工具 --------------------- +7.1 第一个插件:导流工具 +~~~~~~~~~~~~~~~~~~~~~~~~ **作用**\ :用于将自己博客上流量引导到自己的公众号上。 @@ -298,7 +298,7 @@ WordPress 真的是没法比,因为这两种产品定位本身就不一样。 游客无法阅读博客的全部内容,因为会有一半的内容会被隐藏,就像这样。 -|image11| +|image12| 如想要浏览完整内容,需要点击 “阅读全文” 进行解锁: @@ -306,7 +306,7 @@ WordPress 真的是没法比,因为这两种产品定位本身就不一样。 2. 发送 ``more`` ,获取到的验证码; 3. 在如下文本框中输入验证码。 -这样就可以永久解锁本博客的所有干货文章。 |image12| +这样就可以永久解锁本博客的所有干货文章。 |image13| 思路有了,那么如何实现呢? @@ -396,7 +396,7 @@ WordPress 真的是没法比,因为这两种产品定位本身就不一样。 - 指定我们的本地生成的 requirements.txt(使用 pip freeze >requirements.txt) -|image13| +|image14| 同时你如果之前是看过我写的教程,使用过我的中文检索插件,那你要注意了。 @@ -409,8 +409,8 @@ Python 3.x ,所以这里的代码也要对应修改。 一切按照上面的步骤全部设置完成后,提交Github后,再次从 readthedocs 构建就可以看到效果了。 -第二个插件:百度统计 --------------------- +3.2 第二个插件:百度统计 +~~~~~~~~~~~~~~~~~~~~~~~~ **作用**\ :用于收集个人网站的访问数据。 @@ -428,11 +428,11 @@ Python 3.x ,所以这里的代码也要对应修改。 然后在网站列表新增一个你的网站,我的信息如下: -|image14| +|image15| 填写完成,就可以获取一段属于你的网站的专属 js 代码(下面第一步)。 -|image15| +|image16| 第二步内容,是教你如何安装这段 js 代码。 @@ -461,14 +461,31 @@ Python 3.x ,所以这里的代码也要对应修改。 构建完成后,去执行第三步,代码安装检查。像我下面这样,就是安装完成了。 -|image16| +|image17| 这个插件安装完成后,如果你的网站有流量,可以过个一个小时,点击一下查看报告查看你网站的详细访问数据。 -|image17| +|image18| 数据真的非常全面,你可以知道,访客都是从哪里访问(直接访问,Google等),每篇文章的点击量(你就知道哪篇是爆款?),每天有多少老访问客,多少新访客等等,更多维度的数据你可以自己去体验一下。 +###3.3 第三个插件:评论系统 + +先到这个\ `网站 `__\ 去注册一个 disqus +帐号,我使用了 gmail 帐号进行注册 + +|image19| + +然后根据指引填写好资料 + +|image20| + +选择基础版 + +|image21| + +|image22| + 附录:参考文档 -------------- @@ -478,27 +495,30 @@ Python 3.x ,所以这里的代码也要对应修改。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190511160523.png -.. |image1| image:: http://image.python-online.cn/20190511161056.png -.. |image2| image:: http://image.python-online.cn/20190511161130.png -.. |image3| image:: http://image.python-online.cn/20190511161147.png -.. |image4| image:: http://image.python-online.cn/20190511161212.png -.. |image5| image:: http://image.python-online.cn/20190511161240.png -.. |image6| image:: http://image.python-online.cn/20190511161255.png -.. |image7| image:: http://image.python-online.cn/20190511161311.png -.. |image8| image:: http://image.python-online.cn/20190511161334.png -.. |image9| image:: http://image.python-online.cn/20190511161414.png -.. |image10| image:: http://image.python-online.cn/20190511161426.png -.. |image11| image:: http://image.python-online.cn/20191015230346.png -.. |image12| image:: http://image.python-online.cn/20191015230502.png -.. |image13| image:: http://image.python-online.cn/20191015234452.png -.. |image14| image:: http://image.python-online.cn/20191016205336.png -.. |image15| image:: http://image.python-online.cn/20191016205653.png -.. |image16| image:: http://image.python-online.cn/20191015225652.png -.. |image17| image:: http://image.python-online.cn/20191016211012.png +|image23| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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/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 b3f6ba6..d6c3a95 100644 --- a/source/c04/c04_04.md +++ b/source/c04/c04_04.md @@ -1,5 +1,7 @@ # 4.4 Jupyter NoteBook 使用指南 +![](http://image.iswbm.com/20200602135014.png) + --- @@ -27,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 就好。 @@ -35,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) 还有一点,命令的执行是可间断的,某个命令执行错误,不会导致整个程序中断,这将很方便我们调试代码,只要改完代码,再重新执行该行代码即可,而不用重新执行全部代码。 @@ -151,21 +153,21 @@ 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) 好了,大概就是这些内容。 ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_04.rst b/source/c04/c04_04.rst old mode 100755 new mode 100644 index 43240a9..a694a1d --- a/source/c04/c04_04.rst +++ b/source/c04/c04_04.rst @@ -1,6 +1,8 @@ 4.4 Jupyter NoteBook 使用指南 ============================= +|image0| + -------------- 今天来安利一款 Python开发 @@ -38,7 +40,7 @@ Anaconda 4.4.1 下载安装 -------------- -到官网下载 `Anaconda `__ |image0| +到官网下载 `Anaconda `__ |image1| 我这里选择下载 Py3.6,下载下来是 exe 文件(600多M)。安装即可。 安装完成后,还会提示你是否安装 VS Code,自行选择。不安装直接 Skip 就好。 @@ -47,18 +49,18 @@ Anaconda -------------- 点击打开 Anaconda 。会出现这个界面。第一眼,我看到了很多工具,包含 -NoteBook,qtconsole,VS Ccode等。 |image1| +NoteBook,qtconsole,VS Ccode等。 |image2| 这里就介绍一下,我最经常使用的 NoteBook 就好了。其他的大家自行尝试。 -选择 NoteBook,点击 Launch,会启动浏览器打开一个 web 页面。 |image2| +选择 NoteBook,点击 Launch,会启动浏览器打开一个 web 页面。 |image3| -点击右上角的new,就可以进入 Python3 的交互界面。 |image3| +点击右上角的new,就可以进入 Python3 的交互界面。 |image4| 来感受一下,如何使用这个工具。 每一行的命令,都在一个可编辑的输入框。即使你的命令输入有误,你也不用从头写代码,直接编辑后重新执行即可。 -|image4| +|image5| 还有一点,命令的执行是可间断的,某个命令执行错误,不会导致整个程序中断,这将很方便我们调试代码,只要改完代码,再重新执行该行代码即可,而不用重新执行全部代码。 @@ -220,35 +222,34 @@ Markdown下快捷键 其实以上快捷键,在非编辑模式下,按 h 就会出现快捷键帮助菜单。 -|image5| |image6| +|image6| |image7| 4.4.4 导出笔记文件 ------------------ NoteBook 既然支持 Markdown ,你已经也能想到它可以用来记录学习笔记。 它提供多种常用的文件格式,md,rst,pdf等。如果你希望再次编辑,可以保存为ipynb,这是Jupyter的文件格式,可以再次打开进行编辑。 -|image7| +|image8| 以前我学习 Pandas 的时候,也曾经使用它做过笔记,输出的是PDF文件,可以按目录导航,相当方便。 -|image8| +|image9| 好了,大概就是这些内容。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190511163102.png -.. |image1| image:: http://image.python-online.cn/20190511163123.png -.. |image2| image:: http://image.python-online.cn/20190511163137.png -.. |image3| image:: http://image.python-online.cn/20190511163145.png -.. |image4| image:: http://image.python-online.cn/20190511163200.png -.. |image5| image:: http://image.python-online.cn/20190511163245.png -.. |image6| image:: http://image.python-online.cn/20190511163253.png -.. |image7| image:: http://image.python-online.cn/20190511163304.png -.. |image8| image:: http://image.python-online.cn/20190511163311.png +|image10| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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 51be5bc..0f9ad0e 100644 --- a/source/c04/c04_05.md +++ b/source/c04/c04_05.md @@ -1,130 +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 +``` + +查询当前环境中可升级的包 + +```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 +``` -在大多数情况下,对于一个程序开发人员,电脑的操作系统的最佳选择不应该是 Windows,而是 Mac 或者 Linux。 +**3.2 限定版本进行软件包安装** -Mac 固然很好,但是价钱实在不亲民,让很多人连体验都体验不了,只能停留在想象中,那是属于“别人家的电脑”。如果你想装个黑苹果,那我劝你还是放弃吧,你可以去尝试一下。 +以下三种,对单个 python 包的版本进行了约束 -除了 Mac 外,还有开源免费的 Linux 可以选择,可以让你 远离游戏和一切娱乐项目,而专注于开发。 +```shell +# 所安装的包的版本为 2.1.2 +$ pip install pkg==2.1.2 -Linux 的发行版本有很多,这里选择 Ubuntu 并没有什么特殊的缘由。只是个人感觉在桌面版本 Ubuntu 会比其他发行版做得更加人性,更加成熟。而你如果想使用 CentOS,Debian等,在本篇教程中,换汤不换药,应该也是同样适用的。 +# 所安装的包必须大于等于 2.1.2 +$ pip install pkg>=2.1.2 -## 4.5.1 准备工作 +# 所安装的包必须小于等于 2.1.2 +$ pip install pkg<=2.1.2 +``` -由于本教程,是在双硬盘下安装的。所以你要确保你的电脑上有两块硬盘,如果没有,可以去购买一块,这里强烈推荐 SSD,不要再买 HDD 了。因为在我的对比之下,安装速度差的不是一点半点,HDD 下安装可能需要20-30分钟吧(没有去注意,反正挺久),SSD 下安装,啾的一下,很快就好,真的超级快的。 +以下命令用于管理/控制整个 python 环境的包版本 -另外,你还需要两个软件,这里我会打包准备好,你可以 扫描二维码,关注我公众号后,回复「双系统」直接获取。 +```shell +# 导出依赖包列表 +pip freeze >requirements.txt -最后,如果你怕误操作,将新系统覆盖到你原来的物理盘上,导致你的数据丢失,你最好进行备份,但是我相信你只要跟着文章一步一步来操作,不会出现这种意外。 +# 从依赖包列表中安装 +pip install -r requirements.txt -总结如下: +# 确保当前环境软件包的版本(并不确保安装) +pip install -c constraints.txt +``` -- 双硬盘(SSD) -- U盘(4G+) -- 两个软件(UltraISO + DiskGenius) -- iso文件(Ubuntu 16.04) -- 数据无价,注意备份 -## 4.5.2 安装硬盘 -若你的电脑已经有两块硬盘(都是HDD也无防,没有强制),那你可以直接跳过,进入第二步。 +**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 ~]$ +``` -由于我此前只有一块硬盘,所以为了这次顺利的安装,我特地网购了一块 SSD。其实在此之前,我有尝试在单硬盘上尝试安装,也已经成功安装了,但是在开机重启后,电脑无法得知该从哪个分区引导系统,导致我的电脑直接无法使用,不论是 Windows 还是新装的 Ubuntu 都进入不到系统,好在我不慌,我家里有好几个 U盘,其中一个 U盘,装的是 PE系统,里面有DiskGenius工具,可以自动修复引导。这才得以重新进入 Windows。 +当你身处个人用户环境中,python 导包时会先检索当前用户环境中是否已安装这个包,已安装则优先使用,未安装则使用全局环境中的包。 -关于单个硬盘引导的问题,我曾尝试使用 EasyBCD 和 NTBOOTautofix 来创建新的引导。但是都没有成功,为了减少折腾的成本,我才直接选择双硬盘安装的。如果有单硬盘安装经验的朋友,欢迎与我交流,我也想学习一下。 +验证如下: -这里上一张我安装硬盘的记念图。 +```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'] +>>> -![](http://image.python-online.cn/20190511163441.png) +``` -你没有看错,我的是台式机。说起这台电脑,还是前年我自己搜罗配置单,自己从各大电商平台,有京东,淘宝,还有天猫买了所有的配件,然后自己一件一件组装起来的。还是挺有感情的,虽然也是渣渣配置,但是这个过程还是很愉快的。在有了每一次装体验后,后面我还给别人装过好几台,如果说高三是我知识储备最高的时期,那前年就是我动手能力最强的时期的。组装过将近十台的电脑。这都是题外话了。 +**3.6 延长超时时间** -第一次装好硬盘后呢,要进入原先的 Windows 系统,检查一下,我们安装的硬盘有没有装成功。由于我装了工具,开始键和你们的可能不一样(不过真的是Win10),你也可以右击桌面 「我的电脑」,再点击「管理」。 -![](http://image.python-online.cn/20190511163457.png) -如果硬盘安装成功,这里会有一块未分配的盘,如图中的 硬盘0。 -第一次使用,需要初始化硬盘,记得选 GPT。 -![](http://image.python-online.cn/20190511163510.png) +若网络情况不是很好,在安装某些包时经常会因为 ReadTimeout 而失败。 -这里可以不用急着分区(在后面安装系统时会让你分的),如果你要提前分好(使用DiskGenius),也没有关系。 +对于这种情况,一般重试几次就好了。 -## 4.5.3 制作U盘系统 +但是这样难免有些麻烦,有没有更好的解决方法呢? -有安装过系统的人(Windows),正常都知道,我们使用U盘安装一个 PE 系统,然后通过这个 U盘 过渡,将真正的操作系统写入硬盘中。 +有的,可以通过延长超时时间。 -那么如何安装这个 U盘 系统呢,你首先需要先去官网下载一个对应的系统的 iso文件,Ubuntu 的话,你可以去官网下载:https://www.ubuntu.com/download/desktop,我这里下载的是 16.04 的。 +```shell +$ pip install --default-timeout=100 +``` -此外,你还需要一个可以 iso文件 写入到U盘中的工具,这里我使用UltraISO ,你可以关注公众号后,在后台回复关键字「双系统」直接获取下载地址。 -iso 和 UltraISO 都准备完成后,就可以安装U盘系统了。 -首先,打开软件,点击 文件 - 打开,选择你所下载的 iso 文件。出现如下界面 +## 4. 卸载软件包 -![](http://image.python-online.cn/20190511163520.png) +就一条命令,不再赘述 -再点击 启动 - 写入硬盘映像 - 写入 +```shell +$ pip uninstall pkg +``` -![](http://image.python-online.cn/20190511163531.png) -如一切顺利,U盘就制作完成,一般 99.99% 都不会在这地方出错。 -## 4.5.4 关闭快速启动 +## 5. 升级软件包 -大家都知道,win10的开机速度有多快,具体原理我就不讲了,有兴趣可以搜索引擎查找。 +想要对现有的 python 进行升级,其本质上也是先从 pypi 上下载最新版本的包,再对其进行安装。所以升级也是使用 `pip install`,只不过要加一个参数 `--upgrade`。 -但在这里,必须关闭 win10的快速启动功能。方法如下:取消勾选「启用快速启动」,点击保存修改。然后就可以正常关机了。 +``` +$ pip install --upgrade pkg +``` -![](http://image.python-online.cn/20190511163542.png) +在升级的时候,其实还有一个不怎么用到的选项 `--upgrade-strategy`,它是用来指定升级策略。 +它的可选项只有两个: -## 4.5.5 安装Ubuntu +- `eager` :升级全部依赖包 +- `only-if-need`:只有当旧版本不能适配新的父依赖包时,才会升级。 -在安装之前呢,首先你要根据你的主板,查找选择 启动顺序 的快捷键。由于我的主板是 MSI ,所以我的快捷键是 F11。当然你可以选择,按DEL键进入 Bios,更改顺序。但是我这里不想这么麻烦,因为安装完后又要改回来。 +在 pip 10.0 版本之后,这个选项的默认值是 `only-if-need`,因此如下两种写法是一互致的。 -查找完后,你可以对电脑开机了,按住你的快捷键,选择启动方式。由于我们要从U盘启动,所以这里选择 +```shell +pip install --upgrade pkg1 +pip install --upgrade pkg1 --upgrade-strategy only-if-need +``` -这里一定要注意,选择最后一个,自定义选项。 -![](http://image.python-online.cn/20190511163550.png) +## 6. 配置文件 +由于在使用 pip 安装一些包时,默认会使用 pip 的官方源,所以经常会报网络超时失败。 -终于到了分区的这一步了,这是最关键的一步。对于Linux不熟悉的人,到这里可能会懵逼。不用怕,这里给你提供一个最简单的分区配置。具体为什么这么分,我想你并不关心吧? +常用的解决办法是,在安装包时,使用 `-i` 参数指定一个国内的镜像源。但是每次指定就很麻烦呀,还要打超长的一串字母。 -- efi:系统分区,我分20G,分太多了。 -- swap:系统交换分区,设置为8G -- /:剩下全部分给根目录,类型选 ext4 +这时候,其实可以将这个源写进 pip 的配置文件里。以后安装的时候,就默认从你配置的这个 源里安装了。 -对,就是这么简单粗暴。如果你想更加精细一点,你还可以自定义 /home,/usr等。 +那怎么配置呢?文件文件在哪? -分区完成后,一定要注意如下这二个红框,选择安装启动引导器的设备为 我们刚刚设置的efi分区。检查无误后,就可以点击「现在安装」。 -![](http://image.python-online.cn/20190511163559.png) +使用` win+r` 输入 `%APPDATA%` 进入用户资料文件夹,查看有没有一个 pip 的文件夹,若没有则创建之。 -接下来就是 选择时区 - 配置键盘。 -![](http://image.python-online.cn/20190511163612.png) +然后进入这个 文件夹,新建一个 `pip.ini` 的文件,内容如下 -![](http://image.python-online.cn/20190511163633.png) +```ini +[global] +time-out=60 +index-url=https://pypi.tuna.tsinghua.edu.cn/simple/ +[install] +trusted-host=tsinghua.edu.cn +``` -到了这里,你应该可以长舒第一口气了。成功了一半了。 -![](http://image.python-online.cn/20190511163700.png) -如果你和我一样使用 SSD ,应该不出5分钟系统就可以安装完毕。弹出如下界面。点击现在重启。 -![](http://image.python-online.cn/20190511163711.png) -重启的过程,记住还是一样按住你的快捷键,我这里仍然是 F11,看到没有,已经有一个叫 ubuntu的启动设备。就它了,选择进入系统。接下来,就是选择要以哪种模式进入ubuntu,你根据需要去选吧。 -![](http://image.python-online.cn/20190511163722.png) +以上几乎包含了 pip 的所有常用使用场景,为了方便,我将其整理成一张表格,如果你需要,可以关注我的公众号(Python编程时光),后台回复“pip”,可获取高清无水印图片。 -## 4.5.6 效果展示 +![](http://image.iswbm.com/20191105200041.png) -由于默认的Ubuntu主题也是丑得可以,经过一个晚上的美化,它变成如下这般帅气逼人。 -![](http://image.python-online.cn/20190511163731.png) -![](http://image.python-online.cn/20190511163750.png) +![](http://image.iswbm.com/20200607174235.png) -![](http://image.python-online.cn/20190511163757.png) -![](http://image.python-online.cn/20190511163805.png) ----- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) diff --git a/source/c04/c04_05.rst b/source/c04/c04_05.rst old mode 100755 new mode 100644 index 86b863c..7353b73 --- a/source/c04/c04_05.rst +++ b/source/c04/c04_05.rst @@ -1,182 +1,392 @@ -4.5 Win10+Ubuntu 双系统安装教程 -=============================== +4.5 最全的 pip 使用指南 +======================= --------------- +|image0| -在大多数情况下,对于一个程序开发人员,电脑的操作系统的最佳选择不应该是 -Windows,而是 Mac 或者 Linux。 +所有的 Python 开发者都清楚,Python +之所以如此受欢迎,能够在众多高级语言中,脱颖而出,除了语法简单,上手容易之外,更多还要归功于 +Python 生态的完备,有数以万计的 Python 爱好者愿意以 Python +为基础封装出各种有利于开发的第三方工具包。 -Mac -固然很好,但是价钱实在不亲民,让很多人连体验都体验不了,只能停留在想象中,那是属于“别人家的电脑”。如果你想装个黑苹果,那我劝你还是放弃吧,你可以去尝试一下。 +这才使用我们能够以最快的速度开发出一个满足基本需要的项目,而不是每次都重复造轮子。 -除了 Mac 外,还有开源免费的 Linux 可以选择,可以让你 -远离游戏和一切娱乐项目,而专注于开发。 +Python +从1991年诞生到现在,已经过去28个年头了,这其间产生了数以万计的第三方包,且每个包都会不断更新,会有越来越多的版本。 -Linux 的发行版本有很多,这里选择 Ubuntu -并没有什么特殊的缘由。只是个人感觉在桌面版本 Ubuntu -会比其他发行版做得更加人性,更加成熟。而你如果想使用 -CentOS,Debian等,在本篇教程中,换汤不换药,应该也是同样适用的。 +当你在一个复杂的项目环境中,如果没有一个有效的依赖包管理方案,项目的维护将会是一个大问题。 -4.5.1 准备工作 --------------- +pip 是官方推荐的包管理工具,在大多数开发者眼里,pip 几乎是 Python +的标配。 -由于本教程,是在双硬盘下安装的。所以你要确保你的电脑上有两块硬盘,如果没有,可以去购买一块,这里强烈推荐 -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) -- 数据无价,注意备份 +- 还有其他的,这里不一一列出。 -4.5.2 安装硬盘 --------------- +今天的主角是 pip +,大家肯定不会陌生。但我相信不少人,只是熟悉几个常用的用法,而对于其他几个低频且实用的用法,却知之甚少,这两天,我查阅官方文档,把这些用法整理了一下,应该是网络上比较全的介绍。 -若你的电脑已经有两块硬盘(都是HDD也无防,没有强制),那你可以直接跳过,进入第二步。 +1. 查询软件包 +------------- -由于我此前只有一块硬盘,所以为了这次顺利的安装,我特地网购了一块 -SSD。其实在此之前,我有尝试在单硬盘上尝试安装,也已经成功安装了,但是在开机重启后,电脑无法得知该从哪个分区引导系统,导致我的电脑直接无法使用,不论是 -Windows 还是新装的 Ubuntu 都进入不到系统,好在我不慌,我家里有好几个 -U盘,其中一个 U盘,装的是 -PE系统,里面有DiskGenius工具,可以自动修复引导。这才得以重新进入 -Windows。 +查询当前环境安装的所有软件包 -关于单个硬盘引导的问题,我曾尝试使用 EasyBCD 和 NTBOOTautofix -来创建新的引导。但是都没有成功,为了减少折腾的成本,我才直接选择双硬盘安装的。如果有单硬盘安装经验的朋友,欢迎与我交流,我也想学习一下。 +.. code:: shell -这里上一张我安装硬盘的记念图。 + $ pip list -|image0| +查询 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 -第一次装好硬盘后呢,要进入原先的 Windows -系统,检查一下,我们安装的硬盘有没有装成功。由于我装了工具,开始键和你们的可能不一样(不过真的是Win10),你也可以右击桌面 -「我的电脑」,再点击「管理」。 |image1| -如果硬盘安装成功,这里会有一块未分配的盘,如图中的 硬盘0。 -第一次使用,需要初始化硬盘,记得选 GPT。 |image2| + # 退出 wangbm 用户,在 root 用户环境中发现 requests 未安装 + [root@localhost ~]$ pip list | grep requests + [root@localhost ~]$ -这里可以不用急着分区(在后面安装系统时会让你分的),如果你要提前分好(使用DiskGenius),也没有关系。 +当你身处个人用户环境中,python +导包时会先检索当前用户环境中是否已安装这个包,已安装则优先使用,未安装则使用全局环境中的包。 -4.5.3 制作U盘系统 ------------------ +验证如下: -有安装过系统的人(Windows),正常都知道,我们使用U盘安装一个 PE -系统,然后通过这个 U盘 过渡,将真正的操作系统写入硬盘中。 +.. code:: python -那么如何安装这个 U盘 系统呢,你首先需要先去官网下载一个对应的系统的 -iso文件,Ubuntu -的话,你可以去官网下载:https://www.ubuntu.com/download/desktop,我这里下载的是 -16.04 的。 + >>> 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'] + >>> -此外,你还需要一个可以 iso文件 写入到U盘中的工具,这里我使用UltraISO -,你可以关注公众号后,在后台回复关键字「双系统」直接获取下载地址。 +**3.6 延长超时时间** -iso 和 UltraISO 都准备完成后,就可以安装U盘系统了。 +若网络情况不是很好,在安装某些包时经常会因为 ReadTimeout 而失败。 -首先,打开软件,点击 文件 - 打开,选择你所下载的 iso 文件。出现如下界面 +对于这种情况,一般重试几次就好了。 -|image3| +但是这样难免有些麻烦,有没有更好的解决方法呢? -再点击 启动 - 写入硬盘映像 - 写入 +有的,可以通过延长超时时间。 -|image4| +.. code:: shell -如一切顺利,U盘就制作完成,一般 99.99% 都不会在这地方出错。 + $ pip install --default-timeout=100 -4.5.4 关闭快速启动 ------------------- +4. 卸载软件包 +------------- -大家都知道,win10的开机速度有多快,具体原理我就不讲了,有兴趣可以搜索引擎查找。 +就一条命令,不再赘述 -但在这里,必须关闭 -win10的快速启动功能。方法如下:取消勾选「启用快速启动」,点击保存修改。然后就可以正常关机了。 +.. code:: shell -|image5| + $ pip uninstall pkg -4.5.5 安装Ubuntu ----------------- +5. 升级软件包 +------------- -在安装之前呢,首先你要根据你的主板,查找选择 启动顺序 -的快捷键。由于我的主板是 MSI ,所以我的快捷键是 -F11。当然你可以选择,按DEL键进入 -Bios,更改顺序。但是我这里不想这么麻烦,因为安装完后又要改回来。 +想要对现有的 python 进行升级,其本质上也是先从 pypi +上下载最新版本的包,再对其进行安装。所以升级也是使用 +``pip install``\ ,只不过要加一个参数 ``--upgrade``\ 。 -查找完后,你可以对电脑开机了,按住你的快捷键,选择启动方式。由于我们要从U盘启动,所以这里选择 +:: -这里一定要注意,选择最后一个,自定义选项。 |image6| + $ pip install --upgrade pkg -终于到了分区的这一步了,这是最关键的一步。对于Linux不熟悉的人,到这里可能会懵逼。不用怕,这里给你提供一个最简单的分区配置。具体为什么这么分,我想你并不关心吧? +在升级的时候,其实还有一个不怎么用到的选项 +``--upgrade-strategy``\ ,它是用来指定升级策略。 -- efi:系统分区,我分20G,分太多了。 -- swap:系统交换分区,设置为8G -- /:剩下全部分给根目录,类型选 ext4 +它的可选项只有两个: -对,就是这么简单粗暴。如果你想更加精细一点,你还可以自定义 -/home,/usr等。 +- ``eager`` :升级全部依赖包 +- ``only-if-need``\ :只有当旧版本不能适配新的父依赖包时,才会升级。 -分区完成后,一定要注意如下这二个红框,选择安装启动引导器的设备为 -我们刚刚设置的efi分区。检查无误后,就可以点击「现在安装」。 |image7| +在 pip 10.0 版本之后,这个选项的默认值是 +``only-if-need``\ ,因此如下两种写法是一互致的。 -接下来就是 选择时区 - 配置键盘。 |image8| +.. code:: shell -|image9| + pip install --upgrade pkg1 + pip install --upgrade pkg1 --upgrade-strategy only-if-need -到了这里,你应该可以长舒第一口气了。成功了一半了。 |image10| +6. 配置文件 +----------- -如果你和我一样使用 SSD -,应该不出5分钟系统就可以安装完毕。弹出如下界面。点击现在重启。 -|image11| +由于在使用 pip 安装一些包时,默认会使用 pip +的官方源,所以经常会报网络超时失败。 -重启的过程,记住还是一样按住你的快捷键,我这里仍然是 -F11,看到没有,已经有一个叫 -ubuntu的启动设备。就它了,选择进入系统。接下来,就是选择要以哪种模式进入ubuntu,你根据需要去选吧。 -|image12| +常用的解决办法是,在安装包时,使用 ``-i`` +参数指定一个国内的镜像源。但是每次指定就很麻烦呀,还要打超长的一串字母。 -4.5.6 效果展示 --------------- +这时候,其实可以将这个源写进 pip +的配置文件里。以后安装的时候,就默认从你配置的这个 源里安装了。 -由于默认的Ubuntu主题也是丑得可以,经过一个晚上的美化,它变成如下这般帅气逼人。 +那怎么配置呢?文件文件在哪? -|image13| +使用\ ``win+r`` 输入 ``%APPDATA%`` 进入用户资料文件夹,查看有没有一个 +pip 的文件夹,若没有则创建之。 -|image14| +然后进入这个 文件夹,新建一个 ``pip.ini`` 的文件,内容如下 -|image15| +.. code:: ini -|image16| + [global] + time-out=60 + index-url=https://pypi.tuna.tsinghua.edu.cn/simple/ + [install] + trusted-host=tsinghua.edu.cn --------------- +以上几乎包含了 pip +的所有常用使用场景,为了方便,我将其整理成一张表格,如果你需要,可以关注我的公众号(Python编程时光),后台回复“pip”,可获取高清无水印图片。 -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| - 关注公众号,获取最新干货! +|image2| -.. |image0| image:: http://image.python-online.cn/20190511163441.png -.. |image1| image:: http://image.python-online.cn/20190511163457.png -.. |image2| image:: http://image.python-online.cn/20190511163510.png -.. |image3| image:: http://image.python-online.cn/20190511163520.png -.. |image4| image:: http://image.python-online.cn/20190511163531.png -.. |image5| image:: http://image.python-online.cn/20190511163542.png -.. |image6| image:: http://image.python-online.cn/20190511163550.png -.. |image7| image:: http://image.python-online.cn/20190511163559.png -.. |image8| image:: http://image.python-online.cn/20190511163612.png -.. |image9| image:: http://image.python-online.cn/20190511163633.png -.. |image10| image:: http://image.python-online.cn/20190511163700.png -.. |image11| image:: http://image.python-online.cn/20190511163711.png -.. |image12| image:: http://image.python-online.cn/20190511163722.png -.. |image13| image:: http://image.python-online.cn/20190511163731.png -.. |image14| image:: http://image.python-online.cn/20190511163750.png -.. |image15| image:: http://image.python-online.cn/20190511163757.png -.. |image16| image:: http://image.python-online.cn/20190511163805.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 8d7441f..feaf6e4 100644 --- a/source/c04/c04_06.md +++ b/source/c04/c04_06.md @@ -1,5 +1,7 @@ # 4.6 我的 Git 使用指南 +![](http://image.iswbm.com/20200602135014.png) + --- ## 一、Hello world @@ -48,37 +50,56 @@ $ git rm readme.txt # 删除工作区和暂存区文件,commit后版本库也 ### 1.4 远程仓库 -添加远程仓库 +origin 并不是指得是远程的仓库,而是指得是远程仓库在本地的一个指针。 + +为什么是这个名字呢?仅仅是因为 git 用它做为默认名被广泛使用,一个习惯而已,你也可以叫其他名字。 + +对一个在本地创建的仓库,如果添加远程仓库? ```shell $ git remote add [shortname] [url] # shortname是你命名的远程仓库名字,url格式如下:git@github.com:github_account/repository_name.git -$ git remote add learn_git git@github.com:wongbingming/my_repository.git #注意记得先在GitHub上先新建仓库,my_repository,不然后面推送会出错 +#注意记得先在GitHub上先新建仓库,my_repository,不然后面 push 会出错 +$ git remote add learn_git git@github.com:wongbingming/my_repository.git ``` + + 推送/克隆 ```shell $ git push -u learn_git master #learn_git是之前命名的远程仓库名,master是你要推送到的远程仓库的分支名字 +$ git push -u learn-git master:master $ git clone [github url] # 举例 -$ git clone git@github.com:BingmingWong/test.git +$ git clone git@github.com:iswbm/test.git +``` -# 更改远程仓库地址 -git remote set-url origin git@:you_repo.git +更改远程仓库地址 + +```shell +$ git remote set-url origin git@:you_repo.git ``` +upstream 和 downstream + +Git中的upstream和downstream的概念是相对的。 + +如果A库中的分支x被push到B库中的分支y,则y就是x的upstream,而x就是y的downstream。 + + + ### 1.5 GitHub合并至本地 -``` +```shell $ git merge origin/master ``` ### 1.6 创建SSHkeys -``` +```shell $ ssh-keygen -t rsa -C "youremail@example.com" ``` @@ -91,7 +112,7 @@ $ ssh-keygen -t rsa -C "youremail@example.com" 建完成后,使用如下命令查找位置 -``` +```shell $ find / -name id_rsa.pub ``` @@ -101,7 +122,7 @@ $ find / -name id_rsa.pub ### 2.1 文件状态 -``` +```shell $ git status #如果修改了文件,状态会提示你有文件被修改(但不能告诉你哪里被修改),提示你要commit $ cat readme.txt #查看实体文件的内容 $ git ls-files #查看哪些文件在版本控制下 @@ -109,7 +130,7 @@ $ git ls-files #查看哪些文件在版本控制下 查看当前目录下有哪些远程仓库 -``` +```shell $ git remote # 远程仓库名字 $ git remote -v # verbose的缩写,显示详细信息,包括项目url 需要注意的是,当前Git Shell在不同目录下,git remote显示的远程仓库就不同 @@ -117,13 +138,13 @@ $ git remote -v # verbose的缩写,显示详细信息,包括项目url 查看提交的历史 -``` +```shell $ git log --pretty=oneline --abbrev-commit ``` 查看不同的地方(修改) -``` +```shell $ git diff # 工作区(work dict)和暂存区(stage)的比较 $ git diff --cached # 是暂存区(stage)和分支(master)的比较 意思就是说,我们修改文件并保存实体文件,可以使用git diff查看不同之处,再确定是否add到暂存区 @@ -156,11 +177,13 @@ git log -p ### 2.2 Git日志 -``` +```shell $ git log # 可以查看who在when修改了文件(会写出版本说明),但是这个看着眼花缭乱 $ git log --pretty=online # 这样,每行只显示一次修改,修改信息只有:commitid + 版本说明 $ git reflog # 显示所有修改的日志 + +$ git log --graph --all --decorate # 以可视化图的形式展示 ``` @@ -169,11 +192,11 @@ $ git reflog # 显示所有修改的日志 使用 diff 进行查看 -![](http://image.python-online.cn/20191217150942.png) +![](http://image.iswbm.com/20191217150942.png) 查看两个 commit 之间的修改 -``` +```shell $ git diff commit_id1 commit_id2 $ git diff commit_id^! @@ -183,14 +206,14 @@ $ git diff commit_id1 commit_id2 --stat 查看某个 commit 的改动 -``` -git show commit_id -git show --stat commit_id +```shell +$ git show commit_id +$ git show --stat commit_id ``` 查看两个分支之间的差异 -``` +```shell # 查看两个分支有哪些文件发生改变了 git diff stable/2.2.9 stable/2.3.0 --stat @@ -342,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 回退远程提交 @@ -360,6 +383,19 @@ $ git push origin HEAD --force +### 3.7 删除版本库里的文件 + +```shell +# 加了 --cached 就不会删除工作区的文件 +$ git rm --cached file1.txt + +$ git commit -m "remove file1.txt" + +$ git push origin branch_name +``` + + + ## 四、分支管理 ### 4.1 新建-切换-删除分支 @@ -493,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) 添加完后,可以使用这个命令测试一下。 ``` @@ -531,7 +567,7 @@ Git 的使用全都是命令行的,由于没有那么直观,所以很容易 需要注意的是,使用它需要你进行一步操作。在 7.2 章节,我在机器上生成了一对私钥和公钥,在这里需要配置一下才能正常访问和提交我们的Github仓库。 -![](http://image.python-online.cn/20190430235625.png) +![](http://image.iswbm.com/20190430235625.png) @@ -564,4 +600,4 @@ Git 的使用全都是命令行的,由于没有那么直观,所以很容易 --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_06.rst b/source/c04/c04_06.rst old mode 100755 new mode 100644 index 5c4d314..a3e83b5 --- a/source/c04/c04_06.rst +++ b/source/c04/c04_06.rst @@ -1,6 +1,8 @@ 4.6 我的 Git 使用指南 ===================== +|image0| + -------------- 一、Hello world @@ -56,39 +58,55 @@ add/commit 前撤销对文件的修改 1.4 远程仓库 ~~~~~~~~~~~~ -添加远程仓库 +origin 并不是指得是远程的仓库,而是指得是远程仓库在本地的一个指针。 + +为什么是这个名字呢?仅仅是因为 git +用它做为默认名被广泛使用,一个习惯而已,你也可以叫其他名字。 + +对一个在本地创建的仓库,如果添加远程仓库? .. code:: shell $ git remote add [shortname] [url] # shortname是你命名的远程仓库名字,url格式如下:git@github.com:github_account/repository_name.git - $ git remote add learn_git git@github.com:wongbingming/my_repository.git #注意记得先在GitHub上先新建仓库,my_repository,不然后面推送会出错 + #注意记得先在GitHub上先新建仓库,my_repository,不然后面 push 会出错 + $ git remote add learn_git git@github.com:wongbingming/my_repository.git 推送/克隆 .. code:: shell $ git push -u learn_git master #learn_git是之前命名的远程仓库名,master是你要推送到的远程仓库的分支名字 + $ git push -u learn-git master:master $ git clone [github url] # 举例 - $ git clone git@github.com:BingmingWong/test.git + $ git clone git@github.com:iswbm/test.git + +更改远程仓库地址 + +.. code:: shell + + $ git remote set-url origin git@:you_repo.git + +upstream 和 downstream - # 更改远程仓库地址 - git remote set-url origin git@:you_repo.git +Git中的upstream和downstream的概念是相对的。 + +如果A库中的分支x被push到B库中的分支y,则y就是x的upstream,而x就是y的downstream。 1.5 GitHub合并至本地 ~~~~~~~~~~~~~~~~~~~~ -:: +.. code:: shell $ git merge origin/master 1.6 创建SSHkeys ~~~~~~~~~~~~~~~ -:: +.. code:: shell $ ssh-keygen -t rsa -C "youremail@example.com" @@ -101,7 +119,7 @@ add/commit 前撤销对文件的修改 建完成后,使用如下命令查找位置 -:: +.. code:: shell $ find / -name id_rsa.pub @@ -111,7 +129,7 @@ add/commit 前撤销对文件的修改 2.1 文件状态 ~~~~~~~~~~~~ -:: +.. code:: shell $ git status #如果修改了文件,状态会提示你有文件被修改(但不能告诉你哪里被修改),提示你要commit $ cat readme.txt #查看实体文件的内容 @@ -119,7 +137,7 @@ add/commit 前撤销对文件的修改 查看当前目录下有哪些远程仓库 -:: +.. code:: shell $ git remote # 远程仓库名字 $ git remote -v # verbose的缩写,显示详细信息,包括项目url @@ -127,13 +145,13 @@ add/commit 前撤销对文件的修改 查看提交的历史 -:: +.. code:: shell $ git log --pretty=oneline --abbrev-commit 查看不同的地方(修改) -:: +.. code:: shell $ git diff # 工作区(work dict)和暂存区(stage)的比较 $ git diff --cached # 是暂存区(stage)和分支(master)的比较 @@ -165,23 +183,25 @@ add/commit 前撤销对文件的修改 2.2 Git日志 ~~~~~~~~~~~ -:: +.. code:: shell $ git log # 可以查看who在when修改了文件(会写出版本说明),但是这个看着眼花缭乱 $ git log --pretty=online # 这样,每行只显示一次修改,修改信息只有:commitid + 版本说明 $ git reflog # 显示所有修改的日志 + $ git log --graph --all --decorate # 以可视化图的形式展示 + 2.3 查看修改 ~~~~~~~~~~~~ 使用 diff 进行查看 -|image0| +|image1| 查看两个 commit 之间的修改 -:: +.. code:: shell $ git diff commit_id1 commit_id2 $ git diff commit_id^! @@ -191,14 +211,14 @@ add/commit 前撤销对文件的修改 查看某个 commit 的改动 -:: +.. code:: shell - git show commit_id - git show --stat commit_id + $ git show commit_id + $ git show --stat commit_id 查看两个分支之间的差异 -:: +.. code:: shell # 查看两个分支有哪些文件发生改变了 git diff stable/2.2.9 stable/2.3.0 --stat @@ -350,11 +370,11 @@ add/commit 前撤销对文件的修改 很简单,先使用 ``git reflog`` 找到你的 reset 的 commit id -|image1| +|image2| 然后再次使用 ``git reset`` 指定 commit id 回到一次修改add前的状态 -|image2| +|image3| 3.6 回退远程提交 ~~~~~~~~~~~~~~~~ @@ -367,6 +387,18 @@ add/commit 前撤销对文件的修改 # 回退一个版本 $ git push origin HEAD --force +3.7 删除版本库里的文件 +~~~~~~~~~~~~~~~~~~~~~~ + +.. code:: shell + + # 加了 --cached 就不会删除工作区的文件 + $ git rm --cached file1.txt + + $ git commit -m "remove file1.txt" + + $ git push origin branch_name + 四、分支管理 ------------ @@ -512,10 +544,10 @@ add/commit 前撤销对文件的修改 ssh-keygen -t rsa -C "wongbingming@163.com" -|image3| 生成的私钥 ``/c/Users/wangbm/.ssh/id_rsa``\ ,由本机电脑保存。 +|image4| 生成的私钥 ``/c/Users/wangbm/.ssh/id_rsa``\ ,由本机电脑保存。 生成的仅钥 ``/c/Users/wangbm/.ssh/id_rsa.pub``\ ,需要在Github上添加。 -在github上添加ssh keys方法如下: |image4| +在github上添加ssh keys方法如下: |image5| 添加完后,可以使用这个命令测试一下。 @@ -539,7 +571,7 @@ add/commit 前撤销对文件的修改 git config --global user.name 'BingmingWong' git config --global user.email wongbingming@163.com -配置完后我们就可以进行clone线上代码了。 |image5| +配置完后我们就可以进行clone线上代码了。 |image6| 7.3 使用 SourceTree ~~~~~~~~~~~~~~~~~~~ @@ -563,7 +595,7 @@ Desktop真心觉得不好用)。 需要注意的是,使用它需要你进行一步操作。在 7.2 章节,我在机器上生成了一对私钥和公钥,在这里需要配置一下才能正常访问和提交我们的Github仓库。 -|image6| +|image7| 如果想要用户名和密码登陆,可以将上面的 OpenSSH 改成 PuTTY/Plink就行了。 @@ -593,16 +625,15 @@ Desktop真心觉得不好用)。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20191217150942.png -.. |image1| image:: http://image.python-online.cn/20191231165152.png -.. |image2| image:: http://image.python-online.cn/20191231165239.png -.. |image3| image:: https://i.loli.net/2018/04/15/5ad2c06e8893d.png -.. |image4| image:: http://image.python-online.cn/20190511163855.png -.. |image5| image:: https://i.loli.net/2018/04/15/5ad2c2a9813b9.png -.. |image6| image:: http://image.python-online.cn/20190430235625.png +|image8| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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.iswbm.com/20190511163855.png +.. |image6| image:: https://i.loli.net/2018/04/15/5ad2c2a9813b9.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 226fd3c..4d509af 100644 --- a/source/c04/c04_07.md +++ b/source/c04/c04_07.md @@ -1,5 +1,7 @@ # 4.7 Hexo 搭建博客教程 +![](http://image.iswbm.com/20200602135014.png) + --- 现在越来越多的人喜欢利用Github搭建静态网站,原因不外乎简单省钱。本人也利用hexo+github搭建了本博客,用于分享一些心得。在此过程中,折腾博客的各种配置以及功能占具了我一部分时间,在此详细记录下我是如何利用hexo+github搭建静态博客以及一些配置相关问题,以免过后遗忘,且当备份之用。 @@ -12,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) - 运行你的博客项目(用于调试),查看生成的文章是否符合自己的预期。 @@ -38,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,其他配置如下 @@ -66,7 +68,7 @@ $ hexo d 在第一次部署后,请到 github 后台,补充你的 网站信息,否则无法访问。 -![](http://image.python-online.cn/image-20200321171008622.png) +![](http://image.iswbm.com/image-20200321171008622.png) 一切完成之后,你就可以通过上面的网址来访问我的博客了。 @@ -77,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`文件,内容就是我的域名 @@ -126,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头格式 @@ -147,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) ## 四、美化博客 @@ -207,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 @@ -348,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) 代码如下: ``` @@ -668,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操作 @@ -785,4 +787,4 @@ git clone git@github.com:iswbm/iswbm.github.io.git --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_07.rst b/source/c04/c04_07.rst old mode 100755 new mode 100644 index e59ddf6..0179476 --- a/source/c04/c04_07.rst +++ b/source/c04/c04_07.rst @@ -1,6 +1,8 @@ 4.7 Hexo 搭建博客教程 ===================== +|image0| + -------------- 现在越来越多的人喜欢利用Github搭建静态网站,原因不外乎简单省钱。本人也利用hexo+github搭建了本博客,用于分享一些心得。在此过程中,折腾博客的各种配置以及功能占具了我一部分时间,在此详细记录下我是如何利用hexo+github搭建静态博客以及一些配置相关问题,以免过后遗忘,且当备份之用。 @@ -16,7 +18,7 @@ - 下载安装hexo。方法:打开终端 运行\ ``npm install -g hexo``\ (要翻墙) -|image0| +|image1| 1.2 初始化项目 ~~~~~~~~~~~~~~ @@ -24,7 +26,7 @@ - 新创建一个目录,比如叫做 ``iswbm-blog`` - 使用终端进入该文件夹内,执行\ ``hexo init`` (初始化项目) -|image1| +|image2| - 运行你的博客项目(用于调试),查看生成的文章是否符合自己的预期。 @@ -47,7 +49,7 @@ - 在Github上创建名字为XXX.github.io的项目,XXX为自己的github用户名。然后 clone 到你的本地电脑上。 - |image2| + |image3| - 打开该文件夹内的\ ``_config.yml``\ 配置文件,将其中的type设置为git,其他配置如下 @@ -76,7 +78,7 @@ 在第一次部署后,请到 github 后台,补充你的 网站信息,否则无法访问。 -|image3| +|image4| 一切完成之后,你就可以通过上面的网址来访问我的博客了。 @@ -88,9 +90,9 @@ 我们可以自己去阿里云购买一个域名(我的是 iswbm.com),然后将其 CNAME 到你的博客地址上。 -- 去阿里云 域名云解析 |image4| +- 去阿里云 域名云解析 |image5| -- 然后到对应的GitHub仓库,绑定我的个性域名 |image5| +- 然后到对应的GitHub仓库,绑定我的个性域名 |image6| - 然后再在你本地的项目里的 ``source`` 目录下新建 ``CNAME``\ 文件,内容就是我的域名 @@ -143,7 +145,7 @@ Do not just seek happiness for yourself. Seek happiness for all. Through kindness. Through mercy. {% endblockquote %} -效果如下 |image6| +效果如下 |image7| 3.2 一键生成md头格式 ~~~~~~~~~~~~~~~~~~~~ @@ -166,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 @@ -243,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 @@ -407,7 +409,7 @@ next主题集成leanCloud,我只需稍微配置下(在主题配置文件) 接着打开\ ``themes/next/layout/_macro/post.swig``\ 文件,添加如下下代码,注意位置 -|image7| 代码如下: +|image8| 代码如下: :: @@ -550,7 +552,7 @@ Next 主题的配置文件有一个原生 bug,就是菜单项后面会多一个空格,这会导致你在页面访问 about,tags等页面时,会报 404,原因是地址后面多了个空格。 -|image8| +|image9| 五、其他实用功能 ---------------- @@ -756,7 +758,7 @@ about,tags等页面时,会报 404,原因是地址后面多了个空格。 在web界面新建分支,命名为\ ``hexo`` 在web界面设置 ``hexo`` 为默认分支,因为我们只会在这个分支上进行操作。 -|image9| +|image10| 6.2 本地PC操作 ~~~~~~~~~~~~~~ @@ -785,7 +787,7 @@ clone项目到本地: git push origin hexo 将博客原始文件添加进来,但是有一些是没必要放的,具体要放哪些文件,看图片。 -|image10| +|image11| :: @@ -889,20 +891,19 @@ bash窗口,不然会提示找不到npm命令) -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/image-20200321163152876.png -.. |image1| image:: http://image.python-online.cn/image-20200321163746032.png -.. |image2| image:: http://image.python-online.cn/image-20200321165634287.png -.. |image3| image:: http://image.python-online.cn/image-20200321171008622.png -.. |image4| image:: http://image.python-online.cn/image-20200321171939919.png -.. |image5| image:: http://image.python-online.cn/image-20200321171821683.png -.. |image6| image:: http://image.python-online.cn/17-9-10/85269241.jpg -.. |image7| image:: http://image.python-online.cn/17-9-9/63041495.jpg -.. |image8| image:: /Users/MING/Library/Application%20Support/typora-user-images/image-20200321210014963.png -.. |image9| image:: http://image.python-online.cn/image-20200321193444320.png -.. |image10| image:: https://i.loli.net/2018/04/15/5ad31888232e9.png +|image12| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |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.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.md b/source/c04/c04_08.md index 3d2347a..fc5b950 100644 --- a/source/c04/c04_08.md +++ b/source/c04/c04_08.md @@ -1,5 +1,7 @@ # 4.8 珍惜生命,远离鼠标 +![](http://image.iswbm.com/20200602135014.png) + --- 珍惜生命,远离鼠标。能用快捷键解决的事情,绝对不要用鼠标来做。 @@ -403,4 +405,4 @@ i 输入模式(没什么用),注意的是退出输入模式(搜索框 --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](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 index 095af96..0e3c231 --- a/source/c04/c04_08.rst +++ b/source/c04/c04_08.rst @@ -1,6 +1,8 @@ 4.8 珍惜生命,远离鼠标 ====================== +|image0| + -------------- 珍惜生命,远离鼠标。能用快捷键解决的事情,绝对不要用鼠标来做。 @@ -428,7 +430,8 @@ URL相关 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c04/c04_09.md b/source/c04/c04_09.md index cb03b79..b9e8bbe 100644 --- a/source/c04/c04_09.md +++ b/source/c04/c04_09.md @@ -1,5 +1,7 @@ # 4.9 MySQL的基本使用 +![](http://image.iswbm.com/20200602135014.png) + --- 数据库是每个后端程序员都必须扎实掌握的一项基本技能,而做为最主流的关系型数据库 MySQL,上手也极为容易,这里是我几年前自学 MySQL 时做下的笔记,现整理出来,一共两篇,这是第一篇。 @@ -619,4 +621,4 @@ set character_set_results = gbk; --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_09.rst b/source/c04/c04_09.rst old mode 100755 new mode 100644 index 1ac04d2..ff86349 --- a/source/c04/c04_09.rst +++ b/source/c04/c04_09.rst @@ -1,6 +1,8 @@ 4.9 MySQL的基本使用 =================== +|image0| + -------------- 数据库是每个后端程序员都必须扎实掌握的一项基本技能,而做为最主流的关系型数据库 @@ -226,7 +228,7 @@ MySQL,上手也极为容易,这里是我几年前自学 MySQL SQL中将数据类型分成了三大类: ``数值类型``, ``字符串类型``\ 和\ ``时间日期类型`` -|image0| +|image1| 4.1 数值型 ~~~~~~~~~~ @@ -236,13 +238,13 @@ SQL中将数据类型分成了三大类: ``数值类型``, 整数型有符号之分(正负) -|image1| +|image2| 创建表或新增字段的时候,如未指定,默认是有符号的。 | 那么如何指定呢? | 只要在建表或新增字段的时候,指定\ ``unsigned`` -| |image2| +| |image3| | **显示宽度** | 显示宽度,最终显示的位数。 @@ -250,7 +252,7 @@ SQL中将数据类型分成了三大类: ``数值类型``, | 零填充+显示宽度的意义: 保证数据格式 | 不足显示宽度的话,需要在前面增加前导0来满足宽度(需要设定zerofill) - |image3| + |image4| 4.1.2 小数型 ^^^^^^^^^^^^ @@ -268,7 +270,7 @@ SQL中将数据类型分成了三大类: ``数值类型``, | 分为两种精度 | ``Float``: 单精度, 占用4个字节存储数据, 精度范围大概为7位左右 | ``Double``: 双精度,占用8个字节存储数据, 精度范围大概为15位左右 -| |image4| +| |image5| 浮点的使用方式 @@ -298,16 +300,16 @@ SQL中将数据类型分成了三大类: ``数值类型``, HH:ii:ss格式与datetime完全一致 | ``Year``: 年份,两种形式, year(2)和year(4): 1901-2156 -|image5| +|image6| -timestamp默认是自动更新当前时间的(在记录创建或更新时更新时间) |image6| +timestamp默认是自动更新当前时间的(在记录创建或更新时更新时间) |image7| **插入数据** - time:可以是负数,而且可以是很大的负数 - year:可以使用2位数插入(>=70的为1970-1999,<=69的为2000-2069),也可以使用4位数 -|image7| +|image8| 4.3 字符串类型 ~~~~~~~~~~~~~~ @@ -336,7 +338,7 @@ timestamp默认是自动更新当前时间的(在记录创建或更新时更 ``varchar(10)``: 的确存了10个汉字, utf8环境, 10 \* 3 + 1 = 31(bytes),存储了3个汉字: 3 \* 3 + 1 = 10(bytes) -| |image8| +| |image9| 从上图来看,如果长度比较固定,譬如身份证,手机号码等,还是选用定长,因为定长相对变长效率高。 | 如果长度是浮动的,那么就要选择变长,可以在一定长度节省空间。 @@ -360,10 +362,10 @@ timestamp默认是自动更新当前时间的(在记录创建或更新时更 //如enum(‘男’,’女’,’不男不女’,’保密’); | ``使用``: 存储数据,只能存储上面定义好的数据 -|image9| +|image10| | 插入数据 -| |image10| +| |image11| **作用之一** @@ -376,7 +378,7 @@ timestamp默认是自动更新当前时间的(在记录创建或更新时更 | ``证明字段存储的数据是数值``: 将数据取出来 + 0 就可以判断出原来的数据存的到底是字符串还是数值: 如果是字符串最终结果永远为0, 否则就是其他值. -| |image11| 因为枚举实际存储的是数值,所以可以直接插入数值. |image12| +| |image12| 因为枚举实际存储的是数值,所以可以直接插入数值. |image13| 4.5 集合字符串 ~~~~~~~~~~~~~~ @@ -386,13 +388,13 @@ timestamp默认是自动更新当前时间的(在记录创建或更新时更 | ``集合使用方式``: 定义: Set(元素列表) | 使用: 可以使用元素列表中的元素(多个), 使用逗号分隔 -创建集合字段 |image13| 插入数据:可以使用多个元素字符串组合, -也可以直接插入数值 |image14| 查询结果 |image15| +创建集合字段 |image14| 插入数据:可以使用多个元素字符串组合, +也可以直接插入数值 |image15| 查询结果 |image16| 为什么会很这样? 98是什么东西?3为什么表示(篮球,足球)? | 原来在数据库内部,set是用二进制表示的。每个元素都对应一个二进制位。 -| |image16| +| |image17| 五、列属性 ---------- @@ -408,7 +410,7 @@ auto_increment,comment 两个值: NULL(默认的)和NOT NULL(不为空) -|image17| +|image18| 在实际应用过程中,应尽量保证数据不为空,空是没有任何意义的。并且不能参与运算。很有可能会出错。 5.2 列描述 @@ -417,7 +419,7 @@ auto_increment,comment | 列描述: comment, 描述, 没有实际含义: 是专门用来描述字段,会根据表创建语句保存: 用来给程序猿(数据库管理员)来进行了解的. -| |image18| +| |image19| 5.3 默认值 ~~~~~~~~~~ @@ -427,9 +429,9 @@ auto_increment,comment 默认值关键字: default -生效:只要插入数据的时候,不给值,就会自动赋予默认值 |image19| +生效:只要插入数据的时候,不给值,就会自动赋予默认值 |image20| 如果是全字段插入数据,那我们没法跳过,且又不知道默认值是什么?就可以使用default -|image20| +|image21| 5.4 主键 ~~~~~~~~ @@ -442,12 +444,12 @@ auto_increment,comment 优点:简单直接 -缺点:只能使用一个字段作为主键 |image21| +缺点:只能使用一个字段作为主键 |image22| **方案二** 在创建表的时候, 在所有的字段之后, 使用primary key(主键字段列表)来创建主键(如果有多个字段作为主键,可以是复合主键) -|image22| +|image23| **方案三** @@ -456,7 +458,7 @@ key(主键字段列表)来创建主键(如果有多个字段作为主键,可以 Alter table 表名 add primary key(字段列表); -``前提``: 表中字段对应的数据本身是独立的(不重复) |image23| +``前提``: 表中字段对应的数据本身是独立的(不重复) |image24| 5.4.2删除主键 ^^^^^^^^^^^^^ @@ -465,7 +467,7 @@ Alter table 表名 add primary key(字段列表); alter table 表名 drop primary key; -|image24| +|image25| 5.4.3 更新主键 ^^^^^^^^^^^^^^ @@ -490,10 +492,10 @@ Alter table 表名 add primary key(字段列表); 5.5.2 如何触发自增长 ^^^^^^^^^^^^^^^^^^^^ -|image25| +|image26| | 如何确定下一次是什么自增长呢? 可以通过查看表创建语句看到. -| |image26| +| |image27| 5.5.3 修改自增长 ^^^^^^^^^^^^^^^^ @@ -501,7 +503,7 @@ Alter table 表名 add primary key(字段列表); 由于一张表只能有一个自增长字段,所以要改变自增长字段,需先删除再增加。 | 修改下次自增长的值。必须大于当前自增长数字的最大值,小于不生效。 -| |image27| +| |image28| **修改起始值和步长** 查看自增长对应的变量: @@ -513,7 +515,7 @@ Alter table 表名 add primary key(字段列表); $ set auto_increment_increment = 5 $ set auto_increment_offset = 10 -|image28| +|image29| 5.5.4 删除自增长 ^^^^^^^^^^^^^^^^ @@ -525,7 +527,7 @@ Alter table 表名 add primary key(字段列表); alter table 表名 modify 字段 字段类型; # 不写自增长属性就行 -|image29| +|image30| 5.6 唯一键 ~~~~~~~~~~ @@ -573,14 +575,14 @@ key)就可以解决表中有多个字段需要唯一性约束的问题. 创建表的时候增加外键: 在所有的表字段之后,使用 foreign key(外键字段) references 外部表(主键字段) -|image30| +|image31| :: 在新增表之后增加外键: 修改表结构 Alter table 表名 add [constraint 外键名字] foreign key(外键字段) references 父表(主键字段); -|image31| +|image32| 5.7.2 更新/删除外键 ^^^^^^^^^^^^^^^^^^^ @@ -637,10 +639,10 @@ key)就可以解决表中有多个字段需要唯一性约束的问题. alter table 表名 drop foreign key 外键名; alter table 表名 add foreign key 外键名 references 父表(主键字段) on delete set null on update cascade; -|image32| +|image33| 来实例操作一下,级联模式和置空模式是怎样的。 ``更新操作: 级联更新`` -|image33| ``删除操作: 置空`` |image34| +|image34| ``删除操作: 置空`` |image35| 5.8 索引 ~~~~~~~~ @@ -692,7 +694,7 @@ SO,如果是提交的是英文的话,不会有冲突。因为都是一个字 show variables like 'character_set%'; -|image35| +|image36| 图上这个字符集编码,就不会出错,我们可以正常的插入中文数据。 如果发现\ ``character_set_client``\ 和\ ``character_set_results``\ 是utf8,那很有可能会出错。 @@ -710,45 +712,44 @@ SO,如果是提交的是英文的话,不会有冲突。因为都是一个字 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: https://i.loli.net/2017/08/25/599feabef31a6.png -.. |image1| image:: https://i.loli.net/2017/08/25/59a0345577dc0.png -.. |image2| image:: https://i.loli.net/2017/08/25/59a037907f5ee.png -.. |image3| image:: https://i.loli.net/2017/08/25/59a03b9be4226.png -.. |image4| image:: https://i.loli.net/2017/08/25/59a03e48e42b6.png -.. |image5| image:: https://i.loli.net/2017/08/25/59a040f5eb751.png -.. |image6| image:: https://i.loli.net/2017/08/25/59a0418497d8f.png -.. |image7| image:: https://i.loli.net/2017/08/25/59a041fe483c2.png -.. |image8| image:: https://i.loli.net/2017/08/26/59a0cbe8b2790.png -.. |image9| image:: https://i.loli.net/2017/08/26/59a0ce69d1c60.png -.. |image10| image:: https://i.loli.net/2017/08/26/59a0cebd074ab.png -.. |image11| image:: https://i.loli.net/2017/08/26/59a0cf774e2d4.png -.. |image12| image:: https://i.loli.net/2017/08/26/59a0cfddc52da.png -.. |image13| image:: https://i.loli.net/2017/08/26/59a0d14772b49.png -.. |image14| image:: https://i.loli.net/2017/08/26/59a0d17313a77.png -.. |image15| image:: https://i.loli.net/2017/08/26/59a0d19d4b54f.png -.. |image16| image:: https://i.loli.net/2017/08/26/59a0d20543ba3.png -.. |image17| image:: https://i.loli.net/2017/08/26/59a0d2ed1ecf2.png -.. |image18| image:: https://i.loli.net/2017/08/26/59a0d35dd3a86.png -.. |image19| image:: https://i.loli.net/2017/08/26/59a0d3d6b85e4.png -.. |image20| image:: https://i.loli.net/2017/08/26/59a0d464bb8f1.png -.. |image21| image:: https://i.loli.net/2017/08/26/59a0f8aa02375.png -.. |image22| image:: https://i.loli.net/2017/08/26/59a0f9141a909.png -.. |image23| image:: https://i.loli.net/2017/08/26/59a0f9be5b003.png -.. |image24| image:: https://i.loli.net/2017/08/26/59a0fa9a27f35.png -.. |image25| image:: https://i.loli.net/2017/08/26/59a0fc0cbb82e.png -.. |image26| image:: https://i.loli.net/2017/08/26/59a0fc5759662.png -.. |image27| image:: https://i.loli.net/2017/08/26/59a0fd26410ed.png -.. |image28| image:: https://i.loli.net/2017/08/26/59a0fd9bc76f9.png -.. |image29| image:: https://i.loli.net/2017/08/26/59a101add19bb.png -.. |image30| image:: https://i.loli.net/2017/08/27/59a25b5ba7837.png -.. |image31| image:: https://i.loli.net/2017/08/27/59a25be55ad8e.png -.. |image32| image:: https://i.loli.net/2017/08/27/59a25ecf889b5.png -.. |image33| image:: https://i.loli.net/2017/08/27/59a261734e896.png -.. |image34| image:: https://i.loli.net/2017/08/27/59a261734ff8b.png -.. |image35| image:: https://i.loli.net/2017/08/25/599fc9aa85094.png +|image37| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: https://i.loli.net/2017/08/25/599feabef31a6.png +.. |image2| image:: https://i.loli.net/2017/08/25/59a0345577dc0.png +.. |image3| image:: https://i.loli.net/2017/08/25/59a037907f5ee.png +.. |image4| image:: https://i.loli.net/2017/08/25/59a03b9be4226.png +.. |image5| image:: https://i.loli.net/2017/08/25/59a03e48e42b6.png +.. |image6| image:: https://i.loli.net/2017/08/25/59a040f5eb751.png +.. |image7| image:: https://i.loli.net/2017/08/25/59a0418497d8f.png +.. |image8| image:: https://i.loli.net/2017/08/25/59a041fe483c2.png +.. |image9| image:: https://i.loli.net/2017/08/26/59a0cbe8b2790.png +.. |image10| image:: https://i.loli.net/2017/08/26/59a0ce69d1c60.png +.. |image11| image:: https://i.loli.net/2017/08/26/59a0cebd074ab.png +.. |image12| image:: https://i.loli.net/2017/08/26/59a0cf774e2d4.png +.. |image13| image:: https://i.loli.net/2017/08/26/59a0cfddc52da.png +.. |image14| image:: https://i.loli.net/2017/08/26/59a0d14772b49.png +.. |image15| image:: https://i.loli.net/2017/08/26/59a0d17313a77.png +.. |image16| image:: https://i.loli.net/2017/08/26/59a0d19d4b54f.png +.. |image17| image:: https://i.loli.net/2017/08/26/59a0d20543ba3.png +.. |image18| image:: https://i.loli.net/2017/08/26/59a0d2ed1ecf2.png +.. |image19| image:: https://i.loli.net/2017/08/26/59a0d35dd3a86.png +.. |image20| image:: https://i.loli.net/2017/08/26/59a0d3d6b85e4.png +.. |image21| image:: https://i.loli.net/2017/08/26/59a0d464bb8f1.png +.. |image22| image:: https://i.loli.net/2017/08/26/59a0f8aa02375.png +.. |image23| image:: https://i.loli.net/2017/08/26/59a0f9141a909.png +.. |image24| image:: https://i.loli.net/2017/08/26/59a0f9be5b003.png +.. |image25| image:: https://i.loli.net/2017/08/26/59a0fa9a27f35.png +.. |image26| image:: https://i.loli.net/2017/08/26/59a0fc0cbb82e.png +.. |image27| image:: https://i.loli.net/2017/08/26/59a0fc5759662.png +.. |image28| image:: https://i.loli.net/2017/08/26/59a0fd26410ed.png +.. |image29| image:: https://i.loli.net/2017/08/26/59a0fd9bc76f9.png +.. |image30| image:: https://i.loli.net/2017/08/26/59a101add19bb.png +.. |image31| image:: https://i.loli.net/2017/08/27/59a25b5ba7837.png +.. |image32| image:: https://i.loli.net/2017/08/27/59a25be55ad8e.png +.. |image33| image:: https://i.loli.net/2017/08/27/59a25ecf889b5.png +.. |image34| image:: https://i.loli.net/2017/08/27/59a261734e896.png +.. |image35| image:: https://i.loli.net/2017/08/27/59a261734ff8b.png +.. |image36| image:: https://i.loli.net/2017/08/25/599fc9aa85094.png +.. |image37| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_10.md b/source/c04/c04_10.md index 384ef36..0d6ba6a 100644 --- a/source/c04/c04_10.md +++ b/source/c04/c04_10.md @@ -1,5 +1,7 @@ # 4.10 MySQL的高级进阶 +![](http://image.iswbm.com/20200602135014.png) + --- ## 一、增/删/改数据 @@ -468,4 +470,4 @@ WHERE table_schema LIKE '%zabbix%' ORDER BY Total desc; --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_10.rst b/source/c04/c04_10.rst old mode 100755 new mode 100644 index b11822d..439f67e --- a/source/c04/c04_10.rst +++ b/source/c04/c04_10.rst @@ -1,6 +1,8 @@ 4.10 MySQL的高级进阶 ==================== +|image0| + -------------- 一、增/删/改数据 @@ -42,7 +44,7 @@ create table 新表 like 源表; -|image0| +|image1| 再插入表数据 @@ -141,7 +143,7 @@ Where原理: where是唯一一个直接从磁盘获取数据的时候就开始 从磁盘取出一条记录, 开始进行where判断: 判断的结果如果成立保存到内存;如果失败直接放弃. -|image1| +|image2| 2.5 group by子句 ~~~~~~~~~~~~~~~~ @@ -168,15 +170,15 @@ Where原理: where是唯一一个直接从磁盘获取数据的时候就开始 | ``group_concat(字段)`` | 可以实现分组后,对某个字段进行连接 -| |image2| +| |image3| 回溯统计(汇总统计)\ ``with rollup`` 任何一个分组后都会有一个小组, 最后都需要向上级分组进行汇报统计: 根据当前分组的字段. 这就是回溯统计: 回溯统计的时候会将分组字段置空. -如果只对一个字段分组汇总统计 |image3| 对两个字段进行分组统计汇总 -|image4| +如果只对一个字段分组汇总统计 |image4| 对两个字段进行分组统计汇总 +|image5| 2.6 Having子句 ~~~~~~~~~~~~~~ @@ -190,11 +192,11 @@ where是针对磁盘数据进行判断: 进入到内存之后,会进行分组操 Having能做where能做的几乎所有事情, 但是where却不能做having能做的很多事情. -``比较`` 分组统计的结果或者说统计函数都只有having能够使用. |image5| +``比较`` 分组统计的结果或者说统计函数都只有having能够使用. |image6| Having能够使用字段别名: where不能: where是从磁盘取数据,而名字只可能是字段名: -别名是在字段进入到内存后才会产生. |image6| +别名是在字段进入到内存后才会产生. |image7| 2.7 Order by子句 ~~~~~~~~~~~~~~~~ @@ -207,20 +209,20 @@ Order by: 排序, 根据某个字段进行升序或者降序排序, 依赖校对 Order by 字段名 [asc|desc]; -- asc是升序(默认的),desc是降序 排序可以进行多字段排序: 先根据某个字段进行排序, -然后排序好的内部,再按照某个数据进行再次排序: |image7| +然后排序好的内部,再按照某个数据进行再次排序: |image8| 2.8 Limit子句 ~~~~~~~~~~~~~ limit子句:限制返回数据的量。 -有两种使用方式 方案1: 只用来限制长度(数据量): limit 数据量; |image8| -方案2: 限制起始位置,限制数量: limit 起始位置,长度; |image9| +有两种使用方式 方案1: 只用来限制长度(数据量): limit 数据量; |image9| +方案2: 限制起始位置,限制数量: limit 起始位置,长度; |image10| 2.9 执行顺序【重要】 ~~~~~~~~~~~~~~~~~~~~ -|image10| +|image11| 三、连接查询 ------------ @@ -268,11 +270,11 @@ limit子句:限制返回数据的量。 左表 [inner] join 右表 on 左表.字段 = 右表.字段; on表示连接条件:,条件字段就是代表相同的业务含义(如my_student.c_id和my_class.id) -|image11| 字段别名以及表别名的使用: +|image12| 字段别名以及表别名的使用: 在查询数据的时候,不同表有同名字段,这个时候需要加上表名才能区分, -而表名太长, 通常可以使用别名. |image12| +而表名太长, 通常可以使用别名. |image13| -内连接还可以使用where代替on关键字(where没有on效率高) |image13| +内连接还可以使用where代替on关键字(where没有on效率高) |image14| 3.3 外连接 ~~~~~~~~~~ @@ -291,7 +293,7 @@ limit子句:限制返回数据的量。 【基本语法】 左表 left/right join 右表 on 左表.字段 = 右表.字段; -|image14| 虽然左连接和右连接有主表差异, 但是显示的结果: +|image15| 虽然左连接和右连接有主表差异, 但是显示的结果: 左表的数据在左边,右表数据在右边. 左连接和右连接可以互转. 3.4 自然连接 @@ -349,7 +351,7 @@ limit子句:限制返回数据的量。 All: 保留所有(不管重复) Distinct: 去重(整个重复): 默认的 -|image15| 联合查询只要求字段数一样, 跟数据类型无关 |image16| +|image16| 联合查询只要求字段数一样, 跟数据类型无关 |image17| 4.2 它的意义 ~~~~~~~~~~~~ @@ -363,8 +365,8 @@ limit子句:限制返回数据的量。 4.3 order by的使用 ~~~~~~~~~~~~~~~~~~ -在联合查询中: order by不能直接使用,需要对查询语句使用括号才行 |image17| -若要orderby生效: 必须搭配limit: limit使用限定的最大数即可. |image18| +在联合查询中: order by不能直接使用,需要对查询语句使用括号才行 |image18| +若要orderby生效: 必须搭配limit: limit使用限定的最大数即可. |image19| 五、子查询 ---------- @@ -417,7 +419,7 @@ limit子句:限制返回数据的量。 Select id from my_class where c_name = ‘PHP0710’; -- id一定只有一个值(一行一列) | 标量子查询实现 -| |image19| +| |image20| 5.3 列子查询 ~~~~~~~~~~~~ @@ -439,7 +441,7 @@ limit子句:限制返回数据的量。 Select id from my_class; -列子查询实现 |image20| +列子查询实现 |image21| ``IN``\ :在指定项内,同 IN(项1,项2,…)。 等价于 ``= ANY`` @@ -468,7 +470,7 @@ limit子句:限制返回数据的量。 Select max(age),max(height) from my_student; -``行子查询``: 需要构造\ ``行元素``: 行元素由多个字段构成 |image21| +``行子查询``: 需要构造\ ``行元素``: 行元素由多个字段构成 |image22| 5.5表子查询 ~~~~~~~~~~~ @@ -490,7 +492,7 @@ limit子句:限制返回数据的量。 Select * from my_student group by c_id; -- 每个班选出第一个学生 -``表子查询``: ``from子查询``: 得到的结果作为from的数据源 |image22| +``表子查询``: ``from子查询``: 得到的结果作为from的数据源 |image23| 5.6 exits子查询 ~~~~~~~~~~~~~~~ @@ -505,7 +507,7 @@ limit子句:限制返回数据的量。 **举个例子**\ 更好理解 -|image23| +|image24| 六、系统查询 ------------ @@ -536,33 +538,32 @@ limit子句:限制返回数据的量。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: https://ooo.0o0.ooo/2017/08/26/59a1429722061.png -.. |image1| image:: https://i.loli.net/2017/08/27/59a2307e7b6bc.png -.. |image2| image:: https://i.loli.net/2017/08/27/59a2329680727.png -.. |image3| image:: https://i.loli.net/2017/08/27/59a2346a1c446.png -.. |image4| image:: https://i.loli.net/2017/08/27/59a2346a304d9.png -.. |image5| image:: https://i.loli.net/2017/08/27/59a235b1adb8e.png -.. |image6| image:: https://i.loli.net/2017/08/27/59a235b1aee6d.png -.. |image7| image:: https://i.loli.net/2017/08/27/59a23643148a5.png -.. |image8| image:: https://i.loli.net/2017/08/27/59a23722a893b.png -.. |image9| image:: https://i.loli.net/2017/08/27/59a23722bcedf.png -.. |image10| image:: https://i.loli.net/2017/08/27/59a2416c37929.png -.. |image11| image:: https://i.loli.net/2017/08/27/59a24f9d9e1eb.png -.. |image12| image:: https://i.loli.net/2017/08/27/59a24f9db31ba.png -.. |image13| image:: https://i.loli.net/2017/08/27/59a2507e611d2.png -.. |image14| image:: https://i.loli.net/2017/08/27/59a2517b690fd.png -.. |image15| image:: https://i.loli.net/2017/08/27/59a26a86275ed.png -.. |image16| image:: https://i.loli.net/2017/08/27/59a26a8612ff5.png -.. |image17| image:: https://i.loli.net/2017/08/27/59a26b27cd2bb.png -.. |image18| image:: https://i.loli.net/2017/08/27/59a26b26bffdc.png -.. |image19| image:: https://i.loli.net/2017/08/27/59a26f38468d8.png -.. |image20| image:: https://i.loli.net/2017/08/27/59a26f3847710.png -.. |image21| image:: https://i.loli.net/2017/08/27/59a2756c66952.png -.. |image22| image:: https://i.loli.net/2017/08/27/59a27651cd78e.png -.. |image23| image:: https://i.loli.net/2017/08/27/59a2784f6562d.png +|image25| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: https://ooo.0o0.ooo/2017/08/26/59a1429722061.png +.. |image2| image:: https://i.loli.net/2017/08/27/59a2307e7b6bc.png +.. |image3| image:: https://i.loli.net/2017/08/27/59a2329680727.png +.. |image4| image:: https://i.loli.net/2017/08/27/59a2346a1c446.png +.. |image5| image:: https://i.loli.net/2017/08/27/59a2346a304d9.png +.. |image6| image:: https://i.loli.net/2017/08/27/59a235b1adb8e.png +.. |image7| image:: https://i.loli.net/2017/08/27/59a235b1aee6d.png +.. |image8| image:: https://i.loli.net/2017/08/27/59a23643148a5.png +.. |image9| image:: https://i.loli.net/2017/08/27/59a23722a893b.png +.. |image10| image:: https://i.loli.net/2017/08/27/59a23722bcedf.png +.. |image11| image:: https://i.loli.net/2017/08/27/59a2416c37929.png +.. |image12| image:: https://i.loli.net/2017/08/27/59a24f9d9e1eb.png +.. |image13| image:: https://i.loli.net/2017/08/27/59a24f9db31ba.png +.. |image14| image:: https://i.loli.net/2017/08/27/59a2507e611d2.png +.. |image15| image:: https://i.loli.net/2017/08/27/59a2517b690fd.png +.. |image16| image:: https://i.loli.net/2017/08/27/59a26a86275ed.png +.. |image17| image:: https://i.loli.net/2017/08/27/59a26a8612ff5.png +.. |image18| image:: https://i.loli.net/2017/08/27/59a26b27cd2bb.png +.. |image19| image:: https://i.loli.net/2017/08/27/59a26b26bffdc.png +.. |image20| image:: https://i.loli.net/2017/08/27/59a26f38468d8.png +.. |image21| image:: https://i.loli.net/2017/08/27/59a26f3847710.png +.. |image22| image:: https://i.loli.net/2017/08/27/59a2756c66952.png +.. |image23| image:: https://i.loli.net/2017/08/27/59a27651cd78e.png +.. |image24| image:: https://i.loli.net/2017/08/27/59a2784f6562d.png +.. |image25| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_11.md b/source/c04/c04_11.md index dd6891d..49fb61f 100644 --- a/source/c04/c04_11.md +++ b/source/c04/c04_11.md @@ -1,4 +1,6 @@ -# 4.11 不能不会的远程调试技巧 +# 4.11 超详细图文教你如何使用 PyCharm 进行远程调试 + +![](http://image.iswbm.com/20200602135014.png) 这一篇文章是以前的文章,有的朋友已经看过,但是没有关系,因为这次我准备介绍这几大调试工具都是如何调试,又该如何选择。 @@ -17,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` @@ -35,7 +37,7 @@ Tools -> Deployment -> configuration - Type:设定为SFTP -![](http://image.python-online.cn/20190113105858.png) +![](http://image.iswbm.com/20190113105858.png) 点击`OK`后,进入如下界面,你可以按我的备注,填写信息: @@ -48,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. 下载远程解释器 为什么需要这步呢? @@ -88,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,所以你一定要知道你的项目的入口程序。如果这个入口程序已经包含在你的项目代码中,那么请略过这一步。 @@ -126,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. 友情提醒 按照文章的试调试代码,会自动同步代码至远端,千万不要在生产环境使用,一定要在开发环境中使用,否则后果自负。 @@ -158,4 +160,4 @@ WantedBy=multi-user.target --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_11.rst b/source/c04/c04_11.rst index 38235d9..a27e659 100644 --- a/source/c04/c04_11.rst +++ b/source/c04/c04_11.rst @@ -1,5 +1,7 @@ -4.11 不能不会的远程调试技巧 -=========================== +4.11 超详细图文教你如何使用 PyCharm 进行远程调试 +================================================ + +|image0| 这一篇文章是以前的文章,有的朋友已经看过,但是没有关系,因为这次我准备介绍这几大调试工具都是如何调试,又该如何选择。 @@ -23,20 +25,20 @@ Google,这里不涉及。 区别就在于,本地调试不需要事前配置,只要你的代码准备好了,随时可以开始 Debug 。而远程调试需要不少前置步骤,这些设置过程,也是本文的主要内容。 -4.11.1 新建一个项目 -~~~~~~~~~~~~~~~~~~~ +1. 新建一个项目 +~~~~~~~~~~~~~~~ 首先,要在Pycharm中新建一个空的项目,后面我们拉服务器上的项目代码就会放置在这个项目目录下。我这边的名字是 NOVA,你可以自己定义。 -|image0| +|image1| -4.11.2 配置连接服务器 -~~~~~~~~~~~~~~~~~~~~~ +2. 配置连接服务器 +~~~~~~~~~~~~~~~~~ Tools -> Deployment -> configuration -|image1| +|image2| 添加一个\ ``Server`` @@ -44,7 +46,7 @@ Tools -> Deployment -> configuration - Type:设定为SFTP -|image2| +|image3| 点击\ ``OK``\ 后,进入如下界面,你可以按我的备注,填写信息: @@ -57,40 +59,40 @@ Tools -> Deployment -> configuration 这里请注意,要确保你的电脑可以ssh连接到你的服务器,不管是密钥登陆还是密码登陆,如果开启了白名单限制要先解除。 -|image3| +|image4| 填写完成后,切换到\ ``Mappings``\ 选项卡,在箭头位置,填写\ ``\`` -|image4| +|image5| 以上服务器信息配置,全部正确填写完成后,点击\ ``OK`` 接下来,我们要连接远程服务器了。 Tools -> Deployment -> Browse Remote Host -|image5| +|image6| -4.11.3 下载项目代码 -~~~~~~~~~~~~~~~~~~~ +3. 下载项目代码 +~~~~~~~~~~~~~~~ 如果之前填写的服务器登陆信息准确无误的话,现在就可以看到远程的项目代码。 -|image6| +|image7| 选择下载远程代码要本地。 -|image7| +|image8| 下载完成提示。 -|image8| +|image9| 现在的IDE界面应该是这样子的。 -|image9| +|image10| -4.11.4 下载远程解释器 -~~~~~~~~~~~~~~~~~~~~~ +4. 下载远程解释器 +~~~~~~~~~~~~~~~~~ 为什么需要这步呢? @@ -98,16 +100,16 @@ Host 进入 File -> Settings 按图示,添加远程解释器。 -|image10| +|image11| 填写远程服务器信息,跟之前的一样,不再赘述。 -|image11| +|image12| 点击\ ``OK``\ 后,会自动下载远程解释器。如果你的项目比较大,这个时间可能会比较久,请耐心等待。 -4.11.5 添加程序入口 -~~~~~~~~~~~~~~~~~~~ +5. 添加程序入口 +~~~~~~~~~~~~~~~ 因为我们要在本地DEBUG,所以你一定要知道你的项目的入口程序。如果这个入口程序已经包含在你的项目代码中,那么请略过这一步。 @@ -137,35 +139,35 @@ Host 看到那个\ ``ExecStart``\ 没有?那个就是我们程序的入口。 我们只要将其拷贝至我们的Pycharm中,并向远程同步该文件。 -|image12| +|image13| -4.11.6 调试前设置 -~~~~~~~~~~~~~~~~~ +6. 调试前设置 +~~~~~~~~~~~~~ 开启代码自动同步,这样,我们对代码的修改Pycharm都能识别,并且为我们提交到远程服务器。 -|image13| +|image14| 开启 ``Gevent compatible``\ ,如果不开启,在调试过程中,很可能出现无法调试,或者无法追踪/查看变量等问题。 -|image14| +|image15| -4.11.7 开始调试代码 -~~~~~~~~~~~~~~~~~~~ +7. 开始调试代码 +~~~~~~~~~~~~~~~ 在你的程序入口文件处,点击右键,选择Debug即可。 如果你的程序入口,需要引入参数,这是经常有的事,可以的这里配置。 -|image15| +|image16| 配置完点击保存即可。 -|image16| +|image17| -4.11.8 友情提醒 -~~~~~~~~~~~~~~~ +8. 友情提醒 +~~~~~~~~~~~ 按照文章的试调试代码,会自动同步代码至远端,千万不要在生产环境使用,一定要在开发环境中使用,否则后果自负。 @@ -173,26 +175,25 @@ Host -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190113104817.png -.. |image1| image:: http://image.python-online.cn/20190113105512.png -.. |image2| image:: http://image.python-online.cn/20190113105858.png -.. |image3| image:: http://image.python-online.cn/20190113105931.png -.. |image4| image:: http://image.python-online.cn/20190113110928.png -.. |image5| image:: http://image.python-online.cn/20190113111042.png -.. |image6| image:: http://image.python-online.cn/20190113111151.png -.. |image7| image:: http://image.python-online.cn/20190113111217.png -.. |image8| image:: http://image.python-online.cn/20190113111248.png -.. |image9| image:: http://image.python-online.cn/20190113111307.png -.. |image10| image:: http://image.python-online.cn/20190113111747.png -.. |image11| image:: http://image.python-online.cn/20190113111828.png -.. |image12| image:: http://image.python-online.cn/20190113112004.png -.. |image13| image:: http://image.python-online.cn/20190113112055.png -.. |image14| image:: http://image.python-online.cn/20190113113211.png -.. |image15| image:: http://image.python-online.cn/20190113112456.png -.. |image16| image:: http://image.python-online.cn/20190113112649.png +|image18| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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 84f5860..c52c148 100644 --- a/source/c04/c04_12.md +++ b/source/c04/c04_12.md @@ -1,12 +1,14 @@ # 4.12 服务器调试神器:pdb +![](http://image.iswbm.com/20200602135014.png) + 上一篇文章,讲的是 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. 准备文件 在调试之前先将这两个文件准备好(做为演示用),并放在同级目录中。 @@ -38,7 +40,7 @@ if __name__ == '__main__': -## 4.12.2 进入调试模式 +## 2. 进入调试模式 主要有两种方法 @@ -50,7 +52,7 @@ ptyhon -m pdb pdb_demo.py 使用这个方式进入调试模式,会在脚本的第一行开始单步调试。 -![](http://image.python-online.cn/20190118000111.png) +![](http://image.iswbm.com/20190118000111.png) 对于单文件的脚本并没有什么问题,如果是一个大型的项目,项目里有很多的文件,使用这种方式只能大大降低我们的效率。 @@ -65,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 的人都知道,我们执行下一步,执行到下一个断点是 @@ -127,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) @@ -147,5 +149,5 @@ pdb.set_trace() ------ -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_12.rst b/source/c04/c04_12.rst index 0ca9199..cfee977 100644 --- a/source/c04/c04_12.rst +++ b/source/c04/c04_12.rst @@ -1,17 +1,19 @@ 4.12 服务器调试神器:pdb ======================== +|image0| + 上一篇文章,讲的是 Pycharm 的远程调试,若你还没学会,可以点击这里进行查看。 -`不能不会的远程调试技巧 `__ +`不能不会的远程调试技巧 `__ Pycharm 的图形化界面虽然好用,但是在某些场景中,是无法使用的。而 Python 本身已经给我们提供了一个调试神器 – pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文章来帮助你轻松的理解它。 -4.12.1 准备文件 ---------------- +1. 准备文件 +----------- 在调试之前先将这两个文件准备好(做为演示用),并放在同级目录中。 @@ -41,8 +43,8 @@ pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文 myfunc([1,2,3,4]) print("----end-----") -4.12.2 进入调试模式 -------------------- +2. 进入调试模式 +--------------- 主要有两种方法 @@ -54,7 +56,7 @@ pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文 使用这个方式进入调试模式,会在脚本的第一行开始单步调试。 -|image0| +|image1| 对于单文件的脚本并没有什么问题,如果是一个大型的项目,项目里有很多的文件,使用这种方式只能大大降低我们的效率。 @@ -70,12 +72,12 @@ pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文 然后执行时,也不需要再指定\ ``-m pdb``\ 了,直接\ ``python pdb_demo.py`` ,就会直接在这个地方暂停。 -|image1| - |image2| -4.12.3 调试指令 ---------------- +|image3| + +3. 调试指令 +----------- 熟悉 Pycharm 的人都知道,我们执行下一步,执行到下一个断点是 @@ -157,14 +159,14 @@ pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文 其实你大可不必死记这些命令,忘记的时候,只要敲入\ ``help``\ 并回车,就可以看所有的指令了。 -|image3| +|image4| -4.12.4 开始调试 ---------------- +4. 开始调试 +----------- 这里就几个最常用的指定,来演示一遍。 -|image4| +|image5| 这个调试过程,我加了些注释,你应该能够很轻易地理解这种调试方式。 @@ -174,14 +176,13 @@ pdb,可能你还不知道它,为了讲解这个神器,我写了这篇文 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image6| -.. |image0| image:: http://image.python-online.cn/20190118000111.png -.. |image1| image:: http://image.python-online.cn/20190118000234.png -.. |image2| image:: http://image.python-online.cn/20190118000557.png -.. |image3| image:: http://image.python-online.cn/20190118083809.png -.. |image4| image:: http://image.python-online.cn/20190118005507.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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_13.md b/source/c04/c04_13.md index fa1b818..34b4707 100644 --- a/source/c04/c04_13.md +++ b/source/c04/c04_13.md @@ -1,5 +1,7 @@ # 4.13 命令行解析工具:argparse +![](http://image.iswbm.com/20200602135014.png) + Python 做为一个脚本语言,可以很方便地写各种工具。当你在服务端要运行一个工具或服务时,输入参数似乎是一种硬需(当然你也可以通过配置文件来实现)。 如果要以命令行执行,那你需要解析一个命令行参数解析的模块来帮你做这个苦力活。 @@ -407,4 +409,4 @@ single,"--name", '-n',--frequency,module_args,处理函数:main_single --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c04/c04_13.rst b/source/c04/c04_13.rst index fb0f9fc..2059a5a 100644 --- a/source/c04/c04_13.rst +++ b/source/c04/c04_13.rst @@ -1,6 +1,8 @@ 4.13 命令行解析工具:argparse ============================= +|image0| + Python 做为一个脚本语言,可以很方便地写各种工具。当你在服务端要运行一个工具或服务时,输入参数似乎是一种硬需(当然你也可以通过配置文件来实现)。 @@ -431,7 +433,8 @@ arguments)。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c04/c04_14.md b/source/c04/c04_14.md index aa76f4a..672b5ce 100644 --- a/source/c04/c04_14.md +++ b/source/c04/c04_14.md @@ -1,160 +1,161 @@ -# 4.14 虚拟环境:Pipenv +# 4.14 Xshell的高效使用手册 -以前一直使用pip+virtualenv+virtualwrapper管理模块和环境, 但是virtualwrapper在windows上使用不太方便,而且包和环境分开管理确实经常不记得哪个是哪个了。 +![](http://image.iswbm.com/20200602135014.png) +--- -为什么 会推荐 pipenv 呢? - -- 它是 `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 -``` +![](http://image.iswbm.com/20190511162815.png) -如果你的电脑是 windows 的。 +做为一名开发人员,我们难免都会与服务器打交道。 -![](http://image.python-online.cn/Fk6WZ2xbqg2DM3AvnYCpsiKQ4xOn) +有时候是公司的线上生产环境,你需要上去部署公司的项目。 +有时候是在阿里上买的云主机,想自己搭个博客来写写文章。 -需要将如标示路径,加入到 环境变量 PATH 中。 +就像我,是从事云计算相关的,每天远程登陆的服务器都有几十台。 -![](http://image.python-online.cn/FjuJ8yZsgjkzVuBRZHxK1ZnnzaEX) +这时候,掌握一些远程登陆工具的使用技巧是相当有必要的,会大大增加你使用的便利性和愉悦性。 -然后需要重启一下,CMD 终端才能够刷新环境变量。 +优秀的远程工具有很多,Xshell,SecureCRT,PuTTY等等。 -## 4.14.2 创建虚拟环境 +由于小明是个很注重工具顔值的人,所以一开始就选择了Xshell。本篇也只会介绍Xshell的使用技巧,但是快捷键在其他工具上部分是通用的。 -DjangoWebBlog 是我们的项目目录,进入这个目录下创建虚拟环境 +## 快捷入口 -```shell -$ mkdir DjangoWebBlog && cd DjangoWebBlog +**快速命令** -# 在当前目录下创建一个虚拟环境(默认的Python版本) -$ pipenv install -``` +查看 -> 快速命令 -你也可以指定版本创建 +双击底部自定义快速命令。 +![](http://image.iswbm.com/20190511162524.png) -```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.iswbm.com/20190511162607.png) -这边以安装 python2 版本的虚拟环境为例说明。 +**快捷设置** -![](http://image.python-online.cn/20190612211330.png) +工具 -> 选项 -> 键盘和鼠标 -如果你原项目使用的是 requirements.txt 这个管理包的方式,这时候执行 `pipenv --tow` 创建一个虚拟环境后,会找到 requirements.txt ,并根据这里面的依赖包生成 Pipfile文件。 +① 右键粘贴 +② 双击分隔符 +③ 选中即复制 -![](http://image.python-online.cn/20190612213015.png) +![](http://image.iswbm.com/20190511162716.png) -## 4.14.3 查询虚拟环境 +**设置Meta键** -```shell -# 返回项目的路径 -$ pipenv --where +文件 -> 属性 -> 键盘 -# 返回虚拟环境路径 -$ pipenv --venv +一定要打钩,这是后面诸多快捷使用的前提。 +![](http://image.iswbm.com/20190511162730.png) -# 返回该虚拟环境的解释器 -$ 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) --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_14.rst b/source/c04/c04_14.rst index 94d2edf..61ecafb 100644 --- a/source/c04/c04_14.rst +++ b/source/c04/c04_14.rst @@ -1,184 +1,180 @@ -4.14 虚拟环境:Pipenv -===================== +4.14 Xshell的高效使用手册 +========================= -以前一直使用pip+virtualenv+virtualwrapper管理模块和环境, -但是virtualwrapper在windows上使用不太方便,而且包和环境分开管理确实经常不记得哪个是哪个了。 +|image0| -为什么 会推荐 pipenv 呢? +-------------- -- 它是 ``virtualenv`` 和 ``pip`` 的合体,可以合起来使用; -- 使用\ ``Pipfile`` 和 ``Pipfile.lock``\ 替代\ ``requirements.txt`` -- 可以使用 ``pipenv graph``\ 很方便的看出包的依赖关系。 -- 通过加载\ ``.env``\ 文件简化开发工作流程 +|image1| -4.14.1 安装pipenv ------------------ +做为一名开发人员,我们难免都会与服务器打交道。 -如果你的电脑上没有安装 pipenv,可以使用如下方法安装 +有时候是公司的线上生产环境,你需要上去部署公司的项目。 +有时候是在阿里上买的云主机,想自己搭个博客来写写文章。 -.. code:: shell +就像我,是从事云计算相关的,每天远程登陆的服务器都有几十台。 - # mac - $ brew install pipenv +这时候,掌握一些远程登陆工具的使用技巧是相当有必要的,会大大增加你使用的便利性和愉悦性。 - # windows - pip install [--user] pipenv +优秀的远程工具有很多,Xshell,SecureCRT,PuTTY等等。 -如果你的电脑是 windows 的。 +由于小明是个很注重工具顔值的人,所以一开始就选择了Xshell。本篇也只会介绍Xshell的使用技巧,但是快捷键在其他工具上部分是通用的。 -|image0| +快捷入口 +-------- -需要将如标示路径,加入到 环境变量 PATH 中。 +**快速命令** -|image1| - -然后需要重启一下,CMD 终端才能够刷新环境变量。 +查看 -> 快速命令 -4.14.2 创建虚拟环境 -------------------- +双击底部自定义快速命令。 |image2| -DjangoWebBlog 是我们的项目目录,进入这个目录下创建虚拟环境 - -.. 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 版本的虚拟环境为例说明。 +文件 -> 属性 -> 键盘 -|image2| +一定要打钩,这是后面诸多快捷使用的前提。 |image5| -如果你原项目使用的是 requirements.txt 这个管理包的方式,这时候执行 -``pipenv --tow`` 创建一个虚拟环境后,会找到 requirements.txt -,并根据这里面的依赖包生成 Pipfile文件。 +移动光标 +-------- -|image3| +:: -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 +删除元素 +-------- -演示如下: +:: -|image4| + 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 两个窗口来回切换 -|image5| +屏幕模式 +-------- -.. 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`` -|image6| +:: -有的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 -|image7| +然后在xshell配色方案中导入即可 -.env`文件,用来存放一些环境变量。 +附上一个更全的帖子:\ `Xshell快捷键汇总 `__ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image6| -.. |image0| image:: http://image.python-online.cn/Fk6WZ2xbqg2DM3AvnYCpsiKQ4xOn -.. |image1| image:: http://image.python-online.cn/FjuJ8yZsgjkzVuBRZHxK1ZnnzaEX -.. |image2| image:: http://image.python-online.cn/20190612211330.png -.. |image3| image:: http://image.python-online.cn/20190612213015.png -.. |image4| image:: http://image.python-online.cn/20190612213950.png -.. |image5| image:: http://image.python-online.cn/20190612211925.png -.. |image6| image:: http://image.python-online.cn/20190614000336.png -.. |image7| image:: http://image.python-online.cn/20190612215924.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 76c3140..02718b4 100644 --- a/source/c04/c04_15.md +++ b/source/c04/c04_15.md @@ -1,5 +1,7 @@ # 4.15 30个 PyCharm 实用技巧 +![](http://image.iswbm.com/20200602135014.png) + 刚开始做公众号的时候,更新频率正常是一周两到三篇。老读者应该有注意到,以前都是写系列教程,对于读者而言,系列教程会更加友好,学习起来会更容易深入浅出,而对于作者来说,写系列教程,更有一种使命感,而这种使命感是维持更新一大动力。 不写文章的朋友,可能不会知道,写文章也是很费脑子的事情,由于知识的诅咒的存在,往往我会认为我知道的事情,大家也都知道,分享的欲望其实并没有那么强烈,久而久之,这种恶性循环会让我产生更多的焦虑感,产出也越发下降。为了改变这种现状,我打算从今天开始,重新走以前的风格,继续以系列来输出文章。可能会有多个系列在同时进行着,提前告知一下。 @@ -27,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` 来得彻底。 我相信你最终的选择肯定是后者。 @@ -59,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) @@ -71,7 +73,7 @@ Vi 可以满足你对文本操作的所有需求,比可视化界面更加效 安装方法如下,安装完后需要重启 Pycharm 生效。 -![](http://image.python-online.cn/20190323214545.png) +![](http://image.iswbm.com/20190323214545.png) @@ -79,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,然后就可以直接生成如下这段平时都要手动敲入的代码。 @@ -122,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` 展示所有的书签,再进行跳转。 @@ -138,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 命令行调试界面。 @@ -164,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) @@ -188,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) @@ -206,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) 先来说下这个灯泡提示是什么,有什么用? @@ -228,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 一键进行代码性能分析 @@ -287,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列组成一个表格,见下图。 @@ -302,7 +304,7 @@ fun5() 点击 Call Graph(调用关系图)界面直观展示了各函数直接的调用关系、运行时间和时间百分比,见下图。 -![](http://image.python-online.cn/20190507223313.png) +![](http://image.iswbm.com/20190507223313.png) 右上角的4个按钮表示放大、缩小、真实大小、合适大小; @@ -316,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轻松转空格 @@ -344,7 +346,7 @@ fun5() 在图示位置打勾即可开启自动检测。 -![](http://image.python-online.cn/20190423162328.png) +![](http://image.iswbm.com/20190423162328.png) 上面是对一个旧的 Python 模块进行修改时,如何决定当前编辑的缩进方式。 @@ -352,7 +354,7 @@ fun5() 如下图,若在 `Use tab character` 打上勾,则你新建一个 Python 后,就会使用 TAB 进行缩进,反之,则使用四个空格进行缩进。 -![](http://image.python-online.cn/20190423163341.png) +![](http://image.iswbm.com/20190423163341.png) 5. @@ -360,7 +362,7 @@ fun5() PyCharm 有分两个版本,一个是社区版(免费功能有限),一个是专业版(有一些增强功能),详细差异你可以参考这个图,一般来说,社区版用作学习用途是没有问题的。 -![](http://image.python-online.cn/20190506150523.png) +![](http://image.iswbm.com/20190506150523.png) 如果需要使用专业版,网上也有一些注册服务器使用,非常方便,缺点是过一段时间,可能就会失效。这里有一种一劳永逸的方法,但可能仅对早期的 PyCharm 版本有效,可以实现永久激活(到 2099 / 2100年,一定意义上是永久了吧)。 @@ -382,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) @@ -416,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) 另外,以上仅做交流和个人学习使用,请勿商用,有能力的朋友还是希望多支持正版! @@ -434,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 + 鼠标左键 跳进源代码来说,更加方便,,就像微信小程序一样,用完即焚,不会新产生一个标签页,也不需要来回跳转页面。 @@ -454,7 +456,7 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 鼠标左键),可以实现函 前几天打开 PyCharm,发现在导航栏这里出现了很多波浪线,有过 PyCharm 使用经验的同学,就会知道,这是代码中出现了错误。 -![](http://image.python-online.cn/20190613154147.png) +![](http://image.iswbm.com/20190613154147.png) 顺着波浪线,我一层一层地展开目录树,终于找到了那个包含错误的文件。由于是手误,我也不知道我改动了哪一行,看了下这个文件,有将近8000行的代码,难道一行一行地去找? @@ -466,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 快速查看最近的修改 @@ -480,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 静态代码分析检查 @@ -498,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 全方位无死角精准定位 @@ -516,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 解救“中年痴呆” @@ -558,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 随处折叠,实现代码自由 @@ -572,7 +574,7 @@ PyCharm 里代码块的折叠功能,相当的显眼,在代码编辑框的左 如果你和我一样是个键盘党,你可以使用快捷(Mac:按住Command键,再按`+`或者`-` ,Windows:按住Ctrl键,再按`+`或者`-` )进行快速反折叠/折叠。 -![](http://image.python-online.cn/20190629183430.png) +![](http://image.iswbm.com/20190629183430.png) 代码块的折叠和反折叠,应该是一个代码编辑器的基本功能。在这一点上, PyCharm 做为一个 IDE,在这一点上势必要做得更出色,事实证明,它做到了。 @@ -600,7 +602,7 @@ PyCharm 里代码块的折叠功能,相当的显眼,在代码编辑框的左 比如下面这段代码,我只想改myfun 里的的test_name,而对于全局下的同名变量是不应该修改的。如果你全局替换,就会有误伤。 -![](http://image.python-online.cn/20190629211910.png) +![](http://image.iswbm.com/20190629211910.png) 这时候,我们如何做呢? @@ -632,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) 之后你就可以使用这个快捷键删除一个函数(其实这只是删除一个代码块,但是这里只讨论设置方法)。 @@ -648,7 +650,7 @@ PyCharm 打开一个文件,就占用一个标签面。 你有没有发现,不知不觉地,打开的文件越来越多,多到一行标签都装不下,装不下的标签页 PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文件。 -![](http://image.python-online.cn/20190629223534.png) +![](http://image.iswbm.com/20190629223534.png) 点击数字5,你才可以查看隐藏了哪些文件。 @@ -658,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 应用搜索,阅读源码必备 @@ -680,7 +682,7 @@ PyCharm 打开一个文件,就占用一个标签面。 如下图所示,按下快捷键后可以很轻松地看见调用列表。 -![](http://image.python-online.cn/20190629231322.png) +![](http://image.iswbm.com/20190629231322.png) 如果你嫌这快捷键太长了,可以使用 `鼠标中键` 点击这个类,可以达到同样的效果。 @@ -696,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中是很常用的一个取消注释的操作。 @@ -722,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开始 @@ -744,7 +746,7 @@ PyCharm 打开一个文件,就占用一个标签面。 这里就教大家如何设置 PyCharm 的背景色为护眼色,方法如下: -![](http://image.python-online.cn/20190721143450.png) +![](http://image.iswbm.com/20190721143450.png) 设置护眼色,会降低 PyCharm 的顔值,这需要你从中取一个取舍。 @@ -783,7 +785,7 @@ PyCharm 打开一个文件,就占用一个标签面。 这里我提前准备了几种编程语言的 Hello World ,效果如下: -![](http://image.python-online.cn/20191211210012.png) +![](http://image.iswbm.com/20191211210012.png) @@ -797,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) @@ -817,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) @@ -843,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) @@ -915,6 +917,36 @@ PyCharm 打开一个文件,就占用一个标签面。 ![](http://image.iswbm.com/20200420090428.png) +## 4.15.37 快速输入自定义代码片段 + +在 PyCharm 中有一个功能叫 Live Template,它可以用来自定义一些常用的代码片段。 + +比如下面这段,几乎是写 Python 脚本必备的 + +```python +if __name__ == '__main__': +``` + +当你在PyCharm 中编码 python 代码时,只要输入 main ,PyCharm 就会在 Live Template 里找到定义过的代码片段,然后只要直接键入回车,就可以生成这段代码。 + +再比如说,我通常会定义简单的装饰器代码 + +![](http://image.iswbm.com/20200723161209.png) + +这样当我要定义一个最简单的装饰器时,只要输入 `deco` 再直接敲入回车就行啦。 + +![](http://image.iswbm.com/20200723161307.png) + + + + + +[PyCharm 使用技巧](https://blog.csdn.net/xiemanr/category_6928127.html) + + + + + ## 附录 - [PyCharm 快捷键 Mac 版 ](https://resources.jetbrains.com/storage/products/pycharm/docs/PyCharm_ReferenceCard_mac.pdf) @@ -923,4 +955,4 @@ PyCharm 打开一个文件,就占用一个标签面。 --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_15.rst b/source/c04/c04_15.rst index f7babd9..2c25fb8 100644 --- a/source/c04/c04_15.rst +++ b/source/c04/c04_15.rst @@ -1,6 +1,8 @@ 4.15 30个 PyCharm 实用技巧 ========================== +|image0| + 刚开始做公众号的时候,更新频率正常是一周两到三篇。老读者应该有注意到,以前都是写系列教程,对于读者而言,系列教程会更加友好,学习起来会更容易深入浅出,而对于作者来说,写系列教程,更有一种使命感,而这种使命感是维持更新一大动力。 不写文章的朋友,可能不会知道,写文章也是很费脑子的事情,由于知识的诅咒的存在,往往我会认为我知道的事情,大家也都知道,分享的欲望其实并没有那么强烈,久而久之,这种恶性循环会让我产生更多的焦虑感,产出也越发下降。为了改变这种现状,我打算从今天开始,重新走以前的风格,继续以系列来输出文章。可能会有多个系列在同时进行着,提前告知一下。 @@ -32,19 +34,19 @@ Working directory: $ProjectFileDir$ Output filters: $FILE_PATH$\:$LINE$\:$COLUMN$\:.* -|image0| +|image1| 我随意写了一段不符合 pep8 规范的代码。 -|image1| +|image2| 点击右键,选择 ``External Tools`` -> ``AutoPep8`` -|image2| +|image3| 看一下效果,还是挺明显的。 -|image3| +|image4| 你可能会说,Pycharm 本身就自带这个功能了呀,快捷键 ``Command``\ +\ ``Option``\ +\ ``L`` @@ -77,7 +79,7 @@ Pycharm 的隐藏的一个功能 ``Show History``\ ,你可以看到这里有个记录板。如果你想恢复删除的文件,就在删除的记录项点击右键,选择 ``Revert`` 即可恢复。 -|image4| +|image5| 4.15.3 拥抱Vim,远离鼠标 ------------------------ @@ -91,7 +93,7 @@ Pycharm 中 使用 vim 来编辑代码。 安装方法如下,安装完后需要重启 Pycharm 生效。 -|image5| +|image6| 4.15.4 代码模板,效率编码 ------------------------- @@ -99,11 +101,11 @@ Pycharm 中 使用 vim 来编辑代码。 Pycharm 提供的这个代码模板,可以说是相当实用的一个功能了。它可以在你新建一个文件时,按照你预设的模板给你生成一段内容,比如解释器路径,编码方法,作者详细信息等 -|image6| +|image7| 按照上图模板,生成的效果如下。 -|image7| +|image8| 除了新建文件时可以初始化文件,在开发编写代码时,也同样使用 Pycharm 中自带的实用的代码模板,提高你的编码效率。 @@ -111,16 +113,16 @@ Pycharm 当你在键盘中敲入 ``Command`` + ``J`` 时,就可以调出一个面板,从下图可以看出里面有许多预设的模板。 -|image8| +|image9| 如果我们想选择最后一个 main ,可以继续键入 main,然后就可以直接生成如下这段平时都要手动敲入的代码。 -|image9| +|image10| 这里再举个例子,for 循环 可以这样写。 -|image10| +|image11| 4.15.5 使用书签,快速定位 ------------------------- @@ -149,7 +151,7 @@ command + 鼠标左键 加了 ``1`` 这个序号,下次你就可以使用 ``Control`` + ``1`` 直接跳转到这个位置。 -|image11| +|image12| 当然你也可以不加,不加的话就是匿名书签了。你可以使用 ``Shift`` + ``F11`` 展示所有的书签,再进行跳转。 @@ -171,16 +173,16 @@ Pycharm 中可以像 IPython Shell 和 Jupyter Notebook 假如我在调试如下几行简单的代码。在第 3 行处打了个断点。然后点击图示位置 ``Show Python Prompt`` 按钮。 -|image12| +|image13| 就进入了 ``Python Shell`` 的界面,这个Shell 环境和我们当前运行的程序环境是打通的,变量之间可以互相访问,这下你可以轻松地进行调试了。 -|image13| +|image14| 上面我们打了个断点,是为了方便说明这个效果。并不是说一定要打断点。如果不打断点,在脚本执行完成后,也仍然可以在这个界面查看并操作所有变量。 -|image14| +|image15| 现在我们已经可以满足我们的调试的需求,但是每次运行脚本,都要手动点击 ``Show Python Prompt`` @@ -188,11 +190,11 @@ Pycharm 中可以像 IPython Shell 和 Jupyter Notebook 你需要点击图示位置 ``Edit Configurations`` 处。 -|image15| +|image16| 然后在这里打勾选中。 -|image16| +|image17| 设置上之后,之后你每次运行后脚本后,都会默认为你存储所有变量的值,并为你打开 console 命令行调试界面。 @@ -203,12 +205,12 @@ debug 模式运行项目,并打断点。 使用方法就是,在你打了断点后,在图示位置处,点击右键使用 ``Evaluate Expression`` -|image17| +|image18| 就弹出了一个 ``Evaluate Expression`` 窗口,这里 可以运行命令表达式,直接操作变量。 -|image18| +|image19| 4.15.7 指定参数执行脚本 ----------------------- @@ -227,16 +229,16 @@ debug 模式运行项目,并打断点。 对于刚使用 Pycharm 的同学,可能并不知道 Pycharm 也是可以指定参数的。点击下图位置 -|image19| +|image20| 进入设置面板,在 ``Script parameters`` 中填入参数即可。 -|image20| +|image21| 同时在上图的底部,你可以看到,这里可以很方便的切换 解释器,比你跑到这边来要容易得多吧 -|image21| +|image22| 4.15. 8 搜索时过滤测试文件 -------------------------- @@ -248,20 +250,20 @@ debug 模式运行项目,并打断点。 时带来了不小的困扰,你可以从下图的搜索结果中感受一下,搜索一个函数,test 文件里的结果比 正常文件要多很多。 -|image22| +|image23| 这些测试文件的搜索结果,对于我们看源代码不仅没有任何帮助的,更重要的是还干扰视线。于是我就研究了一下,从文件名入手,只要在 ``File mask`` 里填写 ``!test*`` 可以将这些test文件过滤掉。搜索结果一下子清晰很多。 -|image23| +|image24| 4.15.9 关闭烦人的灯泡提示 ------------------------- 本来没有想写这个的,但是知乎上有一位朋友有这个需求,那我研究了下。 -|image24| +|image25| 先来说下这个灯泡提示是什么,有什么用? @@ -275,7 +277,7 @@ debug 模式运行项目,并打断点。 我研究了下,Pycharm (2018版本)里是有开关按钮的,将下图中的这个选项(\ ``Show intention bulb``\ )取消勾选,就可以关闭这个功能。 -|image25| +|image26| 4.15.10 关闭碍眼的波浪线 ------------------------ @@ -283,7 +285,7 @@ debug 模式运行项目,并打断点。 下面我先给出了一小段代码示例,思考一下,为什么name,my_name 不会有波浪线,而 myname 和 wangbm 会有波浪线呢? -|image26| +|image27| Pycharm 本身会实时地对变量名进行检查,如果变量名不是一个已存在的英文单词,就会出现一条波浪线,当一个变量里有多个单词时,Python @@ -296,16 +298,16 @@ myname 会被当成是一个单词对待,由于它在单词库里并没有它 这种风格的变量命名方法,像下面这样(随便找了一段 cloudinit 的代码),是让人挺不舒服的,总有一种代码有 bug 的错觉。 -|image27| +|image28| 那么如何关闭这个非语法级别的波浪线呢?很简单,它的开关就在你的右下角那个像 人头像 一样的按钮 -|image28| +|image29| 然后选择 ``Syntax`` 级别的即可。同样一段代码,效果如下,干净了很多。 -|image29| +|image30| 4.15.11 一键进行代码性能分析 ---------------------------- @@ -348,11 +350,11 @@ cProfile,在某些框架中,也内置了中间件帮助你进行性能分析 点击 Run -> Profile ‘程序’ ,即可进行性能分析。 -|image30| +|image31| 运行完毕后,会自动跳出一个性能统计界面。 -|image31| +|image32| 性能统计界面由Name、Call Count、Time(ms)、Own Time(ms) ,4列组成一个表格,见下图。 @@ -366,7 +368,7 @@ cProfile,在某些框架中,也内置了中间件帮助你进行性能分析 点击 Call Graph(调用关系图)界面直观展示了各函数直接的调用关系、运行时间和时间百分比,见下图。 -|image32| +|image33| 右上角的4个按钮表示放大、缩小、真实大小、合适大小; @@ -383,26 +385,26 @@ Graph(调用关系图)界面直观展示了各函数直接的调用关系、 按照如下提示点击 Git 仓库配置 -|image33| +|image34| 接着输入仓库地址 -|image34| +|image35| 点击 Test,测试连通性,会要求输入密码 -|image35| +|image36| 若一切顺利,则会看到如下界面 -|image36| +|image37| 测试连接成功后,点击 Clone 就可以克隆下来了。 对于以前使用 Git 命令来管理的,现在可以直接使用 PyCharm 的菜单栏来操作,这些功能已经可以满足大多数人的日常需求了,应该是够用了。 -|image37| +|image38| 4.15.13 Tab轻松转空格 --------------------- @@ -416,7 +418,7 @@ Pycharm 在图示位置打勾即可开启自动检测。 -|image38| +|image39| 上面是对一个旧的 Python 模块进行修改时,如何决定当前编辑的缩进方式。 @@ -425,7 +427,7 @@ Pycharm 如下图,若在 ``Use tab character`` 打上勾,则你新建一个 Python 后,就会使用 TAB 进行缩进,反之,则使用四个空格进行缩进。 -|image39| +|image40| 5. @@ -435,7 +437,7 @@ Pycharm PyCharm 有分两个版本,一个是社区版(免费功能有限),一个是专业版(有一些增强功能),详细差异你可以参考这个图,一般来说,社区版用作学习用途是没有问题的。 -|image40| +|image41| 如果需要使用专业版,网上也有一些注册服务器使用,非常方便,缺点是过一段时间,可能就会失效。这里有一种一劳永逸的方法,但可能仅对早期的 PyCharm 版本有效,可以实现永久激活(到 2099 / @@ -465,21 +467,21 @@ PyCharm 版本有效,可以实现永久激活(到 2099 / 将第一步下载的 jar 包放入这个目录,并打开如下两个以 ``vmoptions`` 后缀结尾的文件: -|image41| +|image42| 添加如下这一行(请根据你的实际安装目录自行调整) -|image42| +|image43| 若是 Mac OS 系统,请找到并进入你的 Pycharm 安装启动目录(以我的为例) 将第一步下载的 jar 包放入这个目录 -|image43| +|image44| 并打开如下一个以 ``vmoptions`` 后缀结尾的文件: -|image44| +|image45| **第三步**\ : @@ -495,12 +497,12 @@ PyCharm 版本有效,可以实现永久激活(到 2099 / 若是 Windows 系统,重启 PyCharm 后,查看激活信息:Help -> About -|image45| +|image46| 如果是 Mac OS 系统,重启 PyCharm 后,查看激活信息:PyCharm -> About PyCharm -|image46| +|image47| 另外,以上仅做交流和个人学习使用,请勿商用,有能力的朋友还是希望多支持正版! @@ -521,22 +523,22 @@ Ctrl + 鼠标左键 (Mac 上是:Command + 几个简单的 Example Usage 有助于理解使用。在这一点上,Flask 可以说做得相当好。这边随便截一个 Werkzeug 的例子。 -|image47| +|image48| 假如我们在使用这个类的时候,忘记了这个用法,可以按住 Ctrl + q(Mac暂时未找到对应快捷键),在当前页面就可以快速预览 LocalStack 的接口文档。 -|image48| +|image49| 同样的,如果你对这个类或者函数的代码逻辑感兴趣,也可以使用快速预览的方式在当前页面展示源代码。快捷键是:Ctrl + shift + i (Mac:Command + shift + i)。效果如下 -|image49| +|image50| 如果 PyCharm 检测到的同名函数有多个,可以点这里进行选择查看 -|image50| +|image51| 这两个快捷键比起使用 Ctrl + 鼠标左键 跳进源代码来说,更加方便,,就像微信小程序一样,用完即焚,不会新产生一个标签页,也不需要来回跳转页面。 @@ -547,7 +549,7 @@ q(Mac暂时未找到对应快捷键),在当前页面就可以快速预览 前几天打开 PyCharm,发现在导航栏这里出现了很多波浪线,有过 PyCharm 使用经验的同学,就会知道,这是代码中出现了错误。 -|image51| +|image52| 顺着波浪线,我一层一层地展开目录树,终于找到了那个包含错误的文件。由于是手误,我也不知道我改动了哪一行,看了下这个文件,有将近8000行的代码,难道一行一行地去找? @@ -561,11 +563,11 @@ q(Mac暂时未找到对应快捷键),在当前页面就可以快速预览 我在搜索框输入 Error,就找到了快速定位到错误位置的快捷键 ``F2`` 和 ``Shift+F2`` 可以快速的定位到错误行。 -|image52| +|image53| 使用快捷键 F2 查看了下原来是这里缩进有问题。 -|image53| +|image54| 4.15.17 快速查看最近的修改 -------------------------- @@ -578,7 +580,7 @@ q(Mac暂时未找到对应快捷键),在当前页面就可以快速预览 option+shift+C 可以快速查看最近修改的内容(windows 上应该是alt+shift+c吧) -|image54| +|image55| 4.15.18 静态代码分析检查 ------------------------ @@ -601,11 +603,11 @@ Java,需要将代码编译成机器可识别的语言才可运行,在编译 你只需要像下面这样点击项目文件夹,然后右键,选择 ``Inspect Code``\ ,就可以开启静态检查。 -|image55| +|image56| 我对开源组件 nova 的静态检查发现,其有不规范的地方有数千处。 -|image56| +|image57| 4.15.19 全方位无死角精准定位 ---------------------------- @@ -624,26 +626,26 @@ OpenStack - 精准定位到文件:Windows(Ctrl+Shift+N),Mac(Command+ shift +N) -|image57| +|image58| - 精准定位到类:Windows(Ctrl+N),Mac(Command+N) -|image58| +|image59| - 精准定位到符号:类的所有成员(函数、变量等)都可以称之为符号,Windows(Ctrl+Alt+Shift+N),Mac(Option+Shift+Command+N) -|image59| +|image60| - 精准定位到文件结构:文件结构包括类、函数、变量,这说明上面定位到类和定位到符号的方法,你都可以用这个来代替。 Windows:Ctrl+F12,Mac:Command+F12,如果和我一样是Mac是带touchbar的,键盘上是没有F12的,那你应该先按住 Command + fn,这时 touchbar 上会出现 F12,再按F12即可。 -|image60| +|image61| - 精准定位到某行:Windows(Ctrl+G),Mac(Command+G),如下图定位到第510行第9个字符处。 -|image61| +|image62| 4.15.20 TODO 解救“中年痴呆” --------------------------- @@ -672,14 +674,14 @@ OpenStack 开头。然后,你要查看全局项目中的所有 TODO 事项的时候,可以使用快捷键调出 TODO 面板。如果你是 Mac, 快捷键 是Command + 6,而 Windows 是 Alt+6。 -|image62| +|image63| 另外,我还使用这个来记录下个版本要优化的代码逻辑,要添加的功能。 如果是比较紧急的 BUG,可以使用类似 TODO 的标记 — ``FIXME`` 来区分紧急程度。 -|image63| +|image64| 4.15.21 随处折叠,实现代码自由 ------------------------------ @@ -691,7 +693,7 @@ PyCharm 里代码块的折叠功能,相当的显眼,在代码编辑框的左 如果你和我一样是个键盘党,你可以使用快捷(Mac:按住Command键,再按\ ``+``\ 或者\ ``-`` ,Windows:按住Ctrl键,再按\ ``+``\ 或者\ ``-`` )进行快速反折叠/折叠。 -|image64| +|image65| 代码块的折叠和反折叠,应该是一个代码编辑器的基本功能。在这一点上, PyCharm 做为一个 IDE,在这一点上势必要做得更出色,事实证明,它做到了。 @@ -705,7 +707,7 @@ PyCharm 做为一个 IDE,在这一点上势必要做得更出色,事实证 只要你先选中你想折叠的代码,再按住 Command 紧接着按住 ``.`` 就可以了。效果如下: -|image65| +|image66| (GIF动态只播放两次,重播请刷新页面) @@ -723,7 +725,7 @@ PyCharm 做为一个 IDE,在这一点上势必要做得更出色,事实证 比如下面这段代码,我只想改myfun 里的的test_name,而对于全局下的同名变量是不应该修改的。如果你全局替换,就会有误伤。 -|image66| +|image67| 这时候,我们如何做呢? @@ -733,7 +735,7 @@ PyCharm 做为一个 IDE,在这一点上势必要做得更出色,事实证 操作方法很简单,先选中你的变量,然后使用快捷键 Shift+F6,就可以直接重命名了。 -|image67| +|image68| (GIF动态只播放两次,重播请刷新页面) @@ -752,7 +754,7 @@ Command+y 删除该行,就删除了该函数。 做录制方法如下: -|image68| +|image69| (GIF动态只播放两次,重播请刷新页面) @@ -761,13 +763,13 @@ Command+y 删除该行,就删除了该函数。 这样播放宏显得有点繁琐,个人建议你为这个宏定义一个快捷键,这样会更方便播放宏。 -|image69| +|image70| 设置快捷键时,注意不要和已有的快捷键冲突。 设置好后,查看 Macro,发现PyCharm已经将这个快捷键绑定给这个宏。 -|image70| +|image71| 之后你就可以使用这个快捷键删除一个函数(其实这只是删除一个代码块,但是这里只讨论设置方法)。 @@ -779,7 +781,7 @@ PyCharm 打开一个文件,就占用一个标签面。 你有没有发现,不知不觉地,打开的文件越来越多,多到一行标签都装不下,装不下的标签页 PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文件。 -|image71| +|image72| 点击数字5,你才可以查看隐藏了哪些文件。 @@ -789,11 +791,11 @@ PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文 如下图,将单行显示取消勾选即可。 -|image72| +|image73| 设置完后,有哪些文件就非常清晰了。 -|image73| +|image74| 4.15.25 应用搜索,阅读源码必备 ------------------------------ @@ -812,7 +814,7 @@ PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文 如下图所示,按下快捷键后可以很轻松地看见调用列表。 -|image74| +|image75| 如果你嫌这快捷键太长了,可以使用 ``鼠标中键`` 点击这个类,可以达到同样的效果。 @@ -830,19 +832,19 @@ PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文 对比示例,可以查看下面这张图,UI做的还是挺好看的。 -|image75| +|image76| 4.15.27 以列为单位的块编辑 -------------------------- 先给你出道小题,像下面这段代码,如果在不影响代码的情况下,快速删除后面代码后面的注释呢? -|image76| +|image77| 我能想到的有两种方法,如果像如上这种有规律的注释,可以使用 ``正则匹配`` + ``替换`` 来实现。 -|image77| +|image78| 对于这个场景我想到了可以用 vim来轻松的解决,vim 支持块编辑,可以以列为单位选择区域然后进行操作,这在vim中是很常用的一个取消注释的操作。 @@ -852,7 +854,7 @@ PyCharm 会将其隐藏起来,并以数字的形式告诉你隐藏了几个文 当你按住 alt(windows)或者option(mac),然后使用鼠标进行选择,你会发现这样一件神奇的事情。 -|image78| +|image79| 4.15.28 智能补全,忽略大小写 ---------------------------- @@ -861,17 +863,17 @@ alt(windows)或者option(mac),然后使用鼠标进行选择,你会 当你的对象是以大写字母开头时,而你使用小写字母编写代码时,是不能查找到该函数的,你必须得先切换成大写再输入一遍。 -|image79| +|image80| 如何避免这种尴尬的情况? 只要在配置中关闭大小写匹配即可。 -|image80| +|image81| 效果如下: -|image81| +|image82| 4.15.29 保护眼睛,从PyCharm开始 ------------------------------- @@ -884,7 +886,7 @@ alt(windows)或者option(mac),然后使用鼠标进行选择,你会 这里就教大家如何设置 PyCharm 的背景色为护眼色,方法如下: -|image82| +|image83| 设置护眼色,会降低 PyCharm 的顔值,这需要你从中取一个取舍。 @@ -930,7 +932,7 @@ Debug ,而远程调试需要不少前置步骤。 这里我提前准备了几种编程语言的 Hello World ,效果如下: -|image83| +|image84| 4.15.32 轻松实现 json 格式化 ---------------------------- @@ -944,18 +946,18 @@ Debug ,而远程调试需要不少前置步骤。 以前我经常使用一些在线的网站,比如:https://tool.oschina.net/codeformat/json -|image84| +|image85| 如果你的电脑无法连网,或者不喜欢多记一个网址,完全可以使用 PyCharnm 来解决这一诉求 没有经过美化是这样的: -|image85| +|image86| 按住 ``Ctrl+Alt+L``\ 经过美化后是这样的 -|image86| +|image87| 4.15.33 在Windows上使用 Linux 命令 ---------------------------------- @@ -967,11 +969,11 @@ Debug ,而远程调试需要不少前置步骤。 对于像我这样熟悉 Linux 的开发者来说,Windows 的 那些 CMD 命令带来的糟糕体验是无法忍受的。 -|image87| +|image88| 在弹出的 Bash 窗口,你可以敲入你想使用的 Linux 命令,是不是舒服多了。 -|image88| +|image89| 4.15.34 快速进行代码封装的技巧 ------------------------------ @@ -993,36 +995,36 @@ Debug ,而远程调试需要不少前置步骤。 假如,我现在有如下一段代码,红框标出的代码放在主函数中,有些不太合适,况且这段代码不能让人一眼就看出它是在做什么事情。如何将其进行封装,对我们理清整个主程序的逻辑会有帮助。 -|image89| +|image90| 选中你要封装的代码,然后按住 ``Ctrl``\ +\ ``Alt``\ +\ ``M`` 后,会跳出如下界面,根据自己的需要,修改函数名,选择参数和返回值 -|image90| +|image91| 一切就绪点击 ``OK``\ ,PyCharm 会自动在合适的位置为你定义一个函数名,并将你选中的代码放到里面,其中参数名和返回值也都是按照你的要求,效果如下: -|image91| +|image92| 4.15.35 使用 Git 进行版本管理 ----------------------------- 点击 ``VCS`` -> ``Git`` -> ``Clone`` -|image92| +|image93| 填写git仓库相关信息 -|image93| +|image94| 点击 ``Test``\ ,会尝试连接 git 服务器,中间会让你输入登陆的帐号和密码。 -|image94| +|image95| 点击\ ``OK`` 后,若一切正常会提示连接成功。 -|image95| +|image96| 点击 ``OK`` 后,PyCharm 需要你选择如何打开这个 Git 仓库目录,是在当前窗口中打开,还是新建一个窗口? @@ -1031,14 +1033,14 @@ Debug ,而远程调试需要不少前置步骤。 仓库,为了方便,我选择在当前窗口中打开(注意勾选 ``Add to currently opened projects``\ )。 -|image96| +|image97| 至此,Git 配置完成。 此时你可以 ``VCS`` -> ``Git`` 查看,发现之前这些灰色不可用的按钮都可以使用了。 -|image97| +|image98| 本篇重在讲解 PyCharm 的配置,关于Git 的操作,不属于本篇重点,就不再展开讲了。 @@ -1046,11 +1048,11 @@ Debug ,而远程调试需要不少前置步骤。 若你想对已配置的Git仓库进行修改,可点击 ``File`` -> ``Setting`` -> ``Version Control`` 调出如下界面。 -|image98| +|image99| 不得不说 PyCharm 的这 UI 做得可以,随便改了个东西提交一下 -|image99| +|image100| 4.15.36 快速查看 Git 的修改 --------------------------- @@ -1060,18 +1062,46 @@ Debug ,而远程调试需要不少前置步骤。 第一种当然是使用 git diff -|image100| +|image101| 第二种是使用之前写的 show history -|image101| +|image102| 第三种,也是今天要介绍的,是最简便,也是直接的方法。 在有文本变动的位置,PyCharm 会有提示,如下红色箭头标识处,点击它就可以直接查看,还可以快速回滚。 -|image102| +|image103| + +4.15.37 快速输入自定义代码片段 +------------------------------ + +在 PyCharm 中有一个功能叫 Live +Template,它可以用来自定义一些常用的代码片段。 + +比如下面这段,几乎是写 Python 脚本必备的 + +.. code:: python + + if __name__ == '__main__': + +当你在PyCharm 中编码 python 代码时,只要输入 main ,PyCharm 就会在 Live +Template +里找到定义过的代码片段,然后只要直接键入回车,就可以生成这段代码。 + +再比如说,我通常会定义简单的装饰器代码 + +|image104| + +这样当我要定义一个最简单的装饰器时,只要输入 ``deco`` +再直接敲入回车就行啦。 + +|image105| + +`PyCharm +使用技巧 `__ 附录 ---- @@ -1084,112 +1114,113 @@ Debug ,而远程调试需要不少前置步骤。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190323164120.png -.. |image1| image:: http://image.python-online.cn/20190323211635.png -.. |image2| image:: http://image.python-online.cn/20190323211301.png -.. |image3| image:: http://image.python-online.cn/20190324111603.png -.. |image4| image:: http://image.python-online.cn/20190323153643.png -.. |image5| image:: http://image.python-online.cn/20190323214545.png -.. |image6| image:: http://image.python-online.cn/20190323225704.png -.. |image7| image:: http://image.python-online.cn/20190323225631.png -.. |image8| image:: http://image.python-online.cn/20190323232017.png -.. |image9| image:: https://i.loli.net/2019/03/23/5c965275bf0d7.gif -.. |image10| image:: https://i.loli.net/2019/03/23/5c9653e1b757a.gif -.. |image11| image:: http://image.python-online.cn/20190324111429.png -.. |image12| image:: http://image.python-online.cn/Fi3N02x9OeOPatGdaReam_icn9G_ -.. |image13| image:: http://image.python-online.cn/Fj1W53Txj0iFs5eYhFYh_dHlPtIL -.. |image14| image:: http://image.python-online.cn/FlMsB7B1x6ET9mLOgydTWuTEXuOe -.. |image15| image:: http://image.python-online.cn/FmfL3r0iWx_srT_xMASBEp1ZaaId -.. |image16| image:: http://image.python-online.cn/FiNCYpVlI93gk1zhOdQn4c0A8FMX -.. |image17| image:: http://image.python-online.cn/FrAq1tVRM7Bz948wRqZFzU2PQnI0 -.. |image18| image:: http://image.python-online.cn/Fo2aEraqbj_2KqDt44EzJTVe8pEf -.. |image19| image:: http://image.python-online.cn/FmfL3r0iWx_srT_xMASBEp1ZaaId -.. |image20| image:: http://image.python-online.cn/FujczKwTUPa8l5EEmS0eoh-zL1Nk -.. |image21| image:: http://image.python-online.cn/Fq60WOdcRJopqV6MVoRcIuZclYKx -.. |image22| image:: http://image.python-online.cn/FlXynbyxh8tTrCpc4tVLqycL7JQm -.. |image23| image:: http://image.python-online.cn/FiD91PR1hUu0Ruc6cmZ7EGNM6Be_ -.. |image24| image:: http://image.python-online.cn/FhkX5Ko3LVZL_p7YfitDsTDxvHmL -.. |image25| image:: http://image.python-online.cn/FuSSVa-aMqkfCaf62sbUoX2PLaYM -.. |image26| image:: http://image.python-online.cn/FtFPI89AOKmPLNpNxf-jdkn1BDLW -.. |image27| image:: http://image.python-online.cn/FiKyU6tjQauWXfaVfKLhwi3NkXBf -.. |image28| image:: http://image.python-online.cn/FsAM-8HyzSrLWZJ_lg3ofw84_ibf -.. |image29| image:: http://image.python-online.cn/FgJCtNYkjPfBaTbRxwb3Z6icHqkf -.. |image30| image:: http://image.python-online.cn/20190507222856.png -.. |image31| image:: http://image.python-online.cn/20190507222119.png -.. |image32| image:: http://image.python-online.cn/20190507223313.png -.. |image33| image:: http://image.python-online.cn/20190507215525.png -.. |image34| image:: http://image.python-online.cn/20190507220101.png -.. |image35| image:: http://image.python-online.cn/20190419152120.png -.. |image36| image:: http://image.python-online.cn/20190419152145.png -.. |image37| image:: http://image.python-online.cn/20190507220740.png -.. |image38| image:: http://image.python-online.cn/20190423162328.png -.. |image39| image:: http://image.python-online.cn/20190423163341.png -.. |image40| image:: http://image.python-online.cn/20190506150523.png -.. |image41| image:: http://image.python-online.cn/20190506150010.png -.. |image42| image:: http://image.python-online.cn/20190506150100.png -.. |image43| image:: http://image.python-online.cn/20190507000850.png -.. |image44| image:: http://image.python-online.cn/20190507001025.png -.. |image45| image:: http://image.python-online.cn/20190507001422.png -.. |image46| image:: http://image.python-online.cn/20190507001350.png -.. |image47| image:: http://image.python-online.cn/20190507152911.png -.. |image48| image:: http://image.python-online.cn/20190507152840.png -.. |image49| image:: http://image.python-online.cn/20190507153847.png -.. |image50| image:: http://image.python-online.cn/20190507154027.png -.. |image51| image:: http://image.python-online.cn/20190613154147.png -.. |image52| image:: http://image.python-online.cn/20190613154401.png -.. |image53| image:: http://image.python-online.cn/20190613160905.png -.. |image54| image:: http://image.python-online.cn/20190614235120.png -.. |image55| image:: http://image.python-online.cn/20190616211359.png -.. |image56| image:: http://image.python-online.cn/20190616214310.png -.. |image57| image:: http://image.python-online.cn/20190616221620.png -.. |image58| image:: http://image.python-online.cn/20190616232746.png -.. |image59| image:: http://image.python-online.cn/20190616233827.png -.. |image60| image:: http://image.python-online.cn/20190616235007.png -.. |image61| image:: http://image.python-online.cn/20190616234038.png -.. |image62| image:: http://image.python-online.cn/20190616231649.png -.. |image63| image:: http://image.python-online.cn/20190616232527.png -.. |image64| image:: http://image.python-online.cn/20190629183430.png -.. |image65| image:: https://i.loli.net/2019/06/29/5d17589c1603755790.gif -.. |image66| image:: http://image.python-online.cn/20190629211910.png -.. |image67| image:: https://i.loli.net/2019/06/29/5d1764b94d11128912.gif -.. |image68| image:: https://i.loli.net/2019/06/29/5d176e9ba92e916696.gif -.. |image69| image:: http://image.python-online.cn/20190629221224.png -.. |image70| image:: http://image.python-online.cn/20190629221547.png -.. |image71| image:: http://image.python-online.cn/20190629223534.png -.. |image72| image:: http://image.python-online.cn/20190629224229.png -.. |image73| image:: http://image.python-online.cn/20190629224430.png -.. |image74| image:: http://image.python-online.cn/20190629231322.png -.. |image75| image:: http://image.python-online.cn/20190721125739.png -.. |image76| image:: http://image.python-online.cn/20190721132238.png -.. |image77| image:: http://image.python-online.cn/20190721133403.png -.. |image78| image:: https://i.loli.net/2019/07/21/5d3401410087b61815.gif -.. |image79| image:: http://image.python-online.cn/20190721141327.png -.. |image80| image:: http://image.python-online.cn/20190721141653.png -.. |image81| image:: http://image.python-online.cn/20190721141751.png -.. |image82| image:: http://image.python-online.cn/20190721143450.png -.. |image83| image:: http://image.python-online.cn/20191211210012.png -.. |image84| image:: http://image.python-online.cn/20191211211309.png -.. |image85| image:: http://image.python-online.cn/20191211211334.png -.. |image86| image:: http://image.python-online.cn/20191211211626.png -.. |image87| image:: http://image.python-online.cn/20191211212546.png -.. |image88| image:: http://image.python-online.cn/20191222143741.png -.. |image89| image:: http://image.python-online.cn/20191222141905.png -.. |image90| image:: http://image.python-online.cn/20191222141955.png -.. |image91| image:: http://image.python-online.cn/20191222142223.png -.. |image92| image:: http://image.python-online.cn/20191211100048.png -.. |image93| image:: http://image.python-online.cn/20191211100657.png -.. |image94| image:: http://image.python-online.cn/20191211101706.png -.. |image95| image:: http://image.python-online.cn/20191211101845.png -.. |image96| image:: http://image.python-online.cn/20191211102501.png -.. |image97| image:: http://image.python-online.cn/20191211102826.png -.. |image98| image:: http://image.python-online.cn/20191211133836.png -.. |image99| image:: http://image.python-online.cn/20191211143510.png -.. |image100| image:: http://image.iswbm.com/20200420085524.png -.. |image101| image:: http://image.iswbm.com/20200420090117.png -.. |image102| image:: http://image.iswbm.com/20200420090428.png +|image106| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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.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.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.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.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 +.. |image104| image:: http://image.iswbm.com/20200723161209.png +.. |image105| image:: http://image.iswbm.com/20200723161307.png +.. |image106| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c04/c04_16.md b/source/c04/c04_16.md index 0dbccaa..36dd84f 100644 --- a/source/c04/c04_16.md +++ b/source/c04/c04_16.md @@ -1,75 +1,689 @@ -# 4.16 Python 开发技巧集合 +# 4.16 程序员编码必学:Vim -## 4.16.1 如何在被调用方法中获取调用者的方法名? +![](http://image.iswbm.com/20200602135014.png) -Python:如何在被调用方法中获取调用者的方法名? +我本人是 Vim 的重度使用者,就因为喜欢上这种双手不离键盘就可以操控一切的feel,Vim 可以让我对文本的操作更加精准、高效。 -假设我有2种方法: +对于未使用过 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!可以删除所有的标记 ``` -def method1(self): - ... - a = A.method2() -def method2(self): - ... +当在查看错误日志时,正常的步骤是,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 打开一个视窗并且隐藏之前的所有视窗 ``` +**窗口切换** -如果我不想对method1做任何更改,如何在method2中获取调用者的名称(在此示例中,名称为method1)? +```shell +# 特别说明:Ctrl w <字母> 不需要同时按 -`inspect.getframeinfo` +Ctrl-w h 切换到左边窗口 +Ctrl-w l 切换到右边窗口 -等相关功能检查可以帮助: +Ctrl-w j 切换到下边窗口 +Ctrl-w k 切换到上边窗口 + +# 特别说明:全屏模式下 +:n 切换下一个窗口 +:N 切换上一个窗口 +:bp 切换上一个窗口 + +# 特别说明:非全屏模式 + +:bn 切换下一个窗口,就当前位置的窗口的内容变了,其他窗口不变 +:bN 切换上一个窗口,就当前位置的窗口的内容变了,其他窗口不变 ``` ->>> 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 ->>> +**窗口移动** + +```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. 文档加密 -## 4.16.2 pprint +``` +vim -x file_name -比如说这段 +然后输入密码: +确认密码: -```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'}]} +如果不修改内容也要保存。:wq,不然密码设定不会生效。 ``` -可以使用pprint +## 18. 录制宏 + +按q键加任意字母开始录制,再按q键结束录制(这意味着vim中的宏不可嵌套),使用的时候@加宏名,比如qa。。。q录制名为a的宏,@a使用这个宏。 + +## 19. 执行命令 + + +```shell + +# 重复前一次命令 +. + +# 执行shell命令 +:!command + +# 比如列出当前目录下文件 +:!ls -```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} +# 执行脚本 +:!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) --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_16.rst b/source/c04/c04_16.rst index 51c01a4..7c6fba3 100644 --- a/source/c04/c04_16.rst +++ b/source/c04/c04_16.rst @@ -1,78 +1,728 @@ -4.16 Python 开发技巧集合 +4.16 程序员编码必学:Vim ======================== -4.16.1 如何在被调用方法中获取调用者的方法名? ---------------------------------------------- +|image0| -Python:如何在被调用方法中获取调用者的方法名? +我本人是 Vim +的重度使用者,就因为喜欢上这种双手不离键盘就可以操控一切的feel,Vim +可以让我对文本的操作更加精准、高效。 -假设我有2种方法: +对于未使用过 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 - def method1(self): - ... - a = A.method2() + i 在当前位置生前插入 + I 在当前行首插入 - def method2(self): - ... + a 在当前位置后插入 + A 在当前行尾插入 -如果我不想对method1做任何更改,如何在method2中获取调用者的名称(在此示例中,名称为method1)? + o 在当前行之后插入一行 + O 在当前行之前插入一行 -``inspect.getframeinfo`` +4. 查找命令 +----------- -等相关功能检查可以帮助: +最简单的查找 + +.. 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:: 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 + + >> 向右缩进 + << 取消缩进 -.. code:: python +如何你要对代码进行缩进,还可以用 ``==`` +对当前行缩进,如果要对多行对待缩进,则使用 +n\ ``==``\ ,这种方式要求你所编辑的文件的扩展名是被vim所识别的,比如\ ``.py``\ 文件。 - >>> 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 +:: + + :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:: 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 + + # 友情提示:键盘切记不要处于中文状态 + + 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 的一些使用总结,希望对你能有帮助。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +最后,送你一张 Vim 的键盘图,你可以将它设置为你的电脑桌面,对你初学 Vim +可能会有帮助。 + +你可以\ **关注本公众号「Python编程时光」**\ ,在后台回复“**vim**” +,即可获取高清大图。 + +.. figure:: http://image.iswbm.com/20190804222221.png + :alt: 图1 + + 图1 + +.. figure:: http://image.iswbm.com/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_17.md b/source/c04/c04_17.md index 9f3dd10..8f1ed7c 100644 --- a/source/c04/c04_17.md +++ b/source/c04/c04_17.md @@ -1,5 +1,7 @@ # 4.17 详解 23 种设计模式 +![](http://image.iswbm.com/20200602135014.png) + ## 4.17.1 策略模式 @@ -20,7 +22,7 @@ - Stragety:策略基类 - ConcreteStragety:具体策略 -![](http://image.python-online.cn/20190414144511.png) +![](http://image.iswbm.com/20190414144511.png) 以第一个超市做活动的场景来举个例子。 @@ -274,7 +276,7 @@ class User: 验证结果 -![](http://image.python-online.cn/20190512113846.png) +![](http://image.iswbm.com/20190512113846.png) - 使用装饰器 @@ -303,7 +305,7 @@ class User: 验证结果 -![](http://image.python-online.cn/20190512113917.png) +![](http://image.iswbm.com/20190512113917.png) - 使用元类 @@ -326,7 +328,7 @@ class User(metaclass=MetaSingleton): 验证结果 -![](http://image.python-online.cn/20190512114028.png) +![](http://image.iswbm.com/20190512114028.png) 以上的代码,一般情况下没有问题,但在并发场景中,就会出现线程安全的问题。 @@ -464,4 +466,4 @@ for i in range(10): --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c04/c04_17.rst b/source/c04/c04_17.rst index 018f952..8e812cb 100644 --- a/source/c04/c04_17.rst +++ b/source/c04/c04_17.rst @@ -1,6 +1,8 @@ 4.17 详解 23 种设计模式 ======================= +|image0| + 4.17.1 策略模式 --------------- @@ -20,7 +22,7 @@ - Stragety:策略基类 - ConcreteStragety:具体策略 -|image0| +|image1| 以第一个超市做活动的场景来举个例子。 @@ -281,7 +283,7 @@ Order 验证结果 -|image1| +|image2| - 使用装饰器 @@ -310,7 +312,7 @@ Order 验证结果 -|image2| +|image3| - 使用元类 @@ -333,7 +335,7 @@ Order 验证结果 -|image3| +|image4| 以上的代码,一般情况下没有问题,但在并发场景中,就会出现线程安全的问题。 @@ -472,13 +474,12 @@ Order -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image5| -.. |image0| image:: http://image.python-online.cn/20190414144511.png -.. |image1| image:: http://image.python-online.cn/20190512113846.png -.. |image2| image:: http://image.python-online.cn/20190512113917.png -.. |image3| image:: http://image.python-online.cn/20190512114028.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 d8b0542..aedad82 100644 --- a/source/c04/c04_18.md +++ b/source/c04/c04_18.md @@ -1,313 +1,171 @@ -# 4.18 详细的 Mac 使用指南 +# 4.18 如何用好 Python的用户环境? -## 4.18.1 iTerm2 +![](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). -``` -# 分窗口操作 -shift + command + d(横切)command + d(竖切) - -2、历史信息查找和粘贴:command + f,呼出查找功能,找到后 tab 键可以选中找到的文本,通过option + 回车粘贴。 - -3、自动完成:command + ; ,呼出自动完成窗口,根据上下文提供内容选择项 -# 粘贴历史 -shift + command + h +但是还没有好好的介绍一下 Python 的用户环境,原因是自己一直没遇到要使用 `用户环境` 的使用场景,所以就一直懒得写。 -# 回放功能 -option + command + b +恰巧这两天,自己遇到了一个使用用户环境的体验可以完爆虚拟环境的案例,就拿出来分享一下。 -# 光标去哪了? -command + / +## 1. 我的使用背景 -# 在所有的窗口中搜索 -option + command + e -``` +公司有数以万计的服务器,为了对实现对访问记录进行集中管理以及出于安全考虑,每台服务器都有访问限制,必须使用公司的跳板机才能登陆。 -## 4.18.2 截图工具 +每个公司的员工在跳板机上都有自己的用户、 家目录,对于很多需要 root 权限的操作,是高度受限制的。 -1. Xnip:取色+丰富的标注功能+滚动截屏; -2. [Snip](https://snip.qq.com/):小巧轻量+滚动截屏(不能从App Store下载,亲测已经失效); -3. QQ:截屏+录屏+OCR文字识别; -4. Snipaste:贴屏功能; -5. ScreenShot PSD:psd 文件,每种元素都有单独的图层。 +比如我现在我要在跳板机上实现远程登陆大批量的机器进行一些维护工作,当然我这里使用的还是 Python 来实现,这个 Python 脚本里有一些依赖库(比如 之前介绍过的 paramiko 这个神器),在跳板机上中并没有安装。 -## 4.18.3 快捷操作 +![](http://image.iswbm.com/20200427180207.png) -``` -# 查看桌面,并不是返回桌面,再按一下就还原原来的窗口了 -fn + f11 # 通常情况下 -f11 # 在外接键盘下,请先设置 F1,F2 这些键为标准功能键,偏好设置->键盘 +做为普通用户的你,是没有权限安装第三方包的。 -# 最大化窗口与取消 -command + ctrl + f +![](http://image.iswbm.com/20200427180042.png) -# 最小化/隐藏窗口 -command + h +问题就来了,我如何才能在跳板机中使用 paramiko 这个包呢? -# 最小化除了当前active窗口之外的所有窗口 -comand + option + h +## 2. 为何不使用虚拟环境? -# 隐藏除当前窗口外的其他所有窗口 -command + opthon + h +既然不能对全局的 Python 环境进行更改,那我完全可以自己再创建一个环境,只要这个环境里事先装好 paramiko 这个包不就好了。 -# 关闭除访达外的其他程序 -command + q +因此,使用虚拟环境是一种解决方案,但它并不是一个完美的解决方案。 -# 在不同的程序间切换 -command + tab -command + shift + tab +**原因有以下几点**: -# 在一个程序的不同窗口切换 -command + ~ +1、 创建虚拟环境的过程,步骤较多,比较复杂。这里的复杂是相对于我后面要使用的用户环境而言。 -# 强制退出程序 -command + option + esc +2、 虚拟环境是包含一整个 Python 解释器,存在大量与系统重复的包,size比较大,并不轻便。 -# 删除光标处到行首 -command + backspace(删除键) +3、 使用 console 模式调试的话,进入很不方便 -# 新建窗口打开页面 -command + 鼠标左键 -``` +![](http://image.iswbm.com/20200427182334.png) -查询汇率 +就算你不使用 console 模式,你调用脚本的方式,也会很奇怪,你得这样 +```python +$ zabbix_env/bin/python demo.py ``` -command + space -输入 1港币或者1美元 -``` - -将网页图片保存到本地 - -``` -直接拖动图片 -如果不仍想让访达窗口保持在最前面,就按住 command 键 -``` - - - -## 4.18.4 系统设置 - -关闭仪表盘 - -``` -点击系统偏好设置 -> 调度中心 -> 仪表盘 -> 关闭 -``` - -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 - -brew update -``` - -如果要还原 +如果你不想使用这样,可以给这个脚本加个可执行权限,并在脚本的第一行指定你的解释器,省去了一点点麻烦,可即便如此,我仍然感觉很别扭。 ```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 +[wangbm@35ha02 ~]$ cat demo.py +#!/home/wangbm/zabbix_env/bin/python -git -C "$(brew --repo homebrew/cask)" remote set-url origin https://github.com/Homebrew/homebrew-cask.git - -brew update +import zabbix_api +[wangbm@35ha02 ~]$ +[wangbm@35ha02 ~]$ +[wangbm@35ha02 ~]$ chmod +x demo.py +[wangbm@35ha02 ~]$ +[wangbm@35ha02 ~]$ ./demo.py # 可以执行,没有报错 +[wangbm@35ha02 ~]$ ``` -安装docker -```shell -brew cask install docker -``` -## 4.18.6 访达使用技巧 +你可能会问我:为什么不使用 virtualenv + virtualenvwrapper ,这样可以使用 workon 进入虚拟环境。 -详细请看这篇文章([MacOS实用技巧之Finder(访达)的使用](https://www.jianshu.com/p/3666e6954e8a)),非常好的教程。 +原因是跳板机里的都是很古老的包,你看上面的 Python 还是 2.7.5 呢,所以你所说的那些工具通通没有。 -**跳转技巧** -```shell -# 快速打开访达:先打开搜索,再打开个人家目录 -打开搜索:command + option(alt) + space -关闭标签页:command + shift + h -# 返回父级文件夹 -command + ↑ +## 3. 用户环境原理 -# 进入文件夹 -command + ↓ +这里要介绍的这种方案(**用户环境**),可能很多人都没有使用过,甚至没有听过,它算是一个冷门但是非常好用的功能。 -# 前进 后退 -command + [ -comand + ] +操作之前 ,先简单介绍一下它。 -# 快速跳转至第一个文件或最后一个文件 -option + ↑ -option + ↓ +先提一个问题,Python 在查找导入包时,如果我们多个路径都有这个包,那 Python 如何确定应该从哪个路径进行导入呢? +答案是, 搜索导入路径是有优先级的,你可以通过 sys.path 进行查看。 -# 打开指定路径(前提访达得是激活状态的窗口) -# 注意在这里,可以使用 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) -> 回车,重命名 - - -# 选择 -点击 -> 拖拽 -如果想要取消选中,就 command + 点击 - - -# 打开最近使用过的文件夹 -comand + shift + f +使用起来,可以做到用户无感知,跟使用原生的全局环境并没有区别。 -# 显示/隐藏文件 -command + shift + . +## 4. 具体操作方法 -# 查看文件/夹 详情 -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 ``` - - -## 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 ~]$ ``` -练习的话,可以使用这两个网站: +有了这个思路,我就可以先在其他机器(前提自己必须拥有管理员权限 )上,创建一个用户环境,并且安装上 paramiko 这个包。 -练习单字:https://api.ihint.me/shuang/ +然后将这个用户环境,压缩拷贝至跳板机自己的家目录下的 `.local/lib` 目录下并解压。 -练习文章:https://api.ihint.me/zi/ +然后直接使用 python 进入 console 模式,现在已经可以直接使用 paramiko 这个包了。 -对应的 github:https://github.com/BlueSky-07/Shuang +![](http://image.iswbm.com/20200427185854.png) -## 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 上值得推荐的录屏软件](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.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](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 340d81e..cef8a2c 100644 --- a/source/c04/c04_18.rst +++ b/source/c04/c04_18.rst @@ -1,337 +1,197 @@ -4.18 详细的 Mac 使用指南 -======================== - -4.18.1 iTerm2 -------------- - -介绍一下快捷键 - -:: - - # 分窗口操作 - 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 `__\ :小巧轻量+滚动截屏(不能从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 键 - -4.18.4 系统设置 ---------------- - -关闭仪表盘 - -:: - - 点击系统偏好设置 -> 调度中心 -> 仪表盘 -> 关闭 - -finder的显示 +4.18 如何用好 Python的用户环境? +================================ |image0| -`防止电脑温度过高 `__ - -1. 不使用 Adobe Flash 播放器(改用 - HTML5播放器),因为其效能极低,会耗费大量的系统资源,导致电脑温度快速上升。 - -2. 不将电脑放在软的地方,如沙发,枕头等,可以买个散热支架。 - - .. figure:: http://image.python-online.cn/20190810162000.png - :alt: 来自Mac派 - - 来自Mac派 - - 3. 打开「活动监视器」(Alfred就可以打开),杀掉暂没用且cpu使用率最高的程序 +在之前写过一篇关于虚拟环境使用的文章 :\ `Python +虚拟环境使用指南 `__. - |image1| +但是还没有好好的介绍一下 Python 的用户环境,原因是自己一直没遇到要使用 +``用户环境`` 的使用场景,所以就一直懒得写。 - 4. MacBook Pro CPU 温度在 5、60℃ - 的时候,风扇会转到两三千转每分钟,只有 CPU 温度达到 70 - 多度或更高时,才会高速运转降温。但这时 Mac 已经很热了。 +恰巧这两天,自己遇到了一个使用用户环境的体验可以完爆虚拟环境的案例,就拿出来分享一下。 - 我们可以借用软件,手动让散热风扇全速运转,这样就能更快的散热。常用的软件有 - Macs Fan Control、TG Pro、smcFanControl,三个用下来我比较推荐 Macs - Fan Control。 - - Macs Fan Control 可以查看各 CPU - 核心的温度、主板、电池、内存温度等。可以分别调节两个风扇的转速,也可以设定条件自动调整转速。安装后就可以在系统状态栏看到电脑目前的温度和转速。 - - 风扇转得快了,散热自然也快了,但是风扇声音也更大了。建议只在非常烫(超过60℃?)的时候开启。 +1. 我的使用背景 +--------------- -4.18.5 brew 的使用 ------------------- +公司有数以万计的服务器,为了对实现对访问记录进行集中管理以及出于安全考虑,每台服务器都有访问限制,必须使用公司的跳板机才能登陆。 -设置国内源 +每个公司的员工在跳板机上都有自己的用户、 家目录,对于很多需要 root +权限的操作,是高度受限制的。 -.. code:: shell +比如我现在我要在跳板机上实现远程登陆大批量的机器进行一些维护工作,当然我这里使用的还是 +Python 来实现,这个 Python 脚本里有一些依赖库(比如 之前介绍过的 +paramiko 这个神器),在跳板机上中并没有安装。 - git -C "$(brew --repo)" remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git +|image1| - 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 +|image2| - brew update +问题就来了,我如何才能在跳板机中使用 paramiko 这个包呢? -如果要还原 +2. 为何不使用虚拟环境? +----------------------- -.. code:: shell +既然不能对全局的 Python +环境进行更改,那我完全可以自己再创建一个环境,只要这个环境里事先装好 +paramiko 这个包不就好了。 - 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 +1、 +创建虚拟环境的过程,步骤较多,比较复杂。这里的复杂是相对于我后面要使用的用户环境而言。 - brew update +2、 虚拟环境是包含一整个 Python +解释器,存在大量与系统重复的包,size比较大,并不轻便。 -安装docker +3、 使用 console 模式调试的话,进入很不方便 -.. 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 - - # 返回父级文件夹 - command + ↑ - - # 进入文件夹 - command + ↓ - - # 前进 后退 - command + [ - comand + ] - - # 快速跳转至第一个文件或最后一个文件 - option + ↑ - option + ↓ - + [wangbm@35ha02 ~]$ cat demo.py + #!/home/wangbm/zabbix_env/bin/python - # 打开指定路径(前提访达得是激活状态的窗口) - # 注意在这里,可以使用 tab 补全 - shift + command + g + import zabbix_api + [wangbm@35ha02 ~]$ + [wangbm@35ha02 ~]$ + [wangbm@35ha02 ~]$ chmod +x demo.py + [wangbm@35ha02 ~]$ + [wangbm@35ha02 ~]$ ./demo.py # 可以执行,没有报错 + [wangbm@35ha02 ~]$ -**操作文件与文件夹** +你可能会问我:为什么不使用 virtualenv + virtualenvwrapper ,这样可以使用 +workon 进入虚拟环境。 -.. code:: shell - - # enter - 重命名文件夹 - - # 选中所有文件,并将这些文件归档入一个新的文件夹 - 右键 -> 用所选项目新建的文件夹(Ctrl+Command+n) -> 回车,重命名 +原因是跳板机里的都是很古老的包,你看上面的 Python 还是 2.7.5 +呢,所以你所说的那些工具通通没有。 +3. 用户环境原理 +--------------- - # 选择 - 点击 -> 拖拽 - 如果想要取消选中,就 command + 点击 - +这里要介绍的这种方案(\ **用户环境**\ ),可能很多人都没有使用过,甚至没有听过,它算是一个冷门但是非常好用的功能。 - # 打开最近使用过的文件夹 - comand + shift + f +操作之前 ,先简单介绍一下它。 - # 显示/隐藏文件 - command + shift + . +先提一个问题,Python 在查找导入包时,如果我们多个路径都有这个包,那 +Python 如何确定应该从哪个路径进行导入呢? - # 查看文件/夹 详情 - command + i +答案是, 搜索导入路径是有优先级的,你可以通过 sys.path 进行查看。 - # mac 中拷贝和复制不一样 - command + c 拷贝 - command + d 复制(会多出一个副本),或者使用鼠标拖动,但是记住要按option - command + v 粘贴 - command + option + v 称动 ,或者使用鼠标拖动 +.. code:: python - # 可以设置搜索的范围 - command + 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 + n +可以看到路径 ``/home/wangbm/.local/lib/python2.7/site-packages`` +是优先于 ``/usr/lib64/python2.7/site-packages`` 路径的。 - # 关闭访达标签页,如果是最后一个标签页,则关闭访达 - command + w +这就是 **用户环境** +的原理,只要我们将包装在自己家目录下,就可以优先于全局环境中进行查找。 -**定制服务(复制文件路径)** +使用起来,可以做到用户无感知,跟使用原生的全局环境并没有区别。 -.. code:: shell +4. 具体操作方法 +--------------- - # 复制文件路径,有两种方法 - # 【第一种】:快捷键 - command + option + c - # 若你使用 alfred ,快捷键会冲突,解决方法:先右键,再 option,选择将 xx 拷贝为路径名称 +创建一个用户环境,并安装上你所需要的包,一条命令就能搞定,这可比虚拟环境简单方便多了。 - # 第二种:使用服务 - 参考 https://sspai.com/post/33422 +那么怎么操作呢? -**在 iTerm2中打开访达** +只要你在使用 pip 安装包时,加上 ``--user`` 参数,pip +就会将其安装在当前用户的 ``~/.local/lib/python2.x/site-packages`` +下,而其他用户的 python 则不会受影响。 .. code:: shell - # 在当前目录打开 - open . - - # 在指定目录打开 - open ~/Code - -4.18.7 使用小鹤双拼 -------------------- + $ pip install --user pkg -2018 款的 MBP 系统是 10.13.6 -,这个系统支持的双拼是自然码,若想使用小鹤双拼,可以使用如下命令 +这里要注意的是,不能使用这种方式,亲测它会将包装到全局环境下,具体原因我还没有深究。 .. code:: shell - defaults write com.apple.inputmethod.CoreChineseEngineFramework shuangpinLayout 4 + $ python -m pip install --user pkg -同样的,还有更多的方案,都可以使用命令来修改 +为了让你理解这个过程,我这里来举个例子,并且验证其是否可以做到用户隔离。 .. 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 - -练习的话,可以使用这两个网站: - -练习单字: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 - 的使用? `__ - --------------- - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190810161513.png -.. |image1| image:: http://image.python-online.cn/20190810162315.png + # 在全局环境中未安装 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/c04/c04_19.md b/source/c04/c04_19.md deleted file mode 100644 index 031f998..0000000 --- a/source/c04/c04_19.md +++ /dev/null @@ -1,687 +0,0 @@ -# 4.19 程序员编码必学:Vim - -我本人是 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.python-online.cn/image-20200320125724880.png) diff --git a/source/c04/c04_19.rst b/source/c04/c04_19.rst deleted file mode 100644 index 09a9255..0000000 --- a/source/c04/c04_19.rst +++ /dev/null @@ -1,725 +0,0 @@ -4.19 程序员编码必学:Vim -======================== - -我本人是 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 - --------------- - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! diff --git a/source/c04/c04_20.md b/source/c04/c04_20.md deleted file mode 100644 index 8edd8ca..0000000 --- a/source/c04/c04_20.md +++ /dev/null @@ -1,38 +0,0 @@ -# 4.20 学会使用谷歌搜索引擎 - -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 9e31f91..0000000 --- a/source/c04/c04_20.rst +++ /dev/null @@ -1,38 +0,0 @@ -4.20 学会使用谷歌搜索引擎 -========================= - -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_21.md b/source/c04/c04_21.md deleted file mode 100644 index 1e5e425..0000000 --- a/source/c04/c04_21.md +++ /dev/null @@ -1,362 +0,0 @@ -# 4.21 最全的 pip 使用指南,50% 你可能没用过 - -所有的 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.python-online.cn/image-20200320125724880.png) - - - diff --git a/source/c04/c04_21.rst b/source/c04/c04_21.rst deleted file mode 100644 index b0a08a7..0000000 --- a/source/c04/c04_21.rst +++ /dev/null @@ -1,377 +0,0 @@ -4.21 最全的 pip 使用指南,50% 你可能没用过 -========================================== - -所有的 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”,可获取高清无水印图片。 - -|image0| - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20191105200041.png - diff --git a/source/c04/c04_22.md b/source/c04/c04_22.md deleted file mode 100644 index e3ca425..0000000 --- a/source/c04/c04_22.md +++ /dev/null @@ -1,12 +0,0 @@ -# 4.22 用好 Chrome 必看 - -## 开启阅读模式 - -开启阅读模式: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 c791369..0000000 --- a/source/c04/c04_22.rst +++ /dev/null @@ -1,14 +0,0 @@ -4.22 用好 Chrome 必看 -===================== - -开启阅读模式 ------------- - -开启阅读模式:chrome://flags/#enable-reader-mode - -|image0| - -开启完成后,需要重启浏览器,你可以随便打开一篇博客,然后在地址栏右边会有一个阅读模式的按钮。 - -.. |image0| 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 577c09c..0000000 --- a/source/c04/c04_23.md +++ /dev/null @@ -1,54 +0,0 @@ -# 4.23 电脑使用技巧 - -## 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 63db2c5..0000000 --- a/source/c04/c04_23.rst +++ /dev/null @@ -1,61 +0,0 @@ -4.23 电脑使用技巧 -================= - -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`` -點擊,選擇你想要添加的輸入方案,比如這裏選擇 -五筆-拼音,再點中,然後選擇皮膚,最後就會觸發重新部署,配置生效。 - -|image0| - -.. |image0| 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 7ddfaf5..0000000 --- a/source/c04/c04_24.md +++ /dev/null @@ -1,169 +0,0 @@ -# 4.24 Python “用户环境”的一次完美应用 - -在之前写过一篇关于虚拟环境使用的文章 :[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.python-online.cn/image-20200320125724880.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 c0804c8..0000000 --- a/source/c04/c04_24.rst +++ /dev/null @@ -1,196 +0,0 @@ -4.24 Python “用户环境”的一次完美应用 -==================================== - -在之前写过一篇关于虚拟环境使用的文章 :\ `Python -虚拟环境使用指南 `__. - -但是还没有好好的介绍一下 Python 的用户环境,原因是自己一直没遇到要使用 -``用户环境`` 的使用场景,所以就一直懒得写。 - -恰巧这两天,自己遇到了一个使用用户环境的体验可以完爆虚拟环境的案例,就拿出来分享一下。 - -1. 我的使用背景 ---------------- - -公司有数以万计的服务器,为了对实现对访问记录进行集中管理以及出于安全考虑,每台服务器都有访问限制,必须使用公司的跳板机才能登陆。 - -每个公司的员工在跳板机上都有自己的用户、 家目录,对于很多需要 root -权限的操作,是高度受限制的。 - -比如我现在我要在跳板机上实现远程登陆大批量的机器进行一些维护工作,当然我这里使用的还是 -Python 来实现,这个 Python 脚本里有一些依赖库(比如 之前介绍过的 -paramiko 这个神器),在跳板机上中并没有安装。 - -|image0| - -做为普通用户的你,是没有权限安装第三方包的。 - -|image1| - -问题就来了,我如何才能在跳板机中使用 paramiko 这个包呢? - -2. 为何不使用虚拟环境? ------------------------ - -既然不能对全局的 Python -环境进行更改,那我完全可以自己再创建一个环境,只要这个环境里事先装好 -paramiko 这个包不就好了。 - -因此,使用虚拟环境是一种解决方案,但它并不是一个完美的解决方案。 - -**原因有以下几点**\ : - -1、 -创建虚拟环境的过程,步骤较多,比较复杂。这里的复杂是相对于我后面要使用的用户环境而言。 - -2、 虚拟环境是包含一整个 Python -解释器,存在大量与系统重复的包,size比较大,并不轻便。 - -3、 使用 console 模式调试的话,进入很不方便 - -|image2| - -就算你不使用 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 -这个包了。 - -|image3| - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.iswbm.com/20200427180207.png -.. |image1| image:: http://image.iswbm.com/20200427180042.png -.. |image2| image:: http://image.iswbm.com/20200427182334.png -.. |image3| image:: http://image.iswbm.com/20200427185854.png - diff --git a/source/c05/c05_01.md b/source/c05/c05_01.md index 5525f87..30bed32 100644 --- a/source/c05/c05_01.md +++ b/source/c05/c05_01.md @@ -1,5 +1,7 @@ # 5.1 图解九大经典排序算法 +![](http://image.iswbm.com/20200602135014.png) + --- @@ -17,7 +19,7 @@ **排序原理** :选取一个数组中的某个数,将整个数组分为两个子数组(小于此数的为一组,大于此数的为一组)。然后对分出的子数组,重复以上步骤。 **原理图解**: -![快速排序](http://image.python-online.cn/Fpj4DFN_YCtfmJwb_85QnsuIVLqk) +![快速排序](http://image.iswbm.com/Fpj4DFN_YCtfmJwb_85QnsuIVLqk) 代码实现: @@ -48,7 +50,7 @@ def quick_sort(array, reverse=False): **排序原理** :对相邻的元素进行两两比较,顺序相反则进行交换,这样,每一趟会将最小或最大的元素“浮”到顶端,最终达到完全有序。 **原理图解** : -![|冒泡排序|](http://image.python-online.cn/FvbrVECeq58hY8TptG4ilkL5Owcc) +![|冒泡排序|](http://image.iswbm.com/FvbrVECeq58hY8TptG4ilkL5Owcc) 代码实现: @@ -79,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 @@ -108,7 +110,7 @@ def select_sort(array, reverse=False): **排序原理** :每次将一个待排序的元素与已排序的元素进行逐一比较,直到找到合适的位置按大小插入。通俗地说,就类似我们打牌的时候,给牌进行排序。 **原理图解** : -![](http://image.python-online.cn/FmLrNuhfNcnYnLGoYJv-YbpBPV7n) +![](http://image.iswbm.com/FmLrNuhfNcnYnLGoYJv-YbpBPV7n) 代码实现: ```python @@ -152,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) **代码实现**: @@ -201,7 +203,7 @@ def shell_sort(array, reverse=False): 其实难点,在于如何在剩余堆里,找到最大数(或者最小数)。 **原理图解**: -![堆排序](http://image.python-online.cn/FgRFOfPhrL0yeUuGzly5309APCnD) +![堆排序](http://image.iswbm.com/FgRFOfPhrL0yeUuGzly5309APCnD) **代码实现**: @@ -281,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) **代码实现**: @@ -327,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 @@ -377,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 @@ -409,4 +411,4 @@ def radix_sort(array, reverse=False): -------------- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c05/c05_01.rst b/source/c05/c05_01.rst old mode 100755 new mode 100644 index 8f77543..210f381 --- a/source/c05/c05_01.rst +++ b/source/c05/c05_01.rst @@ -1,6 +1,8 @@ 5.1 图解九大经典排序算法 ======================== +|image0| + -------------- 排序算法,可谓是算法中的基础,在面试中,也是面试官最喜欢考察的。经常会让你手写一个选择排序、冒泡排序,如果能在最短的时间内顺畅地写出来,一定会让面试官眼前一亮。 @@ -78,7 +80,7 @@ **排序原理** :遍历n趟(n为数组的长度),每一趟都从「待排序」的元素中排出最小(或最大)的元素。 -**原理图解** : |image2| +**原理图解** : |image3| 代码实现: @@ -109,7 +111,7 @@ **排序原理** :每次将一个待排序的元素与已排序的元素进行逐一比较,直到找到合适的位置按大小插入。通俗地说,就类似我们打牌的时候,给牌进行排序。 -**原理图解** : |image3| +**原理图解** : |image4| 代码实现: @@ -158,7 +160,7 @@ **原理图解**\ : -|image4| |希尔排序| +|image5| |希尔排序| **代码实现**\ : @@ -288,7 +290,7 @@ **原理图解**\ : |分而治之思想| 重点其实是这个“治”的过程,如何实现将两个有序数组合并起来?思路大概是这样的。 -|image8| |合并两个有序数组| +|image9| |合并两个有序数组| **代码实现**\ : 这里只实现正序,感兴趣的同学,可以试倒序。 @@ -419,21 +421,20 @@ sgnificant digital),LSD 的排序方式由键值的最右边开始,而 MSD -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |快速排序| image:: http://image.python-online.cn/Fpj4DFN_YCtfmJwb_85QnsuIVLqk -.. |\|冒泡排序\|| image:: http://image.python-online.cn/FvbrVECeq58hY8TptG4ilkL5Owcc -.. |image2| image:: http://image.python-online.cn/FmZ_24t62gF32Dg3AgtZe-U5OuLY -.. |image3| image:: http://image.python-online.cn/FmLrNuhfNcnYnLGoYJv-YbpBPV7n -.. |image4| 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 -.. |image8| 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 +|image13| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |快速排序| 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.md b/source/c05/c05_02.md index fe6f528..5c8b536 100644 --- a/source/c05/c05_02.md +++ b/source/c05/c05_02.md @@ -1,5 +1,7 @@ # 5.2 递归算法:走楼梯会思考的题 +![](http://image.iswbm.com/20200602135014.png) + 今天来看一道有点意思的题目,有点意思的意思呢,不是说难,而是题目一想好像很难,但是如果找对了解决的思路,就能迎刃而解了。 问题是: @@ -112,4 +114,4 @@ calc_step_loop(40) --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](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 index c5abf2d..ae496fc --- a/source/c05/c05_02.rst +++ b/source/c05/c05_02.rst @@ -1,6 +1,8 @@ 5.2 递归算法:走楼梯会思考的题 ============================== +|image0| + 今天来看一道有点意思的题目,有点意思的意思呢,不是说难,而是题目一想好像很难,但是如果找对了解决的思路,就能迎刃而解了。 问题是: >假如这里有 n 个台阶,你可以选择每次完成一个台阶 或者 @@ -120,7 +122,8 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c05/c05_03.md b/source/c05/c05_03.md index eb3dd2e..798dad7 100644 --- a/source/c05/c05_03.md +++ b/source/c05/c05_03.md @@ -1,5 +1,7 @@ # 5.3 哈希算法:安全方面的算法应用 +![](http://image.iswbm.com/20200602135014.png) + 昨天准备登陆某网站的时候,在尝试了几次常用密码失败后,我点击了“忘记密码”,娴熟地填入手机号码,随即就收到了一条来自陌生号码的短信,里面包含着一个六个数字串,将这个数字串填入网站提供的输入框,就进入了密码重置流程。 这里有一点细节,值得我们注意,为什么我忘记了密码,你不直接把这个密码返回给我?而是给我一个不相关的口令来重置密码? @@ -102,7 +104,7 @@ echo -n hello | md5sum 彩虹表确实像它的名字一样美好,至少黑客眼里是这样。下表是7位以内密码在不同字符集下构造出的彩虹表的情况,彩虹表中哈希链的长度和个数随着字符集的增长而增长,彩虹表的大小和生成时间也随之成倍增加。7位数字组合在彩虹表面前简直就是秒破,即使最复杂的7位密码不到一个小时就能破解,如果采用普通的暴力攻击,破解时间可能需要三周。 -![](http://image.python-online.cn/20190112181126.png) +![](http://image.iswbm.com/20190112181126.png) @@ -144,5 +146,5 @@ print(calc(365, 23)) # 0.500001752183 --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c05/c05_03.rst b/source/c05/c05_03.rst old mode 100755 new mode 100644 index 823304f..6b47675 --- a/source/c05/c05_03.rst +++ b/source/c05/c05_03.rst @@ -1,6 +1,8 @@ 5.3 哈希算法:安全方面的算法应用 ================================ +|image0| + 昨天准备登陆某网站的时候,在尝试了几次常用密码失败后,我点击了“忘记密码”,娴熟地填入手机号码,随即就收到了一条来自陌生号码的短信,里面包含着一个六个数字串,将这个数字串填入网站提供的输入框,就进入了密码重置流程。 这里有一点细节,值得我们注意,为什么我忘记了密码,你不直接把这个密码返回给我?而是给我一个不相关的口令来重置密码? @@ -121,7 +123,7 @@ B 可以读取和更改用户 A 的信息,这无疑带来了很大的安全隐 彩虹表确实像它的名字一样美好,至少黑客眼里是这样。下表是7位以内密码在不同字符集下构造出的彩虹表的情况,彩虹表中哈希链的长度和个数随着字符集的增长而增长,彩虹表的大小和生成时间也随之成倍增加。7位数字组合在彩虹表面前简直就是秒破,即使最复杂的7位密码不到一个小时就能破解,如果采用普通的暴力攻击,破解时间可能需要三周。 -|image0| +|image1| 32位的16进制,我们可以计算一下,可以表示多少原值呢? @@ -159,10 +161,9 @@ B 可以读取和更改用户 A 的信息,这无疑带来了很大的安全隐 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image2| -.. |image0| image:: http://image.python-online.cn/20190112181126.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 2176810..6e403b1 100644 --- a/source/c06/c06_01.md +++ b/source/c06/c06_01.md @@ -1,5 +1,7 @@ # 6.1 一张图带你入门matplotlib +![](http://image.iswbm.com/20200602135014.png) + --- @@ -108,7 +110,7 @@ plt.show() 以上的注释,可以说是很直白啦。一张图表该有的东西都有了,不花哨,但实用。 看看我们的代码输出的图表是啥样的。 -![](http://image.python-online.cn/20190511164650.png) +![](http://image.iswbm.com/20190511164650.png) ---- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.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 8929cd0..121250c --- a/source/c06/c06_01.rst +++ b/source/c06/c06_01.rst @@ -1,6 +1,8 @@ 6.1 一张图带你入门matplotlib ============================ +|image0| + -------------- Python在数据分析领域有一个很完备的生态系统,在数据处理方面有 @@ -63,7 +65,7 @@ pylab 已经不再推荐使用了。所以你如果是一个初学者,在网 ------------------ 前两天的官网上闲逛的时候,发现了一张图,可以说把一个图表该有的元素都很好的展示出来了。从这张图入手,一点一点地实现这里面的每个元素,我们也算是入门了matplotlib了。 -|image0| +|image1| 这里面的大部分元素,我相信会点英文的你都能很轻松的理解。 @@ -127,15 +129,14 @@ ticks(由Locator对象定义),还有ticklabel(由Formatter对象定义 以上的注释,可以说是很直白啦。一张图表该有的东西都有了,不花哨,但实用。 看看我们的代码输出的图表是啥样的。 -|image1| +|image2| -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image3| -.. |image0| image:: https://i.loli.net/2018/08/12/5b6ff3716fdc0.png -.. |image1| image:: http://image.python-online.cn/20190511164650.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: https://i.loli.net/2018/08/12/5b6ff3716fdc0.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 6606e83..864efae 100644 --- a/source/c06/c06_02.md +++ b/source/c06/c06_02.md @@ -1,5 +1,7 @@ # 6.2 详解六种可视化图表 +![](http://image.iswbm.com/20200602135014.png) + --- @@ -33,7 +35,7 @@ plt.show() ``` show image -![](http://image.python-online.cn/20190511164738.png) +![](http://image.iswbm.com/20190511164738.png) ## 02. 散点图 @@ -52,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. 直方图 @@ -93,7 +95,7 @@ plt.show() show image -![](http://image.python-online.cn/20190511164802.png) +![](http://image.iswbm.com/20190511164802.png) ## 04. 柱状图 @@ -126,7 +128,7 @@ plt.show() ``` show image -![](http://image.python-online.cn/20190511164814.png) +![](http://image.iswbm.com/20190511164814.png) ### 4.2 叠加柱状图 ```python @@ -152,7 +154,7 @@ plt.show() ``` show image -![](http://image.python-online.cn/20190511164825.png) +![](http://image.iswbm.com/20190511164825.png) ## 05. 饼图 @@ -178,7 +180,7 @@ plt.show() ``` show image -![](http://image.python-online.cn/20190511164835.png) +![](http://image.iswbm.com/20190511164835.png) ### 5.2 嵌套饼图 @@ -212,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 极轴饼图 @@ -243,7 +245,7 @@ plt.show() ``` show image -![](http://image.python-online.cn/20190511164852.png) +![](http://image.iswbm.com/20190511164852.png) ## 06. 三维图 @@ -270,7 +272,7 @@ plt.show() ``` show image -![](http://image.python-online.cn/20190511164900.png) +![](http://image.iswbm.com/20190511164900.png) ### 6.2 绘制三维平面图 @@ -293,8 +295,8 @@ 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) --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c06/c06_02.rst b/source/c06/c06_02.rst old mode 100755 new mode 100644 index cc807e1..11dbcf0 --- a/source/c06/c06_02.rst +++ b/source/c06/c06_02.rst @@ -1,6 +1,8 @@ 6.2 详解六种可视化图表 ====================== +|image0| + -------------- 可视化图表,有相当多种,但常见的也就下面几种,其他比较复杂一点,大都也是基于如下几种进行组合,变换出来的。对于初学者来说,很容易被这官网上众多的图表类型给吓着了,由于种类太多,几种图表的绘制方法很有可能会混淆起来。 @@ -36,7 +38,7 @@ show image -|image0| +|image1| 02. 散点图 ---------- @@ -56,7 +58,7 @@ show image plt.plot(x, x, 'r--', x, x**2, 'bs', x, x**3, 'g^') plt.show() -show image |image1| +show image |image2| 03. 直方图 ---------- @@ -98,7 +100,7 @@ show image |image1| show image -|image2| +|image3| 04. 柱状图 ---------- @@ -134,7 +136,7 @@ show image show image -|image3| +|image4| 4.2 叠加柱状图 ~~~~~~~~~~~~~~ @@ -161,7 +163,7 @@ show image plt.grid(True) plt.show() -show image |image4| +show image |image5| 05. 饼图 -------- @@ -187,7 +189,7 @@ show image |image4| plt.show() -show image |image5| +show image |image6| 5.2 嵌套饼图 ~~~~~~~~~~~~ @@ -221,7 +223,7 @@ show image |image5| plt.axis('equal') plt.show() -show image |image6| +show image |image7| 5.3 极轴饼图 ~~~~~~~~~~~~ @@ -253,7 +255,7 @@ show image |image6| plt.show() -show image |image7| +show image |image8| 06. 三维图 ---------- @@ -281,7 +283,7 @@ show image |image7| ax.set_xlabel('X') plt.show() -show image |image8| +show image |image9| 6.2 绘制三维平面图 ~~~~~~~~~~~~~~~~~~ @@ -305,23 +307,22 @@ show image |image8| plt.show() -show image |image9| +show image |image10| -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190511164738.png -.. |image1| image:: http://image.python-online.cn/20190511164753.png -.. |image2| image:: http://image.python-online.cn/20190511164802.png -.. |image3| image:: http://image.python-online.cn/20190511164814.png -.. |image4| image:: http://image.python-online.cn/20190511164825.png -.. |image5| image:: http://image.python-online.cn/20190511164835.png -.. |image6| image:: http://image.python-online.cn/20190511164843.png -.. |image7| image:: http://image.python-online.cn/20190511164852.png -.. |image8| image:: http://image.python-online.cn/20190511164900.png -.. |image9| image:: http://image.python-online.cn/20190511164915.png +|image11| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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 144f5f4..bd7e68c 100644 --- a/source/c06/c06_03.md +++ b/source/c06/c06_03.md @@ -1,5 +1,7 @@ # 6.3 如何绘制正余弦函数图象 +![](http://image.iswbm.com/20200602135014.png) + --- @@ -23,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 设置基本元素 @@ -63,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 移动轴线 还记得我们在初高中学习的三角函数图象,可不是这样,它应该是有四个象限的。而这里却是一个四四方方的图表。 @@ -87,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') @@ -95,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命令注解一些我们感兴趣的点。 @@ -135,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 完整代码 @@ -204,4 +206,4 @@ plt.show() --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c06/c06_03.rst b/source/c06/c06_03.rst old mode 100755 new mode 100644 index 1dc0793..09ce0dc --- a/source/c06/c06_03.rst +++ b/source/c06/c06_03.rst @@ -1,6 +1,8 @@ 6.3 如何绘制正余弦函数图象 ========================== +|image0| + -------------- 今天打算通过绘制正弦和余弦函数,从默认的设置开始,一步一步地调整改进,让它变得好看,变成我们初高中学习过的图象那样。通过这个过程来学习如何进行对图表的一些元素的进行调整。 @@ -24,7 +26,7 @@ matplotlib有一套允许定制各种属性的默认设置。你可以几乎控 show() -show image |image0| +show image |image1| 6.3.2 设置基本元素 ------------------ @@ -65,7 +67,7 @@ show image |image0| plt.show() -show image |image1| ## 6.3.3 移动轴线 +show image |image2| ## 6.3.3 移动轴线 还记得我们在初高中学习的三角函数图象,可不是这样,它应该是有四个象限的。而这里却是一个四四方方的图表。 @@ -89,14 +91,14 @@ show image |image1| ## 6.3.3 移动轴线 ax.spines['left'].set_position(('data',0)) 关于\ ``set_position()``\ 这个函数中的data是啥意思?我查了下官网。解释如下 -|image2| 然后最后发现,上面的写法可以用一定更简洁的方式设置,是等价的。 +|image3| 然后最后发现,上面的写法可以用一定更简洁的方式设置,是等价的。 .. code:: python ax.spines['bottom'].set_position('zero') ax.spines['left'].set_position('zero') -show image |image3| ## 6.3.4 添加注释 +show image |image4| ## 6.3.4 添加注释 现在的图形部分已经成型,接下让我们现在使用annotate命令注解一些我们感兴趣的点。 @@ -134,7 +136,7 @@ show image |image3| ## 6.3.4 添加注释 第六个参数,\ ``fontsize``\ ,注释大小; 第七个参数,\ ``arrowprops``\ ,对箭头的类型的一些设置。 -show image |image4| +show image |image5| 6.3.5 完整代码 -------------- @@ -205,14 +207,13 @@ show image |image4| -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image6| -.. |image0| image:: http://image.python-online.cn/20190511164936.png -.. |image1| image:: http://image.python-online.cn/20190511164949.png -.. |image2| image:: http://image.python-online.cn/20190511165003.png -.. |image3| image:: http://image.python-online.cn/20190511165013.png -.. |image4| image:: http://image.python-online.cn/20190511165020.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 4b437ea..90c027a 100644 --- a/source/c06/c06_04.md +++ b/source/c06/c06_04.md @@ -1,5 +1,7 @@ # 6.4 子图与子区 难点突破 +![](http://image.iswbm.com/20200602135014.png) + --- 在 matplotlib 中有两个非常重要,而且很容易混淆的概念,一个是 subplot,一个是axes。这两个概念将贯穿整个 matplotlib 学习历程。在往后进行深度研究之前呢,务必要先弄懂这两个概念,否则将后面的绘制代码,我相信你一定会一头雾水的。 @@ -17,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) 他有好几种写法,这里写我在官网学到的几个方法。 @@ -57,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) @@ -66,7 +68,7 @@ plt.show() 子图(axes),和子区(subplot)非常相似,一个子图可能是由一个或多个子区域构成的。它比子区更加灵活。 它可以是这样 -![](http://image.python-online.cn/20190511165152.png) +![](http://image.iswbm.com/20190511165152.png) 要实现如上这个效果。常用的有两种方法。 @@ -93,7 +95,7 @@ ax5 = plt.subplot(gs[-1, -2]) 这个比较规则的划分我们举个例子看看。 -![](http://image.python-online.cn/20190511165159.png) +![](http://image.iswbm.com/20190511165159.png) 代码如下: ```python @@ -126,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` 是指,离底边的距离。 @@ -143,7 +145,7 @@ plt.show() 同样地,这个我们也来看一个例子。 这个图的亮点,在于中间,多了两个子图,就像往图中贴上了两个插画一样。 -![](http://image.python-online.cn/20190511165229.png) +![](http://image.iswbm.com/20190511165229.png) 那么这个如何实现呢? ```python @@ -188,4 +190,4 @@ plt.show() --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c06/c06_04.rst b/source/c06/c06_04.rst old mode 100755 new mode 100644 index a21e883..3b08843 --- a/source/c06/c06_04.rst +++ b/source/c06/c06_04.rst @@ -1,6 +1,8 @@ 6.4 子图与子区 难点突破 ======================= +|image0| + -------------- 在 matplotlib 中有两个非常重要,而且很容易混淆的概念,一个是 @@ -25,7 +27,7 @@ subplot,一个是axes。这两个概念将贯穿整个 matplotlib 是将当前图像(figure)按 2 行 2 列的布局进行分割,然后取索引为 1 的子图。注意 matlibplot 是完全借鉴了 MATLAB 的思想,所以的起始索引为 -1,不像 Python 的起始索引为 0。 |image0| +1,不像 Python 的起始索引为 0。 |image1| 他有好几种写法,这里写我在官网学到的几个方法。 @@ -70,14 +72,14 @@ subplot,一个是axes。这两个概念将贯穿整个 matplotlib plt.plot(t2, np.cos(2*np.pi*t2), 'r--') plt.show() -|image1| +|image2| 6.4.2 子图 ---------- 子图(axes),和子区(subplot)非常相似,一个子图可能是由一个或多个子区域构成的。它比子区更加灵活。 -它可以是这样 |image2| +它可以是这样 |image3| 要实现如上这个效果。常用的有两种方法。 @@ -105,7 +107,7 @@ subplot,一个是axes。这两个概念将贯穿整个 matplotlib 这个比较规则的划分我们举个例子看看。 -|image3| +|image4| 代码如下: @@ -139,11 +141,11 @@ subplot,一个是axes。这两个概念将贯穿整个 matplotlib 为什么说,子图的灵活性更高呢,因为它允许把图片放置到图像(figure)中的任何地方(如下图)。所以如果我们想要在一个大图片中嵌套一个小点的图片,我们通过子图(axes)来完成它。 -|image4| +|image5| 图中的 axes 是如何实现的,刚开始我也有点懵逼,在查阅了官方文档后,我才明白。 -|image5| +|image6| ``left`` 是指,离左边界的距离。 ``bottom`` 是指,离底边的距离。 ``width`` 是指,子图的宽度。 ``height`` 是指,子图的高度。 @@ -153,7 +155,7 @@ subplot,一个是axes。这两个概念将贯穿整个 matplotlib 同样地,这个我们也来看一个例子。 这个图的亮点,在于中间,多了两个子图,就像往图中贴上了两个插画一样。 -|image6| +|image7| 那么这个如何实现呢? @@ -199,16 +201,15 @@ subplot,一个是axes。这两个概念将贯穿整个 matplotlib -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190511165103.png -.. |image1| image:: http://image.python-online.cn/20190511165132.png -.. |image2| image:: http://image.python-online.cn/20190511165152.png -.. |image3| image:: http://image.python-online.cn/20190511165159.png -.. |image4| image:: http://image.python-online.cn/20190511165211.png -.. |image5| image:: http://image.python-online.cn/20190511165221.png -.. |image6| image:: http://image.python-online.cn/20190511165229.png +|image8| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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.md b/source/c06/c06_05.md index a1b502e..af420e5 100644 --- a/source/c06/c06_05.md +++ b/source/c06/c06_05.md @@ -1,5 +1,7 @@ # 6.5 绘制酷炫的gif动态图 +![](http://image.iswbm.com/20200602135014.png) + --- ## 6.5.1 准备工作 @@ -126,4 +128,4 @@ Image(url='./ming.gif') ![](https://i.loli.net/2018/12/25/5c2226078799b.gif) --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](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 index f1883dd..719cb35 --- a/source/c06/c06_05.rst +++ b/source/c06/c06_05.rst @@ -1,6 +1,8 @@ 6.5 绘制酷炫的gif动态图 ======================= +|image0| + -------------- 6.5.1 准备工作 @@ -137,14 +139,13 @@ matplotlib 给我们提供了一个函数,\ ``animation.FuncAnimation`` # 将 gif 图展示在页面上 Image(url='./ming.gif') -绘制出来的结果如下: |image0| +绘制出来的结果如下: |image1| -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image2| -.. |image0| image:: https://i.loli.net/2018/12/25/5c2226078799b.gif +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: https://i.loli.net/2018/12/25/5c2226078799b.gif +.. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c06/c06_06.md b/source/c06/c06_06.md index 8e8a594..cbba0e1 100644 --- a/source/c06/c06_06.md +++ b/source/c06/c06_06.md @@ -1,5 +1,7 @@ # 6.6 自动生成图像视频 +![](http://image.iswbm.com/20200602135014.png) + ## 6.6.1 准备工作 如果你和我一样使用的是 Jupyter 这个工具的话,在绘制之前,需要安装一个工具,就是 `ffmpeg`。 @@ -8,7 +10,7 @@ 由于我使用的是 Anaconda ,我需要将其安装到我的环境中。首先打开我们的命令行(注意不是一般的CMD),使用windows 的查找入口: -![](http://image.python-online.cn/20190511165315.png) +![](http://image.iswbm.com/20190511165315.png) 然后执行如下命令安装 ``` @@ -121,4 +123,4 @@ HTML(ani.to_html5_video()) 我将这个小短片下载并上传至后台,你可以点击 [公众号原文](https://mp.weixin.qq.com/s/BU4DtJQxtxwEMhGZE8t3CQ) 感受一下。 --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c06/c06_06.rst b/source/c06/c06_06.rst old mode 100755 new mode 100644 index 16a290a..2e97d4a --- a/source/c06/c06_06.rst +++ b/source/c06/c06_06.rst @@ -1,6 +1,8 @@ 6.6 自动生成图像视频 ==================== +|image0| + 6.6.1 准备工作 -------------- @@ -14,7 +16,7 @@ ,我需要将其安装到我的环境中。首先打开我们的命令行(注意不是一般的CMD),使用windows 的查找入口: -|image0| +|image1| 然后执行如下命令安装 @@ -137,10 +139,9 @@ Jupyter NoteBook 里观察整个变化的过程。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image2| -.. |image0| image:: http://image.python-online.cn/20190511165315.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20190511165315.png +.. |image2| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/C07_08.md b/source/c07/C07_08.md index 6e96207..b52cd23 100644 --- a/source/c07/C07_08.md +++ b/source/c07/C07_08.md @@ -1,5 +1,7 @@ # 7.8 Keepalived 部署文档 +![](http://image.iswbm.com/20200602135014.png) + 参考文档:[keepalived搭建zabbix server双机高可用](https://segmentfault.com/a/1190000008684320) ```bash @@ -175,4 +177,4 @@ fi --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c07/c07_01.md b/source/c07/c07_01.md index e5a82cd..8abe598 100644 --- a/source/c07/c07_01.md +++ b/source/c07/c07_01.md @@ -1,1787 +1,1789 @@ -# 7.1 Linux 命令行的艺术 - ---- - -## 一、目录/文件 - -### 1.1 目录文件日常操作 -``` -. -> 当前目录 -.. -> 上一级目录 -.file -> 隐藏文件 -.dir -> 文件夹 -``` -【ls】 查看指定目录文件 -`ls`命令是最常用的linux命令,要配合着选项使用。 - -``` -常用命令 - -ls -lh # 方便查看文件大小 -ls -AsSh -``` -【pwd】:查看当前目录 -``` -pwd等价于/bin/pwd -L,显示当前绝对路径,如果是链接,则显示链接路径 -pwd -P等价于/bin/pwd -P,显示实际路径,而非链接路径 - -如果文件夹被临时删除,pwd还是显示未删除文件夹的那个路径,这时候可以使用/bin/pwd,就会提示当前路径不存在 -``` -【cd】:切换目录 - -``` -cd - # 上一目录 -cd ~ # 家目录 -cd # 家目录 -cd .. # 上一目录 -cd !$ # 将上命令的参数做为cd 参数 -cd ~zabbix # 进入zabbix用户的主目录 -``` - -【touch】:新建文件 -``` -1. 创建新文件同时可以指定一些时间参数 -$ touch newfile - -2. 一次性创建多个文件 -$ touch {1..10}.txt -``` -可以对已有文件修改时间戳(ll显示的时间) -``` -【1】 -touch -d [[CC]YY]MMDD text -touch -t [CC[YY]MMDDhhmm[.SS] text -CC为年数中的前两位,即”世纪数”;YY为年数的后两位,即某世纪中的年数.如果不给出CC的值 -touch -d 20171004 text -touch -t 201710041330.30 text - -【2】 --r:以另一文件为基准更新时间戳 -以file1的时间戳为基准,将file2的改成一样的 -touch -r file1 file2 - -【3】其他不常用参数 --a 或--time=atime或--time=access或--time=use  只更改存取时间。 --c 或--no-create  不建立任何文档。 --f  此参数将忽略不予处理,仅负责解决BSD版本touch指令的兼容性问题。 --m 或--time=mtime或--time=modify  只更改变动时间。 -``` -【mkdir】:新建文件夹 -``` -# 创建一个或多个的同级目录 -mkdir adir bdir - -# 递归创建目录 -# 当前目录下并没有cdir这个文件夹,如果使用单纯mkdir cdir/ddir会报错。 -# 应该使用 -mkdir -p cdir/ddir - -# 指定权限 -mkdir -m 777 dir_name - -# 显示创建信息(成功或失败) -mkdir -v dir_name - -# 使用!$快速进入新建文件夹 -mkdir ming -cd !$ -``` - -【rm | rmdir】 - -``` -# 只能删除空文件夹 -rmdir dirname - -# 删除当前文件夹下所有文件并不提示 -rm -f * - -# 递归删除dirname下所有文件 -rm -r dirname - -# 上面二者结合,递归删除dirname下所有文件并不提示 -rm -rf dirname -``` - -【cp】:复制文件或目录 -``` -# 递归复制old_dir目录下所有文件和文件夹到new_dir文件夹下 -cp -r old_dir new_dir -``` - - -> 注意:默认cp拷贝文件后会使用预设权限,即其他人没有更改的权限,需要使用-p或 -> 者-a文件所有的所有特性都一起复制过来 (拉取源站数据缓存,不更改文件最后修改 -> 时间) - -【mv】:移动或重命名 -``` -# 移动/tmp/test/sample.txt文件到当前目录下 -mv /tmp/test/sample.txt ./ -``` -【cat 】查看文件 - -**作用**:文本文件查看和连接工具,用于查看文本文件的内容。 -**命令格式**:cat file 经常和more、head、tail、less以及管道命令结合使用,如:cat file | more、cat file | head等。 - -三个功能 -``` -1.一次显示整个文件:cat filename -2.从键盘创建一个文件:cat > filename 创建文件,输入内容,Ctrl+d 结束 -3.将几个文件合并为一个文件:cat file1 file2 > file -``` - -命令参数 -``` --A, --show-all 等价于 -vET --b, --number-nonblank 对非空输出行编号 --e 等价于 -vE --E, --show-ends 在每行结束处显示 $ --n, --number 对输出的所有行编号,由1开始对所有输出的行数编号 --s, --squeeze-blank 有连续两行以上的空白行,就代换为一行的空白行 --t 与 -vT 等价 --T, --show-tabs 将跳格字符显示为 ^I --u (被忽略) --v, --show-nonprinting 使用 ^ 和 M- 引用,除了 LFD 和 TAB 之外 -``` -【tac】反向查看 -``` -cat是第一行到最后一行,tac是最后一行到第一行 -``` -【head | tail】 - -``` -# 显示前10行 -head xs.txt 等价于 cat xs.txt|head 等价于 cat xs.txt|head -n 10 - - -# 显示最后10行 -tail xs.txt 等价于 tail -n 10 xs.txt - - -# 显示除末尾10行外 -head -n -10 xs.txt -``` - -【more | less】 - -更具体命令可以参考:[Linux中more和less命令用法](http://www.cnblogs.com/aijianshi/p/5750911.html) -``` -【more】 -# 回车就往下一行显示,空白键就往下一页显示 -# 按 b 键就会往回一页显示,按 q 就会退出 -more xs.txt - -# 查找比较鸡肋,并从该处前两行开始显示输出 -more +/查找内容 xs.txt - -【less】 -# 和more几乎一样的功能,但是less更灵活(可以使用任何vim的移动命令,还有标记功能很使用),比如查找命令,直接像vim一样输入/即可 -less xs.txt - -ma : 使用 a 标记文本的当前位置 -'a : 导航到标记 a 处 - -F :实现和tail -f的功能,实时输出内容,tail +F xs.txt -``` - - - -**小练习** - -选取xs.txt的10-20行 - -``` -head -n 20 xs.txt|tail - -# 更快捷的方法 -sed -n '10,20p' xs.txt -``` - - - -### 1.2 文件处理 - -【wc】 - -统计文件信息 -``` -# 统计所有信息(行数、单词数、字符数) -$ wc /etc/passwd - -# 行数 -$ wc -l /etc/passwd - -# 单词数 # 中文无法统计 -$ wc -w /etc/passwd - -# 字节数 -$ wc -c /etc/passwd - -# 字符数 -$ wc -m /etc/passwd - -# 最长行字节数 -$ wc -L /etc/passwd -``` - -【sort】 - -``` -$ cat /etc/passwd | sort -$ cat /etc/passwd | sort -r - -# 以:为分隔符,对第三列排序,所得的结果,再通过cut以:为分隔符取第三列 -$ cat /etc/passwd | sort -t ":" -k 3 |cut -d ":" -f 3 -``` - -【uniq】 - -``` -# 显示每行重复频率 -uniq -c 文件名 - -# 只显示有重复的行 -uniq -d 文件名 -``` -### 1.3 文字处理 - -【tr】 - -`tr`可以删除或者去重某文本信息中的某些文字。还可以进行替换操作 -``` -# 删除hello,里面所有l,o字符 -$ echo 'hello' | tr -d 'lo' - -# 去重hello里的l -$ echo 'hello' | tr -s 'l' - -# hello里,l换成a,e换成b -$ echo 'hello' | tr 'le' 'ab' -``` - -【col】 - -将tab转换为等数量的空格,或者反转 -``` --x tab转空格 --h 空格转tab(默认) -``` - -``` -# 查看 /etc/protocols 中的不可见字符,可以看到很多 ^I ,这其实就是 Tab 转义成可见字符的符号 -$ cat -A /etc/protocols - -# 使用 col -x 将 /etc/protocols 中的 Tab 转换为空格,然后再使用 cat 查看,你发现 ^I 不见了 -$ cat /etc/protocols | col -x | cat -A -``` - -【sed】 - -文件处理工具。 -使得不需要打开文件就可以对文件进行操作(删除,替换,选取,新增)。以行为单位进行处理。 - -常用选项 - -``` --n∶经过处理后的结果显示出来。不影响真实文件。 --e:直接在指令列模式上进行 sed 的动作编辑;(没明白) --f∶直接将 sed 的动作写在一个档案内, -f filename 则可以执行 filename 内的sed 动作; --i∶直接修改读取的档案内容,而不是由屏幕输出。 -``` -常用命令: -``` -a∶新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~ -c∶取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行! -d∶删除,因为是删除啊,所以 d 后面通常不接任何咚咚; -i∶插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行); -p∶列印,亦即将某个选择的资料印出。通常 p 会与参数 sed -n -s∶取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!例如 1,20s/old/new/g 就是啦! -``` - -删除某行 -``` - sed '1d' somefile #删除第一行 - sed '$d' somefile #删除最后一行 - sed '1,2d' somefile #删除第一行到第二行 - sed '2,$d' somefile #删除第二行到最后一行 - - # 以上,只是输出,源文件并不会替换,若要对源文件进行修改,需加 -i - sed -i '1,2d' somefile -``` -删除指定行的上一行或下一行 - -```shell -删除指定文件的上一行 -sed -i -e :a -e '$!N;s/.*n(.*directory)/1/;ta' -e 'P;D' server.xml -删除指定文件的下一行 -sed -i '/pattern="%/{n;d}' server.xml -``` - -显示某行 - -``` - sed -n '1p' somefile #显示第一行 - sed -n '$p' somefile #显示最后一行 - sed -n '1,2p' somefile #显示第一行到第二行 - sed -n '2,$p' somefile #显示第二行到最后一行 -``` -使用模式进行查询 -``` - sed -n '/ruby/p' somefile #查询包括关键字ruby所在所有行 - sed -n '/\$/p' somefile #查询包括关键字$所在所有行,使用反斜线\屏蔽特殊含义 -``` -插入行(a 表示 append 追加,也可以使用 i,表示 insert,如果你要在第一行插入,就不得不使用 i 了) -``` -sed '1a drink tea' somefile #第一行后增加字符串"drink tea" -sed '1,3a drink tea' somefile #第一行到第三行后增加字符串"drink tea" -sed '1a drink tea\nor coffee' somefile #第一行后增加多行,使用换行符\n -``` -在某一行后面加入一行(示例为第四行) - -``` -sed -i 'N;4addpdf' a.txt -sed -i 'N;4ieepdf' a.txt -``` - - - -替换行 - -``` -sed '1c Hi' somefile #第一行代替为Hi -sed '1,2c Hi' somefile #第一行到第二行代替为Hi -``` -替换行中部分数据 -``` -sed 's/ruby/bird/g somefile' #替换ruby为bird,记住这个并不会更改源文件,只是输出 -sed 's/ruby//g' somefile #删除ruby -``` - -在某行的前一行或后一行添加内容,其中 `\` 可省略,但为了可读性,所以我一般不省略 - -```shell -# 匹配行前加 -sed -i '/2222222222/a\3333333333' test.txt - -# 匹配行前后 -sed -i '/2222222222/i\3333333333' test.txt -``` - - - -【注意】:以上对源文件都不做修改,若要修改,要加上`-i` - -替换换行符 - -```shell -# 将换行符换成逗号 -$ cat a.txt -1 -2 -3 -3 -$ sed ":a;N;s/\n/,/g;ta" a.txt -1,2,3,4 -``` - - - - -### 1.4 文件重构 -**cut** - -【显示每行的某位置的内容】 -``` -# 前五个(包含第五个) -$ cut /etc/passwd -c -5 -# 前五个之后的(包含第五个) -$ cut /etc/passwd -c 5- -# 第五个 -$ cut /etc/passwd -c 5 -# 2到5之间的(包含第五个) -$ cut /etc/passwd -c 2-5 -``` -【以指定的分隔符分隔,并返回某些列】 -``` -# 返回第一列和第六列 -$ cut /etc/passwd -d ":" -f 1,6 -``` -**awk** - -awk是一个强大的文本分析工具。 -简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片(相当于Excel的分列),切开的部分再进行各种分析处理。 - -``` -# 打印空白行,文件有几个空行,就输出几个空行 -awk '/^$/{print "This is a blank line"}' awk.txt - -# 打印全部列,引用变量 -awk '{print $0}' awk.txt - -# 打印前三列,引用变量 -awk '{print $1,$2,$3}' awk.txt - -# 指定间隔符为空格,获取第四列 -awk -F" " '{print $4}' awk.txt - -# 重组表格 -awk -F ':' '{print $1"\t"$7}' awk.txt - -# 表头和结尾,会先输出name,shell -awk -F ':' 'BEGIN {print "name,shell"} {print $1","$7} END {print "blue,/bin/nosh"}' awk.txt - -# 在awk中引用变量变量,在原理是拼接. -limit=30 -df -Th| grep "/dev/vd" | sed 's/%//g' |awk '{ if($6>'"$limit"') print $6}' -``` - - - -### 1.5 其他命令 -**nl** - --b :指定行号指定的方式,主要有两种: -``` --b a :表示不论是否为空行,也同样列出行号(类似 cat -n); --b t :如果有空行,空的那一行不要列出行号(默认值); -``` --n :列出行号表示的方法,主要有三种: -``` --n ln :行号在萤幕的最左方显示; --n rn :行号在自己栏位的最右方显示,且不加 0 ; --n rz :行号在自己栏位的最右方显示,且加 0 ; -``` - - -``` --w :行号栏位的占用的位数。 -nl -b a -n rz -w 3 text -``` - -### 1.6 文件查找 -**which:查询软件** - -在PATH变量指定的路径中,搜索某个系统命令(`可执行文件`)的位置,并且返回第一个搜索结果。 - -参数选项(基本不用) -``` --n  指定文件名长度,指定的长度必须大于或等于所有文件中最长的文件名。 --p  与-n参数相同,但此处的包括了文件的路径。 --w  指定输出时栏位的宽度。 --V  显示版本信息 -``` - -**grep:搜索神器** - -搜索并筛选显示结果。 -该命令经常配合管道命令来控制输出。 -以下 是常用的选项: -![](http://image.python-online.cn/17-9-20/47469030.jpg) - -【非常好用:不打开文件,直接搜索指定目录下文件内的内容】 -``` -$ vim a # wongbingming -$ vim b # wangbingming -$ grep -rnI "bingming" . # 当然这里也可以使用正则表达式 -./a:1:wongbingming -./b:1:wangbingming - -参数解释 --r 递归遍历各个文件夹下的所有文件 --n 显示在文件中的第几行查询到 --I 忽略二进制文件 -``` - -**whreris:简单快速** - -定位可执行文件、源代码文件、帮助文件在文件系统中的位置。 -个搜索很快,因为它并没有从硬盘中依次查找,而是直接从数据库中查询。 -``` -$ whereis who -``` -参数选项 -``` --b 定位可执行文件。 --m 定位帮助文件。 --s 定位源代码文件。 --U 搜索默认路径下除可执行文件、源代码文件、帮助文件以外的其它文件。 --B 指定搜索可执行文件的路径。 --M 指定搜索帮助文件的路径。 --S 指定搜索源代码文件的路径。 -``` -**locate:快而全** - -通过`/var/lib/mlocate/mlocate.db`数据库查找,不过这个数据库也不是实时更新的,系统会使用定时任务每天自动执行`updatedb`命令更新一次,所以有时候你刚添加的文件,它可能会找不到,需要手动执行一次 `updatedb`命令(在我们的环境中必须先执行一次该命令)。 -``` -$ updatedb -$ locate /etc/sh - -# 查找/etc/目录下所有以sh开头的文件 -``` -**find:小而细** - -[鸟哥的Linux私房菜-find](http://linux.vbird.org/linux_basic/0220filemanager.php#find) -[每天一个linux命令(19):find 命令概览](http://www.cnblogs.com/peida/archive/2012/11/13/2767374.html) -[每天一个linux命令(20):find命令之exec](http://www.cnblogs.com/peida/archive/2012/11/14/2769248.html) - -### 1.7 文件传输/下载 -**scp** - -实现不同机器之间传输数据(加密) -[scp详解](http://www.cnblogs.com/peida/archive/2013/03/15/2960802.html) - -**curl** - -Curl是一个命令行方式下传输数据的开源传输工具,支持多种协议包括:FTP,HTTP,HTTPS,IMAP,POP3,TELNET等。同样支持HTTP POST方法,PUT方法,FTP上传,cookie,用户名/密码认证,下载文件端点续传等,功能十分强大。 - -常用的,用于模拟浏览器请求。 -[curl详解](http://blog.csdn.net/zzzmmmkkk/article/details/38569057) - -**wget** - -测试速率 -``` -wget -S http://115.231.74.93:80/lvs.lxdns.net/test.rar && rm -rf test.rar* -``` - - - -### 1.8 文件压缩 -**zcat** - -查看压缩的文件内容 -``` -zcat file.gz -``` -Linux上的压缩格式比Windows上多很多,在 Windows 上最常见的不外乎这三种 `*.zip`,`*.rar`,`*.7z` 后缀的压缩文件。而在 Linux 上面常见的格式除了以上三种外,还有 `*.gz`,`*.xz`,`*.bz2`,`*.tar`,`*.tar.gz`,`*.tar.xz`,`*.tar.bz2`,对于常见的压缩格式,tar已经可以解决,所以这里只介绍tar。tar并不能压缩和解压7z,zip等其他文件 - -**tar** - -压缩示例 -``` -# test是当前目录下一个文件夹 -$ tar -czvphf test.tar.gz old_folder -``` -参数解释 -``` --c 指明创建tar文件 --z 指明生成gz文件 - --v 可视输出,不加就静默压缩 - --f 指明压缩后的文件名,必须-f后必须紧跟文件名,否则无效 --p 当在其他主机还原时希望保留文件的属性 --h 备份链接指向的源文件而不是链接本身 - -``` -解包示例 -``` -$ mkdir new_folder -$ tar -xzvf test.tar.gz -C new_folder -``` -参数解释 -``` --x 解压命令 --z 指明源文件是gz文件 --f 指明压缩后的文件名,必须-f后必须紧跟文件名,否则无效 - --v 可视化输出解包过程,一般不加,静默解包 - --C 指明解压到哪个目录 -``` -其他压缩格式 -``` -*.tar.gz -z -*.tar.xz -J -*tar.bz2 -j - -``` -**gz** - -最简单的压缩格式 - -``` -# 压缩 -gzip somefile - -# 解压 -gzip -d somefile.gz -``` - -**7zip** - -``` -yun install p7zip - -# 解压 -7za x file.7z - -# 压缩 -# 先将要压缩的文件都放到~/test/ 下 -7za a file.7z ~/test/ -``` -**zip** - -``` -# 解压 -unzip some.file - -# 压缩 -zip -r some.zip file1 file2 dir1 dir2... - -# 如果你的目标zip包没有 . ,那么zip会自动加上后缀 .zip -zip stuff * - -# 打包包含隐藏文件的包 -zip stuff .* * - -# 打包 foo 文件夹的文件,但是不包含文件夹本身 -zip -j foo foo/* - -# 断点打包,在磁盘空间不足的使用可以使用 -zip -rm foo foo/tom -zip -rm foo foo/dick -zip -rm foo foo/harry - -# 分多个包,如果foo文件夹下有5G大小的文件,那么会分成两个2g的,一个1g的三个包,分别命名:split.z01,split.z02,split.zip -zip -s 2g -r split.zip foo -``` - -### 1.9 rpm包管理 - -``` -# 解压rpm包 -rpm2cpio xxx.rpm |cpio -div - -# 查看文件属于哪个rpm包 -rpm -qf /path/filename - -# 使用rpmrebuild重新生成rpm包 -# 使用rpmrebuild需要依赖rpmbuild:yum install -y rpmbuild -rpmrebuild xxx -``` - -### 1.10 ftp工具使用 - -```shell -$ ftp -ftp> help -``` - -![](http://image.python-online.cn/20190705182629.png) - - - -## 二、系统管理 - -### 2.0 环境变量 - -变量分为用户变量(env)和shell变量(set) -``` -# 添加shell变量 - -[root@host ~]# myuser=wangbm -[root@host ~]# echo $myuser -wangbm -[root@host ~]# env|grep myuser -[root@host ~]# set|grep myuser -myuser=wangbm - - -# 添加用户变量,会发现用户变量会覆盖shell变量 -[root@host ~]# export myuser=wangbingming -[root@host ~]# env|grep myuser -myuser=wangbingming -[root@host ~]# set|grep myuser -myuser=wangbingming - - -# 删除环境变量 -[root@host ~]# unset myuser -[root@host ~]# env|grep myuser -[root@host ~]# set|grep myuser -``` -一些系统的变量 -``` -# 这里生成的变量,对所有用户生效 -/etc/profile - -# 对特定user生效 -/root/.bash_profile -/home/user/.bashrc - -# 注意修改文件后,要手动source一下 -``` - -### 2.1 相关查询 - -#### 系统信息 - -``` -# 查询开机时间 -$ who -b - -# 查询系统内核 -$ uname -r - -# 查询是否安装某个rpm包 -$ rpm -qa|grep nova - -# service 文件目录 -$ /usr/lib/systemd/system # centos 7.x -$ /etc/init.d # centos 6.x - -# 查询开机自启列表 -$ systemctl list-unit-files -$ chkconfig --list [service_name] - 等级0表示:表示关机 - 等级1表示:单用户模式 - 等级2表示:无网络连接的多用户命令行模式 - 等级3表示:有网络连接的多用户命令行模式 - 等级4表示:不可用 - 等级5表示:带图形界面的多用户模式 - 等级6表示:重新启动 - -# 查看系统版本 -$ cat /etc/redhat-release - -# ------或者------ -$ yum install redhat-lsb -y -$ lsb_release -a - -# 进程树 -$ pstree -p - -# 查看系统运行多长时间 -$ uptime/w - -# 查看系统版本 -lsb_release -a - -# 查看CPU信息 -$ cat /proc/cpuinfo -$ numactk -H - - -# ubuntu 查看更新有哪些是安全更新 -$ apt-get -s --no-download dist-upgrade -V | grep "^Inst.*security.*$" | cut -d " " -f 2 - -# ubuntu 登陆时,显示的安全更新是如何来的,内容:/etc/update-motd.d/90-updates-available -/usr/lib/update-notifier/apt-check --human-readable - -``` - - - -#### 时间查询 - -输出操作系统的当前日期、时间和时区。 -``` -# -s参数用于修改当前的日期和时间 -date –s 2007-10-17 -date –s 18:05:00 - -用法:cd /CNCLog/cache/qsLogBackSrcFull/bkDir/`date +%Y-%m-%d` -``` - -### 2.2 系统分区 - -#### 分区介绍 -Linux的分区,不同于Windows,一定要区别对待,不然会搞不明白。 - -Linux的分区的过程经历以下几个步骤 -``` -1. 设备分区:对硬盘存储空间的划分 -2. 格式化:写入文件系统 -3. 挂载:将分区挂载到目录上,才能访问数据 -``` -关于硬件对应的设备文件名,可以参照下图 -![](http://image.python-online.cn/17-10-15/97911325.jpg) - -其中以硬盘为例来说明 -``` -硬盘可以分为三种 -hd : IDE硬盘接口(淘汰,接口最大传输100来M) -sd : SCSI硬盘接口(淘汰,接口最大传输200M),和SATA硬盘接口 - -现在都是SATA的硬盘接口 - -/dev/sda1 表示的是第一块(a)SATA硬盘的第一个分区(1) -/dev/sdb2 表示的是第二块(b)SATA硬盘的第二个分区(2) -``` -分区类型 -可以分为:`主分区` 、`扩展分区` 、`逻辑分区` -``` -【主分区】:最多只能有4个 (受硬盘结构限制,如果硬盘结构不变,将都被限制) - -【扩展分区】: - 1. 最多只能有一个 - 2. 主分区+扩展分区,一共只能有四个,可以少于 - 3. 扩展分区下面,不能存放数据,只能进行逻辑分区的划分 - -【逻辑分区】:挂载后就是一个目录下的空间,数量不受限制 -``` -格式化做了哪些事 -``` -【目的】 -1. 不是为了清空数据 -2. 主要是为了写入文件系统 - -【文件系统】 -Windows: -FAT16(每个分区大小最大不能超过2G),FAT32(单个分区大小最大16G,单个文件大小不能超过4G),NTFS - -Linux:ext2,ext3,ext4 - -->>>>> 越往后越先进 - -【写入文件系统做了啥】 -1. 分数据块 -把空间分成若干个等大小的数据块(block),每个大小4kb -如果我们有一个文件10kb,那么会占用3个数据块,实际大小就会是12kb - -你可以查看一个文件夹的大小,也都是4kb -ll -l - -2. 建立数据表 -一个文件被分成若干个的数据块,那么如果有用户访问的时候,就需要有一个表把这些数据块拼凑起来。 -这个数据表就记录了这个文件由哪些数据块组成。 -``` -分区说明 -``` -必须分区 -/ 根目录,最高级目录,不分配的话,所有的文件都没存储,软件没法运行 -/swap 交换分区,虚拟内存,4G以下,分2倍,4G以上,和真实内存一样即可 - -推荐分区 -/boot 启动分区,防止/ 分区写满,导致系统无法启动,不需要很大,200M足矣 - - -逻辑分区号:只能从5开始,即使3,4没有被使用 -``` - -#### 分区操作 -关于分区的操作可以参考这个: [分区操作](https://www.cnblogs.com/zishengY/p/7137671.html) - - -``` -## 查看分区表信息 -$ sudo fdisk -l -$ lsblk - -# 删除分区前,先确认有没有挂载点,有的话需要先umount卸载 -$ fdisk /dev/vdb 然后再按d,w -``` - -挂载分区 fstab - -``` -# 当fstab的根分区被注释后,所有的文件都是只读的。连fstab文件也是,无法取消注释 -mount -o remount,rw / -``` - -格化化分区 - -``` -mkfs.ext4 /dev/sdb1 -``` - - - -### 2.3 进程管理 - -**ps、kill、killall** - -参考文档:[ps 命令的十个简单用法](https://www.cnblogs.com/fakerbin/p/6513365.html) - -``` -# 查看当前所有进程 -ps -aux - -# 终止pid为1095的进程 -kill 1095 - -# 强制终止pid为1095的进程 -# 9是信号强度,强制杀死 -# 其他信号,-1 该信号让进程正常关闭,然后重新读取配置文件之后重启 -# -15 正常结束进程的信号,kill命令默认信号 -kill -9 1095 - -# 终止指定程序 -killall 程序名 - -# pkill -pkill -9 httpd 强制终止进程 -pkill -t -9 pts/1 强制杀死pts/1虚拟终端登入的进程 -``` - -**top** - -常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。 -[linux的top命令参数详解](http://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316399.html) - -``` -top 实时得查看进程的状态,以及系统的一些信息(如 CPU、内存信息等),3s刷新一次 -ps 来静态查看当前的进程信息 -pstree 来查看当前活跃进程的树形结构。 -``` - -**pgrep** - -```shell -[root@wangbm web]# pgrep keepalived -21955 -21956 -21957 -[root@wangbm web]# ps -ef|grep keepalived -root 21955 1 0 21:27 ? 00:00:00 /usr/sbin/keepalived -D -root 21956 21955 0 21:27 ? 00:00:00 /usr/sbin/keepalived -D -root 21957 21955 0 21:27 ? 00:00:00 /usr/sbin/keepalived -D -root 22035 12159 0 21:33 pts/0 00:00:00 grep --color=auto keepalived -``` - -**清除僵尸进程** - -一个僵尸进程产生的过程是:父进程调用fork创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中。 - -``` -ps -e -o stat,ppid,pid,cmd | grep -e '^[Zz]' | awk '{print $2}' | xargs kill -9 -``` - - - -### 2.4 设备信息管理 - -**df** - -显示磁盘的相关信息 -``` -df -Th -``` - -**free** - -输出内存的使用情况,m,g分别是指定单位,默认是kb -``` -free -m -free -g -``` -total 表示总内存大小; -used和free分别表示被使用和空闲内存大小; -share指可被多个进程共同享有的内存; buffers和cached用来保留最近访问的文件和数据,当其他进程需要更多的内存时,这些内容可以被缩减; Free命令还可以输出交换空间的相关信息。 - -**ifconfig** - -显示或设置网络设备 - - - -**last** - -列出目前与过去登入系统的用户相关信息。一般可用来查看系统重启记录 - - - -**history** - -屏幕输出当前用户在命令行模式下执行的最后(1000个)命令 - - - -**passwd** - -这个用于修改密码 - -非交互式修改密码 - -``` -echo 'root12#$'| passwd --stdin root -``` - - - -**reboot/shutdown** - -重启/关机 -``` -reboot/shutdown -r now - -# reboot [-n][-w][d][-i] 重新启动计算机,使用权限是系统管理员 --n 重启前不将记录写回硬盘 --w 并不是真的重启,只是把记录写道/var/log/wtmp文件中 --d 不把记录写入/var/log/wtmp文件中 --i 重启谦先把所有与网络相关的装备停止 -``` - -**rpm** - -``` -# 安装rpm包 -rpm -ivh monitor-system-1.2-1.i386.rpm - -# 更新rpm包 -rpm -U/Fvh (F只更新已存在的文件) - -# 查询包中的文件 -rpm -ql monitor-system - -# 查询文件所属的包 -rpm -qf /usr/local/squid/etc/squid.conf(绝对路径/到目录下查找) - -# 查询所有包 -rpm –qa | grep squid - -# 卸载某个rpm包 -rpm –e monitor-system -``` -**开启80端口** - -``` -vi /etc/sysconfig/iptables - -# 在22端口下面添加一行 --A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT - -# 重启防火墙 -/bin/systemctl restart iptables.service -``` -**修改时区** - -``` -# 由EDT(美国)改成CST(中国) - -$ mv /etc/localtime /etc/localtime.bak -$ ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime -``` - -查看机器连接的哪个交换机 - -``` -$ service lldpd restart -$ lldpcli show nei sum # 查看JS_HuaiAnDXXZ4_166.3 -------------------------------------------------------------------------------- -LLDP neighbors: -------------------------------------------------------------------------------- -Interface: eth1, via: LLDP - Chassis: - ChassisID: mac 00:6b:8e:01:1a:07 - SysName: JS_HuaiAnDXXZ4_165.132_IPMI - Port: - PortID: local 39 - PortDescr: Ethernet1/0/39 - TTL: 120 -------------------------------------------------------------------------------- -Interface: eth0, via: LLDP - Chassis: - ChassisID: mac 58:69:6c:62:7b:c2 - SysName: JS_HuaiAnDXXZ4_166.3 - Port: - PortID: ifname TenGigabitEthernet 0/41 - PortDescr: TenGigabitEthernet 0/41 - TTL: 121 -------------------------------------------------------------------------------- -``` - - - -### 2.5 磁盘管理 - -**df** - -显示指定磁盘文件的可用空间。 -这里要理解挂载的概念。 - -所有的设备(磁盘/等其他介质)都需要挂载在一个目录,Linux才能访问数据。 - -具体可以参考这篇文章:[df 命令](http://www.cnblogs.com/peida/archive/2012/12/07/2806483.html) - -**常用命令** - -``` -$ df -Th -$ df -lh - - -$ df -h 以1024来换算 -$ df -H 以1000来换算 - -$ df -t ext3 指定设备类型 -$ df -T 文件系统的类型 -$ df -i 查看inode的使用情况 -``` -关于inode可以查看:[inode的理解](https://www.cnblogs.com/itech/archive/2012/05/15/2502284.html) - -**du** - -查看目录的容量 -``` -$ du -h -d 0 ~ - -# 参数解释 --h 以人能看得懂的方式显示 --d 扫描的目录层级,0表示只有一个指定目录,1表示次级目录 - -~ 是家目录,这里可以选你指定的目录 - -$ du -s -# 查看当前所处目录总容量大小,单位是k,不可跟-d - -$ du -a -# 查看当前所处目录及所有子目录的所有文件,单位是k,建议不用 -``` -查看文件的大小 -``` -$ du -h file.txt -``` - -**dd** - -`dd`命令用于转换和复制文件,不过它的复制不同于`cp`。之前提到过关于 Linux的很重要的一点,一切即文件,在 Linux上,硬件的设备驱动(如硬盘)和特殊设备文件(如`/dev/zero`和`/dev/random`)都像普通文件一样,只要在各自的驱动程序中实现了对应的功能,`dd`也可以读取自和/或写入到这些文件。这样,`dd`也可以用在备份硬件的引导扇区、获取一定数量的随机数据或者空数据等任务中。dd程序也可以在复制时处理数据,例如转换字节序、或在 `ASCII` 与 `EBCDIC`编码间互换。 - - -这里有篇文章讲得很好:[Linux-dd命令详解](http://www.cnblogs.com/dkblog/archive/2009/09/18/1980715.html) - -补充一点 -``` -# 输出到文件 -$ dd of=test bs=10 count=1 # 或者 dd if=/dev/stdin of=test bs=10 count=1 - -# 输出到标准输出 -$ dd if=/dev/stdin of=/dev/stdout bs=10 count=1 -# 注 -在打完了这个命令后,继续在终端打字,作为你的输入 -``` -前面说到dd在拷贝的同时还可以实现数据转换,那下面就举一个简单的例子:将输出的英文字符转换为大写再写入文件 -``` -dd if=/dev/stdin of=test bs=10 count=1 conv=ucase -``` - - -#### 制作虚拟镜像并挂载 -``` -# 制作虚拟镜像 -$ dd if=/dev/zero of=virtual.img bs=1M count=256 -$ mkfs.ext4 virtual.img - -# 使用一个循环设备(/dev/loop)连接虚拟镜像文件 -$ sudo losetup /dev/loop0 virtual.img - -# 挂载前查看下当前已挂载的设备 -$ sudo mount - -# 挂载 -$ sudo mkdir /mnt/point -$ sudo mount /dev/loop0 /mnt/point - -# 上面连接虚拟镜像文件和挂载设备两条命令可以合并成一条,挂载类型可以省略,会自动识别 -$ sudo mount -o loop [-t ext4] /mnt/point - -# 再次查看挂载的设备,就可以发现我们新挂载的 -$ sudo mount - - -# 卸载:一定要加-fl。不然无法卸载 -$ sudo umount -fl /mnt/point - -# 查看所有与文件相关联的 loop 设备 -$ losetup -a -# 卸载 -$ losetup -d /dev/loop0 -``` - -使用dd拷贝磁盘可以,但是如果想要快速生成大文件,速度就相当慢了,可以试试fallocate命令 - -```shell -fallocate -l 100G test_file -``` - -### 2.6 任务管理 - -#### 一次性任务(at) -##### 创建定时任务 -``` -$ sudo apt-get install at -$ at now+5 minutes -at>/bin/ls -at> # 就是Ctrl+d -job 2 at Sum Oct 15 14:58:00 2017 # 结束的时候,告知执行时间 -``` -##### 管理定时任务 -``` -$ atq # 可以查看当前还有那些定时任务,会显示任务号 - -# 查看具体任务代码 -$ at -c - -# 删除任务 -$ atrm -``` -#### 例行性任务(crontab) -参考以下文章 -1. [每天一个linux命令(50):crontab命令](http://www.cnblogs.com/peida/archive/2013/01/08/2850483.html) -2. [鸟哥的私房菜](http://linux.vbird.org/linux_basic/0430cron.php) - -做个示例 -``` -# 检查crond服务是否启动 -$ ps aux|grep crond - -# 创建任务 -$ crontab -e - -# 输入1 回车 -# 任务是:每天凌晨3点备份日志到/home/temp/目录,文件名为日期 -# 跳到文件尾部输入任务:* 3 * * * cp alternatives.lob /home/temp/$(date \%Y-\%m-\%d)。保存退出 -# 注意%需要加\来转义,不然会被当成换行使用 - -# 查看任务 -$ crontab -l - -# 删除任务(当前用户),当然也可以指定用户 -$ crontab -r [-u user] - -``` - -除了以上,例行性任务还有可能在 /etc/cron.daily/ ,/etc/cron.hourly/, /etc/cron.monthly/, /etc/cron.weekly/ 下面。 - -如果messages 不能正常回滚,可以考虑加个参数 `-f` - -``` -/usr/sbin/logrotate -f /etc/logrotate.conf -``` - -如果还不行,检查一下 `/etc/logrotate.d/syslog` - -``` -chmod 644 /etc/logrotate.d/syslog -``` - -## 三、用户/权限管理 - -### 3.1 用户管理 - -**创建用户** - -``` -【useradd和adduser区别】 -useradd:只创建用户,创建完了用 passwd lilei去设置新用户的密码。更像一种命令。 - -adduser:会创建用户,创建目录,创建密码(提示你设置),做这一系列的操作。更像是一个程序,执行确认一系列操作。 -``` - -**删除用户** - -``` -sudo deluser lilei --remove-home -``` - -**切换用户** - -``` -su [user] # 切换到其他用户,环境变量不变,还是当前路径,如果不指定user,就切换到root - -su - [user] # 切换到其他用户,环境变量也切换,路径变为user家目录,如果不指定username,就切换到root,相当于使用user进行登录 -``` - -Ubuntu切换用户 -``` -# 切换到root -$ sudo su - -# 切换到普通用户 -$ su user -``` -CentOS - -``` -$ su -$ su root -$ su - -$ su -root - -$ su user -$ su -user -``` -**用户组** - -``` -1. 每个用户都至少属于一个用户组,创建的时候如果不指定,就和当前用户的组一样(root用户组除外)。 -2. 一个用户可以属于多个用户组。 -``` - -``` -# 查看所属用户组 -$ groups [user] -``` - -**把用户加入到用户组** - -```shell -# 把 zabbix 加入到 root 用户组 -usermod -G root zabbix -``` - -另外还有其他方法,可查阅:[在 Linux 中把用户添加到组的四个方法](https://linux.cn/article-10768-1.html) - -**UID/GID/组ID** - -``` -# 显示相关信息,如要查看root用户的信息 -id root - -# 所有文件都保存在/etc/)asswd -vi /etc/passwd -``` - -管理用户:[查看用户的UID和GID](http://blog.csdn.net/jackailson/article/details/50993427) -管理密码:[Linux下/etc/shadow文件](http://blog.csdn.net/u011641885/article/details/46681697) - - - -**添加sudo用户组** - -``` -【第一种方法】 -在root下,visudo或者vi /etc/sudoers,找到root ALL=(ALL)的下一行添加一行,user ALL=(ALL),user是对应的用户名 - -【第二种方法】 -在root下,使用命令sudo usermod -G sudo user,将user加入sudo用户组 -``` - -**sudo免密** - -当有些操作只有root用户才能操作的时候,怎么办? -1.我们需要切换到root用户操作。 -2. 当前用户属于sudo组,可以使用sudo [command] -3. 使用sudo输入一次密码免密使用5分钟。还是太麻烦,可以配置当前用户免密执行sudo。 - -如何免密配置 -``` -vi /etc/sudoers.d/ - -# 添加内容 -# 如果要指定特定的命令不需要密码的话,就把ALL替换成命令路径,如下 -# NOPASSWD: /sbin/mount, (root) NOPASSWD: /bin/umount - ALL=(ALL) NOPASSWD:ALL -Defaults:shiyanlou !requiretty - -# 有时候,虽然用户设置免密了,但是还是需要输入密码,是group覆盖了,需要把group也设成免密。 -``` - -参考资料:[sudo免密](http://www.cnblogs.com/kungfupanda/p/4305049.html) - - - -### 3.2 权限管理 - -**更改文件所有者和所属组** - -``` -# [sudo] chown 用户组:用户 文件/文件夹,以下两种均可 -chown root:zabbix some.txt -chown root.zabbix some.txt - -# 只更改所属用户,[sudo] chowm 用户 文件/文件夹 -chown zabbix some.txt - -# 只更改所属用户组,[sudo] chown .用户组 文件/文件夹 -chown .root some.txt -``` - -**修改文件权限** - -文件权限有`读`、`写`、`执行`三种 -分别对应数字4,2,1,也就是2^2,2^1,2^0 - -如何修改文件权限 -``` -【第一种方法】 -chmod 777 文件 - -【第二种方法】:加减的方法 -g、o、u、a分别表示 group、others、user和all -+、-、= 分别表示增加、去掉和设置相应的权限。 - -举个例子 -比如一个文件权限是:-wr-wr-wr- -chmod go-wr 文件 - -然后文件权限就变成:-wr------- - -再设置回原来的 -chmod ugo=wr 文件 - -或者将user和group改为可执行 -chmod ug=wrx,o=wr 文件 -``` -**禁止修改、删除、移动文件** - -`chattr -i`和`chattr +i` - -``` -+ :在原有参数设定基础上,追加参数。 -- :在原有参数设定基础上,移除参数。 - -命令:chattr [ -RV ] [ -v version ] [ mode ] files - -A: 文件或目录的 atime (access time)不可被修改(modified), 可以有效预防例如手提电脑磁盘I/O错误的发生。 -S: 硬盘I/O同步选项,功能类似sync。 -a: 即append,设定该参数后,只能向文件中添加数据,而不能删除,多用于服务器日志文 件安全,只有root才能设定这个属性。 -c: 即compresse,设定文件是否经压缩后再存储。读取时需要经过自动解压操作。 -d: 即no dump,设定文件不能成为dump程序的备份目标。 -i: 设定文件不能被删除、改名、设定链接关系,同时不能写入或新增内容。i参数对于文件 系统的安全设置有很大帮助。 -j: 即journal,设定此参数使得当通过 mount参数:data=ordered 或者 data=writeback 挂 载的文件系统,文件在写入时会先被记录(在journal中)。如果filesystem被设定参数为 data=journal,则该参数自动失效。 -s: 保密性地删除文件或目录,即硬盘空间被全部收回。 -u: 与s相反,当设定为u时,数据内容其实还存在磁盘中,可以用于undeletion. -``` - -切换用户执行命令 - -``` -$ su - zabbix -s /bin/bash - -# 退出的话,使用exit -``` - -## 四、网络管理 - -### 4.1 iptables -命令格式 -``` -iptables [-t table] 命令 [chain] [rules] [-j target] - -【参数解释】 -table 表名:filter、nat、mangle、raw,后两者不常用 -命令 对链的操作命令 - -P或–policy 定义默认策略 - -L或–list 查看iptables规则列表 - -A或—append 在规则列表的最后增加1条规则 - -I或–insert 在指定的位置插入1条规则 - -D或–delete 从规则列表中删除1条规则 - -R或–replace 替换规则列表中的某条规则 - -F或–flush 删除表中所有规则 - -Z或–zero 将表中数据包计数器和流量计数器归零 -chain 链名:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTIN - -rules 规则,可以说是匹配规则。 - 分为 - 1. 【通用匹配】 - -s: 指定作为源地址匹配,必须是IP,取反,就加一个! - -d: 表示匹配目标地址 - -p: 用于匹配协议的(这里的协议通常有3种,TCP/UDP/ICMP) - -i eth0:从这块网卡流入的数据,流入一般用在INPUT和PREROUTING上 - -o eth0:从这块网卡流出的数据,流出一般在OUTPUT和POSTROUTING上 - - 2. 【扩展匹配】 - -p tcp: TCP协议的扩展。一般有三种扩展 - --dport: 指定目标端口,--dport 21或者 --dport 21-23 (此时表示21,22,23),不能表示非连续端口 - --sport: 指定源端口 - - --tcp-fiags: TCP的标志位(SYN,ACK,FIN,PSH,RST,URG) - 对于它,一般要跟两个参数: - 1.检查的标志位 - 2.必须为1的标志位 - --tcpflags syn,ack,fin,rst syn = --syn - 表示检查这4个位,这4个位中syn必须为1,其他的必须为0。所以这个意思就是用于检测三次握手的第一次包的。对于这种专门匹配第一包的SYN为1的包,还有一种简写方式,叫做--syn - - -p udp: UDP协议的扩展 - --dport - --sport - - -p icmp: icmp数据报文的扩展 - --icmp-type: - echo-request(请求回显), 一般用8 来表示 - echo-reply (响应的数据包) 一般用0来表示 -``` - -参考文章 -1. [ubuntu配置iptables](http://wiki.ubuntu.org.cn/IptablesHowTo) -2. [netfilter/iptables全攻略](http://www.linuxso.com/linuxpeixun/10332.html) -3. [iptables详解](http://blog.chinaunix.net/uid-26495963-id-3279216.html) - -### 4.2 端口相关 - -查询端口占用情况 - -``` -lsof -i:10051 -``` - -开放端口 - -``` -# 在filter表里添加规则 --A INPUT -m state --state NEW -m tcp -p tcp --dport 8500 -j ACCEPT - -# 重启iptables -$ service iptables restart - -# 监听端口 -$ nc -lp 8500 & - -# 检测是否开启完成 -$ netstat -tunl|grep 8500 -``` - -端口转发 - -``` -# 按开放端口配置好后才可转发。 -# 假设我们现在要将36.250.x.x的8500端口转发到192.168.2.55的80端口上 - -# filter表 --A INPUT -m state --state NEW -m tcp -p tcp --dport 8500 -j ACCEPT - -# nat表 --A PREROUTING -d 36.250.x.x -m tcp -p tcp --dport 8500 -j DNAT --to-destination 192.168.2.55:80 - -# 测试一下,在浏览器输入 36.250.x.x:8500 -# 就可以看到我们的内容了。 -``` - -### 4.3 路由相关 - -路由转发:snat - -``` - -# 1、开启转发 -# vim /etc/sysctl.conf,sysctl -p 该命令可以查看是否修改成功 -net.ipv4.ip_forwaed=1 - -# 或者可以执行下面这两条命令其中一条 -echo 1 > /proc/sys/net/ipv4/ip_forward -sysctl -w net.ipv4.ip_forward=1 - -# 2、在网关节点的 /etc/sysconfig/iptables 加上规则。 -*nat -:PREROUTING ACCEPT [4730:370403] -:INPUT ACCEPT [501:29436] -:OUTPUT ACCEPT [3563:214910] -:POSTROUTING ACCEPT [3564:214973] --A POSTROUTING -s 172.20.20.0/24 -o eth0 -j SNAT --to-source 58.xx.xx.xx -COMMIT - -# 重启iptables -systemctl restart iptables -``` - -### 4.4 ip 命令 - -``` -# ubuntu 临时修改网络,需要刷新一下 -ip addr flush dev ens4 -``` - - - -## 五、Shell命令 - -### 5.1 执行顺序控制 -``` -1、&& -方式:command1 && command2 -如果command1执行成功,则执行command2 - -2、|| -方式:command1 || command2 -如果command1执行失败,则执行command2 -``` -### 5.2 管道通信 - -``` -$ ps aux | grep mysqld - -# 将ps aux得到的结果传给grep命令 -``` -### 5.3 重定向 - -`>/dev/null 2>&1 &` - -分析下这个语句 -`command >/dev/null 2>&1 &` -执行command后的标准输出不在屏幕显示,而是直接丢入/dev/null 垃圾桶,如果有错误输出,则重定向到标准输出。最后&表示在后台运行。 - -### 5.4 其他常用 - -【nohup】 - -一般在一终端或一个SSH连接运行一个软件或服务,该软件或服务的生命周期受终端/SSH连接影响,关闭后就自动也停止。 -为了将程序放在后台运行,可以使用nohup命令 -``` -$ nohup 程序路径/程序名 & -``` - -【alias】 -给常用的长命令取别名,变成短的,提高效率 -``` -# 查看现有别名 -$ alias - -# 添加别名 -$ alias catgra='cat /var/lib/mysql/grastate.dat' - -# 取消别名状态 -# 比如我们的ll,系统默认给我们加了别名,ll='ls -l --color=auto',也就是加上颜色效果 -# 如果我们不要颜色效果,可以这样使用转义符,使用原生的命令 -$ \ll somedir -``` -这里转几条来自 「**良许Linux**」的总结,非常实用,你可以跟着设置一下 - -1、压缩包文件,特别是 tar 文件在 Linux 下使用非常广泛,但是 tar 命令的选项又非常多,也不好记住。所以我们可以将常用的几个选项定义为一个别名 **untar** ,这样我们需要解压 tar 文件时,直接 **untar filename** 即可。 - -``` -alias untar='tar -zxvf ' -``` - -2、我们下载一个很大的文件时,突然网络异常中断了,我们重新下载是不是很抓狂?别担心,我们的 wget 命令有个 -c 选项,支持断点下载,我们也可以将它设置为别名: - -``` -alias wget='wget -c ' -``` - -3、有时我们需要生成一个 20 个字符的随机数密码,我们可以使用 openssl 命令,但完整的命令又很长很不方便,我们可以设置别名: - -``` -alias getpass="openssl rand -base64 20" -``` - -4、下载一个文件之后,我们想要校验一下它的 checksum 值,可以将这个命令封装为一个别名 **sha** ,之后我们 **sha filename** 就可以校验文件的 checksum 值。 - -``` -alias sha='shasum -a 256 ' -``` - -5、正常情况下,ping 命令将无限次输出,但其实没多大意义。我们可以使用 -c 命令将其限制为 5 次输出,然后设置为别名 **ping** ,使用时,**ping url** 即可。 - -``` -alias ping='ping -c 5' -``` - -6、如果我们想随时随地启动一个 web 服务器,我们可以使用这个别名: - -``` -alias www='python -m SimpleHTTPServer 8000' -``` - -7、网速的测试在工作中也经常用到,但 Linux 没有自带命令可用,我们可以借助第三方工具 **speedtest-cli** 。这个工具可以直接从 Github 上下载,使用方法里面也有详细介绍。我们需要先使用 **speedtest-cli** 命令来选择离我们最近的服务器,然后设置如下别名: - -``` -alias speed='speedtest-cli --server 2406 --simple' -``` - -8、你的公网 IP 是多少?记性好的可以直接背下来,但如果你有 10 台上百台服务器呢?也可以背下来,然后参加最强大脑。其实有个命令可以直接查询,但那个命令太变态,不好记,果断设置为别名。 - -``` -alias ipe='curl ipinfo.io/ip' -``` - -9、如何知道自己的局域网 IP ?这个命令同样变态,果断设置别名。 - -``` -alias ipi='ipconfig getifaddr en0' -``` - -10、最后,清屏,我们可以使用 **ctrl + l** 快捷键,也可以将 **clear** 命令定义得更短,这样使用起来更直接,更粗暴。 - -``` -alias c='clear' -``` - -【column】 - -一个很好用的命令,经常用于管道符后,进行文本展示 -``` -$ mount -sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime) -proc on /proc type proc (rw,nosuid,nodev,noexec,relatime) -devtmpfs on /dev type devtmpfs (rw,nosuid,size=3996196k,nr_inodes=999049,mode=755) -securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime) - -# 使用后,t是按表格的形式展示,-s "@" 指定分割符 -$ mount | column -t -sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime) -proc on /proc type proc (rw,nosuid,nodev,noexec,relatime) -devtmpfs on /dev type devtmpfs (rw,nosuid,size=3996196k,nr_inodes=999049,mode=755) -securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime) -``` - - -## 六、业务相关 - - -查看虚机的CPU是否支持虚拟化 - -``` -egrep '(vmx|svm)' /proc/cpuinfo -# 如果有标红的vmx(Intel)、svm(AMD)说明就支持,virtualbox不支持(坑) - -# 或者直接查看 -vi /proc/cpuinfo -``` -查看模块是否加载 -``` -lsmod | grep kvm -``` -开启服务 -``` -# CentOS 6 -service xx start - -# CentOS 7 -systemctl start libvirtd -``` -开机自启动服务 -``` -CentOS 6 -chkconfig acpid on - -# CentOS 7 -systemctl enable libvirtd -``` -开启shh登录,修改端口等配置 -``` -vi /etc/ssh/sshd_config -------------------------------------- -Port 57891 -PasswordAuthentication yes -ClientAliveInterval 60 -ClientAliveCountMax 10 -``` -关闭防火墙 -``` -setenforce 0 -service firewalld stop -chkconfig firewalld off -``` -换yum源 -``` -yum install wget - -mv /etc/yum.repos.d/CentOS-Base.repo /root/ - -# 下载yum源,这里是CentOS6的,请下载对应版本 -wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo - -yum clean all -yum makecache -``` - -磁盘修复 - -```shell - xfs_repair /dev/vda2 -L -``` - - - -## 附:推荐阅读 - -- [命令行的艺术](https://github.com/jlevy/the-art-of-command-line/blob/master/README-zh.md) - - - ---- - -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +# 7.1 Linux 命令行的艺术 + +![](http://image.iswbm.com/20200602135014.png) + +--- + +## 一、目录/文件 + +### 1.1 目录文件日常操作 +``` +. -> 当前目录 +.. -> 上一级目录 +.file -> 隐藏文件 +.dir -> 文件夹 +``` +【ls】 查看指定目录文件 +`ls`命令是最常用的linux命令,要配合着选项使用。 + +``` +常用命令 + +ls -lh # 方便查看文件大小 +ls -AsSh +``` +【pwd】:查看当前目录 +``` +pwd等价于/bin/pwd -L,显示当前绝对路径,如果是链接,则显示链接路径 +pwd -P等价于/bin/pwd -P,显示实际路径,而非链接路径 + +如果文件夹被临时删除,pwd还是显示未删除文件夹的那个路径,这时候可以使用/bin/pwd,就会提示当前路径不存在 +``` +【cd】:切换目录 + +``` +cd - # 上一目录 +cd ~ # 家目录 +cd # 家目录 +cd .. # 上一目录 +cd !$ # 将上命令的参数做为cd 参数 +cd ~zabbix # 进入zabbix用户的主目录 +``` + +【touch】:新建文件 +``` +1. 创建新文件同时可以指定一些时间参数 +$ touch newfile + +2. 一次性创建多个文件 +$ touch {1..10}.txt +``` +可以对已有文件修改时间戳(ll显示的时间) +``` +【1】 +touch -d [[CC]YY]MMDD text +touch -t [CC[YY]MMDDhhmm[.SS] text +CC为年数中的前两位,即”世纪数”;YY为年数的后两位,即某世纪中的年数.如果不给出CC的值 +touch -d 20171004 text +touch -t 201710041330.30 text + +【2】 +-r:以另一文件为基准更新时间戳 +以file1的时间戳为基准,将file2的改成一样的 +touch -r file1 file2 + +【3】其他不常用参数 +-a 或--time=atime或--time=access或--time=use  只更改存取时间。 +-c 或--no-create  不建立任何文档。 +-f  此参数将忽略不予处理,仅负责解决BSD版本touch指令的兼容性问题。 +-m 或--time=mtime或--time=modify  只更改变动时间。 +``` +【mkdir】:新建文件夹 +``` +# 创建一个或多个的同级目录 +mkdir adir bdir + +# 递归创建目录 +# 当前目录下并没有cdir这个文件夹,如果使用单纯mkdir cdir/ddir会报错。 +# 应该使用 +mkdir -p cdir/ddir + +# 指定权限 +mkdir -m 777 dir_name + +# 显示创建信息(成功或失败) +mkdir -v dir_name + +# 使用!$快速进入新建文件夹 +mkdir ming +cd !$ +``` + +【rm | rmdir】 + +``` +# 只能删除空文件夹 +rmdir dirname + +# 删除当前文件夹下所有文件并不提示 +rm -f * + +# 递归删除dirname下所有文件 +rm -r dirname + +# 上面二者结合,递归删除dirname下所有文件并不提示 +rm -rf dirname +``` + +【cp】:复制文件或目录 +``` +# 递归复制old_dir目录下所有文件和文件夹到new_dir文件夹下 +cp -r old_dir new_dir +``` + + +> 注意:默认cp拷贝文件后会使用预设权限,即其他人没有更改的权限,需要使用-p或 +> 者-a文件所有的所有特性都一起复制过来 (拉取源站数据缓存,不更改文件最后修改 +> 时间) + +【mv】:移动或重命名 +``` +# 移动/tmp/test/sample.txt文件到当前目录下 +mv /tmp/test/sample.txt ./ +``` +【cat 】查看文件 + +**作用**:文本文件查看和连接工具,用于查看文本文件的内容。 +**命令格式**:cat file 经常和more、head、tail、less以及管道命令结合使用,如:cat file | more、cat file | head等。 + +三个功能 +``` +1.一次显示整个文件:cat filename +2.从键盘创建一个文件:cat > filename 创建文件,输入内容,Ctrl+d 结束 +3.将几个文件合并为一个文件:cat file1 file2 > file +``` + +命令参数 +``` +-A, --show-all 等价于 -vET +-b, --number-nonblank 对非空输出行编号 +-e 等价于 -vE +-E, --show-ends 在每行结束处显示 $ +-n, --number 对输出的所有行编号,由1开始对所有输出的行数编号 +-s, --squeeze-blank 有连续两行以上的空白行,就代换为一行的空白行 +-t 与 -vT 等价 +-T, --show-tabs 将跳格字符显示为 ^I +-u (被忽略) +-v, --show-nonprinting 使用 ^ 和 M- 引用,除了 LFD 和 TAB 之外 +``` +【tac】反向查看 +``` +cat是第一行到最后一行,tac是最后一行到第一行 +``` +【head | tail】 + +``` +# 显示前10行 +head xs.txt 等价于 cat xs.txt|head 等价于 cat xs.txt|head -n 10 + + +# 显示最后10行 +tail xs.txt 等价于 tail -n 10 xs.txt + + +# 显示除末尾10行外 +head -n -10 xs.txt +``` + +【more | less】 + +更具体命令可以参考:[Linux中more和less命令用法](http://www.cnblogs.com/aijianshi/p/5750911.html) +``` +【more】 +# 回车就往下一行显示,空白键就往下一页显示 +# 按 b 键就会往回一页显示,按 q 就会退出 +more xs.txt + +# 查找比较鸡肋,并从该处前两行开始显示输出 +more +/查找内容 xs.txt + +【less】 +# 和more几乎一样的功能,但是less更灵活(可以使用任何vim的移动命令,还有标记功能很使用),比如查找命令,直接像vim一样输入/即可 +less xs.txt + +ma : 使用 a 标记文本的当前位置 +'a : 导航到标记 a 处 + +F :实现和tail -f的功能,实时输出内容,tail +F xs.txt +``` + + + +**小练习** + +选取xs.txt的10-20行 + +``` +head -n 20 xs.txt|tail + +# 更快捷的方法 +sed -n '10,20p' xs.txt +``` + + + +### 1.2 文件处理 + +【wc】 + +统计文件信息 +``` +# 统计所有信息(行数、单词数、字符数) +$ wc /etc/passwd + +# 行数 +$ wc -l /etc/passwd + +# 单词数 # 中文无法统计 +$ wc -w /etc/passwd + +# 字节数 +$ wc -c /etc/passwd + +# 字符数 +$ wc -m /etc/passwd + +# 最长行字节数 +$ wc -L /etc/passwd +``` + +【sort】 + +``` +$ cat /etc/passwd | sort +$ cat /etc/passwd | sort -r + +# 以:为分隔符,对第三列排序,所得的结果,再通过cut以:为分隔符取第三列 +$ cat /etc/passwd | sort -t ":" -k 3 |cut -d ":" -f 3 +``` + +【uniq】 + +``` +# 显示每行重复频率 +uniq -c 文件名 + +# 只显示有重复的行 +uniq -d 文件名 +``` +### 1.3 文字处理 + +【tr】 + +`tr`可以删除或者去重某文本信息中的某些文字。还可以进行替换操作 +``` +# 删除hello,里面所有l,o字符 +$ echo 'hello' | tr -d 'lo' + +# 去重hello里的l +$ echo 'hello' | tr -s 'l' + +# hello里,l换成a,e换成b +$ echo 'hello' | tr 'le' 'ab' +``` + +【col】 + +将tab转换为等数量的空格,或者反转 +``` +-x tab转空格 +-h 空格转tab(默认) +``` + +``` +# 查看 /etc/protocols 中的不可见字符,可以看到很多 ^I ,这其实就是 Tab 转义成可见字符的符号 +$ cat -A /etc/protocols + +# 使用 col -x 将 /etc/protocols 中的 Tab 转换为空格,然后再使用 cat 查看,你发现 ^I 不见了 +$ cat /etc/protocols | col -x | cat -A +``` + +【sed】 + +文件处理工具。 +使得不需要打开文件就可以对文件进行操作(删除,替换,选取,新增)。以行为单位进行处理。 + +常用选项 + +``` +-n∶经过处理后的结果显示出来。不影响真实文件。 +-e:直接在指令列模式上进行 sed 的动作编辑;(没明白) +-f∶直接将 sed 的动作写在一个档案内, -f filename 则可以执行 filename 内的sed 动作; +-i∶直接修改读取的档案内容,而不是由屏幕输出。 +``` +常用命令: +``` +a∶新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~ +c∶取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行! +d∶删除,因为是删除啊,所以 d 后面通常不接任何咚咚; +i∶插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行); +p∶列印,亦即将某个选择的资料印出。通常 p 会与参数 sed -n +s∶取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!例如 1,20s/old/new/g 就是啦! +``` + +删除某行 +``` + sed '1d' somefile #删除第一行 + sed '$d' somefile #删除最后一行 + sed '1,2d' somefile #删除第一行到第二行 + sed '2,$d' somefile #删除第二行到最后一行 + + # 以上,只是输出,源文件并不会替换,若要对源文件进行修改,需加 -i + sed -i '1,2d' somefile +``` +删除指定行的上一行或下一行 + +```shell +删除指定文件的上一行 +sed -i -e :a -e '$!N;s/.*n(.*directory)/1/;ta' -e 'P;D' server.xml +删除指定文件的下一行 +sed -i '/pattern="%/{n;d}' server.xml +``` + +显示某行 + +``` + sed -n '1p' somefile #显示第一行 + sed -n '$p' somefile #显示最后一行 + sed -n '1,2p' somefile #显示第一行到第二行 + sed -n '2,$p' somefile #显示第二行到最后一行 +``` +使用模式进行查询 +``` + sed -n '/ruby/p' somefile #查询包括关键字ruby所在所有行 + sed -n '/\$/p' somefile #查询包括关键字$所在所有行,使用反斜线\屏蔽特殊含义 +``` +插入行(a 表示 append 追加,也可以使用 i,表示 insert,如果你要在第一行插入,就不得不使用 i 了) +``` +sed '1a drink tea' somefile #第一行后增加字符串"drink tea" +sed '1,3a drink tea' somefile #第一行到第三行后增加字符串"drink tea" +sed '1a drink tea\nor coffee' somefile #第一行后增加多行,使用换行符\n +``` +在某一行后面加入一行(示例为第四行) + +``` +sed -i 'N;4addpdf' a.txt +sed -i 'N;4ieepdf' a.txt +``` + + + +替换行 + +``` +sed '1c Hi' somefile #第一行代替为Hi +sed '1,2c Hi' somefile #第一行到第二行代替为Hi +``` +替换行中部分数据 +``` +sed 's/ruby/bird/g somefile' #替换ruby为bird,记住这个并不会更改源文件,只是输出 +sed 's/ruby//g' somefile #删除ruby +``` + +在某行的前一行或后一行添加内容,其中 `\` 可省略,但为了可读性,所以我一般不省略 + +```shell +# 匹配行前加 +sed -i '/2222222222/a\3333333333' test.txt + +# 匹配行前后 +sed -i '/2222222222/i\3333333333' test.txt +``` + + + +【注意】:以上对源文件都不做修改,若要修改,要加上`-i` + +替换换行符 + +```shell +# 将换行符换成逗号 +$ cat a.txt +1 +2 +3 +3 +$ sed ":a;N;s/\n/,/g;ta" a.txt +1,2,3,4 +``` + + + + +### 1.4 文件重构 +**cut** + +【显示每行的某位置的内容】 +``` +# 前五个(包含第五个) +$ cut /etc/passwd -c -5 +# 前五个之后的(包含第五个) +$ cut /etc/passwd -c 5- +# 第五个 +$ cut /etc/passwd -c 5 +# 2到5之间的(包含第五个) +$ cut /etc/passwd -c 2-5 +``` +【以指定的分隔符分隔,并返回某些列】 +``` +# 返回第一列和第六列 +$ cut /etc/passwd -d ":" -f 1,6 +``` +**awk** + +awk是一个强大的文本分析工具。 +简单来说awk就是把文件逐行的读入,以空格为默认分隔符将每行切片(相当于Excel的分列),切开的部分再进行各种分析处理。 + +``` +# 打印空白行,文件有几个空行,就输出几个空行 +awk '/^$/{print "This is a blank line"}' awk.txt + +# 打印全部列,引用变量 +awk '{print $0}' awk.txt + +# 打印前三列,引用变量 +awk '{print $1,$2,$3}' awk.txt + +# 指定间隔符为空格,获取第四列 +awk -F" " '{print $4}' awk.txt + +# 重组表格 +awk -F ':' '{print $1"\t"$7}' awk.txt + +# 表头和结尾,会先输出name,shell +awk -F ':' 'BEGIN {print "name,shell"} {print $1","$7} END {print "blue,/bin/nosh"}' awk.txt + +# 在awk中引用变量变量,在原理是拼接. +limit=30 +df -Th| grep "/dev/vd" | sed 's/%//g' |awk '{ if($6>'"$limit"') print $6}' +``` + + + +### 1.5 其他命令 +**nl** + +-b :指定行号指定的方式,主要有两种: +``` +-b a :表示不论是否为空行,也同样列出行号(类似 cat -n); +-b t :如果有空行,空的那一行不要列出行号(默认值); +``` +-n :列出行号表示的方法,主要有三种: +``` +-n ln :行号在萤幕的最左方显示; +-n rn :行号在自己栏位的最右方显示,且不加 0 ; +-n rz :行号在自己栏位的最右方显示,且加 0 ; +``` + + +``` +-w :行号栏位的占用的位数。 +nl -b a -n rz -w 3 text +``` + +### 1.6 文件查找 +**which:查询软件** + +在PATH变量指定的路径中,搜索某个系统命令(`可执行文件`)的位置,并且返回第一个搜索结果。 + +参数选项(基本不用) +``` +-n  指定文件名长度,指定的长度必须大于或等于所有文件中最长的文件名。 +-p  与-n参数相同,但此处的包括了文件的路径。 +-w  指定输出时栏位的宽度。 +-V  显示版本信息 +``` + +**grep:搜索神器** + +搜索并筛选显示结果。 +该命令经常配合管道命令来控制输出。 +以下 是常用的选项: +![](http://image.iswbm.com/17-9-20/47469030.jpg) + +【非常好用:不打开文件,直接搜索指定目录下文件内的内容】 +``` +$ vim a # wongbingming +$ vim b # wangbingming +$ grep -rnI "bingming" . # 当然这里也可以使用正则表达式 +./a:1:wongbingming +./b:1:wangbingming + +参数解释 +-r 递归遍历各个文件夹下的所有文件 +-n 显示在文件中的第几行查询到 +-I 忽略二进制文件 +``` + +**whreris:简单快速** + +定位可执行文件、源代码文件、帮助文件在文件系统中的位置。 +个搜索很快,因为它并没有从硬盘中依次查找,而是直接从数据库中查询。 +``` +$ whereis who +``` +参数选项 +``` +-b 定位可执行文件。 +-m 定位帮助文件。 +-s 定位源代码文件。 +-U 搜索默认路径下除可执行文件、源代码文件、帮助文件以外的其它文件。 +-B 指定搜索可执行文件的路径。 +-M 指定搜索帮助文件的路径。 +-S 指定搜索源代码文件的路径。 +``` +**locate:快而全** + +通过`/var/lib/mlocate/mlocate.db`数据库查找,不过这个数据库也不是实时更新的,系统会使用定时任务每天自动执行`updatedb`命令更新一次,所以有时候你刚添加的文件,它可能会找不到,需要手动执行一次 `updatedb`命令(在我们的环境中必须先执行一次该命令)。 +``` +$ updatedb +$ locate /etc/sh + +# 查找/etc/目录下所有以sh开头的文件 +``` +**find:小而细** + +[鸟哥的Linux私房菜-find](http://linux.vbird.org/linux_basic/0220filemanager.php#find) +[每天一个linux命令(19):find 命令概览](http://www.cnblogs.com/peida/archive/2012/11/13/2767374.html) +[每天一个linux命令(20):find命令之exec](http://www.cnblogs.com/peida/archive/2012/11/14/2769248.html) + +### 1.7 文件传输/下载 +**scp** + +实现不同机器之间传输数据(加密) +[scp详解](http://www.cnblogs.com/peida/archive/2013/03/15/2960802.html) + +**curl** + +Curl是一个命令行方式下传输数据的开源传输工具,支持多种协议包括:FTP,HTTP,HTTPS,IMAP,POP3,TELNET等。同样支持HTTP POST方法,PUT方法,FTP上传,cookie,用户名/密码认证,下载文件端点续传等,功能十分强大。 + +常用的,用于模拟浏览器请求。 +[curl详解](http://blog.csdn.net/zzzmmmkkk/article/details/38569057) + +**wget** + +测试速率 +``` +wget -S http://115.231.74.93:80/lvs.lxdns.net/test.rar && rm -rf test.rar* +``` + + + +### 1.8 文件压缩 +**zcat** + +查看压缩的文件内容 +``` +zcat file.gz +``` +Linux上的压缩格式比Windows上多很多,在 Windows 上最常见的不外乎这三种 `*.zip`,`*.rar`,`*.7z` 后缀的压缩文件。而在 Linux 上面常见的格式除了以上三种外,还有 `*.gz`,`*.xz`,`*.bz2`,`*.tar`,`*.tar.gz`,`*.tar.xz`,`*.tar.bz2`,对于常见的压缩格式,tar已经可以解决,所以这里只介绍tar。tar并不能压缩和解压7z,zip等其他文件 + +**tar** + +压缩示例 +``` +# test是当前目录下一个文件夹 +$ tar -czvphf test.tar.gz old_folder +``` +参数解释 +``` +-c 指明创建tar文件 +-z 指明生成gz文件 + +-v 可视输出,不加就静默压缩 + +-f 指明压缩后的文件名,必须-f后必须紧跟文件名,否则无效 +-p 当在其他主机还原时希望保留文件的属性 +-h 备份链接指向的源文件而不是链接本身 + +``` +解包示例 +``` +$ mkdir new_folder +$ tar -xzvf test.tar.gz -C new_folder +``` +参数解释 +``` +-x 解压命令 +-z 指明源文件是gz文件 +-f 指明压缩后的文件名,必须-f后必须紧跟文件名,否则无效 + +-v 可视化输出解包过程,一般不加,静默解包 + +-C 指明解压到哪个目录 +``` +其他压缩格式 +``` +*.tar.gz -z +*.tar.xz -J +*tar.bz2 -j + +``` +**gz** + +最简单的压缩格式 + +``` +# 压缩 +gzip somefile + +# 解压 +gzip -d somefile.gz +``` + +**7zip** + +``` +yun install p7zip + +# 解压 +7za x file.7z + +# 压缩 +# 先将要压缩的文件都放到~/test/ 下 +7za a file.7z ~/test/ +``` +**zip** + +``` +# 解压 +unzip some.file + +# 压缩 +zip -r some.zip file1 file2 dir1 dir2... + +# 如果你的目标zip包没有 . ,那么zip会自动加上后缀 .zip +zip stuff * + +# 打包包含隐藏文件的包 +zip stuff .* * + +# 打包 foo 文件夹的文件,但是不包含文件夹本身 +zip -j foo foo/* + +# 断点打包,在磁盘空间不足的使用可以使用 +zip -rm foo foo/tom +zip -rm foo foo/dick +zip -rm foo foo/harry + +# 分多个包,如果foo文件夹下有5G大小的文件,那么会分成两个2g的,一个1g的三个包,分别命名:split.z01,split.z02,split.zip +zip -s 2g -r split.zip foo +``` + +### 1.9 rpm包管理 + +``` +# 解压rpm包 +rpm2cpio xxx.rpm |cpio -div + +# 查看文件属于哪个rpm包 +rpm -qf /path/filename + +# 使用rpmrebuild重新生成rpm包 +# 使用rpmrebuild需要依赖rpmbuild:yum install -y rpmbuild +rpmrebuild xxx +``` + +### 1.10 ftp工具使用 + +```shell +$ ftp +ftp> help +``` + +![](http://image.iswbm.com/20190705182629.png) + + + +## 二、系统管理 + +### 2.0 环境变量 + +变量分为用户变量(env)和shell变量(set) +``` +# 添加shell变量 + +[root@host ~]# myuser=wangbm +[root@host ~]# echo $myuser +wangbm +[root@host ~]# env|grep myuser +[root@host ~]# set|grep myuser +myuser=wangbm + + +# 添加用户变量,会发现用户变量会覆盖shell变量 +[root@host ~]# export myuser=wangbingming +[root@host ~]# env|grep myuser +myuser=wangbingming +[root@host ~]# set|grep myuser +myuser=wangbingming + + +# 删除环境变量 +[root@host ~]# unset myuser +[root@host ~]# env|grep myuser +[root@host ~]# set|grep myuser +``` +一些系统的变量 +``` +# 这里生成的变量,对所有用户生效 +/etc/profile + +# 对特定user生效 +/root/.bash_profile +/home/user/.bashrc + +# 注意修改文件后,要手动source一下 +``` + +### 2.1 相关查询 + +#### 系统信息 + +``` +# 查询开机时间 +$ who -b + +# 查询系统内核 +$ uname -r + +# 查询是否安装某个rpm包 +$ rpm -qa|grep nova + +# service 文件目录 +$ /usr/lib/systemd/system # centos 7.x +$ /etc/init.d # centos 6.x + +# 查询开机自启列表 +$ systemctl list-unit-files +$ chkconfig --list [service_name] + 等级0表示:表示关机 + 等级1表示:单用户模式 + 等级2表示:无网络连接的多用户命令行模式 + 等级3表示:有网络连接的多用户命令行模式 + 等级4表示:不可用 + 等级5表示:带图形界面的多用户模式 + 等级6表示:重新启动 + +# 查看系统版本 +$ cat /etc/redhat-release + +# ------或者------ +$ yum install redhat-lsb -y +$ lsb_release -a + +# 进程树 +$ pstree -p + +# 查看系统运行多长时间 +$ uptime/w + +# 查看系统版本 +lsb_release -a + +# 查看CPU信息 +$ cat /proc/cpuinfo +$ numactk -H + + +# ubuntu 查看更新有哪些是安全更新 +$ apt-get -s --no-download dist-upgrade -V | grep "^Inst.*security.*$" | cut -d " " -f 2 + +# ubuntu 登陆时,显示的安全更新是如何来的,内容:/etc/update-motd.d/90-updates-available +/usr/lib/update-notifier/apt-check --human-readable + +``` + + + +#### 时间查询 + +输出操作系统的当前日期、时间和时区。 +``` +# -s参数用于修改当前的日期和时间 +date –s 2007-10-17 +date –s 18:05:00 + +用法:cd /CNCLog/cache/qsLogBackSrcFull/bkDir/`date +%Y-%m-%d` +``` + +### 2.2 系统分区 + +#### 分区介绍 +Linux的分区,不同于Windows,一定要区别对待,不然会搞不明白。 + +Linux的分区的过程经历以下几个步骤 +``` +1. 设备分区:对硬盘存储空间的划分 +2. 格式化:写入文件系统 +3. 挂载:将分区挂载到目录上,才能访问数据 +``` +关于硬件对应的设备文件名,可以参照下图 +![](http://image.iswbm.com/17-10-15/97911325.jpg) + +其中以硬盘为例来说明 +``` +硬盘可以分为三种 +hd : IDE硬盘接口(淘汰,接口最大传输100来M) +sd : SCSI硬盘接口(淘汰,接口最大传输200M),和SATA硬盘接口 + +现在都是SATA的硬盘接口 + +/dev/sda1 表示的是第一块(a)SATA硬盘的第一个分区(1) +/dev/sdb2 表示的是第二块(b)SATA硬盘的第二个分区(2) +``` +分区类型 +可以分为:`主分区` 、`扩展分区` 、`逻辑分区` +``` +【主分区】:最多只能有4个 (受硬盘结构限制,如果硬盘结构不变,将都被限制) + +【扩展分区】: + 1. 最多只能有一个 + 2. 主分区+扩展分区,一共只能有四个,可以少于 + 3. 扩展分区下面,不能存放数据,只能进行逻辑分区的划分 + +【逻辑分区】:挂载后就是一个目录下的空间,数量不受限制 +``` +格式化做了哪些事 +``` +【目的】 +1. 不是为了清空数据 +2. 主要是为了写入文件系统 + +【文件系统】 +Windows: +FAT16(每个分区大小最大不能超过2G),FAT32(单个分区大小最大16G,单个文件大小不能超过4G),NTFS + +Linux:ext2,ext3,ext4 + +->>>>> 越往后越先进 + +【写入文件系统做了啥】 +1. 分数据块 +把空间分成若干个等大小的数据块(block),每个大小4kb +如果我们有一个文件10kb,那么会占用3个数据块,实际大小就会是12kb + +你可以查看一个文件夹的大小,也都是4kb +ll -l + +2. 建立数据表 +一个文件被分成若干个的数据块,那么如果有用户访问的时候,就需要有一个表把这些数据块拼凑起来。 +这个数据表就记录了这个文件由哪些数据块组成。 +``` +分区说明 +``` +必须分区 +/ 根目录,最高级目录,不分配的话,所有的文件都没存储,软件没法运行 +/swap 交换分区,虚拟内存,4G以下,分2倍,4G以上,和真实内存一样即可 + +推荐分区 +/boot 启动分区,防止/ 分区写满,导致系统无法启动,不需要很大,200M足矣 + + +逻辑分区号:只能从5开始,即使3,4没有被使用 +``` + +#### 分区操作 +关于分区的操作可以参考这个: [分区操作](https://www.cnblogs.com/zishengY/p/7137671.html) + + +``` +## 查看分区表信息 +$ sudo fdisk -l +$ lsblk + +# 删除分区前,先确认有没有挂载点,有的话需要先umount卸载 +$ fdisk /dev/vdb 然后再按d,w +``` + +挂载分区 fstab + +``` +# 当fstab的根分区被注释后,所有的文件都是只读的。连fstab文件也是,无法取消注释 +mount -o remount,rw / +``` + +格化化分区 + +``` +mkfs.ext4 /dev/sdb1 +``` + + + +### 2.3 进程管理 + +**ps、kill、killall** + +参考文档:[ps 命令的十个简单用法](https://www.cnblogs.com/fakerbin/p/6513365.html) + +``` +# 查看当前所有进程 +ps -aux + +# 终止pid为1095的进程 +kill 1095 + +# 强制终止pid为1095的进程 +# 9是信号强度,强制杀死 +# 其他信号,-1 该信号让进程正常关闭,然后重新读取配置文件之后重启 +# -15 正常结束进程的信号,kill命令默认信号 +kill -9 1095 + +# 终止指定程序 +killall 程序名 + +# pkill +pkill -9 httpd 强制终止进程 +pkill -t -9 pts/1 强制杀死pts/1虚拟终端登入的进程 +``` + +**top** + +常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。 +[linux的top命令参数详解](http://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316399.html) + +``` +top 实时得查看进程的状态,以及系统的一些信息(如 CPU、内存信息等),3s刷新一次 +ps 来静态查看当前的进程信息 +pstree 来查看当前活跃进程的树形结构。 +``` + +**pgrep** + +```shell +[root@wangbm web]# pgrep keepalived +21955 +21956 +21957 +[root@wangbm web]# ps -ef|grep keepalived +root 21955 1 0 21:27 ? 00:00:00 /usr/sbin/keepalived -D +root 21956 21955 0 21:27 ? 00:00:00 /usr/sbin/keepalived -D +root 21957 21955 0 21:27 ? 00:00:00 /usr/sbin/keepalived -D +root 22035 12159 0 21:33 pts/0 00:00:00 grep --color=auto keepalived +``` + +**清除僵尸进程** + +一个僵尸进程产生的过程是:父进程调用fork创建子进程后,子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中。 + +``` +ps -e -o stat,ppid,pid,cmd | grep -e '^[Zz]' | awk '{print $2}' | xargs kill -9 +``` + + + +### 2.4 设备信息管理 + +**df** + +显示磁盘的相关信息 +``` +df -Th +``` + +**free** + +输出内存的使用情况,m,g分别是指定单位,默认是kb +``` +free -m +free -g +``` +total 表示总内存大小; +used和free分别表示被使用和空闲内存大小; +share指可被多个进程共同享有的内存; buffers和cached用来保留最近访问的文件和数据,当其他进程需要更多的内存时,这些内容可以被缩减; Free命令还可以输出交换空间的相关信息。 + +**ifconfig** + +显示或设置网络设备 + + + +**last** + +列出目前与过去登入系统的用户相关信息。一般可用来查看系统重启记录 + + + +**history** + +屏幕输出当前用户在命令行模式下执行的最后(1000个)命令 + + + +**passwd** + +这个用于修改密码 + +非交互式修改密码 + +``` +echo 'root12#$'| passwd --stdin root +``` + + + +**reboot/shutdown** + +重启/关机 +``` +reboot/shutdown -r now + +# reboot [-n][-w][d][-i] 重新启动计算机,使用权限是系统管理员 +-n 重启前不将记录写回硬盘 +-w 并不是真的重启,只是把记录写道/var/log/wtmp文件中 +-d 不把记录写入/var/log/wtmp文件中 +-i 重启谦先把所有与网络相关的装备停止 +``` + +**rpm** + +``` +# 安装rpm包 +rpm -ivh monitor-system-1.2-1.i386.rpm + +# 更新rpm包 +rpm -U/Fvh (F只更新已存在的文件) + +# 查询包中的文件 +rpm -ql monitor-system + +# 查询文件所属的包 +rpm -qf /usr/local/squid/etc/squid.conf(绝对路径/到目录下查找) + +# 查询所有包 +rpm –qa | grep squid + +# 卸载某个rpm包 +rpm –e monitor-system +``` +**开启80端口** + +``` +vi /etc/sysconfig/iptables + +# 在22端口下面添加一行 +-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT + +# 重启防火墙 +/bin/systemctl restart iptables.service +``` +**修改时区** + +``` +# 由EDT(美国)改成CST(中国) + +$ mv /etc/localtime /etc/localtime.bak +$ ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime +``` + +查看机器连接的哪个交换机 + +``` +$ service lldpd restart +$ lldpcli show nei sum # 查看JS_HuaiAnDXXZ4_166.3 +------------------------------------------------------------------------------- +LLDP neighbors: +------------------------------------------------------------------------------- +Interface: eth1, via: LLDP + Chassis: + ChassisID: mac 00:6b:8e:01:1a:07 + SysName: JS_HuaiAnDXXZ4_165.132_IPMI + Port: + PortID: local 39 + PortDescr: Ethernet1/0/39 + TTL: 120 +------------------------------------------------------------------------------- +Interface: eth0, via: LLDP + Chassis: + ChassisID: mac 58:69:6c:62:7b:c2 + SysName: JS_HuaiAnDXXZ4_166.3 + Port: + PortID: ifname TenGigabitEthernet 0/41 + PortDescr: TenGigabitEthernet 0/41 + TTL: 121 +------------------------------------------------------------------------------- +``` + + + +### 2.5 磁盘管理 + +**df** + +显示指定磁盘文件的可用空间。 +这里要理解挂载的概念。 + +所有的设备(磁盘/等其他介质)都需要挂载在一个目录,Linux才能访问数据。 + +具体可以参考这篇文章:[df 命令](http://www.cnblogs.com/peida/archive/2012/12/07/2806483.html) + +**常用命令** + +``` +$ df -Th +$ df -lh + + +$ df -h 以1024来换算 +$ df -H 以1000来换算 + +$ df -t ext3 指定设备类型 +$ df -T 文件系统的类型 +$ df -i 查看inode的使用情况 +``` +关于inode可以查看:[inode的理解](https://www.cnblogs.com/itech/archive/2012/05/15/2502284.html) + +**du** + +查看目录的容量 +``` +$ du -h -d 0 ~ + +# 参数解释 +-h 以人能看得懂的方式显示 +-d 扫描的目录层级,0表示只有一个指定目录,1表示次级目录 + +~ 是家目录,这里可以选你指定的目录 + +$ du -s +# 查看当前所处目录总容量大小,单位是k,不可跟-d + +$ du -a +# 查看当前所处目录及所有子目录的所有文件,单位是k,建议不用 +``` +查看文件的大小 +``` +$ du -h file.txt +``` + +**dd** + +`dd`命令用于转换和复制文件,不过它的复制不同于`cp`。之前提到过关于 Linux的很重要的一点,一切即文件,在 Linux上,硬件的设备驱动(如硬盘)和特殊设备文件(如`/dev/zero`和`/dev/random`)都像普通文件一样,只要在各自的驱动程序中实现了对应的功能,`dd`也可以读取自和/或写入到这些文件。这样,`dd`也可以用在备份硬件的引导扇区、获取一定数量的随机数据或者空数据等任务中。dd程序也可以在复制时处理数据,例如转换字节序、或在 `ASCII` 与 `EBCDIC`编码间互换。 + + +这里有篇文章讲得很好:[Linux-dd命令详解](http://www.cnblogs.com/dkblog/archive/2009/09/18/1980715.html) + +补充一点 +``` +# 输出到文件 +$ dd of=test bs=10 count=1 # 或者 dd if=/dev/stdin of=test bs=10 count=1 + +# 输出到标准输出 +$ dd if=/dev/stdin of=/dev/stdout bs=10 count=1 +# 注 +在打完了这个命令后,继续在终端打字,作为你的输入 +``` +前面说到dd在拷贝的同时还可以实现数据转换,那下面就举一个简单的例子:将输出的英文字符转换为大写再写入文件 +``` +dd if=/dev/stdin of=test bs=10 count=1 conv=ucase +``` + + +#### 制作虚拟镜像并挂载 +``` +# 制作虚拟镜像 +$ dd if=/dev/zero of=virtual.img bs=1M count=256 +$ mkfs.ext4 virtual.img + +# 使用一个循环设备(/dev/loop)连接虚拟镜像文件 +$ sudo losetup /dev/loop0 virtual.img + +# 挂载前查看下当前已挂载的设备 +$ sudo mount + +# 挂载 +$ sudo mkdir /mnt/point +$ sudo mount /dev/loop0 /mnt/point + +# 上面连接虚拟镜像文件和挂载设备两条命令可以合并成一条,挂载类型可以省略,会自动识别 +$ sudo mount -o loop [-t ext4] /mnt/point + +# 再次查看挂载的设备,就可以发现我们新挂载的 +$ sudo mount + + +# 卸载:一定要加-fl。不然无法卸载 +$ sudo umount -fl /mnt/point + +# 查看所有与文件相关联的 loop 设备 +$ losetup -a +# 卸载 +$ losetup -d /dev/loop0 +``` + +使用dd拷贝磁盘可以,但是如果想要快速生成大文件,速度就相当慢了,可以试试fallocate命令 + +```shell +fallocate -l 100G test_file +``` + +### 2.6 任务管理 + +#### 一次性任务(at) +##### 创建定时任务 +``` +$ sudo apt-get install at +$ at now+5 minutes +at>/bin/ls +at> # 就是Ctrl+d +job 2 at Sum Oct 15 14:58:00 2017 # 结束的时候,告知执行时间 +``` +##### 管理定时任务 +``` +$ atq # 可以查看当前还有那些定时任务,会显示任务号 + +# 查看具体任务代码 +$ at -c + +# 删除任务 +$ atrm +``` +#### 例行性任务(crontab) +参考以下文章 +1. [每天一个linux命令(50):crontab命令](http://www.cnblogs.com/peida/archive/2013/01/08/2850483.html) +2. [鸟哥的私房菜](http://linux.vbird.org/linux_basic/0430cron.php) + +做个示例 +``` +# 检查crond服务是否启动 +$ ps aux|grep crond + +# 创建任务 +$ crontab -e + +# 输入1 回车 +# 任务是:每天凌晨3点备份日志到/home/temp/目录,文件名为日期 +# 跳到文件尾部输入任务:* 3 * * * cp alternatives.lob /home/temp/$(date \%Y-\%m-\%d)。保存退出 +# 注意%需要加\来转义,不然会被当成换行使用 + +# 查看任务 +$ crontab -l + +# 删除任务(当前用户),当然也可以指定用户 +$ crontab -r [-u user] + +``` + +除了以上,例行性任务还有可能在 /etc/cron.daily/ ,/etc/cron.hourly/, /etc/cron.monthly/, /etc/cron.weekly/ 下面。 + +如果messages 不能正常回滚,可以考虑加个参数 `-f` + +``` +/usr/sbin/logrotate -f /etc/logrotate.conf +``` + +如果还不行,检查一下 `/etc/logrotate.d/syslog` + +``` +chmod 644 /etc/logrotate.d/syslog +``` + +## 三、用户/权限管理 + +### 3.1 用户管理 + +**创建用户** + +``` +【useradd和adduser区别】 +useradd:只创建用户,创建完了用 passwd lilei去设置新用户的密码。更像一种命令。 + +adduser:会创建用户,创建目录,创建密码(提示你设置),做这一系列的操作。更像是一个程序,执行确认一系列操作。 +``` + +**删除用户** + +``` +sudo deluser lilei --remove-home +``` + +**切换用户** + +``` +su [user] # 切换到其他用户,环境变量不变,还是当前路径,如果不指定user,就切换到root + +su - [user] # 切换到其他用户,环境变量也切换,路径变为user家目录,如果不指定username,就切换到root,相当于使用user进行登录 +``` + +Ubuntu切换用户 +``` +# 切换到root +$ sudo su + +# 切换到普通用户 +$ su user +``` +CentOS + +``` +$ su +$ su root +$ su - +$ su -root + +$ su user +$ su -user +``` +**用户组** + +``` +1. 每个用户都至少属于一个用户组,创建的时候如果不指定,就和当前用户的组一样(root用户组除外)。 +2. 一个用户可以属于多个用户组。 +``` + +``` +# 查看所属用户组 +$ groups [user] +``` + +**把用户加入到用户组** + +```shell +# 把 zabbix 加入到 root 用户组 +usermod -G root zabbix +``` + +另外还有其他方法,可查阅:[在 Linux 中把用户添加到组的四个方法](https://linux.cn/article-10768-1.html) + +**UID/GID/组ID** + +``` +# 显示相关信息,如要查看root用户的信息 +id root + +# 所有文件都保存在/etc/)asswd +vi /etc/passwd +``` + +管理用户:[查看用户的UID和GID](http://blog.csdn.net/jackailson/article/details/50993427) +管理密码:[Linux下/etc/shadow文件](http://blog.csdn.net/u011641885/article/details/46681697) + + + +**添加sudo用户组** + +``` +【第一种方法】 +在root下,visudo或者vi /etc/sudoers,找到root ALL=(ALL)的下一行添加一行,user ALL=(ALL),user是对应的用户名 + +【第二种方法】 +在root下,使用命令sudo usermod -G sudo user,将user加入sudo用户组 +``` + +**sudo免密** + +当有些操作只有root用户才能操作的时候,怎么办? +1.我们需要切换到root用户操作。 +2. 当前用户属于sudo组,可以使用sudo [command] +3. 使用sudo输入一次密码免密使用5分钟。还是太麻烦,可以配置当前用户免密执行sudo。 + +如何免密配置 +``` +vi /etc/sudoers.d/ + +# 添加内容 +# 如果要指定特定的命令不需要密码的话,就把ALL替换成命令路径,如下 +# NOPASSWD: /sbin/mount, (root) NOPASSWD: /bin/umount + ALL=(ALL) NOPASSWD:ALL +Defaults:shiyanlou !requiretty + +# 有时候,虽然用户设置免密了,但是还是需要输入密码,是group覆盖了,需要把group也设成免密。 +``` + +参考资料:[sudo免密](http://www.cnblogs.com/kungfupanda/p/4305049.html) + + + +### 3.2 权限管理 + +**更改文件所有者和所属组** + +``` +# [sudo] chown 用户组:用户 文件/文件夹,以下两种均可 +chown root:zabbix some.txt +chown root.zabbix some.txt + +# 只更改所属用户,[sudo] chowm 用户 文件/文件夹 +chown zabbix some.txt + +# 只更改所属用户组,[sudo] chown .用户组 文件/文件夹 +chown .root some.txt +``` + +**修改文件权限** + +文件权限有`读`、`写`、`执行`三种 +分别对应数字4,2,1,也就是2^2,2^1,2^0 + +如何修改文件权限 +``` +【第一种方法】 +chmod 777 文件 + +【第二种方法】:加减的方法 +g、o、u、a分别表示 group、others、user和all ++、-、= 分别表示增加、去掉和设置相应的权限。 + +举个例子 +比如一个文件权限是:-wr-wr-wr- +chmod go-wr 文件 + +然后文件权限就变成:-wr------- + +再设置回原来的 +chmod ugo=wr 文件 + +或者将user和group改为可执行 +chmod ug=wrx,o=wr 文件 +``` +**禁止修改、删除、移动文件** + +`chattr -i`和`chattr +i` + +``` ++ :在原有参数设定基础上,追加参数。 +- :在原有参数设定基础上,移除参数。 + +命令:chattr [ -RV ] [ -v version ] [ mode ] files + +A: 文件或目录的 atime (access time)不可被修改(modified), 可以有效预防例如手提电脑磁盘I/O错误的发生。 +S: 硬盘I/O同步选项,功能类似sync。 +a: 即append,设定该参数后,只能向文件中添加数据,而不能删除,多用于服务器日志文 件安全,只有root才能设定这个属性。 +c: 即compresse,设定文件是否经压缩后再存储。读取时需要经过自动解压操作。 +d: 即no dump,设定文件不能成为dump程序的备份目标。 +i: 设定文件不能被删除、改名、设定链接关系,同时不能写入或新增内容。i参数对于文件 系统的安全设置有很大帮助。 +j: 即journal,设定此参数使得当通过 mount参数:data=ordered 或者 data=writeback 挂 载的文件系统,文件在写入时会先被记录(在journal中)。如果filesystem被设定参数为 data=journal,则该参数自动失效。 +s: 保密性地删除文件或目录,即硬盘空间被全部收回。 +u: 与s相反,当设定为u时,数据内容其实还存在磁盘中,可以用于undeletion. +``` + +切换用户执行命令 + +``` +$ su - zabbix -s /bin/bash + +# 退出的话,使用exit +``` + +## 四、网络管理 + +### 4.1 iptables +命令格式 +``` +iptables [-t table] 命令 [chain] [rules] [-j target] + +【参数解释】 +table 表名:filter、nat、mangle、raw,后两者不常用 +命令 对链的操作命令 + -P或–policy 定义默认策略 + -L或–list 查看iptables规则列表 + -A或—append 在规则列表的最后增加1条规则 + -I或–insert 在指定的位置插入1条规则 + -D或–delete 从规则列表中删除1条规则 + -R或–replace 替换规则列表中的某条规则 + -F或–flush 删除表中所有规则 + -Z或–zero 将表中数据包计数器和流量计数器归零 +chain 链名:PREROUTING,INPUT,FORWARD,OUTPUT,POSTROUTIN + +rules 规则,可以说是匹配规则。 + 分为 + 1. 【通用匹配】 + -s: 指定作为源地址匹配,必须是IP,取反,就加一个! + -d: 表示匹配目标地址 + -p: 用于匹配协议的(这里的协议通常有3种,TCP/UDP/ICMP) + -i eth0:从这块网卡流入的数据,流入一般用在INPUT和PREROUTING上 + -o eth0:从这块网卡流出的数据,流出一般在OUTPUT和POSTROUTING上 + + 2. 【扩展匹配】 + -p tcp: TCP协议的扩展。一般有三种扩展 + --dport: 指定目标端口,--dport 21或者 --dport 21-23 (此时表示21,22,23),不能表示非连续端口 + --sport: 指定源端口 + + --tcp-fiags: TCP的标志位(SYN,ACK,FIN,PSH,RST,URG) + 对于它,一般要跟两个参数: + 1.检查的标志位 + 2.必须为1的标志位 + --tcpflags syn,ack,fin,rst syn = --syn + 表示检查这4个位,这4个位中syn必须为1,其他的必须为0。所以这个意思就是用于检测三次握手的第一次包的。对于这种专门匹配第一包的SYN为1的包,还有一种简写方式,叫做--syn + + -p udp: UDP协议的扩展 + --dport + --sport + + -p icmp: icmp数据报文的扩展 + --icmp-type: + echo-request(请求回显), 一般用8 来表示 + echo-reply (响应的数据包) 一般用0来表示 +``` + +参考文章 +1. [ubuntu配置iptables](http://wiki.ubuntu.org.cn/IptablesHowTo) +2. [netfilter/iptables全攻略](http://www.linuxso.com/linuxpeixun/10332.html) +3. [iptables详解](http://blog.chinaunix.net/uid-26495963-id-3279216.html) + +### 4.2 端口相关 + +查询端口占用情况 + +``` +lsof -i:10051 +``` + +开放端口 + +``` +# 在filter表里添加规则 +-A INPUT -m state --state NEW -m tcp -p tcp --dport 8500 -j ACCEPT + +# 重启iptables +$ service iptables restart + +# 监听端口 +$ nc -lp 8500 & + +# 检测是否开启完成 +$ netstat -tunl|grep 8500 +``` + +端口转发 + +``` +# 按开放端口配置好后才可转发。 +# 假设我们现在要将36.250.x.x的8500端口转发到192.168.2.55的80端口上 + +# filter表 +-A INPUT -m state --state NEW -m tcp -p tcp --dport 8500 -j ACCEPT + +# nat表 +-A PREROUTING -d 36.250.x.x -m tcp -p tcp --dport 8500 -j DNAT --to-destination 192.168.2.55:80 + +# 测试一下,在浏览器输入 36.250.x.x:8500 +# 就可以看到我们的内容了。 +``` + +### 4.3 路由相关 + +路由转发:snat + +``` + +# 1、开启转发 +# vim /etc/sysctl.conf,sysctl -p 该命令可以查看是否修改成功 +net.ipv4.ip_forwaed=1 + +# 或者可以执行下面这两条命令其中一条 +echo 1 > /proc/sys/net/ipv4/ip_forward +sysctl -w net.ipv4.ip_forward=1 + +# 2、在网关节点的 /etc/sysconfig/iptables 加上规则。 +*nat +:PREROUTING ACCEPT [4730:370403] +:INPUT ACCEPT [501:29436] +:OUTPUT ACCEPT [3563:214910] +:POSTROUTING ACCEPT [3564:214973] +-A POSTROUTING -s 172.20.20.0/24 -o eth0 -j SNAT --to-source 58.xx.xx.xx +COMMIT + +# 重启iptables +systemctl restart iptables +``` + +### 4.4 ip 命令 + +``` +# ubuntu 临时修改网络,需要刷新一下 +ip addr flush dev ens4 +``` + + + +## 五、Shell命令 + +### 5.1 执行顺序控制 +``` +1、&& +方式:command1 && command2 +如果command1执行成功,则执行command2 + +2、|| +方式:command1 || command2 +如果command1执行失败,则执行command2 +``` +### 5.2 管道通信 + +``` +$ ps aux | grep mysqld + +# 将ps aux得到的结果传给grep命令 +``` +### 5.3 重定向 + +`>/dev/null 2>&1 &` + +分析下这个语句 +`command >/dev/null 2>&1 &` +执行command后的标准输出不在屏幕显示,而是直接丢入/dev/null 垃圾桶,如果有错误输出,则重定向到标准输出。最后&表示在后台运行。 + +### 5.4 其他常用 + +【nohup】 + +一般在一终端或一个SSH连接运行一个软件或服务,该软件或服务的生命周期受终端/SSH连接影响,关闭后就自动也停止。 +为了将程序放在后台运行,可以使用nohup命令 +``` +$ nohup 程序路径/程序名 & +``` + +【alias】 +给常用的长命令取别名,变成短的,提高效率 +``` +# 查看现有别名 +$ alias + +# 添加别名 +$ alias catgra='cat /var/lib/mysql/grastate.dat' + +# 取消别名状态 +# 比如我们的ll,系统默认给我们加了别名,ll='ls -l --color=auto',也就是加上颜色效果 +# 如果我们不要颜色效果,可以这样使用转义符,使用原生的命令 +$ \ll somedir +``` +这里转几条来自 「**良许Linux**」的总结,非常实用,你可以跟着设置一下 + +1、压缩包文件,特别是 tar 文件在 Linux 下使用非常广泛,但是 tar 命令的选项又非常多,也不好记住。所以我们可以将常用的几个选项定义为一个别名 **untar** ,这样我们需要解压 tar 文件时,直接 **untar filename** 即可。 + +``` +alias untar='tar -zxvf ' +``` + +2、我们下载一个很大的文件时,突然网络异常中断了,我们重新下载是不是很抓狂?别担心,我们的 wget 命令有个 -c 选项,支持断点下载,我们也可以将它设置为别名: + +``` +alias wget='wget -c ' +``` + +3、有时我们需要生成一个 20 个字符的随机数密码,我们可以使用 openssl 命令,但完整的命令又很长很不方便,我们可以设置别名: + +``` +alias getpass="openssl rand -base64 20" +``` + +4、下载一个文件之后,我们想要校验一下它的 checksum 值,可以将这个命令封装为一个别名 **sha** ,之后我们 **sha filename** 就可以校验文件的 checksum 值。 + +``` +alias sha='shasum -a 256 ' +``` + +5、正常情况下,ping 命令将无限次输出,但其实没多大意义。我们可以使用 -c 命令将其限制为 5 次输出,然后设置为别名 **ping** ,使用时,**ping url** 即可。 + +``` +alias ping='ping -c 5' +``` + +6、如果我们想随时随地启动一个 web 服务器,我们可以使用这个别名: + +``` +alias www='python -m SimpleHTTPServer 8000' +``` + +7、网速的测试在工作中也经常用到,但 Linux 没有自带命令可用,我们可以借助第三方工具 **speedtest-cli** 。这个工具可以直接从 Github 上下载,使用方法里面也有详细介绍。我们需要先使用 **speedtest-cli** 命令来选择离我们最近的服务器,然后设置如下别名: + +``` +alias speed='speedtest-cli --server 2406 --simple' +``` + +8、你的公网 IP 是多少?记性好的可以直接背下来,但如果你有 10 台上百台服务器呢?也可以背下来,然后参加最强大脑。其实有个命令可以直接查询,但那个命令太变态,不好记,果断设置为别名。 + +``` +alias ipe='curl ipinfo.io/ip' +``` + +9、如何知道自己的局域网 IP ?这个命令同样变态,果断设置别名。 + +``` +alias ipi='ipconfig getifaddr en0' +``` + +10、最后,清屏,我们可以使用 **ctrl + l** 快捷键,也可以将 **clear** 命令定义得更短,这样使用起来更直接,更粗暴。 + +``` +alias c='clear' +``` + +【column】 + +一个很好用的命令,经常用于管道符后,进行文本展示 +``` +$ mount +sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime) +proc on /proc type proc (rw,nosuid,nodev,noexec,relatime) +devtmpfs on /dev type devtmpfs (rw,nosuid,size=3996196k,nr_inodes=999049,mode=755) +securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime) + +# 使用后,t是按表格的形式展示,-s "@" 指定分割符 +$ mount | column -t +sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime) +proc on /proc type proc (rw,nosuid,nodev,noexec,relatime) +devtmpfs on /dev type devtmpfs (rw,nosuid,size=3996196k,nr_inodes=999049,mode=755) +securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime) +``` + + +## 六、业务相关 + + +查看虚机的CPU是否支持虚拟化 + +``` +egrep '(vmx|svm)' /proc/cpuinfo +# 如果有标红的vmx(Intel)、svm(AMD)说明就支持,virtualbox不支持(坑) + +# 或者直接查看 +vi /proc/cpuinfo +``` +查看模块是否加载 +``` +lsmod | grep kvm +``` +开启服务 +``` +# CentOS 6 +service xx start + +# CentOS 7 +systemctl start libvirtd +``` +开机自启动服务 +``` +CentOS 6 +chkconfig acpid on + +# CentOS 7 +systemctl enable libvirtd +``` +开启shh登录,修改端口等配置 +``` +vi /etc/ssh/sshd_config +------------------------------------- +Port 57891 +PasswordAuthentication yes +ClientAliveInterval 60 +ClientAliveCountMax 10 +``` +关闭防火墙 +``` +setenforce 0 +service firewalld stop +chkconfig firewalld off +``` +换yum源 +``` +yum install wget + +mv /etc/yum.repos.d/CentOS-Base.repo /root/ + +# 下载yum源,这里是CentOS6的,请下载对应版本 +wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo + +yum clean all +yum makecache +``` + +磁盘修复 + +```shell + xfs_repair /dev/vda2 -L +``` + + + +## 附:推荐阅读 + +- [命令行的艺术](https://github.com/jlevy/the-art-of-command-line/blob/master/README-zh.md) + + + +--- + +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c07/c07_01.rst b/source/c07/c07_01.rst old mode 100755 new mode 100644 index 0e49392..dab1083 --- a/source/c07/c07_01.rst +++ b/source/c07/c07_01.rst @@ -1,6 +1,8 @@ 7.1 Linux 命令行的艺术 ====================== +|image0| + -------------- 一、目录/文件 @@ -519,7 +521,7 @@ awk是一个强大的文本分析工具。 **grep:搜索神器** 搜索并筛选显示结果。 该命令经常配合管道命令来控制输出。 以下 -是常用的选项: |image0| +是常用的选项: |image1| 【非常好用:不打开文件,直接搜索指定目录下文件内的内容】 @@ -740,7 +742,7 @@ Linux上的压缩格式比Windows上多很多,在 Windows 上最常见的不 $ ftp ftp> help -|image1| +|image2| 二、系统管理 ------------ @@ -876,7 +878,7 @@ Linux的分区的过程经历以下几个步骤 2. 格式化:写入文件系统 3. 挂载:将分区挂载到目录上,才能访问数据 -关于硬件对应的设备文件名,可以参照下图 |image2| +关于硬件对应的设备文件名,可以参照下图 |image3| 其中以硬盘为例来说明 @@ -1935,12 +1937,11 @@ url** 即可。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image4| -.. |image0| image:: http://image.python-online.cn/17-9-20/47469030.jpg -.. |image1| image:: http://image.python-online.cn/20190705182629.png -.. |image2| image:: http://image.python-online.cn/17-10-15/97911325.jpg +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |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 40e7226..93fd94a 100644 --- a/source/c07/c07_02.md +++ b/source/c07/c07_02.md @@ -1,16 +1,18 @@ # 7.2 Zabbix 监控部署文档 +![](http://image.iswbm.com/20200602135014.png) + --- ## 7.2.1 Zabbix架构图 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 可以非常简便的实现了集中式、分布式监控。 @@ -205,7 +207,7 @@ $ systemctl start zabbix-proxy 在使用 Proxy 的时候,需要在 Server 端的web 界面上,先注册一下。 -![](http://image.python-online.cn/20190404201313.png) +![](http://image.iswbm.com/20190404201313.png) @@ -254,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) @@ -264,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) 如果以上这些不能满足你的需要,也没有关系,你也可以自己写脚本获取监控数据。 @@ -286,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` 这个脚本。 @@ -301,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) @@ -313,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样式,也是我网上找来的,我觉得还挺不错,这里也贴出来 @@ -353,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 样式 @@ -389,7 +391,7 @@ openstack-nova-api 是一个服务名,它将作为一个参数,传递给`isA 上面邮件中,有设置了一些颜色的自定义宏,我是在这里设置的。 -![](http://image.python-online.cn/20190404205837.png) +![](http://image.iswbm.com/20190404205837.png) 上面的动作,都是写的发邮件,也可以远程执行脚本,比如,我们监控服务,当服务被人为关闭了,我们可以让Zabbix Agent执行重启服务的命令。 @@ -418,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 配置发件人 @@ -426,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 的邮件,则说明配置成功。 @@ -457,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 是一个脚本,需要我们来自己写,内容就是如何将我们的告警内容发送出去。 @@ -475,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) 配置好收件人后 ,发件人呢? @@ -501,7 +503,7 @@ service postfix stop zabbix 自带 mysql 的监控模板,监控项不多,只有 14 项。 -![](http://image.python-online.cn/20190409103417.png) +![](http://image.iswbm.com/20190409103417.png) 这个模板在使用前,需要进行两个配置。 @@ -527,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` 这个模板上。 @@ -636,4 +638,4 @@ update items set history='30d' where hostid in (select hostid from hosts where h --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c07/c07_02.rst b/source/c07/c07_02.rst old mode 100755 new mode 100644 index a79bfc4..513c842 --- a/source/c07/c07_02.rst +++ b/source/c07/c07_02.rst @@ -1,6 +1,8 @@ 7.2 Zabbix 监控部署文档 ======================= +|image0| + -------------- 7.2.1 Zabbix架构图 @@ -10,12 +12,12 @@ zabbix 的使用架构主要有两种,如果你的监控环境比较单一,可以使用简单的\ ``Server`` -> ``Agent``\ ,由zabbix-agent 直接上报给 zabbix-server。 -|image0| +|image1| 如果监控的环境比较复杂,就比如我们的线上的生产环境是分布式集群,最好使用的 ``Server``-> ``Proxy`` -> ``Agent`` -|image1| +|image2| Zabbix Proxy 可以代替 Zabbix Server 检索客户端的数据,然后把数据汇报给 Zabbix Server, 并且在一定程度上分担了 Zabbix Server 的压力。Zabbix @@ -234,7 +236,7 @@ Zabbix Proxy 是一个数据收集器,它不计算触发器、不处理事件 在使用 Proxy 的时候,需要在 Server 端的web 界面上,先注册一下。 -|image2| +|image3| 7.2.2.3 部署 Agent 端 ~~~~~~~~~~~~~~~~~~~~~ @@ -282,26 +284,26 @@ Agent 就会发送心跳到 Proxy ,再转到Zabbix Server上,Server 按照如下图点击,Event source注意选择\ ``Auto registration`` -|image3| - |image4| +|image5| + 7.2.3.2 配置监控项 ~~~~~~~~~~~~~~~~~~ 一般情况下,我们的监控项,不会独立存在,而是依托于模板而存在。所以我们在创建监控项前,要首先创建一个模板。 -|image5| +|image6| 创建完模板后,点进模板的 items 按钮,正常情况下,这里会有很多监控项,但由于我们还没有创建,所以现在一个也没有。现在你可以自己点右上角(\ ``Create Item``\ )自己创建一个,创建界面是这样的 -|image6| +|image7| 这里说一下,zabbix 自带有许多的模板,其实对于一些党规的监控项(说白了,就是zabbix给我们造好了轮子),这些模板已经足够了。这边只截了一小部分。 -|image7| +|image8| 如果以上这些不能满足你的需要,也没有关系,你也可以自己写脚本获取监控数据。 @@ -318,7 +320,7 @@ Agent 就会发送心跳到 Proxy ,再转到Zabbix Server上,Server 那我在 web 界面上配置 监控项,就可以这样写 -|image8| +|image9| openstack-nova-api 是一个服务名,它将作为一个参数,传递给\ ``isActive.sh`` 这个脚本。 @@ -337,11 +339,11 @@ Proxy,然后由Proxy上报Server。 你可以通过点击下图的操作,查看最近上报的数据(我这里选择value直接查看值,你可以选择Graph,按图表的形式查看) -|image9| +|image10| 数据是这样的。 -|image10| +|image11| 7.2.3.4 配置动作 ~~~~~~~~~~~~~~~~ @@ -349,11 +351,11 @@ Proxy,然后由Proxy上报Server。 假如,我们要监控当 CPU 使用率超过90% 就发个通知邮件,那我们就要新增一个发邮件的动作。 -|image11| +|image12| 然后点击 ``Operations``\ , 添加触发的动作类型,比如发邮件 -|image12| +|image13| 其中的HTML样式,也是我网上找来的,我觉得还挺不错,这里也贴出来 @@ -391,7 +393,7 @@ Proxy,然后由Proxy上报Server。 按钮,它是说当我们的问题解决后,要让zabbix做些什么,比如我想让 当CPU的使用率降下来后,发一个邮件通知一下。 -|image13| +|image14| 邮件的 HTML 样式 @@ -427,7 +429,7 @@ Proxy,然后由Proxy上报Server。 上面邮件中,有设置了一些颜色的自定义宏,我是在这里设置的。 -|image14| +|image15| 上面的动作,都是写的发邮件,也可以远程执行脚本,比如,我们监控服务,当服务被人为关闭了,我们可以让Zabbix Agent执行重启服务的命令。 @@ -459,7 +461,7 @@ Agent执行重启服务的命令。 那在ACTION 里如何配置呢,看下图。你可以选择在 agent或者proxy,或者server执行都可以,非常灵活。 -|image15| +|image16| 7.2.3.5 配置发件人 ~~~~~~~~~~~~~~~~~~ @@ -468,7 +470,7 @@ Zabbix 要能发送邮件,需要配置邮箱发送方。这里以163邮箱为 vim /etc/mail.rc -|image16| +|image17| 配置完成后,可用以下命令测试一下配置是否有效,若 yyy@163.com 能收到一条来自xxx@163.com 的邮件,则说明配置成功。 @@ -499,7 +501,7 @@ outlook 的形式展现。 先添加媒介:Administration - Media Types - Create media type -|image17| +|image18| 上面用到 sendmail.sh 是一个脚本,需要我们来自己写,内容就是如何将我们的告警内容发送出去。 @@ -520,11 +522,11 @@ Script parameters。 然后再创建用户,并添加邮箱:Administration - Users - Admin - Media -|image18| +|image19| 在创建用户的时候,要指定用户组,要注意这个用户组的权限一定要有相应主机组的权限,否则无法发送告警邮件。 -|image19| +|image20| 配置好收件人后 ,发件人呢? @@ -547,7 +549,7 @@ Script parameters。 zabbix 自带 mysql 的监控模板,监控项不多,只有 14 项。 -|image20| +|image21| 这个模板在使用前,需要进行两个配置。 @@ -577,7 +579,7 @@ zabbix 自带 mysql 的监控模板,监控项不多,只有 14 项。 能够及时将配置文件路径修改过来就行。如下图所示,你只要修改下方 ``HOME`` 变量值。 -|image21| +|image22| 然后记得去 web界面在 该host主机上link到 ``Template DB MySQL`` 这个模板上。 @@ -693,31 +695,30 @@ float ,log, text 等,所以计算存在一定的误差,需留有冗余 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190404193811.png -.. |image1| image:: http://image.python-online.cn/20190404194416.png -.. |image2| image:: http://image.python-online.cn/20190404201313.png -.. |image3| image:: http://image.python-online.cn/20190404205221.png -.. |image4| image:: http://image.python-online.cn/20190404205617.png -.. |image5| image:: http://image.python-online.cn/20190404202122.png -.. |image6| image:: http://image.python-online.cn/20190404202353.png -.. |image7| image:: http://image.python-online.cn/20190404210213.png -.. |image8| image:: http://image.python-online.cn/20190404213125.png -.. |image9| image:: http://image.python-online.cn/20190404202855.png -.. |image10| image:: http://image.python-online.cn/20190404202937.png -.. |image11| image:: http://image.python-online.cn/20190404203425.png -.. |image12| image:: http://image.python-online.cn/20190404203805.png -.. |image13| image:: http://image.python-online.cn/20190404204212.png -.. |image14| image:: http://image.python-online.cn/20190404205837.png -.. |image15| image:: http://image.python-online.cn/20190404212423.png -.. |image16| image:: http://image.python-online.cn/20190411205822.png -.. |image17| image:: http://image.python-online.cn/20190417202834.png -.. |image18| image:: http://image.python-online.cn/20190404204534.png -.. |image19| image:: http://image.python-online.cn/20190605173956.png -.. |image20| image:: http://image.python-online.cn/20190409103417.png -.. |image21| image:: http://image.python-online.cn/20190409104026.png +|image23| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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 26fb469..aeab306 100644 --- a/source/c07/c07_03.md +++ b/source/c07/c07_03.md @@ -1,5 +1,7 @@ # 7.3 Docker:Hello world +![](http://image.iswbm.com/20200602135014.png) + --- ## 1. 安装Docker @@ -243,12 +245,12 @@ 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) --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c07/c07_03.rst b/source/c07/c07_03.rst old mode 100755 new mode 100644 index 785a324..edce98a --- a/source/c07/c07_03.rst +++ b/source/c07/c07_03.rst @@ -1,6 +1,8 @@ 7.3 Docker:Hello world ======================= +|image0| + -------------- 1. 安装Docker @@ -283,15 +285,14 @@ namespace 有下面六种 6. User namespace 让容器能够管理自己的用户,host 不能看到容器中创建的用户。 -正在运行的容器 |image0| 文件夹内容 |image1| +正在运行的容器 |image1| 文件夹内容 |image2| -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image3| -.. |image0| image:: http://image.python-online.cn/17-12-23/44035514.jpg -.. |image1| image:: http://image.python-online.cn/17-12-23/20133481.jpg +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |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 676a753..7337b1f 100644 --- a/source/c07/c07_04.md +++ b/source/c07/c07_04.md @@ -1,5 +1,7 @@ # 7.4 Docker:关于镜像 +![](http://image.iswbm.com/20200602135014.png) + --- ## 一、镜像的基本内容 @@ -31,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镜像 ``` @@ -41,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的。 @@ -64,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 版本下运行),则不建议用容器,这种场景虚拟机可能更合适。 @@ -108,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 @@ -119,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改成如下,也是需要重新构建 @@ -132,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) 友情提示 ``` @@ -213,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) **推荐用法** ``` @@ -308,4 +310,4 @@ docker history image --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c07/c07_04.rst b/source/c07/c07_04.rst old mode 100755 new mode 100644 index f186d28..fce8b33 --- a/source/c07/c07_04.rst +++ b/source/c07/c07_04.rst @@ -1,6 +1,8 @@ 7.4 Docker:关于镜像 ==================== +|image0| + -------------- 一、镜像的基本内容 @@ -39,7 +41,7 @@ hub下载的image可以在这里搜索\ `dockerfile /etc/netns/ns1/resolv.conf --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c07/c07_05.rst b/source/c07/c07_05.rst old mode 100755 new mode 100644 index 0667b18..23440b7 --- a/source/c07/c07_05.rst +++ b/source/c07/c07_05.rst @@ -1,6 +1,8 @@ 7.5 Docker:网络通信 ==================== +|image0| + -------------- 7.5.1 三种local网络 @@ -141,7 +143,7 @@ key-vlaue 软件,我们这里使用 Consul。 # 这里的 eth1 是可以和 192.168.2.55 通信的网卡 --cluster-store=consul://192.168.2.55:8500 --cluster-advertise=eth1:2376 -|image0| +|image1| 然后重启 @@ -150,7 +152,7 @@ key-vlaue 软件,我们这里使用 Consul。 systemctl daemon-reload # 刷新配置,不然修改的配置不会生效 systemctl restart docker.service -在浏览器上输入地址 |image1| +在浏览器上输入地址 |image2| 至此,\ ``Consul`` 安装成功。 @@ -177,7 +179,7 @@ Consul 进行同步了。 # bm-docker-02 docker run -itd --name bbox2 --network ov_net1 busybox -试着ping一下在两台 host 上的网络是否可通 |image2| +试着ping一下在两台 host 上的网络是否可通 |image3| 查看一下ip网卡信息。 @@ -185,7 +187,7 @@ Consul 进行同步了。 docker exec bbox2 ip r -|image3| +|image4| 会发现使用 ``overlay`` 网络会有两张网卡。这是为什么呢? @@ -317,13 +319,12 @@ Socket发些“奇怪”的数据。 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image5| -.. |image0| image:: http://image.python-online.cn/18-1-28/92519416.jpg -.. |image1| image:: http://image.python-online.cn/18-1-28/37395940.jpg -.. |image2| image:: https://i.loli.net/2018/01/28/5a6de8702428c.png -.. |image3| image:: https://i.loli.net/2018/01/28/5a6de73776390.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |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.md b/source/c07/c07_06.md index 2aece07..76a9555 100644 --- a/source/c07/c07_06.md +++ b/source/c07/c07_06.md @@ -1,5 +1,7 @@ # 7.6 Docker:存储与多主机 +![](http://image.iswbm.com/20200602135014.png) + --- ## 一、关于存储 @@ -280,4 +282,4 @@ docker-machine scp bm-docker-01:/tmp/a bm-docker-02:/tmp/b --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](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 index 9a01a73..5358702 --- a/source/c07/c07_06.rst +++ b/source/c07/c07_06.rst @@ -1,6 +1,8 @@ 7.6 Docker:存储与多主机 ======================== +|image0| + -------------- 一、关于存储 @@ -107,7 +109,7 @@ Data Volume } ] -二者对比 |image0| +二者对比 |image1| 相关命令 @@ -292,7 +294,7 @@ Generic驱动 `__ --generic-ssh-port 57891 \ bm-docker-02 -|image1| +|image2| centos的配置文件路径如下,ubuntu的有所不同 @@ -301,7 +303,7 @@ centos的配置文件路径如下,ubuntu的有所不同 /etc/systemd/system/multi-user.target.wants/docker.service 通过查看进程,左边是用docker-machine安装有docker,和手动安装有所不同 -红框标出的,表示,允许远程连接。 |image2| +红框标出的,表示,允许远程连接。 |image3| 2.3 管理Docker Machine ~~~~~~~~~~~~~~~~~~~~~~ @@ -326,12 +328,11 @@ centos的配置文件路径如下,ubuntu的有所不同 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image4| -.. |image0| image:: https://i.loli.net/2017/12/30/5a473ba8f374f.png -.. |image1| image:: https://i.loli.net/2018/01/03/5a4ce6eeaff7d.png -.. |image2| image:: https://i.loli.net/2018/01/03/5a4ce964d3e73.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: https://i.loli.net/2017/12/30/5a473ba8f374f.png +.. |image2| image:: https://i.loli.net/2018/01/03/5a4ce6eeaff7d.png +.. |image3| image:: https://i.loli.net/2018/01/03/5a4ce964d3e73.png +.. |image4| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c07/c07_07.md b/source/c07/c07_07.md index 948fab4..1a46442 100644 --- a/source/c07/c07_07.md +++ b/source/c07/c07_07.md @@ -1,5 +1,7 @@ # 7.7 SaltStack 入门指南 +![](http://image.iswbm.com/20200602135014.png) + --- ## 一、安装SaltStack @@ -353,4 +355,4 @@ salt minion01 saltutil.sync_grains # 只同步grains --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c07/c07_07.rst b/source/c07/c07_07.rst old mode 100755 new mode 100644 index 7af2e7c..88a4d23 --- a/source/c07/c07_07.rst +++ b/source/c07/c07_07.rst @@ -1,6 +1,8 @@ 7.7 SaltStack 入门指南 ====================== +|image0| + -------------- 一、安装SaltStack @@ -416,7 +418,8 @@ salt命令格式 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c07/c07_08.rst b/source/c07/c07_08.rst index a0a1c35..de57c84 100644 --- a/source/c07/c07_08.rst +++ b/source/c07/c07_08.rst @@ -1,6 +1,8 @@ 7.8 Keepalived 部署文档 ======================= +|image0| + 参考文档:\ `keepalived搭建zabbix server双机高可用 `__ @@ -173,7 +175,8 @@ chk_zabbix.sh -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c07/c07_09.md b/source/c07/c07_09.md index 9b46279..6c540ca 100644 --- a/source/c07/c07_09.md +++ b/source/c07/c07_09.md @@ -1,5 +1,7 @@ # 7.9 Ansible 入门指南使用手册 +![](http://image.iswbm.com/20200602135014.png) + ## 1. 有用的小技巧 ### 1. 只输出错误的信息 diff --git a/source/c07/c07_09.rst b/source/c07/c07_09.rst index d263d02..54849d2 100644 --- a/source/c07/c07_09.rst +++ b/source/c07/c07_09.rst @@ -1,6 +1,8 @@ 7.9 Ansible 入门指南使用手册 ============================ +|image0| + 1. 有用的小技巧 --------------- @@ -239,3 +241,6 @@ 1. `Ansible 配置全解 `__ + +.. |image0| image:: http://image.iswbm.com/20200602135014.png + diff --git a/source/c07/c07_10.md b/source/c07/c07_10.md index 5c9e3f9..64bee83 100644 --- a/source/c07/c07_10.md +++ b/source/c07/c07_10.md @@ -1,5 +1,7 @@ # 7.10 Ansible API 最全使用文档(中文) +![](http://image.iswbm.com/20200602135014.png) + 大家都知道 Ansible 是一个轻量级的部署工具,这里的轻量体现在哪里呢? master 与 节点间使用ssh通信,也不需要像salt那样需要在客户端装minion,然后在master和minion各起一个服务。 @@ -10,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月份才开始的项目,目前来说成熟度还不够。 @@ -118,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 @@ -161,15 +163,15 @@ 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) --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c07/c07_10.rst b/source/c07/c07_10.rst index 494ce9f..5d1f203 100644 --- a/source/c07/c07_10.rst +++ b/source/c07/c07_10.rst @@ -1,6 +1,8 @@ 7.10 Ansible API 最全使用文档(中文) ===================================== +|image0| + 大家都知道 Ansible 是一个轻量级的部署工具,这里的轻量体现在哪里呢? master 与 @@ -14,7 +16,7 @@ master 与 无意中使用 pip search 进行搜索,还真的有 ansible-api -|image0| +|image1| 这是 2018 年10月份才开始的项目,目前来说成熟度还不够。 @@ -128,7 +130,7 @@ ansible-api 会调用 ansible 库的命令,这个过程不能指定 ansible.cf 然后由于原生的 ansible-api 的bug,需要修改代码,在如下函数位置(\ ``/usr/local/python3/lib/python3.7/site-packages/ansible_api/callback.py``\ )添加一个参数 -|image1| +|image2| 通过执行命令,即可开启 ansible server @@ -173,24 +175,23 @@ ansible-api 会调用 ansible 库的命令,这个过程不能指定 ansible.cf 发送了请求后,返回的结果如下 -|image2| +|image3| rc 为0,表示所有节点都没有出现 fatal 致命错误(有设置 ignore_errors 的错误也会返回0). rc 为非0,表示有 fatal 致命错误,说明有部分节点部署/升级失败。 -|image3| +|image4| -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image5| -.. |image0| image:: http://image.python-online.cn/20190716111523.png -.. |image1| image:: http://image.python-online.cn/20190716112113.png -.. |image2| image:: http://image.python-online.cn/20190716112824.png -.. |image3| image:: http://image.python-online.cn/20190716112838.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 9d5f6ca..ac60ad7 100644 --- a/source/c07/c07_11.md +++ b/source/c07/c07_11.md @@ -1,5 +1,7 @@ # 7.11 K8S:基础入门 +![](http://image.iswbm.com/20200602135014.png) + 一些基础命令 ``` @@ -64,7 +66,7 @@ kubectl get namespace K8s 角色详解 -![](http://image.python-online.cn/20190907162015.png) +![](http://image.iswbm.com/20190907162015.png) 其中 Controller 还分为几种: @@ -78,4 +80,4 @@ K8s 角色详解 -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c07/c07_11.rst b/source/c07/c07_11.rst index 7951bb0..ffe862f 100644 --- a/source/c07/c07_11.rst +++ b/source/c07/c07_11.rst @@ -1,6 +1,8 @@ 7.11 K8S:基础入门 ================== +|image0| + 一些基础命令 :: @@ -61,7 +63,7 @@ K8s 角色详解 -|image0| +|image1| 其中 Controller 还分为几种: @@ -75,10 +77,9 @@ K8s 角色详解 里可以运行一个容器(最常用),也可以运行多个容器,若运行多个容器,那这几个容器的工作必定有着紧密的联系,而且需要直接 **共享资源**\ 。 -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image2| -.. |image0| image:: http://image.python-online.cn/20190907162015.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 2ffe069..577fcd2 100644 --- a/source/c07/c07_12.md +++ b/source/c07/c07_12.md @@ -1,6 +1,32 @@ -# 7.12 Linux 包管理工具:yum 和 rpm +# 7.12 yum 命令使用指南及问题排查方法 -## yum +![](http://image.iswbm.com/20200602135014.png) + + + +## 安装 + +加个 `-y` 可以在安装包时代替用户自动响应 yes + +```shell +$ yum install -y xxx +``` + +加个 `--installroot=/usr/local/` 指定将软件安装在 /usr/local 目录下,而不安装在默认路径下 + +```shell +$ yum install --installroot=/usr/local/ -y xxx +``` + +重新安装 == 卸载再安装 + +```shell +$ yum reinstall -y xxx +``` + + + +## 下载 只下载安装包 @@ -15,72 +41,299 @@ yumdownloader --resolve --destdir /home/wangbm/ perl-Sys-Virt.x86_64 yumdownloader --resolve --destdir /home/test/ systemd-devel-219-62.el7_6.2.x86_64 ``` -查看一个包的安装时间 +## 查看 + +查看具体包的信息 + +```shell +$ yum info zabbix-agent +``` + +查看配置并启用的仓库中有哪些包,包括已安装和未安装的 + +```shell +$ yum list +``` + +查看有哪些包可以升级的 + +```shell +$ yum list updates +``` + +查询一个命令是由哪个包提供的 + +```shell +$ yum provides xxx +``` + +查询一个模块/共享库/文件是哪个包提供的 + +```shell +$ yum whatprovides xxx +``` + +列出所有的容器 + +```shell +$ yum repolist all +``` + +查询某个仓库下的所有包 + +```shell +$ yum repo-pkgs list +``` + +查看一个包的所有依赖项 + +```shell +$ yum deplist httpd +``` + + + +## 搜索 + +在配置并启用的仓库中搜索包 + +```shell +$ yum search zabbix-agent +``` + + + +## 升级 + +```shell +$ yum update xxx +``` + + + +## 删除 + +```shell +$ yum remove -y xxx +``` + + + +## 清理 + +清理已下载的软件文件 + +```shell +$ yum clean packages +``` + +清理已下载的软件文件头 + +```shell +$ yum clean headers +``` +清理下载过的容器相关数据 +```shell +$ yum clean all ``` -[root@ws_compute01 ~]# rpm -qi|grep zlib-1.2.7-17.el7.i686 -rpm: no arguments given for query -[root@ws_compute01 ~]# rpm -qi zlib-1.2.7-17.el7.i686 -Name : zlib -Version : 1.2.7 -Release : 17.el7 -Architecture: i686 -Install Date: Tue 13 Aug 2019 10:22:41 AM CST -Group : System Environment/Libraries -Size : 184798 -License : zlib and Boost -Signature : RSA/SHA256, Mon 21 Nov 2016 05:05:03 AM CST, Key ID 24c6a8a7f4a80eb5 -Source RPM : zlib-1.2.7-17.el7.src.rpm -Build Date : Sun 06 Nov 2016 02:07:22 AM CST -Build Host : worker1.bsys.centos.org -Relocations : (not relocatable) -Packager : CentOS BuildSystem -Vendor : CentOS -URL : http://www.zlib.net/ -Summary : The compression and decompression library -Description : -Zlib is a general-purpose, patent-free, lossless data compression -library which is used by many different programs. + + + +## 分组 + +查看分组 + +```shell +$ yum grouplist ``` -查看一个包在系统已配的源里都有哪些版本 +查看分组信息 +```shell +$ yum groupinfo ``` -[root@ws_compute01 ~]# yum provides -Loaded plugins: fastestmirror, versionlock -Loading mirror speeds from cached hostfile - * epel: hkg.mirror.rackspace.com -1:libguestfs-tools-1.28.1-1.55.el7.centos.noarch : System administration tools for virtual machines -Repo : ansible-master +安装一整组软件 -1:libguestfs-tools-1.28.1-1.55.el7.centos.noarch : System administration tools for virtual machines -Repo : libvirt-3.9.0 +```shell +$ yum groupinstall +``` +删除某个组 -1:libguestfs-tools-1.28.1-1.55.el7.centos.noarch : System administration tools for virtual machines -Repo : wsbase +```shell +$ yum groupremove +``` +## 历史 -1:libguestfs-tools-1.38.2-12.el7.noarch : System administration tools for virtual machines -Repo : ansible-master +```shell +$ yum history +``` +## 语言 -1:libguestfs-tools-1.38.2-12.el7.noarch : System administration tools for virtual machines -Repo : libvirt-4.5.0 +列出已安装的语言 +```shell +$ yum langlist +``` -1:libguestfs-tools-1.38.2-12.el7.noarch : System administration tools for virtual machines -Repo : wsupdates +为语言安装适当的语言包 +```shell +$ yum langinstall +``` -1:libguestfs-tools-1.38.2-12.el7.noarch : System administration tools for virtual machines -Repo : @libvirt-4.5.0 +删除语言的已安装语言包 +```shell +$ yum langremove ``` +## 其他 + +执行事务 + +```shell +$ yum load-transaction /tmp/yum_save_tx.xxx.n0EVjx.yumtx +``` + +生成元数据缓存 + +```shell +$ yum makecache +``` + +进入交互式模式 + +```shell +$ yum shell +``` + + + +## 选项 + +```shell +-h, - help显示此帮助消息并退出 +-t, - 容忍错误 +-C,--cacheonly完全从系统缓存运行,不更新缓存 + +-q, - 安静的操作 +-v, - verbose详细操作 +-y, - assumeyes对所有问题都回答是 + +--assumeno对所有问题都回答否 +--version显示Yum版本并退出 +--installroot = [path]设置安装root + +--obsoletes在更新期间启用过时处理 +--noplugins禁用Yum插件 +--nogpgcheck禁用gpg签名检查 + + +-skip-broken 跳过没有解决问题的包 +--color = COLOR控制是否使用颜色 + +--downloadonly不更新,只需下载 +--downloaddir = DLDIR指定存储包的备用目录 +--setopt = SETOPTS设置任意配置和repo选项 +--bugfix在更新中包含bugfix相关包 +--security在更新中包含安全相关的包 +--advisory = ADVS, - advisories = ADVS 更新包括修复给定建议所需的包 +--bzs = BZS 更新包括修复给定BZ所需的包 +--cves = CVES 更新包括修复给定CVE所需的包 +--sec-severity = SEVS, - secseverity = SEVS +``` + +配置文件位置 + +```shell +-c [config file], - config = [config file] +``` + +最大命令等待时间 + +```shell +-R [minute], - randomwait = [minute] +``` + +调试输出级别 + +```shell +-d [debug level], - debug level = [debug level] +``` + +在repos中,在list / search命令中显示重复项 + +```shell +--showduplicates +``` + +错误输出级别 + +```shell +-e [error level], - errorlevel = [error level] +``` + + +调试rpm的输出级别 + +```shell +--rpmverbosity = [debug level name] +``` + +启用/禁用仓库(允许使用通配符) + +```shell +--enablerepo = [repo] +--disablerepo = [repo] +``` + +按名称或glob排除包 + +```shell +-x [package], - exclude = [package] +``` + +禁用从main,repo或所有内容的排除 + +```shell +--disableexcludes = [plugin] +``` + +禁用包含repo或所有内容的includepkgs + +```shell +--disableincludes = [plugin] +``` + +按名称禁用插件 + +```shell +--disableplugin = [plugin] +``` + +按名称启用插件 + +```shell +--enableplugin = [plugin] +``` + +在yum config和repo文件中设置$ releasever的值 + +```shell +--releasever = RELEASEVER +``` + + + + + yum-utils 使用 ```shell @@ -101,11 +354,13 @@ $ rpm -e glibc-common-2.17-196.el7_4.2.x86_64 +## 问题排查记录 + 查找一个 so 文件是属于哪个 rpm 包 经常在安装一个包的时候,会报如下的错误,找不到某 so 文件 -![](http://image.python-online.cn/20191219152328.png) +![](http://image.iswbm.com/20191219152328.png) 如果是缺一个包,那我们安装它就行了,缺 so 文件,那咋弄? @@ -134,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) @@ -144,67 +399,8 @@ $ rpm -qa --last | grep python-nova-tests $ yumdb info python-nova-tests ``` -![](http://image.python-online.cn/20191225175350.png) - - - -查看rpm包的版本 - -```bash -# 查看软件包的详细信息 -rpm -qpi xxx.rpm - -# 查看软件包所包含的目录和文件 -rpm -qpl xxx.rpm - -# 查看软件包的文档所在的位置 -rpm -qpd xxx.rpm - -# 查看软件包的配置文件 -rpm -qpc rpm - -# 查看软件包的依赖关系 -rpm -qpR xxx.rpm -``` - - - -## rpm - -```shell -rpm -ivh xxx.rpm-------安装软件 - -   -e-----------------卸载指定软件,不能是安装包名称 - -   -q-----------------查询指定软件是否安装,跟软件名称 - -   -qi----------------查询已经安装的软件的信息 - -  -ql-----------------查询已经安装的软件中包含什么样的内容 - -  -qf /etc/fstab ----查询这个文件是由哪个安装包产生的 - -  -qc-----------------查询已经安装的软件中包含的配置文件 - -  -qd-----------------查询已经安装的软件中包含的doc文件 - -  -q --scripts ------查询软件的脚本内容 - -  -Uvh----------------升级软件 - -  -Fvh----------------刷新 - -  -p------------------对于未安装的软件包查询信息,需要额外加此选项 - -  -qip----------------查询一个尚款安装的安装包的信息 - -  -qpc----------------查询一个尚未安装的安装包的配置文件 - -  -qpd----------------查询一个尚未安装的安装包的doc文件 - -  -qpl----------------查询一个尚未安装的安装包包含的信息 -``` +![](http://image.iswbm.com/20191225175350.png) -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c07/c07_12.rst b/source/c07/c07_12.rst index a2dce10..d489a16 100644 --- a/source/c07/c07_12.rst +++ b/source/c07/c07_12.rst @@ -1,8 +1,32 @@ -7.12 Linux 包管理工具:yum 和 rpm -================================= +7.12 yum 命令使用指南及问题排查方法 +=================================== -yum ---- +|image0| + +安装 +---- + +加个 ``-y`` 可以在安装包时代替用户自动响应 yes + +.. code:: shell + + $ yum install -y xxx + +加个 ``--installroot=/usr/local/`` 指定将软件安装在 /usr/local +目录下,而不安装在默认路径下 + +.. code:: shell + + $ yum install --installroot=/usr/local/ -y xxx + +重新安装 == 卸载再安装 + +.. code:: shell + + $ yum reinstall -y xxx + +下载 +---- 只下载安装包 @@ -17,68 +41,290 @@ yum yumdownloader --resolve --destdir /home/wangbm/ perl-Sys-Virt.x86_64 yumdownloader --resolve --destdir /home/test/ systemd-devel-219-62.el7_6.2.x86_64 -查看一个包的安装时间 +查看 +---- + +查看具体包的信息 + +.. code:: shell + + $ yum info zabbix-agent + +查看配置并启用的仓库中有哪些包,包括已安装和未安装的 + +.. code:: shell + + $ yum list + +查看有哪些包可以升级的 + +.. code:: shell + + $ yum list updates + +查询一个命令是由哪个包提供的 + +.. code:: shell + + $ yum provides xxx + +查询一个模块/共享库/文件是哪个包提供的 + +.. code:: shell + + $ yum whatprovides xxx + +列出所有的容器 + +.. code:: shell + + $ yum repolist all + +查询某个仓库下的所有包 + +.. code:: shell + + $ yum repo-pkgs list + +查看一个包的所有依赖项 + +.. code:: shell + + $ yum deplist httpd + +搜索 +---- + +在配置并启用的仓库中搜索包 + +.. code:: shell + + $ yum search zabbix-agent + +升级 +---- + +.. code:: shell + + $ yum update xxx + +删除 +---- + +.. code:: shell + + $ yum remove -y xxx + +清理 +---- + +清理已下载的软件文件 + +.. code:: shell + + $ yum clean packages + +清理已下载的软件文件头 + +.. code:: shell + + $ yum clean headers + +清理下载过的容器相关数据 + +.. code:: shell + + $ yum clean all + +分组 +---- + +查看分组 -:: +.. code:: shell - [root@ws_compute01 ~]# rpm -qi|grep zlib-1.2.7-17.el7.i686 - rpm: no arguments given for query - [root@ws_compute01 ~]# rpm -qi zlib-1.2.7-17.el7.i686 - Name : zlib - Version : 1.2.7 - Release : 17.el7 - Architecture: i686 - Install Date: Tue 13 Aug 2019 10:22:41 AM CST - Group : System Environment/Libraries - Size : 184798 - License : zlib and Boost - Signature : RSA/SHA256, Mon 21 Nov 2016 05:05:03 AM CST, Key ID 24c6a8a7f4a80eb5 - Source RPM : zlib-1.2.7-17.el7.src.rpm - Build Date : Sun 06 Nov 2016 02:07:22 AM CST - Build Host : worker1.bsys.centos.org - Relocations : (not relocatable) - Packager : CentOS BuildSystem - Vendor : CentOS - URL : http://www.zlib.net/ - Summary : The compression and decompression library - Description : - Zlib is a general-purpose, patent-free, lossless data compression - library which is used by many different programs. + $ yum grouplist -查看一个包在系统已配的源里都有哪些版本 +查看分组信息 -:: +.. code:: shell - [root@ws_compute01 ~]# yum provides - Loaded plugins: fastestmirror, versionlock - Loading mirror speeds from cached hostfile - * epel: hkg.mirror.rackspace.com - 1:libguestfs-tools-1.28.1-1.55.el7.centos.noarch : System administration tools for virtual machines - Repo : ansible-master + $ yum groupinfo +安装一整组软件 - 1:libguestfs-tools-1.28.1-1.55.el7.centos.noarch : System administration tools for virtual machines - Repo : libvirt-3.9.0 +.. code:: shell + $ yum groupinstall - 1:libguestfs-tools-1.28.1-1.55.el7.centos.noarch : System administration tools for virtual machines - Repo : wsbase +删除某个组 +.. code:: shell - 1:libguestfs-tools-1.38.2-12.el7.noarch : System administration tools for virtual machines - Repo : ansible-master + $ yum groupremove + +历史 +---- + +.. code:: shell + $ yum history - 1:libguestfs-tools-1.38.2-12.el7.noarch : System administration tools for virtual machines - Repo : libvirt-4.5.0 +语言 +---- +列出已安装的语言 + +.. code:: shell + + $ yum langlist + +为语言安装适当的语言包 + +.. code:: shell - 1:libguestfs-tools-1.38.2-12.el7.noarch : System administration tools for virtual machines - Repo : wsupdates + $ yum langinstall + +删除语言的已安装语言包 + +.. code:: shell + + $ yum langremove + +其他 +---- + +执行事务 + +.. code:: shell + + $ yum load-transaction /tmp/yum_save_tx.xxx.n0EVjx.yumtx + +生成元数据缓存 + +.. code:: shell + $ yum makecache - 1:libguestfs-tools-1.38.2-12.el7.noarch : System administration tools for virtual machines - Repo : @libvirt-4.5.0 +进入交互式模式 + +.. code:: shell + + $ yum shell + +选项 +---- + +.. code:: shell + + -h, - help显示此帮助消息并退出 + -t, - 容忍错误 + -C,--cacheonly完全从系统缓存运行,不更新缓存 + + -q, - 安静的操作 + -v, - verbose详细操作 + -y, - assumeyes对所有问题都回答是 + + --assumeno对所有问题都回答否 + --version显示Yum版本并退出 + --installroot = [path]设置安装root + + --obsoletes在更新期间启用过时处理 + --noplugins禁用Yum插件 + --nogpgcheck禁用gpg签名检查 + + + -skip-broken 跳过没有解决问题的包 + --color = COLOR控制是否使用颜色 + + --downloadonly不更新,只需下载 + --downloaddir = DLDIR指定存储包的备用目录 + --setopt = SETOPTS设置任意配置和repo选项 + --bugfix在更新中包含bugfix相关包 + --security在更新中包含安全相关的包 + --advisory = ADVS, - advisories = ADVS 更新包括修复给定建议所需的包 + --bzs = BZS 更新包括修复给定BZ所需的包 + --cves = CVES 更新包括修复给定CVE所需的包 + --sec-severity = SEVS, - secseverity = SEVS + +配置文件位置 + +.. code:: shell + + -c [config file], - config = [config file] + +最大命令等待时间 + +.. code:: shell + + -R [minute], - randomwait = [minute] + +调试输出级别 + +.. code:: shell + + -d [debug level], - debug level = [debug level] + +在repos中,在list / search命令中显示重复项 + +.. code:: shell + + --showduplicates + +错误输出级别 + +.. code:: shell + + -e [error level], - errorlevel = [error level] + +调试rpm的输出级别 + +.. code:: shell + + --rpmverbosity = [debug level name] + +启用/禁用仓库(允许使用通配符) + +.. code:: shell + + --enablerepo = [repo] + --disablerepo = [repo] + +按名称或glob排除包 + +.. code:: shell + + -x [package], - exclude = [package] + +禁用从main,repo或所有内容的排除 + +.. code:: shell + + --disableexcludes = [plugin] + +禁用包含repo或所有内容的includepkgs + +.. code:: shell + + --disableincludes = [plugin] + +按名称禁用插件 + +.. code:: shell + + --disableplugin = [plugin] + +按名称启用插件 + +.. code:: shell + + --enableplugin = [plugin] + +在yum config和repo文件中设置$ releasever的值 + +.. code:: shell + + --releasever = RELEASEVER yum-utils 使用 @@ -98,11 +344,14 @@ yum-utils 使用 # 将上面列出的包卸载 $ rpm -e glibc-common-2.17-196.el7_4.2.x86_64 +问题排查记录 +------------ + 查找一个 so 文件是属于哪个 rpm 包 经常在安装一个包的时候,会报如下的错误,找不到某 so 文件 -|image0| +|image1| 如果是缺一个包,那我们安装它就行了,缺 so 文件,那咋弄? @@ -130,7 +379,7 @@ yum-utils 使用 # 只能查已安装的包的安装日期。截图中,之所以存在两个包,是因为该机器上存在两个版本的包 $ rpm -qa --last | grep python-nova-tests -|image1| +|image2| 查看安装某个包是如何安装的,从哪安装 @@ -138,70 +387,13 @@ yum-utils 使用 $ yumdb info python-nova-tests -|image2| - -查看rpm包的版本 - -.. code:: bash - - # 查看软件包的详细信息 - rpm -qpi xxx.rpm - - # 查看软件包所包含的目录和文件 - rpm -qpl xxx.rpm - - # 查看软件包的文档所在的位置 - rpm -qpd xxx.rpm - - # 查看软件包的配置文件 - rpm -qpc rpm - - # 查看软件包的依赖关系 - rpm -qpR xxx.rpm - -rpm ---- - -.. code:: shell - - rpm -ivh xxx.rpm-------安装软件 - -    -e-----------------卸载指定软件,不能是安装包名称 - -    -q-----------------查询指定软件是否安装,跟软件名称 - -    -qi----------------查询已经安装的软件的信息 - -   -ql-----------------查询已经安装的软件中包含什么样的内容 - -   -qf /etc/fstab ----查询这个文件是由哪个安装包产生的 - -   -qc-----------------查询已经安装的软件中包含的配置文件 - -   -qd-----------------查询已经安装的软件中包含的doc文件 - -   -q --scripts ------查询软件的脚本内容 - -   -Uvh----------------升级软件 - -   -Fvh----------------刷新 - -   -p------------------对于未安装的软件包查询信息,需要额外加此选项 - -   -qip----------------查询一个尚款安装的安装包的信息 - -   -qpc----------------查询一个尚未安装的安装包的配置文件 - -   -qpd----------------查询一个尚未安装的安装包的doc文件 - -   -qpl----------------查询一个尚未安装的安装包包含的信息 - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image3| - 关注公众号,获取最新干货! +|image4| -.. |image0| image:: http://image.python-online.cn/20191219152328.png -.. |image1| image:: http://image.python-online.cn/20191225173340.png -.. |image2| image:: http://image.python-online.cn/20191225175350.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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_13.md b/source/c07/c07_13.md index bc9dba3..dcc0e57 100644 --- a/source/c07/c07_13.md +++ b/source/c07/c07_13.md @@ -1,5 +1,7 @@ # 7.13 基于 ansible-api 二次开发 +![](http://image.iswbm.com/20200602135014.png) + 长久以来,IT 运维在企业内部一直是个耗人耗力的事情。随着虚拟化的大量应用、私有云、容器的不断普及,数据中心内部的压力愈发增加。传统的自动化工具,往往是面向于数据中心特定的一类对象,例如操作系统、虚拟化、网络设备的自动化运维工具往往是不同的。那么,有没有一种数据中心级别的统一的自动化运维工具呢? 答案就是[ Ansible](https://www.ansible.com/)。和传统的自动化工具 (如 Puppet)相比,Ansible 尤其明显的优势: @@ -107,4 +109,4 @@ class ResultCallback(CallbackModule): -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c07/c07_13.rst b/source/c07/c07_13.rst index 7bf2666..a7b39d5 100644 --- a/source/c07/c07_13.rst +++ b/source/c07/c07_13.rst @@ -1,6 +1,8 @@ 7.13 基于 ansible-api 二次开发 ============================== +|image0| + 长久以来,IT 运维在企业内部一直是个耗人耗力的事情。随着虚拟化的大量应用、私有云、容器的不断普及,数据中心内部的压力愈发增加。传统的自动化工具,往往是面向于数据中心特定的一类对象,例如操作系统、虚拟化、网络设备的自动化运维工具往往是不同的。那么,有没有一种数据中心级别的统一的自动化运维工具呢? @@ -115,7 +117,8 @@ ansible 的 api 开发出来的吗? - v2_runner_on_skipped:部署任务跳过时,会调用 - v2_playbook_on_stats:所有的部署任务完成时,调用 -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c07/c07_14.md b/source/c07/c07_14.md index b0031db..36d764f 100644 --- a/source/c07/c07_14.md +++ b/source/c07/c07_14.md @@ -1,5 +1,7 @@ # 7.14 Linux 如何写判断语句 +![](http://image.iswbm.com/20200602135014.png) + ## 7.14. 如何判断文件 `-e filename`: 如果 filename存在,则为真 @@ -38,4 +40,4 @@ filename1 -ot filename2 如果 filename1比 filename2旧,则为真。 -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c07/c07_14.rst b/source/c07/c07_14.rst index 7bc09f9..2a0f1a7 100644 --- a/source/c07/c07_14.rst +++ b/source/c07/c07_14.rst @@ -1,6 +1,8 @@ 7.14 Linux 如何写判断语句 ========================= +|image0| + 7.14. 如何判断文件 ------------------ @@ -24,7 +26,8 @@ filename可读,则为真 ``-w filename``: 如果 filename可写,则为真 -eq :等于 -ne :不等于 -gt :大于 -ge :大于等于 -lt :小于 -le :小于等于 -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c07/c07_15.md b/source/c07/c07_15.md index 0dbed61..9860a01 100644 --- a/source/c07/c07_15.md +++ b/source/c07/c07_15.md @@ -1,5 +1,7 @@ # 7.15 Mariadb 与 Galera 集群总结 +![](http://image.iswbm.com/20200602135014.png) + MariaDB是MySQL的一个分支,由MySQL的创始人Michael Widenius主导开发,采用GPL授权许可。 开发这个分支的原因之一是Oracle公司收购了MySQL后,有将MySQL闭源的潜在风险,因此社区采用分支的方式来避开这个风险。 @@ -17,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 @@ -214,4 +216,4 @@ show status like '%wsrep%'; - http://www.360doc.com/content/13/0817/15/834950_307820923.shtml - http://blog.itpub.net/30126024/viewspace-2221483/ -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c07/c07_15.rst b/source/c07/c07_15.rst index bbb1e93..82889cc 100644 --- a/source/c07/c07_15.rst +++ b/source/c07/c07_15.rst @@ -1,6 +1,8 @@ 7.15 Mariadb 与 Galera 集群总结 =============================== +|image0| + MariaDB是MySQL的一个分支,由MySQL的创始人Michael Widenius主导开发,采用GPL授权许可。 @@ -20,7 +22,7 @@ Galera 是一个MySQL(也支持MariaDB,Percona) 5. 用户连接集群后,使用跟 MySQL 基本一致。 6. 不会写binlog -关于Galera是如何做到多主的,可以借助这张图来看看\ |image0| +关于Galera是如何做到多主的,可以借助这张图来看看\ |image1| 来源:https://blog.csdn.net/weixin_42867972/article/details/84198696 @@ -217,10 +219,9 @@ wsrep_flow_control_sent 和 wsrep_local_recv_queue_avg - http://www.360doc.com/content/13/0817/15/834950_307820923.shtml - http://blog.itpub.net/30126024/viewspace-2221483/ -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image2| -.. |image0| image:: http://image.python-online.cn/20191213162259.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 0aff82a..2778025 100644 --- a/source/c07/c07_16.md +++ b/source/c07/c07_16.md @@ -1,8 +1,10 @@ # 7.16 Linux 运维之路 +![](http://image.iswbm.com/20200602135014.png) + ## 7.16.1 如何查看并计算 CPU 使用率 -有很多的工具可以查看CPU使用率,使用Linux 自带的 top 是最常用的方式。![](http://image.python-online.cn/20191220202103.png) +有很多的工具可以查看CPU使用率,使用Linux 自带的 top 是最常用的方式。![](http://image.iswbm.com/20191220202103.png) 图中标注解释 @@ -18,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个核。 @@ -89,4 +91,4 @@ result=`random_range 1 60` -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c07/c07_16.rst b/source/c07/c07_16.rst index 9edd7f0..7eb96f7 100644 --- a/source/c07/c07_16.rst +++ b/source/c07/c07_16.rst @@ -1,11 +1,13 @@ 7.16 Linux 运维之路 =================== +|image0| + 7.16.1 如何查看并计算 CPU 使用率 -------------------------------- 有很多的工具可以查看CPU使用率,使用Linux 自带的 top -是最常用的方式。\ |image0| +是最常用的方式。\ |image1| 图中标注解释 @@ -21,11 +23,11 @@ - si,software interrupt: 处理软件中断的CPU时间 - st:这个虚拟机被hypervisor偷去的CPU时间(译注:如果当前处于一个hypervisor下的vm,实际上hypervisor也是要消耗一部分CPU处理时间的)。 -若你觉得这样不够直观,可以按 ``t`` ,切换显示\ |image1| +若你觉得这样不够直观,可以按 ``t`` ,切换显示\ |image2| -若这是多核机器,输入 ``1`` ,可以显示单核CPU使用情况。\ |image2| +若这是多核机器,输入 ``1`` ,可以显示单核CPU使用情况。\ |image3| -若你觉得这样不够直观,可以按 ``t``\ 切换成直方图显示\ |image3| +若你觉得这样不够直观,可以按 ``t``\ 切换成直方图显示\ |image4| 标为 ``2`` 的 栏目,表示各个进程的 cpu 使用率,比如第一个进程 348% 就是占满了3.48个核。 @@ -81,13 +83,12 @@ result=`random_range 1 60` -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image5| -.. |image0| image:: http://image.python-online.cn/20191220202103.png -.. |image1| image:: http://image.python-online.cn/20191220203403.png -.. |image2| image:: http://image.python-online.cn/20191220202408.png -.. |image3| image:: http://image.python-online.cn/20191220203205.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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_17.md b/source/c07/c07_17.md index 529ade8..3d78407 100644 --- a/source/c07/c07_17.md +++ b/source/c07/c07_17.md @@ -1,5 +1,7 @@ # 1.17 ansible 自定义 Jinja2 过滤器 +![](http://image.iswbm.com/20200602135014.png) + 在 ansible 中 Jinja2 的过滤器是作为插件存在于 ansible 的项目中。 它的代码位于 /usr/lib/python2.7/site-packages/ansible/plugins/filter @@ -18,4 +20,4 @@ https://www.jianshu.com/p/ae74f5f39828 -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c07/c07_17.rst b/source/c07/c07_17.rst index ab910d7..9261a6d 100644 --- a/source/c07/c07_17.rst +++ b/source/c07/c07_17.rst @@ -1,6 +1,8 @@ 1.17 ansible 自定义 Jinja2 过滤器 ================================= +|image0| + 在 ansible 中 Jinja2 的过滤器是作为插件存在于 ansible 的项目中。 它的代码位于 /usr/lib/python2.7/site-packages/ansible/plugins/filter @@ -11,7 +13,8 @@ https://blog.oddbit.com/post/2019-04-25-writing-ansible-filter-plugins/ https://www.jianshu.com/p/ae74f5f39828 -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c07/c07_18.md b/source/c07/c07_18.md index f832863..0163a7f 100644 --- a/source/c07/c07_18.md +++ b/source/c07/c07_18.md @@ -1,5 +1,7 @@ # 7.18 Shell中去除字符串前后空格的方法 +![](http://image.iswbm.com/20200602135014.png) + 原文:https://www.jb51.net/article/157327.htm 经常碰到的场景,需要去除字符串中的前后的空格。在Shell中不像其他语言有strip()来处理,不过也是可以使用诸如awk等命令来处理。 @@ -17,45 +19,50 @@ **如果不用awk命令,也可以使用eval命令来达到相同的目的** ``` -[root@``local` `~]``# echo " A BC "`` ``A BC``[root@``local` `~]``# eval echo " A BC "``A BC +[root@local ~]# echo " A BC " + A BC +[root@local ~]# eval echo " A BC " +A BC ``` 或者 ``` -[root@linux ~]``# echo ' A BC ' | python -c "s=raw_input();print(s.strip())"``A BC +[root@linux ~]# echo ' A BC ' | python -c "s=raw_input();print(s.strip())" +A BC ``` 或者 ``` -[root@linux ~]``# s=`echo " A BC "```[root@linux ~]``# echo $s``A BC +[root@linux ~]# s=`echo " A BC "` +[root@linux ~]# echo $s +A BC ``` -或者 +或者(**最简单易记的方法**) ``` -[root@linux ~]``# echo ' A BC ' | sed -e 's/^[ ]*//g' | sed -e 's/[ ]*$//g'``A BC +[root@linux ~]# echo " A BC " | awk '$1=$1' +A BC ``` 或者 ``` -[root@linux ~]``# echo " A BC " | awk '$1=$1'``A BC +[root@linux ~]# echo " A BC " | sed -r 's/^[ \t]+(.*)[ \t]+$//g' +A BC ``` 或者 ``` -[root@linux ~]``# echo " A BC " | sed -r 's/^[ \t]+(.*)[ \t]+$//g'``A BC +[root@linux ~]# echo ' A BC ' | awk '{sub(/^ */, "");sub(/ *$/, "")}1' +A BC ``` -或者 -``` -[root@linux ~]``# echo ' A BC ' | awk '{sub(/^ */, "");sub(/ *$/, "")}1'``A BC -``` -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c07/c07_18.rst b/source/c07/c07_18.rst index 512c97d..09fc58c 100644 --- a/source/c07/c07_18.rst +++ b/source/c07/c07_18.rst @@ -1,6 +1,8 @@ 7.18 Shell中去除字符串前后空格的方法 ==================================== +|image0| + 原文:https://www.jb51.net/article/157327.htm 经常碰到的场景,需要去除字符串中的前后的空格。在Shell中不像其他语言有strip()来处理,不过也是可以使用诸如awk等命令来处理。 @@ -19,45 +21,49 @@ :: - [root@``local` `~]``# echo " A BC "`` ``A BC``[root@``local` `~]``# eval echo " A BC "``A BC + [root@local ~]# echo " A BC " + A BC + [root@local ~]# eval echo " A BC " + A BC 或者 :: - [root@linux ~]``# echo ' A BC ' | python -c "s=raw_input();print(s.strip())"``A BC + [root@linux ~]# echo ' A BC ' | python -c "s=raw_input();print(s.strip())" + A BC 或者 :: - [root@linux ~]``# s=`echo " A BC "```[root@linux ~]``# echo $s``A BC + [root@linux ~]# s=`echo " A BC "` + [root@linux ~]# echo $s + A BC -或者 +或者(\ **最简单易记的方法**\ ) :: - [root@linux ~]``# echo ' A BC ' | sed -e 's/^[ ]*//g' | sed -e 's/[ ]*$//g'``A BC + [root@linux ~]# echo " A BC " | awk '$1=$1' + A BC 或者 :: - [root@linux ~]``# echo " A BC " | awk '$1=$1'``A BC + [root@linux ~]# echo " A BC " | sed -r 's/^[ \t]+(.*)[ \t]+$//g' + A BC 或者 :: - [root@linux ~]``# echo " A BC " | sed -r 's/^[ \t]+(.*)[ \t]+$//g'``A BC - -或者 - -:: + [root@linux ~]# echo ' A BC ' | awk '{sub(/^ */, "");sub(/ *$/, "")}1' + A BC - [root@linux ~]``# echo ' A BC ' | awk '{sub(/^ */, "");sub(/ *$/, "")}1'``A BC +|image1| -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c07/c07_19.md b/source/c07/c07_19.md index 4d9ad8f..1fed505 100644 --- a/source/c07/c07_19.md +++ b/source/c07/c07_19.md @@ -1,5 +1,7 @@ # 7.19 Ansible 使用教程 +![](http://image.iswbm.com/20200602135014.png) + ## 1. 环境准备 安装 Ansible ,需要 epel 源,所以要先配置一下 @@ -45,4 +47,4 @@ $ ansible 172.20.20.1 -m ping -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c07/c07_19.rst b/source/c07/c07_19.rst index d7bcb5d..e18353d 100644 --- a/source/c07/c07_19.rst +++ b/source/c07/c07_19.rst @@ -1,6 +1,8 @@ 7.19 Ansible 使用教程 ===================== +|image0| + 1. 环境准备 ----------- @@ -46,7 +48,8 @@ $ ansible 172.20.20.1 -m ping -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c07/c07_20.md b/source/c07/c07_20.md index 7dc445f..91f0cb9 100644 --- a/source/c07/c07_20.md +++ b/source/c07/c07_20.md @@ -1,5 +1,7 @@ # 7.20 使用 Shell 删除文件的多种方法 +![](http://image.iswbm.com/20200602135014.png) + ## 1. 当前目录下的文件 最经典的方法,删除当前目录下的所有类型的文件 diff --git a/source/c07/c07_20.rst b/source/c07/c07_20.rst index fcb4ca0..7154b00 100644 --- a/source/c07/c07_20.rst +++ b/source/c07/c07_20.rst @@ -1,6 +1,8 @@ 7.20 使用 Shell 删除文件的多种方法 ================================== +|image0| + 1. 当前目录下的文件 ------------------- @@ -46,3 +48,6 @@ 1. 把上面的 ``.`` 全部改为指定的目录 2. 在上面的命令之前,都加上 ``cd ${dest_dir};`` ,并且命令后,加上 ``cd -`` + +.. |image0| image:: http://image.iswbm.com/20200602135014.png + diff --git a/source/c07/c07_21.md b/source/c07/c07_21.md new file mode 100644 index 0000000..e6573c7 --- /dev/null +++ b/source/c07/c07_21.md @@ -0,0 +1,122 @@ +# 7.21 如何快速创建超大文件? + +![](http://image.iswbm.com/20200602135014.png) + +常规的创建文件方式有: + +1. touch +2. vi(m) +3. tee +4. `>` 或 `>>` + +但是这几种都只适合创建小的文本文件,某些情况下出于测试的需要,你需要快速创建一个超大的文件,可能要 上百G。这时候要使用上面几个命令,你可能要等一天的时间,效率非常低。 + +接下来介绍几种我常用的方法 + +## 1. dd + +dd命令,可以从标准输入或指定的文件中读取数据,拷贝至新文件。 + +```shell +$ dd if=/dev/zero of=big_file count=10 bs=1G +``` + +使用 time 命令,可以算出创建一个 10G的文件需要耗时多久? + +```shell +$ time dd if=/dev/zero of=big_file count=10 bs=1G +10+0 records in +10+0 records out +10737418240 bytes (11 GB) copied, 7.93627 s, 1.4 GB/s + +real 0m7.941s +user 0m0.000s +sys 0m7.935s +``` + +花了将近8秒的时间 + +```shell +$ ls -lh big_file +-rw-r--r-- 1 root root 10G Jun 2 10:57 big_file +``` + + + +## 2. fallocate + +fallocate命令可以为文件预分配物理空间。`-l`后接空间大小,默认单位为字节。也可后跟k、m、g、t、p、e来指定单位,分别代表KB、MB、GB、TB、PB、EB。 + +```shell +$ fallocate -l 10G big_file +``` + +使用 time 命令,可以算出创建一个 10G的文件需要耗时多久? + +```shell +$ time fallocate -l 10G big_file +real 0m0.002s +user 0m0.000s +sys 0m0.001s +``` + +居然只有 0.001秒。 + +```shell +$ ls -lh big_file +-rw-r--r-- 1 root root 10G Jun 2 11:01 big_file +``` + +使用 du 命令,查看是否是真正创建了一个 10G大小的文件?从结果来看,是的。 + +```shell +$ du -sh big_file +10G big_file +``` + +## 3. truncate + +truncate命令可以将文件缩减或扩展为指定大小,使用`-s`参数设置大小。 + +```shell +$ truncate -s 10G big_file +``` + +使用 time 命令,可以算出创建一个 10G的文件需要耗时多久? + +```shell +$ time truncate -s 10G big_file +real 0m0.001s +user 0m0.000s +sys 0m0.001s +``` + +也是只有 0.001 秒。 + +不过与 fallocate 不同的是,allocate 创建的文件是真实大小,而 truncate 创建的并不是。 + +使用 ls 和 du 命令,查看的结果是不一样的。 + +```shell +$ ls -lh big_file +-rw-r--r-- 1 root root 10G Jun 2 11:11 big_file + +$ du -sh big_file +0 big_file +``` + +由此可见 truncate 的作用是指定一个文件的大小,如果该文件不存在,就会创建该文件。如果指定文件的大小小于原先的大小,会丢失内容。 + +这个命令指定的文件大小其实是虚拟的。只是显示出来的大小。如果你指定一个非常大的文件。其实服务器剩余空间并不会减少。 + +--- + +以上,是我常用的几种创建大文件的方法,可以根据不同的使用场景进行选择,我一般使用的是 fallocate,速度够快,也是也会真实地占用磁盘空间,符合真实的测试场景。 + + + +--- + + + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c07/c07_21.rst b/source/c07/c07_21.rst new file mode 100644 index 0000000..a2bbd9f --- /dev/null +++ b/source/c07/c07_21.rst @@ -0,0 +1,128 @@ +7.21 如何快速创建超大文件? +=========================== + +|image0| + +常规的创建文件方式有: + +1. touch +2. vi(m) +3. tee +4. ``>`` 或 ``>>`` + +但是这几种都只适合创建小的文本文件,某些情况下出于测试的需要,你需要快速创建一个超大的文件,可能要 +上百G。这时候要使用上面几个命令,你可能要等一天的时间,效率非常低。 + +接下来介绍几种我常用的方法 + +1. dd +----- + +dd命令,可以从标准输入或指定的文件中读取数据,拷贝至新文件。 + +.. code:: shell + + $ dd if=/dev/zero of=big_file count=10 bs=1G + +使用 time 命令,可以算出创建一个 10G的文件需要耗时多久? + +.. code:: shell + + $ time dd if=/dev/zero of=big_file count=10 bs=1G + 10+0 records in + 10+0 records out + 10737418240 bytes (11 GB) copied, 7.93627 s, 1.4 GB/s + + real 0m7.941s + user 0m0.000s + sys 0m7.935s + +花了将近8秒的时间 + +.. code:: shell + + $ ls -lh big_file + -rw-r--r-- 1 root root 10G Jun 2 10:57 big_file + +2. fallocate +------------ + +fallocate命令可以为文件预分配物理空间。\ ``-l``\ 后接空间大小,默认单位为字节。也可后跟k、m、g、t、p、e来指定单位,分别代表KB、MB、GB、TB、PB、EB。 + +.. code:: shell + + $ fallocate -l 10G big_file + +使用 time 命令,可以算出创建一个 10G的文件需要耗时多久? + +.. code:: shell + + $ time fallocate -l 10G big_file + real 0m0.002s + user 0m0.000s + sys 0m0.001s + +居然只有 0.001秒。 + +.. code:: shell + + $ ls -lh big_file + -rw-r--r-- 1 root root 10G Jun 2 11:01 big_file + +使用 du 命令,查看是否是真正创建了一个 10G大小的文件?从结果来看,是的。 + +.. code:: shell + + $ du -sh big_file + 10G big_file + +3. truncate +----------- + +truncate命令可以将文件缩减或扩展为指定大小,使用\ ``-s``\ 参数设置大小。 + +.. code:: shell + + $ truncate -s 10G big_file + +使用 time 命令,可以算出创建一个 10G的文件需要耗时多久? + +.. code:: shell + + $ time truncate -s 10G big_file + real 0m0.001s + user 0m0.000s + sys 0m0.001s + +也是只有 0.001 秒。 + +不过与 fallocate 不同的是,allocate 创建的文件是真实大小,而 truncate +创建的并不是。 + +使用 ls 和 du 命令,查看的结果是不一样的。 + +.. code:: shell + + $ ls -lh big_file + -rw-r--r-- 1 root root 10G Jun 2 11:11 big_file + + $ du -sh big_file + 0 big_file + +由此可见 truncate +的作用是指定一个文件的大小,如果该文件不存在,就会创建该文件。如果指定文件的大小小于原先的大小,会丢失内容。 + +这个命令指定的文件大小其实是虚拟的。只是显示出来的大小。如果你指定一个非常大的文件。其实服务器剩余空间并不会减少。 + +-------------- + +以上,是我常用的几种创建大文件的方法,可以根据不同的使用场景进行选择,我一般使用的是 +fallocate,速度够快,也是也会真实地占用磁盘空间,符合真实的测试场景。 + +-------------- + +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png + diff --git a/source/c07/c07_22.md b/source/c07/c07_22.md new file mode 100644 index 0000000..5e753d4 --- /dev/null +++ b/source/c07/c07_22.md @@ -0,0 +1,188 @@ +# 7.22 rpm 命令详细用法 + +## 安装 + +- `-i`:表示安装 +- `-v`:查看更详细的安装信息 +- `-h`:以安装信息栏显示安装进度 + +```shell +$ rpm -ivh xxx.rpm +``` + +也可以指定 rpm 包的在线网址直接安装 + +```shell +$ rpm -ivh http://website.com/path/xxx.rpm +``` + +## 更新 + +直接更新安装 rpm 包,不管你之前有没有安装过 + +```shell +$ rpm -Uvh xxx.rpm +``` + +只更新已安装过的包 + +```shell +$ rpm -Fvh xxx.rpm +``` + +## 查询 + +查询软件是否安装 + +```shell +$ rpm -q xxx +``` + +查询已安装的软件 + +```shell +$ rpm -qa +``` + +查询已经安装的软件的信息 + +```shell +$ rpm -qi xxx +``` + +查询已经安装的软件中包含什么样的内容 + +```shell +$ rpm -ql +``` + +查询这个文件是由哪个安装包产生的 + +```shell +$ rpm -qf /etc/nova/nova.conf +``` + +查询已经安装的软件中包含的doc文件 + +```shell +$ rpm -qc xxx +``` + +查询已经安装的软件中包含的doc文件 + +```shell +$ rpm -qd +``` + +查询软件的脚本内容 + +```shell +$ rpm -q --scripts xxx +``` + +查询一个软件有关的依赖软件所包含的文件 + +```shell +$ rpm -qR xxx +``` + + + +以上都是查询已安装软件参数 + +如果要查询未安装的软件,只需要加 `-p` 即可 + + + +查询一个尚款安装的安装包的信息 + +```shell +$ rpm -qip xxx +``` + +查询一个尚未安装的安装包的配置文件 + +```shell +$ rpm -qpc xxx +``` + +查询一个尚未安装的安装包的doc文件 + +```shell +$ rpm -qpd xxx +``` + +查询一个尚未安装的安装包包含的信息 + +```shell +$ rpm -qpl xxx +``` + +## 卸载 + +卸载指定软件 + +```shell +$ rpm -e xxx +``` + + + +## 验证 + +查看一个软件里所包含的文件是否被修改过,只有被修改过才会被列出来 + +```shell +$ rpm -V xxx +``` + +加 `-a` 就表示查看所有的软件,后面不用再跟软件名 + +```shell +$ rpm -Va +``` + +加 `-p` 表示查看未安装软件,因此后面跟 rpm 包文件名 + +```shell +$ rpm -Vp xxx.rpm +``` + +查看某个文件是否被修改过 + +```shell +$ rpm -Vf /etc/path/file +``` + + + +## rpmdb + + +重建 rpmdb + +```shell +# 删除rpm数据文件 +rm -f /var/lib/rpm/__db.00* + +# 重建rpm数据文件 +rpm -rebuilddb +``` + + + +## 可选参数 + +- `--nodeps`:当软件由于依赖问题而导致无法安装和卸载时,可以加上这个参数强制进行安装或卸载 +- `--replacefiles`:当某些文件(是由该软件提供的)已经存在于机器上时,再次安装时,会提示文件已存在,此时加上这个参数 就可以直接覆盖。 +- `--replacepkgs`:当你使用 rpm -ivh *.rpm 一批软件时,如果有的包已经安装过了,此时加上这个参数就会直接重新安装,而不会因失败而退出 +- `--force`:--replacefiles 和 --replacepkgs 的综合体。 +- `--test`:测试一下能不能安装,有没有依赖问题,而不会真正去安装它。 +- `--justdb`:当 RPM 数据库损坏或者某些原因产生错误时,可使用这个参数 更新软件在数据库中的相关信息 +- `--nosignature`:跳过数字证书的检查,直接安装 +- `--prefix 新路径`:将软件安装在指定的路径 +- `--noscripts`:安装时,忽略某些命令的执行 + + + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c07/c07_22.rst b/source/c07/c07_22.rst new file mode 100644 index 0000000..0563e93 --- /dev/null +++ b/source/c07/c07_22.rst @@ -0,0 +1,190 @@ +7.22 rpm 命令详细用法 +===================== + +安装 +---- + +- ``-i``\ :表示安装 +- ``-v``\ :查看更详细的安装信息 +- ``-h``\ :以安装信息栏显示安装进度 + +.. code:: shell + + $ rpm -ivh xxx.rpm + +也可以指定 rpm 包的在线网址直接安装 + +.. code:: shell + + $ rpm -ivh http://website.com/path/xxx.rpm + +更新 +---- + +直接更新安装 rpm 包,不管你之前有没有安装过 + +.. code:: shell + + $ rpm -Uvh xxx.rpm + +只更新已安装过的包 + +.. code:: shell + + $ rpm -Fvh xxx.rpm + +查询 +---- + +查询软件是否安装 + +.. code:: shell + + $ rpm -q xxx + +查询已安装的软件 + +.. code:: shell + + $ rpm -qa + +查询已经安装的软件的信息 + +.. code:: shell + + $ rpm -qi xxx + +查询已经安装的软件中包含什么样的内容 + +.. code:: shell + + $ rpm -ql + +查询这个文件是由哪个安装包产生的 + +.. code:: shell + + $ rpm -qf /etc/nova/nova.conf + +查询已经安装的软件中包含的doc文件 + +.. code:: shell + + $ rpm -qc xxx + +查询已经安装的软件中包含的doc文件 + +.. code:: shell + + $ rpm -qd + +查询软件的脚本内容 + +.. code:: shell + + $ rpm -q --scripts xxx + +查询一个软件有关的依赖软件所包含的文件 + +.. code:: shell + + $ rpm -qR xxx + +以上都是查询已安装软件参数 + +如果要查询未安装的软件,只需要加 ``-p`` 即可 + +查询一个尚款安装的安装包的信息 + +.. code:: shell + + $ rpm -qip xxx + +查询一个尚未安装的安装包的配置文件 + +.. code:: shell + + $ rpm -qpc xxx + +查询一个尚未安装的安装包的doc文件 + +.. code:: shell + + $ rpm -qpd xxx + +查询一个尚未安装的安装包包含的信息 + +.. code:: shell + + $ rpm -qpl xxx + +卸载 +---- + +卸载指定软件 + +.. code:: shell + + $ rpm -e xxx + +验证 +---- + +查看一个软件里所包含的文件是否被修改过,只有被修改过才会被列出来 + +.. code:: shell + + $ rpm -V xxx + +加 ``-a`` 就表示查看所有的软件,后面不用再跟软件名 + +.. code:: shell + + $ rpm -Va + +加 ``-p`` 表示查看未安装软件,因此后面跟 rpm 包文件名 + +.. code:: shell + + $ rpm -Vp xxx.rpm + +查看某个文件是否被修改过 + +.. code:: shell + + $ rpm -Vf /etc/path/file + +rpmdb +----- + +重建 rpmdb + +.. code:: shell + + # 删除rpm数据文件 + rm -f /var/lib/rpm/__db.00* + + # 重建rpm数据文件 + rpm -rebuilddb + +可选参数 +-------- + +- ``--nodeps``\ :当软件由于依赖问题而导致无法安装和卸载时,可以加上这个参数强制进行安装或卸载 +- ``--replacefiles``\ :当某些文件(是由该软件提供的)已经存在于机器上时,再次安装时,会提示文件已存在,此时加上这个参数 + 就可以直接覆盖。 +- ``--replacepkgs``\ :当你使用 rpm -ivh \*.rpm + 一批软件时,如果有的包已经安装过了,此时加上这个参数就会直接重新安装,而不会因失败而退出 +- ``--force``\ :–replacefiles 和 –replacepkgs 的综合体。 +- ``--test``\ :测试一下能不能安装,有没有依赖问题,而不会真正去安装它。 +- ``--justdb``\ :当 RPM + 数据库损坏或者某些原因产生错误时,可使用这个参数 + 更新软件在数据库中的相关信息 +- ``--nosignature``\ :跳过数字证书的检查,直接安装 +- ``--prefix 新路径``\ :将软件安装在指定的路径 +- ``--noscripts``\ :安装时,忽略某些命令的执行 + +|image0| + +.. |image0| image:: http://image.iswbm.com/20200607174235.png + diff --git a/source/c08/c08_01.md b/source/c08/c08_01.md index 694ef43..3123f05 100644 --- a/source/c08/c08_01.md +++ b/source/c08/c08_01.md @@ -1,5 +1,7 @@ # 8.1 OpenStack 运维命令 +![](http://image.iswbm.com/20200602135014.png) + --- ## 一、OpenStack @@ -132,7 +134,7 @@ $neutron subnet-create --name public\ public 172.20.20.0/24 # 创建子网,更多选项可以查看 neutron subnet-create -h -neutron subnet-create --name 192.168.2.0/24 --allocation-pool start=192.168.2.200,end=192.168.2.230 --gateway 192.168.2.253 --dns-nameserver 114.114.114.114 --disable-dhcp private 192.168.2.0/24 +neutron subnet-create --name private --allocation-pool start=192.168.2.200,end=192.168.2.230 --gateway 192.168.2.253 --dns-nameserver 114.114.114.114 --disable-dhcp public 192.168.2.0/24 # 查看网络 $ neutron net-show @@ -347,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 -``` ## 三、集群相关 @@ -430,4 +424,4 @@ pkill -9 pacemaker;service pacemaker restart --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c08/c08_01.rst b/source/c08/c08_01.rst old mode 100755 new mode 100644 index 90bb12e..b67a69f --- a/source/c08/c08_01.rst +++ b/source/c08/c08_01.rst @@ -1,6 +1,8 @@ 8.1 OpenStack 运维命令 ====================== +|image0| + -------------- 一、OpenStack @@ -141,7 +143,7 @@ aggregate管理 public 172.20.20.0/24 # 创建子网,更多选项可以查看 neutron subnet-create -h - neutron subnet-create --name 192.168.2.0/24 --allocation-pool start=192.168.2.200,end=192.168.2.230 --gateway 192.168.2.253 --dns-nameserver 114.114.114.114 --disable-dhcp private 192.168.2.0/24 + neutron subnet-create --name private --allocation-pool start=192.168.2.200,end=192.168.2.230 --gateway 192.168.2.253 --dns-nameserver 114.114.114.114 --disable-dhcp public 192.168.2.0/24 # 查看网络 $ neutron net-show @@ -359,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 - 三、集群相关 ------------ @@ -444,7 +435,8 @@ aggregate管理 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! +|image1| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200607174235.png - 关注公众号,获取最新干货! diff --git a/source/c08/c08_02.md b/source/c08/c08_02.md index 2b88eda..ed1b4a2 100644 --- a/source/c08/c08_02.md +++ b/source/c08/c08_02.md @@ -1,5 +1,7 @@ # 8.2 OpenStack 部署SR-IOV +![](http://image.iswbm.com/20200602135014.png) + --- **摘要**: SR-IOV 技术是一种基于硬件的虚拟化解决方案,可提高性能和可伸缩性。SR-IOV 标准允许在虚拟机之间高效共享 PCIe(Peripheral Component Interconnect Express,快速外设组件互连)设备,并且它是在硬件中实现的,可以获得能够与本机性能媲美的 I/O 性能。 @@ -225,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。 @@ -250,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) ## 附录:参考文档 @@ -264,4 +266,4 @@ select * from pci_devices where instance_uuid='5a1c1828-4190-43fa-8e05-ae51b0196 ------ -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c08/c08_02.rst b/source/c08/c08_02.rst old mode 100755 new mode 100644 index 09c3722..18e56ee --- a/source/c08/c08_02.rst +++ b/source/c08/c08_02.rst @@ -1,6 +1,8 @@ 8.2 OpenStack 部署SR-IOV ======================== +|image0| + -------------- **摘要**: SR-IOV @@ -21,13 +23,13 @@ SR-IOV 8.2.1.1 bios开启vt-d, sriov ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -|image0| |image1| +|image1| |image2| 8.2.1.2 开启iommu ~~~~~~~~~~~~~~~~~ vim /etc/sysconfig/grub(或者/etc/default/grub):intel_iommu=on -|image2| 改完后,重新生成配置 +|image3| 改完后,重新生成配置 :: @@ -118,7 +120,7 @@ vim /etc/nova/nova.conf service openstack-nova-compute restart -查看数据库,compute_nodes,确认VF是否有被管理起来 |image3| +查看数据库,compute_nodes,确认VF是否有被管理起来 |image4| 8.2.3 控制节点 -------------- @@ -128,7 +130,7 @@ vim /etc/nova/nova.conf 8.2.3.1 neutron-server ~~~~~~~~~~~~~~~~~~~~~~ -vim /etc/neutron/plugins/ml2/ml2_conf.ini |image4| +vim /etc/neutron/plugins/ml2/ml2_conf.ini |image5| 添加sriov配置文件(如果没有此文件,就添加) vim /etc/neutron/plugins/ml2/ml2_conf_sriov.ini @@ -145,7 +147,7 @@ vim /etc/neutron/plugins/ml2/ml2_conf.ini |image4| --config-file /etc/neutron/plugins/ml2/ml2_conf_sriov.ini -|image5| +|image6| 重启服务,使配置生效 @@ -264,7 +266,7 @@ interface-attach不适用于sriov port。 1、使用neutron port-show ,查看并记录原 port 的 ``binding:profile`` 信息,如果有多个port,把每个port的信息都记录下来。 -|image6| +|image7| 2、nova interface-detach卸载原来的port。 @@ -290,7 +292,7 @@ port-update命令不支持,只能使用curl,需要修改port-id及binding:pr select * from pci_devices where instance_uuid='5a1c1828-4190-43fa-8e05-ae51b0196656'; -|image7| +|image8| 附录:参考文档 -------------- @@ -303,17 +305,16 @@ port-update命令不支持,只能使用curl,需要修改port-id及binding:pr -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: https://i.loli.net/2018/01/19/5a61bfa0ca66f.png -.. |image1| image:: https://i.loli.net/2018/01/19/5a61bfd243111.png -.. |image2| image:: https://i.loli.net/2018/01/19/5a61c022d68d3.png -.. |image3| image:: https://i.loli.net/2018/01/19/5a61c1cf51b58.png -.. |image4| image:: https://i.loli.net/2018/01/19/5a61c1faac447.png -.. |image5| image:: https://i.loli.net/2018/01/19/5a61c246451e7.png -.. |image6| image:: http://image.python-online.cn/20190529202132.png -.. |image7| image:: http://image.python-online.cn/20190529202440.png +|image9| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: https://i.loli.net/2018/01/19/5a61bfa0ca66f.png +.. |image2| image:: https://i.loli.net/2018/01/19/5a61bfd243111.png +.. |image3| image:: https://i.loli.net/2018/01/19/5a61c022d68d3.png +.. |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.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 55fac94..ef1e329 100644 --- a/source/c08/c08_03.md +++ b/source/c08/c08_03.md @@ -1,5 +1,7 @@ # 8.3 制作 OpenStack 镜像 +![](http://image.iswbm.com/20200602135014.png) + --- 这里仅以Ubuntu系统为例,其他系统也是类似,部分不同的地方会给出标注。 @@ -85,7 +87,8 @@ virt-install --name ubuntu-16.04 \ - listen='0.0.0.0'> + + @@ -455,7 +458,7 @@ shutdown -h now 通过 guestfish 工具可以实现不用创建虚拟机就可以修改镜像里的文件内容。 -![](http://image.python-online.cn/20190827200522.png) +![](http://image.iswbm.com/20190827200522.png) ## 8.3.3 KVM 镜像快照 @@ -468,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) ## 附录:参考文档 @@ -484,4 +487,4 @@ $ virsh blockcommit ws_controller01 hda --active --verbose --pivot --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c08/c08_03.rst b/source/c08/c08_03.rst old mode 100755 new mode 100644 index 229c25c..f456eb5 --- a/source/c08/c08_03.rst +++ b/source/c08/c08_03.rst @@ -1,6 +1,8 @@ 8.3 制作 OpenStack 镜像 ======================= +|image0| + -------------- 这里仅以Ubuntu系统为例,其他系统也是类似,部分不同的地方会给出标注。 @@ -97,7 +99,8 @@ - listen='0.0.0.0'> + + @@ -125,7 +128,7 @@ 下载速度十分缓慢,可能需要半个多小时,我已经下载好,上传到百度云盘,需要自取。 -|image0| +|image1| 安装好后,可以尝试连接虚拟机。 如果连接失败,可以自己的排查下原因 @@ -134,7 +137,7 @@ 1. 宿主机的iptables,firewall 2. 端口是否开放,telnet 一下 -|image1| +|image2| 然后根据提示安装系统(注意要先新建一个用户,设置该用户密码,后续要登陆虚拟机使用)。安装完成后,退出spice。 @@ -496,7 +499,7 @@ CentOS6 创建快照前需要先删除\ ``75-persistent-net-generator.rules`` 通过 guestfish 工具可以实现不用创建虚拟机就可以修改镜像里的文件内容。 -|image2| +|image3| 8.3.3 KVM 镜像快照 ------------------ @@ -508,10 +511,10 @@ CentOS6 创建快照前需要先删除\ ``75-persistent-net-generator.rules`` $ virsh snapshot-create-as ws_controller01 2.2.7 --disk-only --atomic $ virsh blockcommit ws_controller01 hda --active --verbose --pivot -|image3| - |image4| +|image5| + 附录:参考文档 -------------- @@ -528,14 +531,13 @@ CentOS6 创建快照前需要先删除\ ``75-persistent-net-generator.rules`` -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image6| -.. |image0| image:: https://i.loli.net/2018/01/27/5a6c34714685d.png -.. |image1| image:: https://i.loli.net/2018/01/27/5a6c34b14c6ec.png -.. |image2| image:: http://image.python-online.cn/20190827200522.png -.. |image3| image:: http://image.python-online.cn/20191211174659.png -.. |image4| image:: http://image.python-online.cn/20191211174956.png +.. |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.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 0c25a83..5aaac02 100644 --- a/source/c08/c08_04.md +++ b/source/c08/c08_04.md @@ -1,5 +1,7 @@ # 8.4 云计算与虚拟化入门通识 +![](http://image.iswbm.com/20200602135014.png) + 经常有朋友问我,你是做什么的呢? 我回答说,云计算。 @@ -10,7 +12,7 @@ 为了让你多了解一些云计算的内容,我想着写这么一篇文章,介绍一下我从事的领域,同时也对云计算和虚拟化这块入门级知识做一个梳理,如果刚好你也想进入这个领域,这份入门通识指南,应该挺适合你的。 -![](http://image.python-online.cn/20190714161353.png) +![](http://image.iswbm.com/20190714161353.png) ## 8.4.1 云计算是什么? @@ -22,7 +24,7 @@ 云,就是将这些零散实体资源变成一个巨大无比的资源池子,有了这个池子,做为个人用户,你不再需要自己你买一个电脑放在家里,做为小型公司,你不需要自己整一个机房,花很多的人力和设备成本去运营这些基础设施。一旦你需要,你就向池子拥有者申请即可。这极大的提高了资源的利用率,以及分配的灵活性。 -![](http://image.python-online.cn/20190716004341.png) +![](http://image.iswbm.com/20190716004341.png) 还有人说,云就像是天上的云一样,聚焦的水汽多了就会下雨,落到地面的雨水又会蒸发到天上,继续等待下一次下雨。云计算里的云,正如大自然里的云一样,可以实现资源的循环利用。你在公有云提供商那里,购买了一年的云主机,一年后资源被回收,可以再分配给其他人使用。 @@ -107,7 +109,7 @@ QEMU是一套由Fabrice Bellard所编写的模拟处理器的自由软件。Qemu 说到了QEMU,其实它也是一个虚拟化软件。作用是什么呢,它相当于一个路由器,当Guest OS的内核想要操作物理硬件时,必须先经由Qemu转发,将操作指令转给真实的硬件。由于所有的指令都要从Qemu里面过一手,因而性能比较差。 -![](http://image.python-online.cn/FjlPaQLTiYCde92WhurWsRx6z8CK) +![](http://image.iswbm.com/FjlPaQLTiYCde92WhurWsRx6z8CK) **总结** @@ -129,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 虚拟化分类 @@ -165,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 @@ -406,7 +408,7 @@ virsh start guest_vm 有了OpenStack,你可以使用 Horizon提供的界面进行虚拟机的管理 -![来源网络,侵删](http://image.python-online.cn/20190714151716.png) +![来源网络,侵删](http://image.iswbm.com/20190714151716.png) 也可以使用nova 的 cli 命令进行创建。 @@ -433,4 +435,4 @@ nova boot \ --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c08/c08_04.rst b/source/c08/c08_04.rst index fa24ae1..ca07413 100644 --- a/source/c08/c08_04.rst +++ b/source/c08/c08_04.rst @@ -1,6 +1,8 @@ 8.4 云计算与虚拟化入门通识 ========================== +|image0| + 经常有朋友问我,你是做什么的呢? 我回答说,云计算。 @@ -11,7 +13,7 @@ 为了让你多了解一些云计算的内容,我想着写这么一篇文章,介绍一下我从事的领域,同时也对云计算和虚拟化这块入门级知识做一个梳理,如果刚好你也想进入这个领域,这份入门通识指南,应该挺适合你的。 -|image0| +|image1| 8.4.1 云计算是什么? -------------------- @@ -25,7 +27,7 @@ computing**\ ),是一种基于\ `互联网 ` 下的文件获取的,这个目录每个虚拟机一个。 -![](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) @@ -405,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) @@ -428,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 低版本的日志输出 @@ -436,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 旧版本网络配置的三个巨坑 @@ -448,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) 为了让你能更加清晰的了解这个网络配置过程,我阅读了这块的源代码。 @@ -467,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给清除掉。 -使用这种方式并不会将第一次配置的旧ip给清除掉。![](http://image.python-online.cn/Fh-5SQ8qYjhJEKovI6LmIpabSy2c) +![](http://image.iswbm.com/Fh-5SQ8qYjhJEKovI6LmIpabSy2c) -这个问题,目前我只在CentOS6 中遇到过。可以通过修改代码让其先 `ifdown` 再 `ifup` 就可以解决这个问题。![](http://image.python-online.cn/20190430231812.png) +这个问题,目前我只在CentOS6 中遇到过。可以通过修改代码让其先 `ifdown` 再 `ifup` 就可以解决这个问题。![](http://image.iswbm.com/20190430231812.png) **坑二** @@ -479,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) **坑三** @@ -500,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 网络是如何启动的?(新版本) @@ -517,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) @@ -549,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) 提取出来其实就三条命令,要注意的是,这三条命令执行是有顺序的 @@ -565,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 阶段进入这里呢,会不会又重复配置网络了呢? @@ -577,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是如何配置上的呢? @@ -604,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 # 查看机器里有哪几张网卡 @@ -624,4 +700,4 @@ ip addr flush dev ens3 --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c08/c08_06.rst b/source/c08/c08_06.rst index 682d12d..c5af0f5 100644 --- a/source/c08/c08_06.rst +++ b/source/c08/c08_06.rst @@ -1,6 +1,8 @@ 8.6 源码解读:Cloud-Init ======================== +|image0| + cloud-init 是 linux 的一个工具,当系统启动时,cloud-init 可从 nova metadata 服务或者 config drive 中获取 metadata,完成一些虚拟机的初始化工作,这些工作有可能是每台虚拟机例行的动作,如配置ip,也有可能是定制化动作,如注入密码等。 @@ -17,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 @@ -30,17 +32,17 @@ metadata,完成一些虚拟机的初始化工作,这些工作有可能是每 比如 local 阶段作为第一个执行的。 -|image0| +|image1| 而后的几个服务(如init阶段),都需要保证 后于 local 已经启动过才能运行。 -|image1| +|image2| 在 **CentOS 6.x** 中,由于系统过于古老并没有 systemd ,它的服务启动顺序是由 ``/etc/rc.d/rc3.d`` 目录进行管理的,按照编号进行从小到大执行。 -|image2| +|image3| 这个启动顺序相当重要,在排查并修复虚拟机创建时 ip 的配置问题的时候会有帮助,主要是 CentOS6.x @@ -63,7 +65,7 @@ cloudinit-local 之后启动才可正常从配置文件中读取 ip 并配置。而当你在镜像里安装 NetworkManager后,默认情况下它的启动顺序是会在 cloudinit-local 之前的。 -|image3| +|image4| 解决方法也很简单,将 ``S23NetworkManager`` 重命名为 ``S54NetworkManager`` 即可。 @@ -94,21 +96,21 @@ NetworkManager后,默认情况下它的启动顺序是会在 cloudinit-local 这里再顺便说一点,在使用 pdb 进行调试时,你会发现一旦执行就会报错或者无法调试。 -|image4| +|image5| 这里就需要一个小技巧。你只需修改cloudinit 的入口文件(CentOS6.x 是在/usr/bin/cloud-init,CentOS7.x 是在 /usr/lib/python2.7/site-packages/cloudinit/cmd/main.py)将红框这行注释。 -|image5| +|image6| 再尝试即可正常调试。 -|image6| +|image7| 从这里也学到了一点,如何要关闭调试功能,只要关闭标准输入就行了。 -|image7| +|image8| 如果你在调试 single,而且你把 /var/lib/cloud 目录删除过,请务必要执行一下 cloud-init init ,不然会取不到 user_data @@ -124,11 +126,11 @@ centos 6.5 的cloudinit 使用的是 python2.6,没有 format ``/usr/lib/python2.7/site-packages/cloudinit/cmd/main.py``)的 main_init 函数中,会调用 ``stages.py:fetch()`` 函数。 -|image8| +|image9| 支持哪些源:是在代码里(settings.py)定义写死的 -|image9| +|image10| 如果你的虚拟机固定只使用其中一种,那可以只留一种,加快cloudinit 的执行速度。 @@ -136,20 +138,20 @@ centos 6.5 的cloudinit 使用的是 python2.6,没有 format find_source 是通过一个一个去执行 对应source模块的 get_data(),如果获取到了数据就直接返回。 -|image10| +|image11| 最经常使用的是 ConfigDrive,来看一下它是如何获取数据的。 -|image11| +|image12| 它会先创建一个临时目录,尝试将 ``/dev/sr0`` 挂载到这个目录下。 -|image12| +|image13| 然后将读取的数据存入 ``/var/lib/cloud/instance/obj.pkl``\ , 而后续执行都将从这里反序列化,提高速度。 -|image13| +|image14| 8.6.5 userdata 使用说明 ----------------------- @@ -323,7 +325,7 @@ cirename0 的网卡。 这就是cloudinit搞的鬼,在cloudinit的local阶段,好像会记录之前的mac地址,如果发现不一致,就会触发rename_interface。 -|image14| +|image15| 8.6.7 虚拟机启动卡住 -------------------- @@ -336,7 +338,7 @@ ephemeral0 拿到对应的 /dev/vdb,并将其写入 /etc/fstab 中。在下次重启时,会根据 fstab 挂载磁盘,如果挂载不上,就会导致虚拟机启动卡住。 -|image15| +|image16| 8.6.8 模块是怎么执行的 ---------------------- @@ -432,7 +434,7 @@ ephemeral0 拿到对应的 /dev/vdb,并将其写入 /etc/fstab 里的代码,只有一个模块使用了 PER_ONCE ,那就是 ``cc_scripts_per_once.py``\ 。 -|image16| +|image17| 其实他们还是有区别的,由下面的代码来看 @@ -442,14 +444,14 @@ ephemeral0 拿到对应的 /dev/vdb,并将其写入 /etc/fstab ``/var/lib/cloud/instances/`` 下的文件获取的,这个目录每个虚拟机一个。 -|image17| +|image18| 如果你的模块里已经配置了频率,则以此为准。若没有配置,则默认为 ``PER_INSTANCE``\ 。 具体函数:\ ``_run_modules`` -|image18| +|image19| 那么cloudinit 是如何控制模块的执行频率呢? @@ -458,15 +460,15 @@ ephemeral0 拿到对应的 /dev/vdb,并将其写入 /etc/fstab 文件,说明已经执行过(返回 False ),如果不存在 sem 文件(返回 True) 说明未执行过。 -|image19| +|image20| sem 文件是怎样的? 这是 ``PER_ONCE`` 的 sem 文件: -|image20| +|image21| -这是 ``PER_INSTANCE`` 的 sem 文件\ |image21| +这是 ``PER_INSTANCE`` 的 sem 文件\ |image22| 8.6.10 如何解析网卡配置 ----------------------- @@ -481,7 +483,7 @@ ubuntu 的网卡配置不是正常我们常见的 json 或者 yaml from cloudinit.net import eni config=eni.parse_deb_config('/etc/network/interfaces.d/50-cloud-init.cfg') -|image22| +|image23| 8.6.11 低版本的日志输出 ----------------------- @@ -491,7 +493,7 @@ ubuntu 的网卡配置不是正常我们常见的 json 或者 yaml 经过排查,你可以在 ``cloudinit/log.py`` 中按下面的改法修改解决 -|image23| +|image24| 8.6.12 旧版本网络配置的三个巨坑 ------------------------------- @@ -507,12 +509,12 @@ ubuntu 的网卡配置不是正常我们常见的 json 或者 yaml 在 cloud-init 0.7.5中,网络信息的读取与配置都是在且仅能在 local 阶段进行的,代码如下,只在 dsmode 为 local 时才会执行 on_first_boot。 -|image24| +|image25| 而如果是虚拟机重启的话,是在stages.py 这边判断是否为新虚拟机的,这也和旧版本有所区别。 -|image25| +|image26| 为了让你能更加清晰的了解这个网络配置过程,我阅读了这块的源代码。 @@ -529,12 +531,14 @@ ubuntu 的网卡配置不是正常我们常见的 json 或者 yaml 如果是按照旧虚拟机创建新的快照镜像,然后使用这个镜像创建新的虚拟机,有可能会在同一块网卡上出现新旧两个ip,这是因为虚拟机在启动过程中,会先读取原网络配置配置ip,然后才会运行 cloud-init 进行新ip的配置,而新ip的配置是使用 ``ifup`` -这个命令\ |image26| +这个命令\ |image27| -使用这种方式并不会将第一次配置的旧ip给清除掉。\ |image27| +使用这种方式并不会将第一次配置的旧ip给清除掉。 + +|image28| 这个问题,目前我只在CentOS6 中遇到过。可以通过修改代码让其先 ``ifdown`` -再 ``ifup`` 就可以解决这个问题。\ |image28| +再 ``ifup`` 就可以解决这个问题。\ |image29| **坑二** @@ -544,7 +548,7 @@ NetworkManager 具体的创建逻辑是在这 -|image29| +|image30| **坑三** @@ -565,12 +569,12 @@ NetworkManager 一个是会自动DHCP获取到一个以ip命名的hostname,并将原来的覆盖掉。 -|image30| +|image31| 为了避免出现这些情况,请务必保证这些包都安装完整(左为 CentOS 7.2,右为 CentOS 6.5)。 -|image31| +|image32| 8.6.13 网络是如何启动的?(新版本) ----------------------------------- @@ -585,17 +589,17 @@ CentOS 6.5)。 在 local 阶段的时候,\ ``existing`` 是 ``check`` ,这是合理的。 -|image32| - |image33| +|image34| + 在local阶段,on_first_boot 的函数 network 是 False ,所以这里也不会写网卡配置文件,自然也不会配置。 -|image34| - |image35| +|image36| + 再往后面看,就可以发现,原来写配置文件的地方是在 ``cmd/main.py`` 里。 .. code:: python @@ -620,11 +624,11 @@ True,意思是会启用网卡,配置ip。 2. 校正网卡名,以 ConfigDrive 的配置为准 3. 将ip信息写入配置文件,并启动网卡 -|image36| +|image37| 那它是如何对网卡进行重命令的呢?看了代码,其实是用ip命令实现的。 -|image37| +|image38| 提取出来其实就三条命令,要注意的是,这三条命令执行是有顺序的 @@ -639,11 +643,11 @@ True,意思是会启用网卡,配置ip。 目录下获取的。每个网卡一个目录,每个目录下都有相应的文件记录相应的信息,比如 ``/sys/class/net/ens3/address`` 记录的是网卡的 mac 地址。 -|image38| +|image39| 接下来就要开始配置网络了,先写网络配置文件,再根据参数选择是否启用网络。 -|image39| +|image40| 如果是重启虚拟机或者 init 阶段进入这里呢,会不会又重复配置网络了呢? @@ -652,7 +656,7 @@ True,意思是会启用网卡,配置ip。 cloudinit 会根据缓存中的虚拟机的uuid来与ConfigDrive 的对比,如果不一样,则认为这台虚拟机是新创建的虚拟机,只有新的虚拟机才会走入这里去配置网络。 -|image40| +|image41| 那问题又来了,虽然上面有个 bring_up 的参数,实际上,通过代码可以发现,在 local 阶段,bring_up 为 False 不会去启用网卡,而在 init 阶段呢,虽然 @@ -688,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 @@ -705,50 +786,51 @@ cloudinit 允许通过 user_data 指定你想在虚拟机启动时,执行的 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190430204707.png -.. |image1| image:: http://image.python-online.cn/20190430204933.png -.. |image2| image:: http://image.python-online.cn/20190430205449.png -.. |image3| image:: http://image.python-online.cn/20190430211900.png -.. |image4| image:: http://image.python-online.cn/20190430213012.png -.. |image5| image:: http://image.python-online.cn/20190430213337.png -.. |image6| image:: http://image.python-online.cn/20190430213429.png -.. |image7| image:: http://image.python-online.cn/20190430213729.png -.. |image8| image:: http://image.python-online.cn/20190430225605.png -.. |image9| image:: http://image.python-online.cn/20190430225726.png -.. |image10| image:: http://image.python-online.cn/20190430230214.png -.. |image11| image:: http://image.python-online.cn/FpqcyL4hWwpaAGzsdreQwXvH4Rx8 -.. |image12| image:: http://image.python-online.cn/20190430230839.png -.. |image13| image:: http://image.python-online.cn/20190430231108.png -.. |image14| image:: http://image.python-online.cn/20190623091911.png -.. |image15| image:: http://image.python-online.cn/20190708175813.png -.. |image16| image:: http://image.python-online.cn/20190910160035.png -.. |image17| image:: http://image.python-online.cn/20190910150305.png -.. |image18| image:: http://image.python-online.cn/20190910142222.png -.. |image19| image:: http://image.python-online.cn/20190910153637.png -.. |image20| image:: http://image.python-online.cn/20190910171359.png -.. |image21| image:: http://image.python-online.cn/20190910171538.png -.. |image22| image:: http://image.python-online.cn/20190906091102.png -.. |image23| image:: http://image.python-online.cn/20190909172153.png -.. |image24| image:: http://image.python-online.cn/20190429104357.png -.. |image25| image:: http://image.python-online.cn/20190829141059.png -.. |image26| image:: http://image.python-online.cn/Fp1TeHSiIMIQoZygbW9VSfAagB_d -.. |image27| image:: http://image.python-online.cn/Fh-5SQ8qYjhJEKovI6LmIpabSy2c -.. |image28| image:: http://image.python-online.cn/20190430231812.png -.. |image29| image:: http://image.python-online.cn/20190430232309.png -.. |image30| image:: http://image.python-online.cn/20190429205735.png -.. |image31| image:: http://image.python-online.cn/20190430232911.png -.. |image32| image:: http://image.python-online.cn/20190911175423.png -.. |image33| image:: http://image.python-online.cn/20190911174648.png -.. |image34| image:: http://image.python-online.cn/20190911173615.png -.. |image35| image:: http://image.python-online.cn/20190911195024.png -.. |image36| image:: http://image.python-online.cn/20190911202425.png -.. |image37| image:: http://image.python-online.cn/20190911202551.png -.. |image38| image:: http://image.python-online.cn/20190911203953.png -.. |image39| image:: http://image.python-online.cn/20190911204805.png -.. |image40| image:: http://image.python-online.cn/20190911205518.png +|image44| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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 b81e476..42abc54 100644 --- a/source/c08/c08_07.md +++ b/source/c08/c08_07.md @@ -1,16 +1,18 @@ # 8.7 OpenStack 实现GPU直通 +![](http://image.iswbm.com/20200602135014.png) + ## 8.7.1 环境准备 检查是否有 GPU 设备:`lspci | grep NVIDIA` (若没有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 @@ -34,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 隐藏虚拟机标识 @@ -50,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加上这段。 @@ -62,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。 @@ -80,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 称重器 @@ -96,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 使用说明 @@ -123,4 +125,4 @@ openstack image set --property img_hide_hypervisor_id=true --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c08/c08_07.rst b/source/c08/c08_07.rst index 0b65413..38c58d3 100644 --- a/source/c08/c08_07.rst +++ b/source/c08/c08_07.rst @@ -1,6 +1,8 @@ 8.7 OpenStack 实现GPU直通 ========================= +|image0| + 8.7.1 环境准备 -------------- @@ -8,11 +10,11 @@ (若没有lspci命令,则安装 yum install pciutils -y) -|image0| +|image1| 此时查看,驱动是 nvidia -|image1| +|image2| :: @@ -39,15 +41,15 @@ 未创建虚拟机 -|image2| +|image3| 创建gpu虚拟机成功,driver 由 nvidia 变为 vfio-pci -|image3| +|image4| 删除虚拟机成功 -|image4| +|image5| 8.7.2.2 隐藏虚拟机标识 ~~~~~~~~~~~~~~~~~~~~~~ @@ -59,7 +61,7 @@ id。 (若没有cpuid,则安装 yum install -y cpuid) -|image5| +|image6| 如何隐藏 hypervisor id,只需要在xml的feature加上这段。 @@ -71,7 +73,7 @@ id。 然后destroy再start一下虚拟机,在虚拟机内部再次查看。 -|image6| +|image7| 在Pike 版本,OpenStack 已经可以根据镜像里是否有\ ``img_hide_hypervisor_id=true`` @@ -92,16 +94,16 @@ Pike 版本自己修改OpenStack 的源码,在xml里加上这段。 具体 Pike版的代码如何实现,主要函数是这段 -|image7| +|image8| 记得在镜像meta里添加 img_hide_hypervisor_id 的字段 -|image8| +|image9| 8.7.2.3. 是否直通成功 ~~~~~~~~~~~~~~~~~~~~~ -在虚拟机内部执行 nvidia-smi\ |image9| +在虚拟机内部执行 nvidia-smi\ |image10| 8.7.2.4 称重器 ~~~~~~~~~~~~~~ @@ -116,7 +118,7 @@ GPU 的宿主机上。 设备数来决定)的宿主机的权重为最低,为了保证这点,这个称重器的权重系数应最大,暂定为200(可以通过配置项 ws_gpu_weight_multiplier 设置)。 -|image10| +|image11| 8.7.3 使用说明 -------------- @@ -148,20 +150,19 @@ GPU 作为一种硬件资源,同样需要在模板中配置,配置方式是 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190419144135.png -.. |image1| image:: http://image.python-online.cn/20190419144044.png -.. |image2| image:: http://image.python-online.cn/20190422201117.png -.. |image3| image:: http://image.python-online.cn/20190422201041.png -.. |image4| image:: http://image.python-online.cn/20190422201117.png -.. |image5| image:: http://image.python-online.cn/20190422205222.png -.. |image6| image:: http://image.python-online.cn/20190422204755.png -.. |image7| image:: http://image.python-online.cn/20190528105408.png -.. |image8| image:: http://image.python-online.cn/20190528105021.png -.. |image9| image:: http://image.python-online.cn/20190528114526.png -.. |image10| image:: http://image.python-online.cn/20190606185531.png +|image12| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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 3acfe11..f0ca601 100644 --- a/source/c08/c08_08.md +++ b/source/c08/c08_08.md @@ -1,5 +1,7 @@ # 8.8 OpenStack 如何使用DHCP? +![](http://image.iswbm.com/20200602135014.png) + ## 8.8.1 为何要使用dhcp? 在创建虚拟机时,虚拟机的ip获取一般有两种方式: @@ -17,7 +19,7 @@ 如下图所示 -![](http://image.python-online.cn/20190514202013.png) +![](http://image.iswbm.com/20190514202013.png) 红色部分是虚拟机内部需要增加的网卡设备。 @@ -78,7 +80,7 @@ TYPE=OVSBridge 确保控制节点都安装了 `neutron-openvswitch-agent` 和 `neutron-dhcp-agent` 等几个包 -![](http://image.python-online.cn/20190514202442.png) +![](http://image.iswbm.com/20190514202442.png) 确保如下几个配置无误 @@ -113,7 +115,7 @@ systemctl restart neutron-openvswitch-agent 通过 ovs-vsctl show 检查环境是否正常 -![](http://image.python-online.cn/20190514202736.png) +![](http://image.iswbm.com/20190514202736.png) @@ -127,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启动起来,就可以立即创建成功。 @@ -149,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 上会因此出现问题 @@ -167,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` 即可。 @@ -185,4 +187,4 @@ systemctl restart neutron-openvswitch-agent --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c08/c08_08.rst b/source/c08/c08_08.rst index 049617e..64457db 100644 --- a/source/c08/c08_08.rst +++ b/source/c08/c08_08.rst @@ -1,6 +1,8 @@ 8.8 OpenStack 如何使用DHCP? ============================ +|image0| + 8.8.1 为何要使用dhcp? ---------------------- @@ -22,7 +24,7 @@ 如下图所示 -|image0| +|image1| 红色部分是虚拟机内部需要增加的网卡设备。 @@ -87,7 +89,7 @@ server网卡设备与ovs网桥。一个network一个tap设备。 确保控制节点都安装了 ``neutron-openvswitch-agent`` 和 ``neutron-dhcp-agent`` 等几个包 -|image1| +|image2| 确保如下几个配置无误 @@ -122,7 +124,7 @@ server网卡设备与ovs网桥。一个network一个tap设备。 通过 ovs-vsctl show 检查环境是否正常 -|image2| +|image3| 8.8.3.3 镜像支持 ~~~~~~~~~~~~~~~~ @@ -139,7 +141,7 @@ NetworkManager, ubuntu 是network-manager)才负责获取ip。 1. 启用DHCP后,dhcp server 会占用一个allocation_pools中的ip。 2. 子网启用了dhcp,ConfigDrive不会携带静态的ip地址(类型为ipv4,左图),而是携带了将类型变更为ipv4_dhcp(右图),虚拟机启动时发送广播消息DHCP - discover从dhcp server获得真正的ip地址。 |image3| + discover从dhcp server获得真正的ip地址。 |image4| 3. 若子网启用了dhcp,并且创建了虚拟机,此时 disable dhcp,只是表象上 dhcp 被关了,而实际 dhcp port 被是占用着,因为 dhcp server 还要给之前创建的虚拟机提供服务。如果再用这个子网创建的虚拟机会使用dhcp @@ -192,17 +194,17 @@ NetworkManager, ubuntu 是network-manager)才负责获取ip。 比如 local 阶段作为第一个执行的。 -|image4| +|image5| 而后的几个服务(如init阶段),都需要保证 后于 local 已经启动过才能运行。 -|image5| +|image6| 在 **CentOS 6.x** 中,由于系统过于古老并没有 systemd ,它的服务启动顺序是由 ``/etc/rc.d/rc3.d`` 目录进行管理的,按照编号进行从小到大执行。 -|image6| +|image7| 这个启动顺序相当重要,在 CentOS6.x 上会因此出现问题 @@ -223,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 @@ -249,16 +251,15 @@ setup_dhcp_port(),从这个函数里可以知道,dhcp-port的创建顺序: -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190514202013.png -.. |image1| image:: http://image.python-online.cn/20190514202442.png -.. |image2| image:: http://image.python-online.cn/20190514202736.png -.. |image3| image:: http://image.python-online.cn/20190514203612.png -.. |image4| image:: http://image.python-online.cn/20190430204707.png -.. |image5| image:: http://image.python-online.cn/20190430204933.png -.. |image6| image:: http://image.python-online.cn/20190430205449.png +|image8| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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 7c9cc13..3a1a520 100644 --- a/source/c08/c08_09.md +++ b/source/c08/c08_09.md @@ -1,5 +1,7 @@ # 8.9 从0到1:全面理解RPC远程调用 +![](http://image.iswbm.com/20200602135014.png) + 什么是RPC呢? 百度百科给出的解释是这样的:“RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议”。这个概念听起来还是比较抽象,没关系,继续往后看,后面概念性的东西,我会讲得足够清楚,让你完全掌握 RPC 的基础内容。在后面的篇章中还会结合其在 OpenStack 中实际应用,一步一步揭开 rpc 的神秘面纱。 @@ -199,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,写起来貌似有些复杂。 @@ -235,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** @@ -291,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) 客户端可以使用命令行,那服务端是不是也可以呢? @@ -313,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中引入消息中间件 @@ -373,7 +375,7 @@ AMQP(Advanced Message Queuing Protocol)是一种基于队列的可靠消息服 为了让你明白这几者的关系,我画了一张模型图。 -![](http://image.python-online.cn/20190630160025.png) +![](http://image.iswbm.com/20190630160025.png) 关于AMQP,有几下几点值得注意: @@ -448,7 +450,7 @@ def consumer(): 其他模型我不太清楚,在 OpenStack 中的应用模型是这样的 -![](http://image.python-online.cn/20190623201427.png) +![](http://image.iswbm.com/20190623201427.png) 至于为什么要如此设计,前面我已经给出了自己的观点。 @@ -463,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 请求。 @@ -483,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): @@ -511,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) 之间数据做序列化或是反序列化的基类。 @@ -634,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) @@ -676,4 +678,4 @@ server.wait() --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c08/c08_09.rst b/source/c08/c08_09.rst index 2c1d900..8309c93 100644 --- a/source/c08/c08_09.rst +++ b/source/c08/c08_09.rst @@ -1,6 +1,8 @@ 8.9 从0到1:全面理解RPC远程调用 =============================== +|image0| + 什么是RPC呢? 百度百科给出的解释是这样的:“RPC(Remote Procedure Call @@ -239,7 +241,7 @@ SimpleXMLRPCServer 标准库来进行编写的。 server = jsonrpclib.Server("http://localhost:8080") -|image0| +|image1| 再来看第二种python-jsonrpc,写起来貌似有些复杂。 @@ -275,7 +277,7 @@ SimpleXMLRPCServer 标准库来进行编写的。 url="http://localhost:8080/jsonrpc" ) -|image1| +|image2| 还记得上面我提到过的 zabbix API,因为我有接触过,所以也拎出来讲讲。zabbix API 也是基于 json-rpc @@ -284,7 +286,7 @@ API,因为我有接触过,所以也拎出来讲讲。zabbix API 也是基于 因为内容较多,这里只带大家打个,zabbix 是如何调用的:直接指明要调用 zabbix server 的哪个方法,要传给这个方法的参数有哪些。 -|image2| +|image3| **03、基于 zerorpc** @@ -339,11 +341,11 @@ MessagePack的,速度相对快,响应时间短,并发高。zerorpc 和 pyj c = zerorpc.Client() c.connect("tcp://127.0.0.1:4242") -|image3| +|image4| 客户端除了可以使用zerorpc框架实现代码调用之外,它还支持使用“命令行”的方式调用。 -|image4| +|image5| 客户端可以使用命令行,那服务端是不是也可以呢? @@ -362,7 +364,7 @@ MessagePack的,速度相对快,响应时间短,并发高。zerorpc 和 pyj zerorpc --client --connect tcp://127.0.0.1:1234 strftime %Y/%m/%d -|image5| +|image6| 8.9.3 往rpc中引入消息中间件 --------------------------- @@ -443,7 +445,7 @@ Qpid,RabbitMQ等。 为了让你明白这几者的关系,我画了一张模型图。 -|image6| +|image7| 关于AMQP,有几下几点值得注意: @@ -518,7 +520,7 @@ Qpid,RabbitMQ等。 其他模型我不太清楚,在 OpenStack 中的应用模型是这样的 -|image7| +|image8| 至于为什么要如此设计,前面我已经给出了自己的观点。 @@ -541,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 @@ -569,7 +571,7 @@ rpc.call 和 rpc.cast transport_url=rabbit://user:passwd@host:5672 - |image8| + |image9| .. code:: python @@ -598,30 +600,30 @@ rpc.call 和 rpc.cast rpc server 要获取消息,需要定义target,就像一个门牌号一样。 - |image9| + |image10| rpc client 要发送消息,也需要有target,说明消息要发到哪去。 - |image10| + |image11| - endpoints:是可供别人远程调用的对象 RPC服务器暴露出endpoint,每个 endpoint 包涵一系列的可被远程客户端通过 transport 调用的方法。直观理解,可以参考nova-conductor创建rpc server的代码,这边的endpoints就是 - ``nova/manager.py:ConductorManager()``\ |image11| + ``nova/manager.py:ConductorManager()``\ |image12| -- dispatcher:分发器,这是 rpc server 才有的概念 |image12|\ 只有通过它 +- dispatcher:分发器,这是 rpc server 才有的概念 |image13|\ 只有通过它 server 端才知道接收到的rpc请求,要交给谁处理,怎么处理? 在client端,是这样指定要调用哪个方法的。 - |image13| + |image14| 而在server端,是如何知道要执行这个方法的呢?这就是dispatcher 要干的事,它从 endpoint 里找到这个方法,然后执行,最后返回。 - |image14| + |image15| - Serializer:在 python 对象和message(notification) 之间数据做序列化或是反序列化的基类。 @@ -737,28 +739,28 @@ rpc server 和rpc client 的四个重要方法 首先在这里先定义合法的 ``notification_event_types``\ ,相当于添加白名单。 -|image15| +|image16| 然后在调用处,使用 ``rpc.get_notifier`` 来发送消息给ceilometer。 -|image16| +|image17| 继续查看 ``rpc.get_notifier`` 做了什么事?如何实现直接info 就能发送消息的。 -|image17| +|image18| 当你使用的event_types 不在白名单内,或者是异常信息。就会给打印warn日志 -|image18| +|image19| 在rabbit里查看队列,notification 是 topic -|image19| +|image20| 而 debug ,info 等是event priority -|image20| +|image21| 参考文章: @@ -782,30 +784,29 @@ rpc server 和rpc client 的四个重要方法 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190623185008.png -.. |image1| image:: http://image.python-online.cn/20190623165341.png -.. |image2| image:: http://image.python-online.cn/20190623171138.png -.. |image3| image:: http://image.python-online.cn/20190623155955.png -.. |image4| image:: http://image.python-online.cn/20190623162725.png -.. |image5| image:: http://image.python-online.cn/20190623191042.png -.. |image6| image:: http://image.python-online.cn/20190630160025.png -.. |image7| image:: http://image.python-online.cn/20190623201427.png -.. |image8| image:: http://image.python-online.cn/20190526182125.png -.. |image9| image:: http://image.python-online.cn/20190526184854.png -.. |image10| image:: http://image.python-online.cn/20190526185217.png -.. |image11| image:: http://image.python-online.cn/20190526221219.png -.. |image12| image:: http://image.python-online.cn/20190526220809.png -.. |image13| image:: http://image.python-online.cn/20190527220820.png -.. |image14| image:: http://image.python-online.cn/20190527220012.png -.. |image15| image:: http://image.python-online.cn/20190526172514.png -.. |image16| image:: http://image.python-online.cn/20190526172725.png -.. |image17| image:: http://image.python-online.cn/20190526173314.png -.. |image18| image:: http://image.python-online.cn/20190526175100.png -.. |image19| image:: http://image.python-online.cn/20190526180708.png -.. |image20| image:: http://image.python-online.cn/20190526181433.png +|image22| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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 f2e3acc..e2d53e0 100644 --- a/source/c08/c08_10.md +++ b/source/c08/c08_10.md @@ -1,10 +1,12 @@ -# 8.16 修改 KVM 镜像文件的三种方法 +# 8.10 修改 KVM 镜像文件的三种方法 + +![](http://image.iswbm.com/20200602135014.png) 如下工具的更多说明,请查看官方文档:http://libguestfs.org/guestfs-recipes.1.html ## 8.16.1 使用 guestfish -![](http://image.python-online.cn/20191111112221.png) +![](http://image.iswbm.com/20191111112221.png) guestfish 里的命令有限,大概会 600 多个,不能 cd,操作文件时,需要使用绝对路径。 @@ -12,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 87f65c6..20abedb 100644 --- a/source/c08/c08_10.rst +++ b/source/c08/c08_10.rst @@ -1,12 +1,14 @@ -8.16 修改 KVM 镜像文件的三种方法 +8.10 修改 KVM 镜像文件的三种方法 ================================ +|image0| + 如下工具的更多说明,请查看官方文档:http://libguestfs.org/guestfs-recipes.1.html 8.16.1 使用 guestfish --------------------- -|image0| +|image1| guestfish 里的命令有限,大概会 600 多个,不能 cd,操作文件时,需要使用绝对路径。 @@ -14,16 +16,17 @@ cd,操作文件时,需要使用绝对路径。 8.16.2 使用 guestmount ---------------------- -|image1| +|image2| 使用完后,记得 umount /home/wangbm/mount 8.16.3 使用 virt-\* 命令 ------------------------ -|image2| +|image3| -.. |image0| image:: http://image.python-online.cn/20191111112221.png -.. |image1| image:: http://image.python-online.cn/20191111112421.png -.. |image2| image:: http://image.python-online.cn/20191111112548.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 cecf80d..9bf4b88 100644 --- a/source/c08/c08_11.md +++ b/source/c08/c08_11.md @@ -1,5 +1,7 @@ # 8.11 OpenStack 问题排查 +![](http://image.iswbm.com/20200602135014.png) + ## 8.11.1 虚拟机启动不了 问题描述 @@ -12,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) 解决方法 @@ -80,4 +82,4 @@ $ nova reboot --hard -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c08/c08_11.rst b/source/c08/c08_11.rst index 44680f0..a40a42e 100644 --- a/source/c08/c08_11.rst +++ b/source/c08/c08_11.rst @@ -1,6 +1,8 @@ 8.11 OpenStack 问题排查 ======================= +|image0| + 8.11.1 虚拟机启动不了 --------------------- @@ -14,7 +16,7 @@ ``/var/log/messages`` 报错如下 -|image0| +|image1| 解决方法 @@ -82,10 +84,9 @@ ConfigDrive 是一个 iso9660 格式的文件,只读。 # 硬重启,重新生成 xml,如果有必要的话 $ nova reboot --hard -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image2| -.. |image0| image:: http://image.python-online.cn/20190530175817.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 aac31f2..5a3ef06 100644 --- a/source/c08/c08_12.md +++ b/source/c08/c08_12.md @@ -1,5 +1,7 @@ # 8.12 OpenStack之主机调度 +![](http://image.iswbm.com/20200602135014.png) + 一般情况下,一个 OpenStack 中,会部署有许多个计算节点。当我们创建一个虚拟机时,OpenStack 如何决定要将我们的虚拟机创建在哪里呢?这就是 openstack-nova-scheduler 要做的事。 顾名思义,它是对集群内的所有计算节点的资源情况进行比较。主要分为两个过程: @@ -16,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,对满足硬性条件的众多主机按照一定的规则进行权重配比。意义:经过称重器计算,选出你更希望在哪台节点上创建虚拟机。 @@ -29,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) 那最终的权值如何计算呢? @@ -64,8 +66,8 @@ LOG.debug("Selected host: %(host)s", {'host': chosen_host}) 当指定宿主机进行虚拟机的创建后,以上所有的过滤器都会无效(不会走代码)。 -![](http://image.python-online.cn/20191011103832.png) +![](http://image.iswbm.com/20191011103832.png) --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c08/c08_12.rst b/source/c08/c08_12.rst index 5057fe5..aca845c 100644 --- a/source/c08/c08_12.rst +++ b/source/c08/c08_12.rst @@ -1,6 +1,8 @@ 8.12 OpenStack之主机调度 ======================== +|image0| + 一般情况下,一个 OpenStack 中,会部署有许多个计算节点。当我们创建一个虚拟机时,OpenStack 如何决定要将我们的虚拟机创建在哪里呢?这就是 openstack-nova-scheduler @@ -25,12 +27,12 @@ nova-conductor,这时候 nova-conductor 才会去调用 nova-compute nova-compute 发创建请求前,会先让 nova-scheduler 选出一台资源充足的计算节点。 -|image0| +|image1| nova-scheduler 的调度主要由两部分组成(nova/scheduler/filter_scheduler.py:FilterScheduler._schedule()) -|image1| +|image2| - 过滤器:filter,将不满足条件(硬性条件,比如内存,cpu,磁盘,pci设备等)的计算节点,直接过滤掉。意义:从过滤器出来的那些计算节点,理论上都可以创建虚拟机。 - 称重器:weigher,对满足硬性条件的众多主机按照一定的规则进行权重配比。意义:经过称重器计算,选出你更希望在哪台节点上创建虚拟机。 @@ -44,25 +46,25 @@ nova-scheduler compute 的 id,可以在 ``_update_from_compute_node`` 函数中添加。它会从compute_nodes 表中取得你想要的信息。 - |image2| + |image3| - spec_obj:你所要请求创建的虚拟机信息(模板,镜像等)。它是从 objects.RequestSpec.from_primitives 中取得的 - |image3| + |image4| 过滤器,它的代码如下: -|image4| +|image5| 称重器,它的规则主要看这段代码。 -|image5| +|image6| 我在代码中,加了几段日志。从左到右,三个不同颜色的内容分别为,原始权值,配重系数(越高说明越占比越大,越影响最终结果),经过 nomalize 后的权值(只有 0 和 1)。 -|image6| +|image7| 那最终的权值如何计算呢? @@ -82,21 +84,20 @@ nova-scheduler 选择到主机后,在日志中会打印三条DEBUG信息,可 当指定宿主机进行虚拟机的创建后,以上所有的过滤器都会无效(不会走代码)。 -|image7| +|image8| -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190424212211.png -.. |image1| image:: http://image.python-online.cn/20190424213430.png -.. |image2| image:: http://image.python-online.cn/20190424214653.png -.. |image3| image:: http://image.python-online.cn/20190424214540.png -.. |image4| image:: http://image.python-online.cn/20190424221602.png -.. |image5| image:: http://image.python-online.cn/20190424215735.png -.. |image6| image:: http://image.python-online.cn/20190424220008.png -.. |image7| image:: http://image.python-online.cn/20191011103832.png +|image9| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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 7b715cc..246870f 100644 --- a/source/c08/c08_13.md +++ b/source/c08/c08_13.md @@ -1,5 +1,7 @@ # 8.13 网络知识必知必会 +![](http://image.iswbm.com/20200602135014.png) + ## 8.13.1 iptables 推荐文章:[朱双印的 iptables 系列文章](http://www.zsythink.net/archives/tag/iptables/) @@ -54,7 +56,7 @@ Netfilter是Linux操作系统核心层内部的一个数据包处理模块,它 **动作** -![](http://image.python-online.cn/20190706114314.png) +![](http://image.iswbm.com/20190706114314.png) ### 命令相关 @@ -85,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) @@ -193,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) 保存规则 @@ -227,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地址是真实准确的。 @@ -312,4 +314,4 @@ ovs-vsctl set port vnet0 tag=4 --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c08/c08_13.rst b/source/c08/c08_13.rst index 1633296..cd83c95 100644 --- a/source/c08/c08_13.rst +++ b/source/c08/c08_13.rst @@ -1,6 +1,8 @@ 8.13 网络知识必知必会 ===================== +|image0| + 8.13.1 iptables --------------- @@ -46,7 +48,7 @@ start启动iptables“服务”,但是其实准确的来说,iptables并没 **动作** -|image0| +|image1| 命令相关 ~~~~~~~~ @@ -77,7 +79,7 @@ start启动iptables“服务”,但是其实准确的来说,iptables并没 多出来的几个字段如何理解呢?我摘取《\ `朱双印的个人日志 `__\ 》的解释到这边。 -|image1| +|image2| iptables 默认为我们配置了域名反解(根据ip解析成域名),这个过程效率很低,我们可以指定参数 @@ -188,7 +190,7 @@ iptables # 那如何更改默认动作呢? iptables -t filter -P FORWARD DROP -|image2| +|image3| 保存规则 @@ -228,7 +230,7 @@ table,主要是 ip与mac地址的映射。如果在 /etc/hosts ``arp -a`` 实现相同功能,只是 ``-a`` 是标准输出格式,没有像使用 ``-e`` 一样类似表格一样的输出效果。 -|image3| +|image4| 此时,我们ping一下 172.20.22.3 这个ip,显然是可以的,因为这里的其对应的mac地址是真实准确的。 @@ -312,13 +314,12 @@ cache里没有这个ip,就会重新发送arp广播,获取到正确的mac地 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image5| -.. |image0| image:: http://image.python-online.cn/20190706114314.png -.. |image1| image:: http://image.python-online.cn/20190706093904.png -.. |image2| image:: http://image.python-online.cn/20190706160632.png -.. |image3| image:: http://image.python-online.cn/20190804162402.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 dd83ae8..a8fc4fc 100644 --- a/source/c08/c08_14.md +++ b/source/c08/c08_14.md @@ -1,5 +1,7 @@ # 8.14 支持 IPv6以及多运营商 +![](http://image.iswbm.com/20200602135014.png) + ## 8.14.1 手工如何配置 IPv6 使用命令设置临时的IPv6 @@ -110,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。 @@ -193,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那侧就无法进一步操作。 @@ -231,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 的代码逻辑。 @@ -243,7 +245,7 @@ neutron port-create --fixed-ip \ 最后另外再说一点无关紧要的,cloudinit 解析后,每张网卡一个ip与一张网卡两个ip的区别如下。 -![](http://image.python-online.cn/20190716180726.png) +![](http://image.iswbm.com/20190716180726.png) @@ -257,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) @@ -297,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) 如何解决这个问题呢? @@ -329,4 +331,4 @@ sudo sysctl -p --- -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) +![](http://image.iswbm.com/20200607174235.png) diff --git a/source/c08/c08_14.rst b/source/c08/c08_14.rst index bfd3507..43552b4 100644 --- a/source/c08/c08_14.rst +++ b/source/c08/c08_14.rst @@ -1,6 +1,8 @@ 8.14 支持 IPv6以及多运营商 ========================== +|image0| + 8.14.1 手工如何配置 IPv6 ------------------------ @@ -109,7 +111,7 @@ 信息写入ConfigDrive,也只有这样cloudinit才能自动为我们配置上 IPv6 的 ip。 -|image0| +|image1| 而对于 Neutron 来说,要使用 IPv6,当然要先创建一个 IPv6 的子网,这里要注意,IPv6 @@ -192,11 +194,11 @@ Port 去创建虚拟机,二是,修改 nova-api 的接口,使之支持。 在一张网卡上配置多个版本的IP(一个IPv4和一个IPv6),只在一个配置文件中配置就可以支持,因此cloudinit的处理的时候,会将同一个tap设备的归为一个同一张网卡。 -|image1| +|image2| 也就是说,cloudinit本身很轻松地就可以支持单网卡多版本IP的配置。 -|image2| +|image3| 而对于多线的IP,由于 Nova 写入ConfigDrive时,尽管一个Port上有多个IPv4或者多个IPv6,同个版本的都会只取第一个写入,自然从cloudinit那侧就无法进一步操作。 @@ -229,7 +231,7 @@ Port 去创建虚拟机,二是,修改 nova-api 的接口,使之支持。 }], - Neutron 接口可以不改,因为创建Port的接口参数中的 fixed_ips - 是一个数组类型,所以我们可以把这些版本信息,运营商信息放进这个数组的每一个元素中。\ |image3| + 是一个数组类型,所以我们可以把这些版本信息,运营商信息放进这个数组的每一个元素中。\ |image4| 所以我们只要修改Neutron 创建 Port 的代码逻辑。 @@ -243,7 +245,7 @@ Port 去创建虚拟机,二是,修改 nova-api 的接口,使之支持。 最后另外再说一点无关紧要的,cloudinit 解析后,每张网卡一个ip与一张网卡两个ip的区别如下。 -|image4| +|image5| 重装 ``openvswith.ko`` 过程 @@ -256,40 +258,40 @@ Port 去创建虚拟机,二是,修改 nova-api 的接口,使之支持。 CentOS 6.x 在使用 ipv6 方面有问题 -|image5| +|image6| 导致在网卡配置文件里的配置的ip 都为none -|image6| +|image7| 上面的 callbak 是 这个函数 -|image7| +|image8| centos6.x 的网络信息不是从 latest 里的 network_data.json 里读取的,而是从 content/0000 里读取 -|image8| +|image9| 这是因为 centos 6.x 配置网络是在 local 阶段做的,而local阶段做的话,就会从 content/0000 -|image9| +|image10| 而 centos7.x 其实也会走 on_first_boot 去配置网络,但是cloudinit 在centos7.x 里 sources.DSMODE_PASS -|image10| +|image11| 导致在 local 阶段,这里配置网络被pass掉,自然也就不会从 content/0000 读取了。 -|image11| +|image12| centos 6.x 配置网络是在 on_first_boot 函数里,这是 local 阶段就会执行的。 -|image12| +|image13| 8.14.5 ubuntu禁用ipv6 --------------------- @@ -297,7 +299,7 @@ centos 6.x 配置网络是在 on_first_boot 函数里,这是 local 如果你给你的ubuntu配置上了ipv6的ip,那当你使用 apt-get install 软件包的时候,会使用ipv6去源安装,这将会使你的安装过程卡住: -|image13| +|image14| 如何解决这个问题呢? @@ -323,23 +325,22 @@ centos 6.x 配置网络是在 on_first_boot 函数里,这是 local -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.python-online.cn/20190716175250.png -.. |image1| image:: http://image.python-online.cn/20190716180655.png -.. |image2| image:: http://image.python-online.cn/20190716180952.png -.. |image3| image:: http://image.python-online.cn/20190804110647.png -.. |image4| image:: http://image.python-online.cn/20190716180726.png -.. |image5| image:: http://image.python-online.cn/20190829103805.png -.. |image6| image:: http://image.python-online.cn/20190829104544.png -.. |image7| image:: http://image.python-online.cn/20190829104806.png -.. |image8| image:: http://image.python-online.cn/20190829110541.png -.. |image9| image:: http://image.python-online.cn/20190829105558.png -.. |image10| image:: http://image.python-online.cn/20190829112446.png -.. |image11| image:: http://image.python-online.cn/20190829111917.png -.. |image12| image:: http://image.python-online.cn/20190829161243.png -.. |image13| image:: http://image.python-online.cn/20190926171038.png +|image15| + +.. |image0| image:: http://image.iswbm.com/20200602135014.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 1e13ac6..8a302ce 100644 --- a/source/c08/c08_15.md +++ b/source/c08/c08_15.md @@ -1,26 +1,28 @@ # 8.15 Neutron 源码解读 -neutron api 的入口是在这里![](http://image.python-online.cn/20190804111844.png) +![](http://image.iswbm.com/20200602135014.png) -在这里会校验并打印请求的信息![](http://image.python-online.cn/20190804111715.png) +neutron api 的入口是在这里![](http://image.iswbm.com/20190804111844.png) -而对网络、子网、port操作的逻辑处理代码的入口都是从这里开始的![](http://image.python-online.cn/20190803181706.png) +在这里会校验并打印请求的信息![](http://image.iswbm.com/20190804111715.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内。 @@ -28,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) @@ -40,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 a063773..219142c 100644 --- a/source/c08/c08_15.rst +++ b/source/c08/c08_15.rst @@ -1,29 +1,31 @@ 8.15 Neutron 源码解读 ===================== -neutron api 的入口是在这里\ |image0| +|image0| -在这里会校验并打印请求的信息\ |image1| +neutron api 的入口是在这里\ |image1| -而对网络、子网、port操作的逻辑处理代码的入口都是从这里开始的\ |image2| +在这里会校验并打印请求的信息\ |image2| + +而对网络、子网、port操作的逻辑处理代码的入口都是从这里开始的\ |image3| 8.15.1 创建Port --------------- -从 neutron api 请求过来后,就会经过这里\ |image3| +从 neutron api 请求过来后,就会经过这里\ |image4| -把 port 信息打印一下\ |image4| +把 port 信息打印一下\ |image5| 点进上面的\ ``self._create_port_db()``\ ,可以看到这里先是创建了一个空壳的port -|image5| +|image6| -再分配ip\ |image6| +再分配ip\ |image7| 上图有一个函数 ``_allocate_ips_for_port``\ ,相当重要,一般人只要从这里关注即可 -|image7| +|image8| 它会先根据port创建请求里的内容,去数据库中一一比对,找出符合条件的子网,当然在找的过程中,会校验请求参数的准确性,比如它是指定ip和subnet创建的port,那么会检查这个ip是否在subnet内。 @@ -33,37 +35,38 @@ neutron api 的入口是在这里\ |image0| 里。如果指定了ip,这个list里的元素就会有 ip_address,否则就只有 subnet_id。 -|image8| - |image9| +|image10| + -------------- 当不指定子网、也不指定ip时(也就是不传fixed_ip),而且 cluster 里 即 有ipv4 的子网也有ipv6的子网,这时候 neutron 会去创建一个既有ipv4也有ipv6的port,只要有一个version的子网里没有可用ip,都会失败。 -|image10| +|image11| 这里是遍历一个version的所有的子网列表,只要能找到一个子网有可用ip,就返回,如果遍历完都没有找到ip,那就要抛出异常了。 -|image11| +|image12| .. code:: python class IpAddressGenerationFailureAllSubnets(IpAddressGenerationFailure): message = _("No more IP addresses available.") -.. |image0| image:: http://image.python-online.cn/20190804111844.png -.. |image1| image:: http://image.python-online.cn/20190804111715.png -.. |image2| image:: http://image.python-online.cn/20190803181706.png -.. |image3| image:: http://image.python-online.cn/20190803182042.png -.. |image4| image:: http://image.python-online.cn/20190803182223.png -.. |image5| image:: http://image.python-online.cn/20190804091016.png -.. |image6| image:: http://image.python-online.cn/20190804091226.png -.. |image7| image:: http://image.python-online.cn/20190804094131.png -.. |image8| image:: http://image.python-online.cn/20190804092214.png -.. |image9| image:: http://image.python-online.cn/20190804091911.png -.. |image10| image:: http://image.python-online.cn/20190809213209.png -.. |image11| image:: http://image.python-online.cn/20190809213223.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 new file mode 100644 index 0000000..eb9283b --- /dev/null +++ b/source/c08/c08_16.md @@ -0,0 +1,373 @@ +# 8.16 详解 Neutron 的 QoS + +QoS 的全称是 Quality of Service,也就是服务质量。 + +Neutron 支持的 [QoS](https://docs.openstack.org/neutron/latest/admin/config-qos.html) 类型有 + +- bandwidth_limit(带宽限速):实现带宽速速 +- DSCP(差分服务代码点):给网络流量包添加一个 DSCP 标记,以此实现流量的优先级 +- minimum_bandwidth:暂时没用过 + + + +## 1. 开户 QoS 支持 + +在控制节点上执行两条命令修改配置 + +```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 + +```shell +$ crm resource cleanup openstack-neutron-server-clone +``` + +在计算节点上执行命令修改配置 + +```shell +$ openstack-config --set /etc/neutron/plugins/ml2/openvswitch_agent.ini agent extensions qos +``` + +然后重启对应的 neutron-agent 服务 + +```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。 + +![](http://image.iswbm.com/20200701155207.png) + +### 原理 + +在 IP 协议分组里有一个 ToS(服务类型) 的字段,就是用来表示 ToS 的。 + +ToS 字段,总共 8 个 bit + +![](http://img.wandouip.com/crawler/article/2019411/546f47120fa14a2a1cfc44c9e8a48e71) + +**前面 3 个 bit** + +为优选权子字段,现在已经废弃,这个字段默认值是000,从wireshark抓包结果看,表示的是: + +![](http://image.iswbm.com/20200723155027.png) + +但是在某些协议中仍然是有用的,比如 OSPFv2 协议 + +![](http://image.iswbm.com/20200701170223.png) + +关于 Precedence (优先级),有如下几种 + +```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字段对应的值: +![](http://image.iswbm.com/20200723154704.png) +翻译过来就是: +![](http://image.iswbm.com/20200723154742.png) +**最小延迟**,对应于对延迟敏感的应用,如telnet和人login等。 +**最大吞吐量**,对应于对吞吐量要求比较高的应用,如FTP文件应用,对文件传输吞吐量有比较高的要求。 +**最高可靠性**,对网络传输可靠性要求高的应用,如使用SNMP的应用、路由协议等等。 +**最小费用**,如NNTP这种用户网络新闻等。 + +**最后 1 个bit** + +这个1bit末尾,没有被使用,必须强制设置为0 + + + +最后,很重要的一点,只有当**网络设备(如交换机等)能够支持**(能够识别IP首部中的ToS字段)识别ToS字段时,这给字段设置才有意义。 + +### 创建 policy + +```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 + +```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 + +```shell +$ neutron port-update --qos-policy qos-dscp +``` + +### 关闭 QoS + +```shell +$ neutron port-update --no-qos-policy +``` + +### 查看规则 + +过滤出 tos 后就能看到 ToS(Type of Service) 的值 + +```shell +$ ovs-ofctl dump-flows br-int | grep tos +``` + +然后再对照这个表,找到对应的 DSCP 的 decimal ,如果 tos 是 64,那么 DSCP mark 就是 16,其实除以 4 就可以了,也不用对照表。其中这个 16 需要跟交换上支持的一样。 + +![](http://image.iswbm.com/20200701155207.png) + +### 其他 + +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 查询命令 + +```shell +$ neutron qos-policy-list +$ neutron qos-bandwidth-limit-rule-list 16fbb0c2-b7ac-4053-a85f-75fb72c3ab55 +``` + + + +在 dpdk 宿主机上查询限速 + +```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 +``` + + + +![](http://image.iswbm.com/20200709171517.png) + + + +测试限速 + +average: 100M + + + + + +```shell +# burst:50 +$ nova meta b1575f81-0a33-4872-995e-2c2dd48d52c8 set __system__vif_inbound_average=12500 __system__vif_inbound_burst=7500 __system__vif_outbound_average=12500 __system__vif_outbound_burst=6000 + + +# burst:60 +$ nova meta b1575f81-0a33-4872-995e-2c2dd48d52c8 set __system__vif_inbound_average=12500 __system__vif_inbound_burst=7500 __system__vif_outbound_average=12500 __system__vif_outbound_burst=7500 + +# burst:70 +$ nova meta b1575f81-0a33-4872-995e-2c2dd48d52c8 set __system__vif_inbound_average=12500 __system__vif_inbound_burst=8750 __system__vif_outbound_average=12500 __system__vif_outbound_burst=8750 + +# burst:80 +$ 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,在不同领域的含义各不相同,而在网络服务中,带宽是指单位时间内的流经数据量。 + +数据量的单位一般有两种:bit 和 Byte。 + +因此,带宽的单位即为,b/s 或者 B/s,如果数值较大,可以使用 K 表示千(Kb/s 或者 KB/s),M 表示 百万(Mb/s 或者 MB/s)。 + +- Mb/s:Million bits per second的缩写,是一种传输速率单位,指每秒传输的位(比特)数量。通常用于运营商带宽速率的计量。 + +- MB/s:Million Bytes per second的缩写,是一种传输速率单位,指每秒传输的字节数量。即是我们平时说的下载速度。 + + + +Byte 和 bit 是不一样的,Byte = 8 bit,而不同的地方单位不同,需要进行换算,下表整理了在 VMP 中的各个位置的单位 + +| type | cloud | meta | xml | qos rule | ovs-vsctl/ovs-appctl | +| ---- | ----- | ---- | ---- | -------- | -------------------- | +| ovs | bit | Byte | Byte | | | +| dpdk | bit | Byte | | bit | bit | + +## 4. 带宽限制效果 + +### CentOS 安装 iperf3 + +源码编译安装 iperf3 + +```shell +yum -y install gcc make wget +cd /tmp +wget https://iperf.fr/download/source/iperf-3.1.3-source.tar.gz +tar zxvf iperf-3.1.3-source.tar.gz +cd iperf-3.1.3 +./configure +make +make install +``` + +使用 rpm 包安装(更简单,推荐) + +```shell +$ rpm -ih ftp://ftp.pbone.net/mirror/archive.fedoraproject.org/fedora/linux/updates/24/x86_64/i/iperf3-3.1.3-1.fc24.x86_64.rpm +``` + +### Windows 安装 iperf3 + +所有的 windows 可用的 iperf3 安装包都可以从这个页面下载:https://iperf.fr/iperf-download.php + +这里直接给出两个 windows 常用的 + +- windows 10 64位:https://iperf.fr/download/windows/iperf-3.1.3-win64.zip +- windows 10 32 位:https://iperf.fr/download/windows/iperf-3.1.3-win32.zip + +下载到电脑到,并解压后,会得到两个文件:`cygwin1.dll` 和 `iperf3.exe`,将这两个文件拷贝到 `c:\windows` 目录下。 + +最后打开 cmd,执行 `iperf3 --version` ,若安装成功,会打印出版本信息。 + +### iperf3 使用前关闭防火墙 + +CentOS 关闭防火墙,只要一条命令 + +```shell +$ systemctl stop firewalld +$ systemctl stop iptables +``` + +windows 10 手动设置一下 + +![](http://image.iswbm.com/20200716112516.png) + + + +### iperf3 的使用 + +iperf3 有客户端 和 服务端之别: + +- 服务端:收包,使用 `-s` 参数指定 + +```shell +$ iperf3 -s +``` + +- 客户端:发包,使用 `-c xx.xx.xx.xx` 来指定要往哪个服务端发包 + +```shell +$ iperf3 -c 172.20.20.200 +``` + +iperf3 还有更多的参数,其中有一些是客户端专用的,有一些是服务端专用的,也有一些是二者共用的。 + +具体可以前往这个地址,进行查阅:https://www.cnblogs.com/yingsong/p/5682080.html + +常用的参数有 + +- `-u`:发送 UDP 包,仅客户端 可用 +- `-p`:后接服务端监听的端口 +- `-i`:设置带宽报告的时间间隔,单位为秒 +- `-t`:设置测试的时长,单位为秒 +- `-w`:设置tcp窗口大小,一般可以不用设置,默认即可 + + + + + +## 5. 注意事项 + +1、vm 在 host1 上,从host2 上 iperf 比在 host1 上限速效果更好 + +2、udp 包限速效果差异很大。 + + + +## 3. 参考文章 + +- https://blog.51cto.com/mangguostudy/2107799 + +- https://www.jianshu.com/p/4b5cc3845f2c + +- https://blog.csdn.net/u011641885/article/details/45640313 + + + +--- + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c08/c08_16.rst b/source/c08/c08_16.rst index 87f65c6..aeb792a 100644 --- a/source/c08/c08_16.rst +++ b/source/c08/c08_16.rst @@ -1,29 +1,390 @@ -8.16 修改 KVM 镜像文件的三种方法 -================================ +8.16 详解 Neutron 的 QoS +======================== -如下工具的更多说明,请查看官方文档:http://libguestfs.org/guestfs-recipes.1.html +QoS 的全称是 Quality of Service,也就是服务质量。 -8.16.1 使用 guestfish ---------------------- +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| -guestfish 里的命令有限,大概会 600 多个,不能 -cd,操作文件时,需要使用绝对路径。 +原理 +~~~~ + +在 IP 协议分组里有一个 ToS(服务类型) 的字段,就是用来表示 ToS 的。 -8.16.2 使用 guestmount ----------------------- +ToS 字段,总共 8 个 bit |image1| -使用完后,记得 umount /home/wangbm/mount +**前面 3 个 bit** -8.16.3 使用 virt-\* 命令 ------------------------- +为优选权子字段,现在已经废弃,这个字段默认值是000,从wireshark抓包结果看,表示的是: |image2| -.. |image0| image:: http://image.python-online.cn/20191111112221.png -.. |image1| image:: http://image.python-online.cn/20191111112421.png -.. |image2| image:: http://image.python-online.cn/20191111112548.png +但是在某些协议中仍然是有用的,比如 OSPFv2 协议 + +|image3| + +关于 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字段对应的值: |image4| 翻译过来就是: |image5| +**最小延迟**\ ,对应于对延迟敏感的应用,如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 +需要跟交换上支持的一样。 + +|image6| + +其他 +~~~~ + +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 + +|image7| + +测试限速 + +average: 100M + +.. code:: shell + + # burst:50 + $ nova meta b1575f81-0a33-4872-995e-2c2dd48d52c8 set __system__vif_inbound_average=12500 __system__vif_inbound_burst=7500 __system__vif_outbound_average=12500 __system__vif_outbound_burst=6000 + + + # burst:60 + $ nova meta b1575f81-0a33-4872-995e-2c2dd48d52c8 set __system__vif_inbound_average=12500 __system__vif_inbound_burst=7500 __system__vif_outbound_average=12500 __system__vif_outbound_burst=7500 + + # burst:70 + $ nova meta b1575f81-0a33-4872-995e-2c2dd48d52c8 set __system__vif_inbound_average=12500 __system__vif_inbound_burst=8750 __system__vif_outbound_average=12500 __system__vif_outbound_burst=8750 + + # burst:80 + $ 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,在不同领域的含义各不相同,而在网络服务中,带宽是指单位时间内的流经数据量。 + +数据量的单位一般有两种:bit 和 Byte。 + +因此,带宽的单位即为,b/s 或者 B/s,如果数值较大,可以使用 K +表示千(Kb/s 或者 KB/s),M 表示 百万(Mb/s 或者 MB/s)。 + +- Mb/s:Million bits per + second的缩写,是一种传输速率单位,指每秒传输的位(比特)数量。通常用于运营商带宽速率的计量。 + +- MB/s:Million Bytes per + second的缩写,是一种传输速率单位,指每秒传输的字节数量。即是我们平时说的下载速度。 + +Byte 和 bit 是不一样的,Byte = 8 +bit,而不同的地方单位不同,需要进行换算,下表整理了在 VMP +中的各个位置的单位 + ++------+-------+------+------+----------+----------------------+ +| type | cloud | meta | xml | qos rule | ovs-vsctl/ovs-appctl | ++======+=======+======+======+==========+======================+ +| ovs | bit | Byte | Byte | | | ++------+-------+------+------+----------+----------------------+ +| dpdk | bit | Byte | | bit | bit | ++------+-------+------+------+----------+----------------------+ + +4. 带宽限制效果 +--------------- + +CentOS 安装 iperf3 +~~~~~~~~~~~~~~~~~~ + +源码编译安装 iperf3 + +.. code:: shell + + yum -y install gcc make wget + cd /tmp + wget https://iperf.fr/download/source/iperf-3.1.3-source.tar.gz + tar zxvf iperf-3.1.3-source.tar.gz + cd iperf-3.1.3 + ./configure + make + make install + +使用 rpm 包安装(更简单,推荐) + +.. code:: shell + + $ rpm -ih ftp://ftp.pbone.net/mirror/archive.fedoraproject.org/fedora/linux/updates/24/x86_64/i/iperf3-3.1.3-1.fc24.x86_64.rpm + +Windows 安装 iperf3 +~~~~~~~~~~~~~~~~~~~ + +所有的 windows 可用的 iperf3 +安装包都可以从这个页面下载:https://iperf.fr/iperf-download.php + +这里直接给出两个 windows 常用的 + +- windows 10 + 64位:https://iperf.fr/download/windows/iperf-3.1.3-win64.zip +- windows 10 32 + 位:https://iperf.fr/download/windows/iperf-3.1.3-win32.zip + +下载到电脑到,并解压后,会得到两个文件:\ ``cygwin1.dll`` 和 +``iperf3.exe``\ ,将这两个文件拷贝到 ``c:\windows`` 目录下。 + +最后打开 cmd,执行 ``iperf3 --version`` ,若安装成功,会打印出版本信息。 + +iperf3 使用前关闭防火墙 +~~~~~~~~~~~~~~~~~~~~~~~ + +CentOS 关闭防火墙,只要一条命令 + +.. code:: shell + + $ systemctl stop firewalld + $ systemctl stop iptables + +windows 10 手动设置一下 + +|image8| + +iperf3 的使用 +~~~~~~~~~~~~~ + +iperf3 有客户端 和 服务端之别: + +- 服务端:收包,使用 ``-s`` 参数指定 + +.. code:: shell + + $ iperf3 -s + +- 客户端:发包,使用 ``-c xx.xx.xx.xx`` 来指定要往哪个服务端发包 + +.. code:: shell + + $ iperf3 -c 172.20.20.200 + +iperf3 +还有更多的参数,其中有一些是客户端专用的,有一些是服务端专用的,也有一些是二者共用的。 + +具体可以前往这个地址,进行查阅:https://www.cnblogs.com/yingsong/p/5682080.html + +常用的参数有 + +- ``-u``\ :发送 UDP 包,仅客户端 可用 +- ``-p``\ :后接服务端监听的端口 +- ``-i``\ :设置带宽报告的时间间隔,单位为秒 +- ``-t``\ :设置测试的时长,单位为秒 +- ``-w``\ :设置tcp窗口大小,一般可以不用设置,默认即可 + +5. 注意事项 +----------- + +1、vm 在 host1 上,从host2 上 iperf 比在 host1 上限速效果更好 + +2、udp 包限速效果差异很大。 + +3. 参考文章 +----------- + +- https://blog.51cto.com/mangguostudy/2107799 + +- https://www.jianshu.com/p/4b5cc3845f2c + +- https://blog.csdn.net/u011641885/article/details/45640313 + +-------------- + +|image9| + +.. |image0| image:: http://image.iswbm.com/20200701155207.png +.. |image1| image:: http://img.wandouip.com/crawler/article/2019411/546f47120fa14a2a1cfc44c9e8a48e71 +.. |image2| image:: http://image.iswbm.com/20200723155027.png +.. |image3| image:: http://image.iswbm.com/20200701170223.png +.. |image4| image:: http://image.iswbm.com/20200723154704.png +.. |image5| image:: http://image.iswbm.com/20200723154742.png +.. |image6| image:: http://image.iswbm.com/20200701155207.png +.. |image7| image:: http://image.iswbm.com/20200709171517.png +.. |image8| image:: http://image.iswbm.com/20200716112516.png +.. |image9| image:: http://image.iswbm.com/20200607174235.png diff --git a/source/c09/c09_01.md b/source/c09/c09_01.md index 23d9ccd..e8ec5cc 100644 --- a/source/c09/c09_01.md +++ b/source/c09/c09_01.md @@ -1,5 +1,7 @@ # 9.1 情人节来了,教你使用 Python 来表白 +![](http://image.iswbm.com/20200602135014.png) + **作者**:@明哥 **公众号**:Python编程时光 @@ -23,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男来说,还真不好意思说,说出来,万一被拒绝了咋办? @@ -39,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) 然后将这张图片发给你的女神,具体话术你自己想咯。 @@ -65,7 +67,7 @@ 用 Excel 画了个图,每一方格代表一个像素,其中若我的字体的大小设置 5(非字号5,而是每个字占用5个像素),效果大概就是如下这样子。 -![](http://image.python-online.cn/20200214104646.png) +![](http://image.iswbm.com/20200214104646.png) 我只要每个像素取出一个像素值,并使用这个像素做为该字的颜色即可,在像素量够多的情况下,从远处看,是能看到我们原来图像的轮廓的。 @@ -141,4 +143,4 @@ img_new.convert('RGB').save("F://save.jpeg") -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c09/c09_01.rst b/source/c09/c09_01.rst index 216c302..f016d28 100644 --- a/source/c09/c09_01.rst +++ b/source/c09/c09_01.rst @@ -1,6 +1,8 @@ 9.1 情人节来了,教你使用 Python 来表白 ====================================== +|image0| + **作者**\ :@明哥 **公众号**\ :Python编程时光 -------------- @@ -23,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 @@ -47,11 +49,11 @@ 这里我以一张高圆圆的图来做一下演示,原图是这样的(分辨率是:2000*1328)。 -|image0| +|image1| 使用我写好的脚本运行后,就生成了这样一张图,请你点击,放大再放大。(惊喜? -|image1| +|image2| 然后将这张图片发给你的女神,具体话术你自己想咯。 @@ -75,7 +77,7 @@ 用 Excel 画了个图,每一方格代表一个像素,其中若我的字体的大小设置 5(非字号5,而是每个字占用5个像素),效果大概就是如下这样子。 -|image2| +|image3| 我只要每个像素取出一个像素值,并使用这个像素做为该字的颜色即可,在像素量够多的情况下,从远处看,是能看到我们原来图像的轮廓的。 @@ -150,12 +152,11 @@ img_new.convert('RGB').save("F://save.jpeg") -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image4| -.. |image0| image:: http://image.python-online.cn/20200214104413.png -.. |image1| image:: http://image.python-online.cn/save.jpeg -.. |image2| image:: http://image.python-online.cn/20200214104646.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 7ef71ad..1e66691 100644 --- a/source/c09/c09_02.md +++ b/source/c09/c09_02.md @@ -1,4 +1,6 @@ -# 9.2 没有这 50 个APP,我的 Mac 将只是一块铁 +# 9.2 我在Mac 上使用哪些APP? + +![](http://image.iswbm.com/20200602135014.png) ## 1. 效率神器 @@ -27,10 +29,16 @@ NewFileMenu 可以解决你的烦恼 -### 3. Magnet/Moon +### 3. Magnet/Moon/Rectangle 分屏工作几乎是办公必备技能。 +Magnet:收费 + +Moon:收费 + +Rectangle:[官网](https://rectangleapp.com)免费下载,支持拖动分屏 + ### 4. 快贴 @@ -39,6 +47,12 @@ https://clipber.com/ 用于多设备间剪切板同步共享 +### CopyLess + +剪切板工具,免费可支持 100 个剪切项目,但是双击粘贴需要在偏好设置里额外安装一个插件(具体打开设置后有步骤指引)。 + +有批量复制粘贴功能。 + ### GoodSync 和 windows 平台同步文件 @@ -79,13 +93,19 @@ Coffee Buzz:阻止电脑休眠,三种模式:永不休眠、屏幕熄灭但 ShortCat:在系统栏也可以搜索聚焦 +TouchBarServer:在外接屏幕上调出 touchbar + +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:视频录制 @@ -101,11 +121,15 @@ Permute 3,精致小巧的视频格式转换工具。 PP 鸭,好用的多格式图片压缩软件。 +Squash for Mac:图片压缩(不能压缩GIF) + +ImageOptim:用过最好用的图片压缩软件(支持 Gif ) + GIF Brewery 3,视频转Gif动图。Mac App Store 免费下载 PhotoBulk,图片批处理修改大小、格式、加水印 Mac App Store 购买下载 - +PDFGuru:PDF 阅读器 ## 5. 文件管理 @@ -131,7 +155,7 @@ Keta:解压缩软件 PicGo:图床上传 -Typora/Bear:Markdown写作工具 +Typora/Bear/MWeb:Markdown写作工具 WPS:Office套件 @@ -139,6 +163,10 @@ TeamViewer:远程控制工具 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 a31b463..cc5857a 100644 --- a/source/c09/c09_02.rst +++ b/source/c09/c09_02.rst @@ -1,5 +1,7 @@ -9.2 没有这 50 个APP,我的 Mac 将只是一块铁 -========================================== +9.2 我在Mac 上使用哪些APP? +=========================== + +|image0| 1. 效率神器 ----------- @@ -26,13 +28,19 @@ Mac 中必不可少的APP,没有它 Mac 基本也就『残废』了。 NewFileMenu 可以解决你的烦恼 -|image0| +|image1| -3. Magnet/Moon -~~~~~~~~~~~~~~ +3. Magnet/Moon/Rectangle +~~~~~~~~~~~~~~~~~~~~~~~~ 分屏工作几乎是办公必备技能。 +Magnet:收费 + +Moon:收费 + +Rectangle:\ `官网 `__\ 免费下载,支持拖动分屏 + 4. 快贴 ~~~~~~~ @@ -40,6 +48,14 @@ https://clipber.com/ 用于多设备间剪切板同步共享 +CopyLess +~~~~~~~~ + +剪切板工具,免费可支持 100 +个剪切项目,但是双击粘贴需要在偏好设置里额外安装一个插件(具体打开设置后有步骤指引)。 + +有批量复制粘贴功能。 + GoodSync ~~~~~~~~ @@ -84,14 +100,20 @@ Buzz:阻止电脑休眠,三种模式:永不休眠、屏幕熄灭但电脑 ShortCat:在系统栏也可以搜索聚焦 +TouchBarServer:在外接屏幕上调出 touchbar + +Hazeover:干扰调节神器,把活动窗口除久的其他范围调暗 + 4. 图片影音 ----------- -Snipaste:截图工具 +`Snipaste `__\ :截图工具,支持钉图 + +iShot:截图工具,支持长截图/滚动截图,钉图 Capture Gif:Gif 录制(不推荐) -Kap Beta:录屏开源免费软件,支持GIF导出,快捷键:\ ``Command Shift 5`` +`Kap `__\ :录屏开源免费软件,支持GIF导出,快捷键:\ ``Command Shift 5`` ScreenFlow:视频录制 @@ -107,10 +129,16 @@ Permute 3,精致小巧的视频格式转换工具。 PP 鸭,好用的多格式图片压缩软件。 +Squash for Mac:图片压缩(不能压缩GIF) + +ImageOptim:用过最好用的图片压缩软件(支持 Gif ) + GIF Brewery 3,视频转Gif动图。Mac App Store 免费下载 PhotoBulk,图片批处理修改大小、格式、加水印 Mac App Store 购买下载 +PDFGuru:PDF 阅读器 + 5. 文件管理 ----------- @@ -138,7 +166,7 @@ Keta:解压缩软件 PicGo:图床上传 -Typora/Bear:Markdown写作工具 +Typora/Bear/MWeb:Markdown写作工具 WPS:Office套件 @@ -146,6 +174,11 @@ TeamViewer:远程控制工具 iText,精准的 OCR 文字识别工具。 +思维导图:MindNode,Xmind + +`KeyCastr `__\ :在录制视频或者 +Gif 时显示你所按的键位 + 7. 系统管理 ----------- @@ -157,5 +190,6 @@ iMazing,最佳 iOS 备份及管理软件,完美替代 iTunes。 Macs Fan Control:控制风扇转速,加快散热 -.. |image0| image:: http://image.iswbm.com/image-20200524183640630.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/image-20200524183640630.png diff --git a/source/c09/c09_03.md b/source/c09/c09_03.md index 64a21df..848f6b7 100644 --- a/source/c09/c09_03.md +++ b/source/c09/c09_03.md @@ -1,5 +1,7 @@ # 9.3 明哥的在线工具集 +![](http://image.iswbm.com/20200602135014.png) + @@ -46,3 +48,28 @@ ai-art(自拍照生成为名画艺术品):https://ai-art.tokyo/en 码力全开:https://www.maliquankai.com/ + + +## 流程图 + + + +http://asciiflow.com/ + +https://www.draw.io/ + + + +## 其他工具 + +随机生成复杂 JSON:http://www.yyyweb.com/demo/inner-show/json-generator.html + +生成 Markdwon 目录:https://ecotrust-canada.github.io/markdown-toc/ + +代码分享:https://paste.ubuntu.com/ + + + +## 黑科技 + +果核剥壳(破解工具):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 0e13613..5c1f7e6 100644 --- a/source/c09/c09_03.rst +++ b/source/c09/c09_03.rst @@ -1,6 +1,8 @@ 9.3 明哥的在线工具集 ==================== +|image0| + 文档 ---- @@ -40,3 +42,28 @@ ai-art(自拍照生成为名画艺术品):https://ai-art.tokyo/en ------ 码力全开:https://www.maliquankai.com/ + +流程图 +------ + +http://asciiflow.com/ + +https://www.draw.io/ + +其他工具 +-------- + +随机生成复杂 +JSON:http://www.yyyweb.com/demo/inner-show/json-generator.html + +生成 Markdwon 目录:https://ecotrust-canada.github.io/markdown-toc/ + +代码分享:https://paste.ubuntu.com/ + +黑科技 +------ + +果核剥壳(破解工具):https://www.ghpym.com/special/softlist + +.. |image0| image:: http://image.iswbm.com/20200602135014.png + diff --git a/source/c09/c09_04.md b/source/c09/c09_04.md new file mode 100644 index 0000000..540a646 --- /dev/null +++ b/source/c09/c09_04.md @@ -0,0 +1,4 @@ +# 9.4 书籍推荐 + +《[网络是怎样连接的](https://book.douban.com/subject/26941639/)》 豆瓣评分:9.1 + diff --git a/source/c09/c09_04.rst b/source/c09/c09_04.rst new file mode 100644 index 0000000..8f7ac82 --- /dev/null +++ b/source/c09/c09_04.rst @@ -0,0 +1,5 @@ +9.4 书籍推荐 +============ + +《\ `网络是怎样连接的 `__\ 》 +豆瓣评分:9.1 diff --git a/source/c09/c09_05.md b/source/c09/c09_05.md new file mode 100644 index 0000000..5dc7121 --- /dev/null +++ b/source/c09/c09_05.md @@ -0,0 +1,98 @@ +# 9.5 让Python 执行任意代码时,都会自动念一段 『平安经』 + +![](http://image.iswbm.com/20200602135014.png) + +最近的"平安经"可谓是引起了不小的风波啊。 + +作为一个正儿八经的程序员,最害怕的就是自己的代码上线出现各种各样的 BUG。 + +为此明哥就研究了一下,如何在你执行任意 Python 代码前,让 Python 解释器自动念上一段平安经,保佑代码不出 BUG 。 + +没想到还真被我研究出来了 + +做好心理准备了嘛? + +我要开始作妖了,噢不,是开始念经了。 + +![](http://image.iswbm.com/20200801221705.png) + +感谢佛祖保佑,Everything is ok,No bugs in the code. + + + +你一定很想知道这是如何实现的吧? + +如果你对 Linux 比较熟悉,就会知道,当你在使用 SSH 远程登陆 Linux 服务器的时候?会读取 `.bash_profile` 文件加载一些环境变量。 + +`.bash_profile` 你可以视其为一个 shell 脚本,可以在这里写一些 shell 代码达到你的定制化需求。 + +而在 Python 中,也有类似 `.bash_profile` 的文件,这个文件一般情况下是不存在的。 + +我们需要新建一个用户环境目录,这个目录比较长,不需要你死记硬背,使用 site 模块的方法就可以获取,然后使用 `mkdir -p` 命令创建它。 + +![](http://image.iswbm.com/20200801220819.png) + +在这个目录下,新建一个 `usercustomize.py` 文件,注意名字必须是这个,换成其他的可就识别不到啦。 + +这个 `usercustomize.py` 的内容如下(明哥注:佛祖只保佑几个 Python 的主要应用方向,毕竟咱是 Python 攻城狮嘛...) + +![](http://image.iswbm.com/20200801221413.png) + +这个文件我放在了我的 github 上,你可以点此前往获取。 + +一切都完成后,无论你是使用 `python xxx.py` 执行脚本 + +![](http://image.iswbm.com/20200801221705.png) + +还是使用 `python` 进入 Python Shell ,都会先念一下平安经保平安。 + +![](http://image.iswbm.com/20200801221457.png) + +除此之外,可还有其他方法呢? + +当然是有,只不过相对来说,会麻烦一点了。 + +先来看一下效果。 + +先查看在 `~/Library/Python/3.9/lib/python/site-packages` 目录下并没有 `usercustomize.py` 文件。 + +但是在执行 python 进入 Python Shell 模式后,还是会打印平安经。 + +![](http://image.iswbm.com/20200801225652.png) + +这又是如何做到的?真见鬼了呀。 + +方法其实也很简单,只要做两件事,就能实现这样的效果: + +**第一件事**,在任意你喜欢的目录下,新建 一个Python 脚本,名字也随意,比如我叫 `startup.py`,内容还是和上面一样 + +![](http://image.iswbm.com/20200801221413.png) + +**第二件事**,设置一个环境变量 PYTHONSTARTUP,指向你的脚本路径 + +```shell +$ export PYTHONSTARTUP=/Users/MING/startup.py +``` + +这样就可以了。 + +但是这种方法只适用于 Python Shell ,只不适合 Python 执行脚本的方法。 + +![](http://image.iswbm.com/20200801230230.png) + +如果要在脚本中实现这种效果,我目前想到最粗糙我笨拙的方法了 -- `手动加载执行` + +![](http://image.iswbm.com/20200801230503.png) + + + +本文分享了两个非常冷门 Python 的黑魔法技巧,可以实现在你执行任意的 Python 代码前,自动召唤佛祖念上一段平安经。 + +希望本篇分享能对你有用,更多关于 Python Shell 的玩法,我已经整理在了我的 Github 上(https://github.com/iswbm/magic-python),可以前往查看。 + + + +--- + + +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c09/c09_05.rst b/source/c09/c09_05.rst new file mode 100644 index 0000000..b1e25a4 --- /dev/null +++ b/source/c09/c09_05.rst @@ -0,0 +1,117 @@ +9.5 让Python 执行任意代码时,都会自动念一段 『平安经』 +====================================================== + +|image0| + +最近的“平安经”可谓是引起了不小的风波啊。 + +作为一个正儿八经的程序员,最害怕的就是自己的代码上线出现各种各样的 BUG。 + +为此明哥就研究了一下,如何在你执行任意 Python 代码前,让 Python +解释器自动念上一段平安经,保佑代码不出 BUG 。 + +没想到还真被我研究出来了 + +做好心理准备了嘛? + +我要开始作妖了,噢不,是开始念经了。 + +|image1| + +感谢佛祖保佑,Everything is ok,No bugs in the code. + +你一定很想知道这是如何实现的吧? + +如果你对 Linux 比较熟悉,就会知道,当你在使用 SSH 远程登陆 Linux +服务器的时候?会读取 ``.bash_profile`` 文件加载一些环境变量。 + +``.bash_profile`` 你可以视其为一个 shell 脚本,可以在这里写一些 shell +代码达到你的定制化需求。 + +而在 Python 中,也有类似 ``.bash_profile`` +的文件,这个文件一般情况下是不存在的。 + +我们需要新建一个用户环境目录,这个目录比较长,不需要你死记硬背,使用 +site 模块的方法就可以获取,然后使用 ``mkdir -p`` 命令创建它。 + +|image2| + +在这个目录下,新建一个 ``usercustomize.py`` +文件,注意名字必须是这个,换成其他的可就识别不到啦。 + +这个 ``usercustomize.py`` 的内容如下(明哥注:佛祖只保佑几个 Python +的主要应用方向,毕竟咱是 Python 攻城狮嘛…) + +|image3| + +这个文件我放在了我的 github 上,你可以点此前往获取。 + +一切都完成后,无论你是使用 ``python xxx.py`` 执行脚本 + +|image4| + +还是使用 ``python`` 进入 Python Shell ,都会先念一下平安经保平安。 + +|image5| + +除此之外,可还有其他方法呢? + +当然是有,只不过相对来说,会麻烦一点了。 + +先来看一下效果。 + +先查看在 ``~/Library/Python/3.9/lib/python/site-packages`` 目录下并没有 +``usercustomize.py`` 文件。 + +但是在执行 python 进入 Python Shell 模式后,还是会打印平安经。 + +|image6| + +这又是如何做到的?真见鬼了呀。 + +方法其实也很简单,只要做两件事,就能实现这样的效果: + +**第一件事**\ ,在任意你喜欢的目录下,新建 一个Python +脚本,名字也随意,比如我叫 ``startup.py``\ ,内容还是和上面一样 + +|image7| + +**第二件事**\ ,设置一个环境变量 PYTHONSTARTUP,指向你的脚本路径 + +.. code:: shell + + $ export PYTHONSTARTUP=/Users/MING/startup.py + +这样就可以了。 + +但是这种方法只适用于 Python Shell ,只不适合 Python 执行脚本的方法。 + +|image8| + +如果要在脚本中实现这种效果,我目前想到最粗糙我笨拙的方法了 – +``手动加载执行`` + +|image9| + +本文分享了两个非常冷门 Python 的黑魔法技巧,可以实现在你执行任意的 +Python 代码前,自动召唤佛祖念上一段平安经。 + +希望本篇分享能对你有用,更多关于 Python Shell 的玩法,我已经整理在了我的 +Github 上(https://github.com/iswbm/magic-python),可以前往查看。 + +-------------- + +|image10| + +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200801221705.png +.. |image2| image:: http://image.iswbm.com/20200801220819.png +.. |image3| image:: http://image.iswbm.com/20200801221413.png +.. |image4| image:: http://image.iswbm.com/20200801221705.png +.. |image5| image:: http://image.iswbm.com/20200801221457.png +.. |image6| image:: http://image.iswbm.com/20200801225652.png +.. |image7| image:: http://image.iswbm.com/20200801221413.png +.. |image8| image:: http://image.iswbm.com/20200801230230.png +.. |image9| image:: http://image.iswbm.com/20200801230503.png +.. |image10| image:: http://image.iswbm.com/20200607174235.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 3ec17c4..598e01b 100644 --- a/source/c10/c10_01.md +++ b/source/c10/c10_01.md @@ -1,275 +1,384 @@ -# 10.1 网络知识扫盲:一篇文章搞懂 DNS +# 10.1 使用 Python 远程登陆服务器的利器 -## 1. DNS 是什么? +![](http://image.iswbm.com/20200602135014.png) -DNS (Domain Name System 的缩写)的作用非常简单,就是根据域名查出IP地址。你可以把它想象成一本巨大的电话本。 +在使用 Python 写一些脚本的时候,在某些情况下,我们需要频繁登陆远程服务去执行一次命令,并返回一些结果。 -举例来说,如果你要访问域名`math.stackexchange.com`,首先要通过DNS查出它的IP地址是`151.101.129.69`。 +在 shell 环境中,我们是这样子做的。 -## 2. 域名的层级 - -由于后面我会讲到 DNS 的解析过程,因此需要你对域名的层级有一些了解 +```shell +$ sshpass -p ${passwd} ssh -p ${port} -l ${user} -o StrictHostKeyChecking=no xx.xx.xx.xx "ls -l" +``` -- 根域名 :`.root` 或者 `.` ,通常是省略的 -- 顶级域名,如 `.com`,`.cn` 等 -- 次级域名,如 `baidu.com` 里的 `baidu`,这个用户是可以注册购买的 -- 主机域名,比如 `baike.baidu.com` 里的`baike`,这个用户是可分配的 +然后你会发现,你的输出有很多你并不需要,但是又不去不掉的一些信息(也许有方法,请留言交流),类似这样 +```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 -``` +对于直接使用 shell 命令,来执行命令的,可以直接使用管道,或者将标准输出重定向到文件的方法取得执行命令返回的结果 + +## 1. 使用 subprocess + +若是使用 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.python-online.cn/image-20200320125724880.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 9e52d2d..70ac45e 100644 --- a/source/c10/c10_01.rst +++ b/source/c10/c10_01.rst @@ -1,352 +1,405 @@ -10.1 网络知识扫盲:一篇文章搞懂 DNS -=================================== +10.1 使用 Python 远程登陆服务器的利器 +===================================== -1. DNS 是什么? ---------------- +|image0| -DNS (Domain Name System -的缩写)的作用非常简单,就是根据域名查出IP地址。你可以把它想象成一本巨大的电话本。 +在使用 Python +写一些脚本的时候,在某些情况下,我们需要频繁登陆远程服务去执行一次命令,并返回一些结果。 -举例来说,如果你要访问域名\ ``math.stackexchange.com``\ ,首先要通过DNS查出它的IP地址是\ ``151.101.129.69``\ 。 +在 shell 环境中,我们是这样子做的。 -2. 域名的层级 -------------- +.. code:: shell -由于后面我会讲到 DNS 的解析过程,因此需要你对域名的层级有一些了解 + $ sshpass -p ${passwd} ssh -p ${port} -l ${user} -o StrictHostKeyChecking=no xx.xx.xx.xx "ls -l" -- 根域名 :\ ``.root`` 或者 ``.`` ,通常是省略的 -- 顶级域名,如 ``.com``\ ,\ ``.cn`` 等 -- 次级域名,如 ``baidu.com`` 里的 ``baidu``\ ,这个用户是可以注册购买的 -- 主机域名,比如 ``baike.baidu.com`` - 里的\ ``baike``\ ,这个用户是可分配的 +然后你会发现,你的输出有很多你并不需要,但是又不去不掉的一些信息(也许有方法,请留言交流),类似这样 -:: +.. code:: shell - 主机名.次级域名.顶级域名.根域名 - - 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地址 + 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 -|image0| +对于直接使用 shell +命令,来执行命令的,可以直接使用管道,或者将标准输出重定向到文件的方法取得执行命令返回的结果 -4. DNS的缓存时间 ----------------- +1. 使用 subprocess +------------------ -上面的几个步骤里,可以看到有两个地方会缓存 DNS -的查询记录,有了缓存,在一定程度上会提高查询效率,但同时在准确率上会有所损失。 +若是使用 Python 来做这件事,通常我们会第一时间,想到使用 +os.popen,os.system,commands,subprocess 等一些命令执行库来间接获取 。 -因此我们在配置 DNS 解析的时候,会有一个 TTL 参数(Time To -Live),意思就是这个缓存可以存活多长时间,过了这个时间,本地 DNS -就会删除这条记录,删除了缓存后,你再访问,就要重新走一遍上面的流程,获取最新的地址。 +但是据我所知,这些库获取的 output +不仅只有标准输出,还包含标准错误(也就是上面那些多余的信息) -|image1| +所以每次都要对 output +进行的数据清洗,然后整理格式化,才能得到我们想要的数据。 -5. DNS 的记录类型 ------------------ +用 subprocess 举个例子,就像这样子 -当我们在阿里云买了一个域名后,可以配置我们主机域名解析规则,也就是 -**记录**\ 。 +.. code:: python -.. figure:: http://image.iswbm.com/image-20200531170212224.png - :alt: 阿里云 域名云解析 + 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) - 阿里云 域名云解析 + # 数据清理,格式化的就不展示了 + -常见的 DNS 记录类型如下 +通过以上的文字 + 代码的展示 ,可以感觉到 ssh 登陆的几大痛点 -- ``A``\ :地址记录(Address),返回域名指向的IP地址。 +- **痛点一**\ :需要额外安装 sshpass(如果不免密的话) +- **痛点二**\ :干扰信息太多,数据清理、格式化相当麻烦 +- **痛点三**\ :代码实现不够优雅(有点土),可读性太差 +- **痛点四**\ :ssh 连接不能复用,一次连接仅能执行一次 +- **痛点五**\ :代码无法全平台,仅能在 Linux 和 OSX 上使用 -- ``NS``\ :域名服务器记录(Name - Server),返回保存下一级域名信息的服务器地址。该记录只能设置为域名,不能设置为IP地址。 -- ``MX``\ :邮件记录(Mail eXchange),返回接收电子邮件的服务器地址。 -- ``CNAME``\ :规范名称记录(Canonical - Name),返回另一个域名,即当前查询的域名是另一个域名的跳转,详见下文。 -- ``PTR``\ :逆向查询记录(Pointer - Record),只用于从IP地址查询域名,详见下文。 +为了解决这几个问题,我搜索了全网关于 Python ssh +的文章,没有看到有完整介绍这方面的技巧的。 -6. DNS 报文结构 ---------------- +为此,我就翻阅了一个很火的 Github 项目: awesome-python-cn +(https://github.com/BingmingWong/awesome-python-cn)。 -后面我将使用 wireshark 抓取 DNS 的数据包,但是在开始之前 ,得先了解一下 -DNS 的报文结构 +期望在这里,找到有一些关于 远程连接 的一些好用的库。 -|image2| +还真的被我找到了两个 -- 事务 ID:DNS 报文的 ID - 标识。对于请求报文和其对应的应答报文,该字段的值是相同的。通过它可以区分 - DNS 应答报文是对哪个请求进行响应的。 -- 标志:DNS 报文中的标志字段。 -- 问题计数:DNS 查询请求的数目。 -- 回答资源记录数:DNS 响应的数目。 -- 权威名称服务器计数:权威名称服务器的数目。 -- 附加资源记录数:额外的记录数目(权威名称服务器对应 IP 地址的数目)。 +- sh.ssh +- Paramiko -7. Wireshark抓包实战 --------------------- +2. 使用 sh.ssh +-------------- -打开 Wireshark 后,使用 ``ping 163.com`` 来发起 DNS 解析请求,使用 -``DNS`` 关键字在Wireshark 过滤。 +首先来介绍第一个,\ ``sh.ssh`` -从抓取的报文整体来看,我们可以粗略获取几个信息 +``sh`` 是一个可以让你通过函数的调用来完成 Linxu/OSX +系统命令的一个库,非常好用,关于它有机会也写篇介绍。 -1. DNS 是应用层协议,传输层协议使用的是 UDP -2. DNS 默认端口是 53 +.. code:: shell -|image3| + $ python3 -m pip install sh -请求和应答的报文的截图我放在了下面,接下来我将逐个分析。 +今天只介绍它其中的一个函数:\ ``ssh`` -**请求** +通常两台机器互访,为了方便,可设置免密登陆,这样就不需要输入密码。 -|image4| +这段代码可以实现免密登陆,并执行我们的命令 ``ls -l`` -**应答** +.. code:: python -|image5| + from sh import ssh + output=ssh("root@xx.xx.xx.xx", "-p 22", "ls -l") + print(output) -Transaction ID -~~~~~~~~~~~~~~ +但有可能 +,我们并不想设置互信免密,为了使这段代码更通用,我假定我们没有设置免密,只能使用密码进行登陆。 -请求和应答的事务ID应当是一个:0xd0d7 +问题就来了,要输入密码,必须得使用交互式的方法来输入呀,在 Python +中要如何实现呢? -Flags -~~~~~ +原来 ssh 方法接收一个 ``_out`` +参数,这个参数可以为一个字符串,表示文件路径,也可以是一个文件对象(或者类文件对象),还可以是一个回调函数,意思是当有标准输出时,就会调用将输出内容传给这个函数。 -标志字段里的内容比较多,每个字段的含义如下 +这就好办了呀。 -- 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),一般是服务器由于设置的策略拒绝给出应答,如服务器不希望对某些请求者给出应答。 +我只要识别到有 ``password:`` 字样,就往标准输入写入我的密码就好了呀。 -Answer RRs -~~~~~~~~~~ +完整代码如下: -回答资源记录数,在应答包里为 2,说明返回了两条查询结果,你可以在 Answer -字段里看到。 +.. code:: python -Authority RRs -~~~~~~~~~~~~~ + 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") -Additionnal 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。 -Answers -~~~~~~~ +尝试运行后,发现程序会一直在运行中,永远不会返回,不会退出,回调函数也永远不会进入。 -应答的主要内容,这里返回两条结果,每条结果里的字段有 +通过调试查看源代码,仍然查不到问题所在,于是去 +`Github `__ 上搜了下,原来在 +2017 年就已经存在这个问题了,到现在 2020 年了还没有修复,看来使用 +``sh.ssh`` 的人并不多,于是我又“追问”了下,期望能得到回复。 -.. code:: shell +|image1| - Name: 查询的域名 - Type: A表示IPv4,AAAA 表示IPv6 - Class: 表示Internet,几乎总是它 - Time to live: 生存时间 - Data length: 数据长度 - Address: 查询到的 IP 地址 +以上这个问题,只有在需要输入密码才会出现,如果设置了机器互信是没有问题的。 -8. DNS 劫持 与 HTTP 劫持 ------------------------- +为了感受 ``sh.ssh`` +的使用效果,我设置了机器互信免密,然后使用如下这段代码。 -通过上面的讲解,我们都知道了,DNS 完成了一次域名到 IP -的映射查询,当你在访问 www.baidu.com 时,能正确返回给你 百度首页的 ip。 +.. code:: python -但如果此时 DNS 解析出现了一些问题,当你想要访问 www.baidu.com -时,却返回给你 www.google.com 的ip,这就是我们常说的 DNS 劫持。 + from sh import ssh -与之容易混淆的有 HTTP 劫持。 + my_server=ssh.bake("root@xx.xx.xx.xx", "-p 22") -那什么是 HTTP 劫持呢? + # 相当于执行登陆一次执行一次命令,执行完就退出登陆 + print(my_server.ls()) -你一定见过当你在访问 -某个网站时,右下角也突然弹出了一个扎眼的广告弹窗。这就是 HTTP 劫持。 + # 可在 sleep 期间,手动登陆服务器,使用 top ,查看当前有多少终端在连接 + time.sleep(5) -借助别人文章里的例子,它们俩的区别就好比是 + # 再次执行这条命令时,登陆终端数将 +1,执行完后,又将 -1 + print(my_server.ifconfig()) -- DNS劫持是你想去机场的时候,把你给丢到火车站。 +惊奇地发现使用 ``bake`` 这种方式,\ ``my_server.ls()`` 和 +``my_server.ifconfig()`` +这种看似是通过同一个ssh连接,执行两次命令,可实际上,你可以在远程机器上,执行 +top 命令看到已连接的终端的变化,会先 ``+1`` 再 +``-1``\ ,说明两次命令的执行是通过两次连接实现的。 -- HTTP劫持是你去机场途中,有人给你塞小广告。 +如此看来,使用 ``sh.ssh`` +可以解决痛点一(如果上述问题能得到解决)、痛点二、痛点三。 -**那么 DNS劫持 是如何产生的呢?** +但是它仍然无法复用 ssh 连接,还是不太方便,不是我理想中的最佳方案。 -下面大概说几种DNS劫持方法: +最重要的一点是, ``sh`` 这个模块,仅支持 Linxu/OSX ,在 Windows +你得使用它的兄弟库 - ``pbs`` ,然后我又去 pypi 看了一眼 +`pbs `__\ ,已经 “年久失修”,没人维护了。 -**1.本机DNS劫持** +|image2| -攻击者通过某些手段使用户的计算机感染上木马病毒,或者恶意软件之后,恶意修改本地DNS配置,比如修改本地hosts文件,缓存等 +至此,我离 “卒”,就差最后一根稻草了。 -**2. 路由DNS劫持** +3. 使用 paramiko +---------------- -很多用户默认路由器的默认密码,攻击者可以侵入到路由管理员账号中,修改路由器的默认配置 +带着最后一丝希望,我尝试使用了 ``paramiko`` 这个库,终于在 ``paramiko`` +这里,找回了本应属于 Python 的那种优雅。 -**3.攻击DNS服务器** +你可以通过如下命令去安装它 -直接攻击DNS服务器,例如对DNS服务器进行DDOS攻击,可以是DNS服务器宕机,出现异常请求,还可以利用某些手段感染dns服务器的缓存,使给用户返回来的是恶意的ip地址 +:: -9. 工具的使用 -------------- + $ python3 -m pip install paramiko -dig 命令 -~~~~~~~~ +然后接下来,就介绍几种常用的 ssh 登陆的方法 -dig是一个在类Unix命令行模式下查询DNS包括NS记录,A记录,MX记录等相关信息的工具。 +方法1:基于用户名和密码的 sshclient 方式登录 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -通过 dig (参数:\ ``+trace``\ )命令,我们可以看到上面描述的 DNS -解析的详细过程 +然后你可以参考如下这段代码,在 Linux/OSX 系统下进行远程连接 -|image6| +.. code:: python -从返回的结果,我们可以看得出几点信息 + import paramiko -1. 我们的本地 DNS 服务器 ip 为 192.168.1.1,端口为53,你可以在 - /etc/resolv.conf 里看到这个配置 -2. 根域名服务器目前全球一共只有十三台,从a.root-servers.net. - ``到``\ m.root-servers.net. - ,它们对应的ip地址,已经内置在本地DNS服务器中。 + ssh = paramiko.SSHClient() + # 允许连接不在know_hosts文件中的主机 + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) -如果你只想看到结果,可以使用 ``+short`` 参数,可以直接返回 www.163.com -对应着哪几个ip + # 建立连接 + ssh.connect("xx.xx.xx.xx", username="root", port=22, password="you_password") -|image7| + # 使用这个连接执行命令 + ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("ls -l") -你也可以加个 ``@`` 参数 ,指定从某个 DNS 服务器进行查询 + # 获取输出 + print(ssh_stdout.read()) -|image8| + # 关闭连接 + ssh.close() -如果你只想查看指定的记录类型 +方法2:基于用户名和密码的 transport 方式登录 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -|image9| +方法1 +是传统的连接服务器、执行命令、关闭的一个操作,多个操作需要连接多次,无法复用连接[**痛点四**]。 -host 命令 -~~~~~~~~~ +有时候需要登录上服务器执行多个操作,比如执行命令、上传/下载文件,方法1 +则无法实现,那就可以使用 transport 的方法。 -``host`` 命令 -可以看作\ ``dig``\ 命令的简化版本,返回当前请求域名的各种记录。 +.. code:: python -|image10| + import paramiko -whois命令 -~~~~~~~~~ + # 建立连接 + trans = paramiko.Transport(("xx.xx.xx.xx", 22)) + trans.connect(username="root", password="you_passwd") -``whois``\ 命令用来查看域名的注册情况。 + # 将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()) -nslookup命令 -~~~~~~~~~~~~ + # 关闭连接 + trans.close() -nslookup也是常用的一个查询 DNS 解析结果的工具 +方法3:基于公钥密钥的 SSHClient 方式登录 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. code:: shell +.. code:: python - $ nslookup [查询的域名] [指定DNS服务器] + import paramiko -|image12| + # 指定本地的RSA私钥文件 + # 如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数 + pkey = paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa', password='12345') -你也可以指定公网的域名服务器进行查询,比如常见的 114.114.114.114 + # 建立连接 + ssh = paramiko.SSHClient() + ssh.connect(hostname='xx.xx.xx.xx', + port=22, + username='you_username', + pkey=pkey) -|image13| + # 执行命令 + stdin, stdout, stderr = ssh.exec_command('ls -l') -10. 手动清理本地缓存 --------------------- + # 结果放到stdout中,如果有错误将放到stderr中 + print(stdout.read()) -MacOS + # 关闭连接 + ssh.close() -.. code:: shell +方法4:基于密钥的 Transport 方式登录 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - $ sudo dscacheutil -flushcache - $ sudo killall -HUP mDNSResponder +.. code:: python -Windows + import paramiko -.. code:: shell + # 指定本地的RSA私钥文件 + # 如果建立密钥对时设置的有密码,password为设定的密码,如无不用指定password参数 + pkey = paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa', password='12345') - $ ipconfig /flushdns + # 建立连接 + trans = paramiko.Transport(('xx.xx.xx.xx', 22)) + trans.connect(username='you_username', pkey=pkey) -Linux + # 将sshclient的对象的transport指定为以上的trans + ssh = paramiko.SSHClient() + ssh._transport = trans -.. code:: shell + # 执行命令,和传统方法一样 + 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| - # 使用NSCD的DNS缓存 - $ sudo /etc/init.d/nscd restart - - # 服务器或者路由器使用DNSMASQ - $ sudo dnsmasq restart - -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! - -.. |image0| image:: http://image.iswbm.com/464291-20170703113844956-354755333.jpg -.. |image1| image:: http://image.iswbm.com/image-20200531141521689.png -.. |image2| image:: http://image.iswbm.com/image-20200531152824672.png -.. |image3| image:: http://image.iswbm.com/20200531175736.png -.. |image4| image:: http://image.iswbm.com/20200531175811.png -.. |image5| image:: http://image.iswbm.com/image-20200531153110621.png -.. |image6| image:: http://image.iswbm.com/image-20200531162810531.png -.. |image7| image:: http://image.iswbm.com/image-20200531164525384.png -.. |image8| image:: http://image.iswbm.com/image-20200531170427834.png -.. |image9| image:: http://image.iswbm.com/image-20200531170543250.png -.. |image10| image:: http://image.iswbm.com/image-20200531171610902.png -.. |image11| image:: http://image.iswbm.com/image-20200531171905345.png -.. |image12| image:: http://image.iswbm.com/image-20200531145109182.png -.. |image13| image:: http://image.iswbm.com/image-20200531145449577.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 13805cb..496da16 100644 --- a/source/c10/c10_02.md +++ b/source/c10/c10_02.md @@ -1,139 +1,253 @@ -# 10.2 网络知识扫盲:如何理解 OSI七层模型 +# 10.2 pretty_errors 解决bug 洁癖 -OSI (Open System Interconnect),即开放式系统互联。一般都叫OSI参考模型,是ISO(国际标准化组织)组织在1985年研究的网络互联模型。 +![](http://image.iswbm.com/20200602135014.png) -![img](https://pic4.zhimg.com/80/v2-854e3df8ea850c977c30cb1deb1f64db_1440w.jpg) +当我们写的一个脚本或程序发生各种不可预知的异常时,如果我们没有进行捕获处理的时候,通常都会致使程序崩溃退出,并且会在终端打印出一堆 **密密麻麻** 的 traceback 堆栈信息来告诉我们,是哪个地方出了问题。 -为什么要有这个模型呢? +就像这样子,天呐,密集恐惧症要犯了都 -主要是为了实现开放系统环境中的互连性、互操作性和应用的可移植性。 +![](http://image.iswbm.com/image-20200307210853246.png) -有了这个模型,各个层的实现就可以独立开发,复用性强,增加自由度的同时,也提高了生产效率。 +上面这段 traceback -## 数据的发送过程 +- 只有黑白两个颜色,无法像代码高亮那样,对肉眼实现太不友好了 +- 无法直接显示报错的代码,排查问题慢人一步,效率太低 -数据包的发起方,一定都是从高层发起,然后从上往下传输数据,下层为上层提供传输服务,下方不关心传输内容,上层不关心下层如何传输。 +那有没有一种办法,可以解决这些问题呢? -就如下面这张图所示 +当然有了,在 Python 中,没有什么问题是一个库解决不了的,如果有,那就等你去开发这个库。 -![](http://image.iswbm.com/20200526233356.png) +今天要介绍的这个库呢,叫做 `pretty-errors` ,从名字上就可以知道它的用途,是用来美化错误信息的。 +通过这条命令你可以安装它 +```shell +$ python3 -m pip install pretty-errors +``` -## 应用层 -代表协议有:HTTP,HTTPS,FTP,SMTP,TELNET -由于日常开发中,我们接触的基本都是 HTTP,这里就以 HTTP 为例。 +## 1. 环境要求 -这层负责的是真正业务上的内容,比如你在你的浏览器上,请求一个百度的首页。 +由于使用了 `pretty-errors` 后,你的 traceback 信息输出,会有代码高亮那样的效果,因此当你在使用测试使用 `pretty-error` 时,请确保你使用的终端可以输出带有颜色的字体。 -览器会将你请求的信息,封装成一个 HTTP 数据包,这个数据包头会包含一些基本的头部信息及请求体(如果有的话)。然后交由下一层处理。 +在 windows 上你可以使用 Powershell,cmder 等 -## 表示层 +在 Mac 上你可以使用自带的终端,或者安装一个更好用的 iTerm2 -代表协议有:ASCII,SSL/TLS 等 +## 2. 效果对比 -这一层的作用是,将应用处理的信息转换为适合网络传输的格式。 +------ -比如我只会说中文,而日本友人只会说日文,那么我们两个是无法交流的。但如果我们都会说英文,交流时我先在心里想好要说的话是什么,再用英语说出来,日本友人听到英文,在心里转换为日语,他就能弄懂我的意思,此时表示层就是各自在心里转化语言。 +随便写一个没有使用 pretty-errors ,并且报错了的程序,是这样子的。 -当浏览器请求回一堆数据,是解析成文本还是图片,就由表示层决定。数据的压缩、加密、打包等功能也都在这层完成。 +![](http://image.iswbm.com/image-20200307212823345.png) -## 会话层 +而使用了 pretty_errors 后,报错信息被美化成这样了。 -代表协议有: `ADSP`、`RPC` 等。 +![](http://image.iswbm.com/image-20200307213534278.png) -负责建立和断开通信连接(数据流动的逻辑通路),以及数据的分割等数据传输相关的管理。 +是不是感觉清楚了不少,那种密密麻麻带来的焦虑感是不是都消失了呢? -比如,何时建立连接,何时断开连接以及保持多久的连接,都是由会话层来进行管理的。 +当然这段代码少,你可能还没感受到,那就来看下 该项目在 Github上的一张效果对比图吧 +![](https://warehouse-camo.cmh1.psfhosted.org/31399c5a034c3989b9e99b35249e8f2f0d40e102/68747470733a2f2f692e696d6775722e636f6d2f306a7045716f622e706e67) -## 传输层 -代表协议有 :TCP,UDP等 +## 3. 配置全局可用 -**传输层起着可靠传输的作用。** +可以看到使用了 pretty_errors 后,无非就是把过滤掉了一些干扰我们视线的无用信息,然后把有用的关键信息给我们高亮显示。 -对于 DNS 域名解析,传输层协议是 UDP +既然既然这样,那 pretty_errors 应该也能支持我们如何自定义我们选用什么样的颜色,怎么排版吧? -而对于 HTTP 请求,传输层协议是 TCP +答案是显而易见的。 -**那这两种协议有什么区别呢?** +pretty_errors 和其他库不太一样,在一定程度上(如果你使用全局配置的话),它并不是开箱即用的,你在使用它之前可能需要做一下配置。 -`TCP` 协议提供可靠的通信传输,简单说就是确认目标能通信的情况下才会传输数据(因此需要三次握手),传输过程如果丢了数据,也会重发。而 `UDP` 协议则不然,不会确认目标能否通信,只会根据协议发到对方地址的端口。至于对方收不收到,丢不丢包,一概不管。 +使用这一条命令,会让你进行配置,可以让你在该环境中运行其他脚本时的 traceback 输出都自动美化。 -无论是哪种协议,其协议头信息都会包含本地端口号,和目标端口号(还有报文长度)。 +```shell +$ python3 -m pretty_errors +``` -加上这层头信息后,封装好的数据包再交由网络层。 +![](http://image.iswbm.com/image-20200307214742135.png) -## 网络层 +配置完成后,你再运行任何脚本,traceback 都会自动美化了。 -代表协议有:IP,ICMP 协议等,其也叫IP层,主要应用是路由器(并非我们家中的路由器,扩展阅读:https://www.zhihu.com/question/52176116)。 +不仅是在我的 iTerm 终端下 -**网络层负责将数据传输到目标地址。** +![](http://image.iswbm.com/image-20200307213534278.png) -网络层将数据从发送端的主机发送到接收端的主机,两台主机间可能会存在很多数据链路,但网络层就是负责找出一条相对顺畅的通路将数据传递过去。传输的地址使用的是IP地址。 +在 PyCharm 中也会 -IP地址和我们的住址有点相似,我们的住址可以从省到市再到街逐步缩小范围,直至我们住址。IP地址也有这样的能力,通过不断转发到更近的IP地址,最终可以到达目标地址。如何选择这条路,就看网络层了。 +![](http://image.iswbm.com/image-20200307215530270.png) -这好比是快递公司的路线规划者。快递公司有很多集散中心,根据集散中心的情况(是否拥堵),找出一条经过n个集散中心的路径将货物(数据)沿路运过去。 +唯一的缺点就是,原先在 PyCharm 中的 traceback 可以直接点击 `文件路径` 直接跳转到对应错误文件代码行,而你如果是在 VSCode 可以使用 下面自定义配置的方案解决这个问题(下面会讲到,参数是:`display_link`)。 -在这层里,会给数据包再加一些头信息,包括本地IP地址,目标IP地址(还有数据包的生存空间TTL),同时还会有一个Protocol字段 表示上层到底是 UDP还是TCP协议,这个是用于对端在接收到这个数据包后知道如何解析。 +![](http://image.iswbm.com/image-20200307215834623.png) -加上这些头信息后,再把**数据报**(或者说**分组**、**报文**)传递给链路层。 +因此,有些情况下,你并不想设置 `pretty_errors` 全局可用。 +那怎么取消之前的配置呢? +只需要再次输出 `python -m pretty_errors`,输出入 `C` 即可清除。 -## 链路层 +![](http://image.iswbm.com/image-20200307214600749.png) -代表协议有:HDLC,PPP,SLIP 等,其也叫 MAC 层,主要应用是交换机,网桥。 -**该层负责物理层面(一个以太网相连)上互连的节点之间的通信传输。** -数据链路层会将0、1序列划分为具有意义的数据帧传送给对端(数据帧的生成与接收)。举个例子可能会更好理解,暂且把需要传输的数据看作为不同来源的水,如果直接倒入池子中时,是无法重新分辨出不同来源的水的。但如果将不同来源的灌入瓶子中并打上记号,那就能区分出不同来源的水。这也就是为什么要划分为具有意义的数据帧传送给对端。 +## 4. 单文件中使用 -数据链路层可以看作是快递公司的司机,他们驾驶着汽车,将打包好的货物(数据帧)从一个城市(物理节点)运输到另一个城市。 +取消全局可用后,你可以根据自己需要,在你需要使用 `pretty-errors` 的脚本文件中导入` pretty_errors `,即可使用 -在这层里,会给数据包再加一些头信息,包括本地的mac地址,目标mac地址,同时还有一个type字段表示上层协议是使用的是IP协议,还是其他什么协议,也是用于对端在接收到这个数据包后知道如何解析。 +```python +import pretty_errors +``` -需要注意的是,数据链路层只负责将数据运送给物理相连的两端,并不负责直接发送到最终地址。 +就像这样 -加上这些头信息后,再把**数据帧**传递给物理层。 +```python +import pretty_errors +def foo(): + 1/0 +if __name__ == "__main__": + foo() +``` -这一层主要解决两个问题: +值得一提的是,使用这种方式,若是你的脚本中,出现语法错误,则输出的异常信息还是按照之前的方式展示,并不会被美化。 -**第一个问题**:网络包是发给谁的,由谁来接收 +因此,为了让美化更彻底,官方推荐你使用 `python -m pretty_errors` -链路层协议头会包含目标MAC和源MAC +## 5. 自定义设置 -**第二个问题**:大家都在发包,谁先发,谁后发? +上面的例子里,我们使用的都是 `pretty_errors` 的默认美化格式,展示的信息并没有那么全。 -对于这个问题,有多种算法可以解决 +比如 -方法一:信道划分,就跟马路上分多个车道一样,你走你的,我走我的,互不影响 +- 它并没有展示报错文件的绝对路径,这将使我们很难定位到是哪个文件里的代码出现错误。 +- 如果能把具体报错的代码,给我们展示在终端屏幕上,就不需要我们再到源码文件中排查原因了。 -方法二:轮流协议,还是以交通举例,今天单号出行,明天双号出行,轮着来 +如果使用了 `pretty_errors` 导致异常信息有丢失,那还不如不使用 `pretty_errors` 呢。 -方法三:随机接入协议,不管三七二十一,有事儿先出门,发现特堵,就回去。错过高峰再出。 +不过,可以告诉你的是,`pretty_errors` 并没有你想象的那么简单。 +它足够开放,支持自定义配置,可以由你选择你需要展示哪些信息,怎么展示? +这里举一个例子 -## 物理层 +```python +import pretty_errors -代表协议有: `RS 232C`、`RS 449/422/423`、`V.24` 和 `X.21`、`X.21bis` 等。 +# 【重点】进行配置 +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, +) -物理层负责0、1比特流(0、1序列)与电压高低、光的闪灭之间的互换。 +# 原来的代码 +def foo(): + 1/0 -物理层其实就是我们日常能接触的物理介质,比如光纤、电缆、还有空气(还有集线器、中继器、调制解调器),根据这些传输介质的不同,二进制流(0和1)会相应地转化成光信号,电信号,电磁波信号。 +if __name__ == "__main__": + foo() +``` -物理层是 `OSI` 七层模型的物理基础,没有它就谈不上数据传输了。 +在你像上面这样使用 `pretty_errrs.configure` 进行配置时,抛出的的异常信息就变成这样了。 +![](http://image.iswbm.com/image-20200308121949011.png) -## 参考文章 -- https://juejin.im/post/59eb06b1f265da430f313c7f \ No newline at end of file +当然了,`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.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 841ba73..bf2c4bf 100644 --- a/source/c10/c10_02.rst +++ b/source/c10/c10_02.rst @@ -1,152 +1,286 @@ -10.2 网络知识扫盲:如何理解 OSI七层模型 -======================================= +10.2 pretty_errors 解决bug 洁癖 +=============================== -OSI (Open System -Interconnect),即开放式系统互联。一般都叫OSI参考模型,是ISO(国际标准化组织)组织在1985年研究的网络互联模型。 +|image0| + +当我们写的一个脚本或程序发生各种不可预知的异常时,如果我们没有进行捕获处理的时候,通常都会致使程序崩溃退出,并且会在终端打印出一堆 +**密密麻麻** 的 traceback 堆栈信息来告诉我们,是哪个地方出了问题。 + +就像这样子,天呐,密集恐惧症要犯了都 + +|image1| + +上面这段 traceback + +- 只有黑白两个颜色,无法像代码高亮那样,对肉眼实现太不友好了 +- 无法直接显示报错的代码,排查问题慢人一步,效率太低 + +那有没有一种办法,可以解决这些问题呢? + +当然有了,在 Python +中,没有什么问题是一个库解决不了的,如果有,那就等你去开发这个库。 + +今天要介绍的这个库呢,叫做 ``pretty-errors`` +,从名字上就可以知道它的用途,是用来美化错误信息的。 -.. figure:: https://pic4.zhimg.com/80/v2-854e3df8ea850c977c30cb1deb1f64db_1440w.jpg - :alt: img +通过这条命令你可以安装它 - img +.. code:: shell -为什么要有这个模型呢? + $ python3 -m pip install pretty-errors -主要是为了实现开放系统环境中的互连性、互操作性和应用的可移植性。 +1. 环境要求 +----------- -有了这个模型,各个层的实现就可以独立开发,复用性强,增加自由度的同时,也提高了生产效率。 +由于使用了 ``pretty-errors`` 后,你的 traceback +信息输出,会有代码高亮那样的效果,因此当你在使用测试使用 +``pretty-error`` 时,请确保你使用的终端可以输出带有颜色的字体。 + +在 windows 上你可以使用 Powershell,cmder 等 + +在 Mac 上你可以使用自带的终端,或者安装一个更好用的 iTerm2 + +2. 效果对比 +----------- -数据的发送过程 -------------- -数据包的发起方,一定都是从高层发起,然后从上往下传输数据,下层为上层提供传输服务,下方不关心传输内容,上层不关心下层如何传输。 +随便写一个没有使用 pretty-errors ,并且报错了的程序,是这样子的。 -就如下面这张图所示 +|image2| -|image0| +而使用了 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`` 即可清除。 -代表协议有:HTTP,HTTPS,FTP,SMTP,TELNET +|image9| -由于日常开发中,我们接触的基本都是 HTTP,这里就以 HTTP 为例。 +4. 单文件中使用 +--------------- -这层负责的是真正业务上的内容,比如你在你的浏览器上,请求一个百度的首页。 +取消全局可用后,你可以根据自己需要,在你需要使用 ``pretty-errors`` +的脚本文件中导入\ ``pretty_errors``\ ,即可使用 -览器会将你请求的信息,封装成一个 HTTP -数据包,这个数据包头会包含一些基本的头部信息及请求体(如果有的话)。然后交由下一层处理。 +.. code:: python -表示层 ------- + import pretty_errors -代表协议有:ASCII,SSL/TLS 等 +就像这样 -这一层的作用是,将应用处理的信息转换为适合网络传输的格式。 +.. code:: python -比如我只会说中文,而日本友人只会说日文,那么我们两个是无法交流的。但如果我们都会说英文,交流时我先在心里想好要说的话是什么,再用英语说出来,日本友人听到英文,在心里转换为日语,他就能弄懂我的意思,此时表示层就是各自在心里转化语言。 + import pretty_errors -当浏览器请求回一堆数据,是解析成文本还是图片,就由表示层决定。数据的压缩、加密、打包等功能也都在这层完成。 + def foo(): + 1/0 -会话层 ------- + if __name__ == "__main__": + foo() -代表协议有: ``ADSP``\ 、\ ``RPC`` 等。 +值得一提的是,使用这种方式,若是你的脚本中,出现语法错误,则输出的异常信息还是按照之前的方式展示,并不会被美化。 -负责建立和断开通信连接(数据流动的逻辑通路),以及数据的分割等数据传输相关的管理。 +因此,为了让美化更彻底,官方推荐你使用 ``python -m pretty_errors`` -比如,何时建立连接,何时断开连接以及保持多久的连接,都是由会话层来进行管理的。 +5. 自定义设置 +------------- -传输层 ------- +上面的例子里,我们使用的都是 ``pretty_errors`` +的默认美化格式,展示的信息并没有那么全。 -代表协议有 :TCP,UDP等 +比如 -**传输层起着可靠传输的作用。** +- 它并没有展示报错文件的绝对路径,这将使我们很难定位到是哪个文件里的代码出现错误。 +- 如果能把具体报错的代码,给我们展示在终端屏幕上,就不需要我们再到源码文件中排查原因了。 -对于 DNS 域名解析,传输层协议是 UDP +如果使用了 ``pretty_errors`` 导致异常信息有丢失,那还不如不使用 +``pretty_errors`` 呢。 -而对于 HTTP 请求,传输层协议是 TCP +不过,可以告诉你的是,\ ``pretty_errors`` 并没有你想象的那么简单。 -**那这两种协议有什么区别呢?** +它足够开放,支持自定义配置,可以由你选择你需要展示哪些信息,怎么展示? -``TCP`` -协议提供可靠的通信传输,简单说就是确认目标能通信的情况下才会传输数据(因此需要三次握手),传输过程如果丢了数据,也会重发。而 -``UDP`` -协议则不然,不会确认目标能否通信,只会根据协议发到对方地址的端口。至于对方收不收到,丢不丢包,一概不管。 +这里举一个例子 -无论是哪种协议,其协议头信息都会包含本地端口号,和目标端口号(还有报文长度)。 +.. 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, + ) -代表协议有:IP,ICMP -协议等,其也叫IP层,主要应用是路由器(并非我们家中的路由器,扩展阅读:https://www.zhihu.com/question/52176116)。 + # 原来的代码 + def foo(): + 1/0 -**网络层负责将数据传输到目标地址。** + if __name__ == "__main__": + foo() -网络层将数据从发送端的主机发送到接收端的主机,两台主机间可能会存在很多数据链路,但网络层就是负责找出一条相对顺畅的通路将数据传递过去。传输的地址使用的是IP地址。 +在你像上面这样使用 ``pretty_errrs.configure`` +进行配置时,抛出的的异常信息就变成这样了。 -IP地址和我们的住址有点相似,我们的住址可以从省到市再到街逐步缩小范围,直至我们住址。IP地址也有这样的能力,通过不断转发到更近的IP地址,最终可以到达目标地址。如何选择这条路,就看网络层了。 +|image10| -这好比是快递公司的路线规划者。快递公司有很多集散中心,根据集散中心的情况(是否拥堵),找出一条经过n个集散中心的路径将货物(数据)沿路运过去。 +当然了,\ ``pretty_errors.configure()`` +还可以接收很多的参数,你可以根据你自己的需要进行配置。 -在这层里,会给数据包再加一些头信息,包括本地IP地址,目标IP地址(还有数据包的生存空间TTL),同时还会有一个Protocol字段 -表示上层到底是 -UDP还是TCP协议,这个是用于对端在接收到这个数据包后知道如何解析。 +5.1 设置颜色 +~~~~~~~~~~~~ -加上这些头信息后,再把\ **数据报**\ (或者说\ **分组**\ 、\ **报文**\ )传递给链路层。 +- ``header_color``\ :设置标题行的颜色。 +- ``timestamp_color``\ :设置时间戳颜色 +- ``default_color``\ :设置默认的颜色 +- ``filename_color``\ :设置文件名颜色 +- ``line_number_color``\ :设置行号颜色。 +- ``function_color``\ :设置函数颜色。 +- ``link_color``\ :设置链接的颜色。 -链路层 ------- +在设置颜色的时候,\ ``pretty_errors`` 提供了一些常用的 +颜色常量供你直接调取。 -代表协议有:HDLC,PPP,SLIP 等,其也叫 MAC 层,主要应用是交换机,网桥。 +- ``BLACK``\ :黑色 +- ``GREY``\ :灰色 +- ``RED``\ :红色 +- ``GREEN``\ :绿色 +- ``YELLOW``\ :黄色 +- ``BLUE``\ :蓝色 +- ``MAGENTA``\ :品红色 +- ``CYAN``\ :蓝绿色 +- ``WHITE``\ :白色 -**该层负责物理层面(一个以太网相连)上互连的节点之间的通信传输。** +而每一种颜色,都相应的匹配的 ``BRIGHT_`` 变体 和 ``_BACKGROUND`` 变体, -数据链路层会将0、1序列划分为具有意义的数据帧传送给对端(数据帧的生成与接收)。举个例子可能会更好理解,暂且把需要传输的数据看作为不同来源的水,如果直接倒入池子中时,是无法重新分辨出不同来源的水的。但如果将不同来源的灌入瓶子中并打上记号,那就能区分出不同来源的水。这也就是为什么要划分为具有意义的数据帧传送给对端。 +其中,\ ``_BACKGROUND`` 用于设置背景色,举个例子如下。 -数据链路层可以看作是快递公司的司机,他们驾驶着汽车,将打包好的货物(数据帧)从一个城市(物理节点)运输到另一个城市。 +|image11| -在这层里,会给数据包再加一些头信息,包括本地的mac地址,目标mac地址,同时还有一个type字段表示上层协议是使用的是IP协议,还是其他什么协议,也是用于对端在接收到这个数据包后知道如何解析。 +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``\ ,以防止出现明显的双换行符。 -链路层协议头会包含目标MAC和源MAC +- ``full_line_newline``\ :当输出的字符满行时,是否要插入换行符。 -**第二个问题**\ :大家都在发包,谁先发,谁后发? +- ``timestamp_function`` + 调用该函数以生成时间戳。默认值为\ ``time.perf_counter``\ 。 -对于这个问题,有多种算法可以解决 +- ``top_first`` 启用后,堆栈跟踪将反转,首先显示堆栈顶部。 -方法一:信道划分,就跟马路上分多个车道一样,你走你的,我走我的,互不影响 +- ``display_arrow`` + 启用后,将针对语法错误显示一个箭头,指向有问题的令牌。 -方法二:轮流协议,还是以交通举例,今天单号出行,明天双号出行,轮着来 +- ``truncate_code`` 启用后,每行代码将被截断以适合行长。 -方法三:随机接入协议,不管三七二十一,有事儿先出门,发现特堵,就回去。错过高峰再出。 +- ``stack_depth`` + 要显示的堆栈跟踪的最大条目数。什么时候\ ``0``\ 将显示整个堆栈,这是默认值。 -物理层 ------- +- ``exception_above`` 启用后,异常将显示在堆栈跟踪上方。 -代表协议有: ``RS 232C``\ 、\ ``RS 449/422/423``\ 、\ ``V.24`` 和 -``X.21``\ 、\ ``X.21bis`` 等。 +- ``exception_below``\ : 启用后,异常显示在堆栈跟踪下方。 -物理层负责0、1比特流(0、1序列)与电压高低、光的闪灭之间的互换。 +- ``reset_stdout`` + 启用后,重置转义序列将写入stdout和stderr;如果您的控制台留下错误的颜色,请启用此选项。 -物理层其实就是我们日常能接触的物理介质,比如光纤、电缆、还有空气(还有集线器、中继器、调制解调器),根据这些传输介质的不同,二进制流(0和1)会相应地转化成光信号,电信号,电磁波信号。 +- ``filename_display`` -物理层是 ``OSI`` 七层模型的物理基础,没有它就谈不上数据传输了。 + 设置文件名的展示方式,有三个选项: ``pretty_errors.FILENAME_COMPACT`` + 、\ ``pretty_errors.FILENAME_EXTENDED``\ ,或者\ ``pretty_errors.FILENAME_FULL`` -参考文章 --------- +以上,就是我对 ``pretty_errors`` +的使用体验,总的来说,这个库功能非常强大,使用效果也特别酷炫,它就跟 +PEP8 +规范一样,没有它是可以,但是有了它会更好一样。对于某些想自定义错误输出场景的人,\ ``pretty_errors`` +会是一个不错的解决方案,明哥把它推荐给你。 -- https://juejin.im/post/59eb06b1f265da430f313c7f +|image12| -.. |image0| image:: http://image.iswbm.com/20200526233356.png +.. |image0| image:: http://image.iswbm.com/20200602135014.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 new file mode 100644 index 0000000..ad4d53c --- /dev/null +++ b/source/c10/c10_03.md @@ -0,0 +1,187 @@ +# 10.3 少有人知的 Python "重试机制":tenacity + +![](http://image.iswbm.com/20200602135014.png) + +为了避免由于一些网络或等其他不可控因素,而引起的功能性问题。比如在发送请求时,会因为网络不稳定,往往会有请求超时的问题。 + +这种情况下,我们通常会在代码中加入重试的代码。重试的代码本身不难实现,但如何写得优雅、易用,是我们要考虑的问题。 + +这里要给大家介绍的是一个第三方库 - `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/c10/c10_03.rst b/source/c10/c10_03.rst new file mode 100644 index 0000000..64c04b8 --- /dev/null +++ b/source/c10/c10_03.rst @@ -0,0 +1,190 @@ +10.3 少有人知的 Python “重试机制”:tenacity +=========================================== + +|image0| + +为了避免由于一些网络或等其他不可控因素,而引起的功能性问题。比如在发送请求时,会因为网络不稳定,往往会有请求超时的问题。 + +这种情况下,我们通常会在代码中加入重试的代码。重试的代码本身不难实现,但如何写得优雅、易用,是我们要考虑的问题。 + +这里要给大家介绍的是一个第三方库 - ``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 + +|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/c10/c10_04.md similarity index 90% rename from source/c01/c01_34.md rename to source/c10/c10_04.md index 45a5b29..09e772d 100644 --- a/source/c01/c01_34.md +++ b/source/c10/c10_04.md @@ -1,4 +1,6 @@ -# 1.34 每日一库:sh,最优雅的命令调用方式 +# 10.4 Linux 上最优雅的命令调用方式:sh + +![](http://image.iswbm.com/20200602135014.png) 在编写 Python 脚本的时候,很经常需要我们去调用系统的命令,方法有很多种,比如 os.popen,os.system,commands,还有 subprocess。 @@ -12,7 +14,7 @@ $ python3 -m pip install sh 这里要注意一点,虽然在 Windows 上也可以安装成功,但是并不能使用,如果你尝试在 Windows 导入 它,会友好地提示你,该库只适用于 Linux 及 OSX 系统,假如你也想要在 Windows 使用,它推荐你使用它的 兄弟库 - `pbs` (https://pypi.org/project/pbs/)。 -![](http://image.python-online.cn/20200227201644.png) +![](http://image.iswbm.com/20200227201644.png) @@ -87,4 +89,4 @@ innodb: foreign key constraint system tables created -![关注公众号,获取最新干货!](http://image.python-online.cn/image-20200320125724880.png) \ No newline at end of file +![](http://image.iswbm.com/20200607174235.png) \ No newline at end of file diff --git a/source/c01/c01_34.rst b/source/c10/c10_04.rst similarity index 87% rename from source/c01/c01_34.rst rename to source/c10/c10_04.rst index d1ecce9..c521e0b 100644 --- a/source/c01/c01_34.rst +++ b/source/c10/c10_04.rst @@ -1,5 +1,7 @@ -1.34 每日一库:sh,最优雅的命令调用方式 -======================================= +10.4 Linux 上最优雅的命令调用方式:sh +===================================== + +|image0| 在编写 Python 脚本的时候,很经常需要我们去调用系统的命令,方法有很多种,比如 @@ -21,7 +23,7 @@ Python 3 为例。 Windows 使用,它推荐你使用它的 兄弟库 - ``pbs`` (https://pypi.org/project/pbs/)。 -|image0| +|image1| 安装完成后,就可以直接使用它了,以下几个示例,非常简单,简单到我感觉只要 demo ,而不需要任何的中文解释就可以让你知道他是如何使用的。 @@ -88,10 +90,9 @@ demo ,而不需要任何的中文解释就可以让你知道他是如何使用 innodb: foreign key constraint system tables created -.. figure:: http://image.python-online.cn/image-20200320125724880.png - :alt: 关注公众号,获取最新干货! - - 关注公众号,获取最新干货! +|image2| -.. |image0| image:: http://image.python-online.cn/20200227201644.png +.. |image0| image:: http://image.iswbm.com/20200602135014.png +.. |image1| image:: http://image.iswbm.com/20200227201644.png +.. |image2| 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 index 93de56a..202c6bf --- a/source/chapters/p01.rst +++ b/source/chapters/p01.rst @@ -26,4 +26,4 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png diff --git a/source/chapters/p02.rst b/source/chapters/p02.rst old mode 100755 new mode 100644 index f0eeafa..6e94a2e --- a/source/chapters/p02.rst +++ b/source/chapters/p02.rst @@ -18,4 +18,4 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png diff --git a/source/chapters/p03.rst b/source/chapters/p03.rst old mode 100755 new mode 100644 index 054b6b7..bde8b30 --- a/source/chapters/p03.rst +++ b/source/chapters/p03.rst @@ -18,4 +18,4 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png diff --git a/source/chapters/p04.rst b/source/chapters/p04.rst old mode 100755 new mode 100644 index e366137..6141a9d --- a/source/chapters/p04.rst +++ b/source/chapters/p04.rst @@ -16,4 +16,4 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png diff --git a/source/chapters/p05.rst b/source/chapters/p05.rst old mode 100755 new mode 100644 index 1a47f88..dbea992 --- a/source/chapters/p05.rst +++ b/source/chapters/p05.rst @@ -28,4 +28,4 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png diff --git a/source/chapters/p06.rst b/source/chapters/p06.rst old mode 100755 new mode 100644 index 5d015f8..c398e34 --- a/source/chapters/p06.rst +++ b/source/chapters/p06.rst @@ -17,4 +17,4 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png diff --git a/source/chapters/p07.rst b/source/chapters/p07.rst old mode 100755 new mode 100644 index 2ae6b0b..4e7675b --- a/source/chapters/p07.rst +++ b/source/chapters/p07.rst @@ -16,4 +16,4 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png diff --git a/source/chapters/p08.rst b/source/chapters/p08.rst old mode 100755 new mode 100644 index 9189ce8..0f25823 --- a/source/chapters/p08.rst +++ b/source/chapters/p08.rst @@ -16,4 +16,4 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png diff --git a/source/chapters/p09.rst b/source/chapters/p09.rst index 4571ac5..5161c9b 100644 --- a/source/chapters/p09.rst +++ b/source/chapters/p09.rst @@ -18,4 +18,4 @@ Python 是一门可以快速开发的语言,使用它可以用最少的时间 -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png diff --git a/source/chapters/p10.rst b/source/chapters/p10.rst old mode 100755 new mode 100644 index 9f0594a..51f6755 --- a/source/chapters/p10.rst +++ b/source/chapters/p10.rst @@ -1,12 +1,8 @@ ============================= -第十章:网络基础 +第十章:好库推荐 ============================= -在找工作面试的过程中,面试官非常喜欢考察基础知识,除了数据结构与算法之外,网络知识也是一个非常重要的考察对象。 - -而网络知识,通常是很抽象,不容易理解的,有很多同学就在这里裁了跟头。 - -正好以前没在这里分享过有关网络的内容,所以打算重新梳理有关网络的一些知识,这些内容在大家面试的时候可能能用得上。 +推荐那些冷门却非常实用的模块。 本章节,会持续更新,敬请关注… @@ -20,4 +16,4 @@ -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png diff --git a/source/conf.py b/source/conf.py old mode 100755 new mode 100644 index 1b0c693..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,6 +127,25 @@ sys.path.append(os.path.abspath(_exts)) html_js_files = [ - 'js/readmore.js', +# 'js/readmore.js', 'js/baidutongji.js', ] + + +author = '王炳明' +copyright = '2020-2024, Python编程时光' +exclude_patterns = ['_build'] +master_doc = 'index' +project = 'Python编程时光' + +# Options for extensions. +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 index 0071da4..15e3000 --- a/source/index.rst +++ b/source/index.rst @@ -22,4 +22,4 @@ Contents: -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png 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 8ffbf73..3f37edf --- a/source/preface.rst +++ b/source/preface.rst @@ -7,8 +7,6 @@ ---------------------------------- 这个博客于2018年6月29日发布完成,使用的是 Sphinx 来生成文档,使用 Github 托管文档,并使用 Read the Doc 发布文档。 -具体搭建教程请查阅 博客构建教程_ - ---------------------------------- 作者的话 ---------------------------------- @@ -31,6 +29,4 @@ ------------------------------ -.. figure:: http://image.python-online.cn/image-20200320125724880.png - -.. _博客构建教程: http://python-online.cn/zh_CN/latest/c04/c04_03.html +.. figure:: http://image.iswbm.com/20200607174235.png diff --git a/source/py_mp_index.md b/source/py_mp_index.md deleted file mode 100644 index 62880d9..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.python-online.cn/image-20200320125724880.png) \ No newline at end of file diff --git a/source/roadmap.rst b/source/roadmap.rst old mode 100755 new mode 100644 index a2f790b..fe0749d --- a/source/roadmap.rst +++ b/source/roadmap.rst @@ -12,4 +12,4 @@ Roadmap -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png 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 d9b6b7a..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: 添加明哥好友 @@ -22,7 +22,9 @@ 2019.7.4 @魏朝阳:提出在《并发编程》中三处笔误,现已更正! +.. disqus :: + :disqus_identifier: thanks -------------- -.. figure:: http://image.python-online.cn/image-20200320125724880.png +.. figure:: http://image.iswbm.com/20200607174235.png