怎样才算学会Python?

你可知道Python其实只是一个语言标准,它的实现程序不止一个,其中官方的实现是CPython,还有
首页 新闻资讯 行业资讯 怎样才算学会Python?

[[237230]]
Python inside the door

Python 实践基础

起源

假如你已经有了编程基础,那么学习一门新语言的困难点绝对不在语法、语义和风格等代码层面上的,而在于语言范式(OO,FP还是Logic),语言的生态(如:依赖管理和包发布等)和工具(编辑器,编译器或者解释器)这些方面,请参看如何高效地学习编程语言。再假如你已经对各类语言范式都有一定的了解,那么***的困难之处就是…细节,它是魔鬼。

我相信真正拥抱一门新语言,花在工具和语言生态上的时间一定很多。庞大的社区利用群体智慧构筑的生态圈充满了各种零碎的知识点,这些知识点可能是前人趟过的陷阱(Common   Gotchas),基于局部共识经由经典项目实践过之后的约定(Convention)和惯用法(Idioms),也可能是总结出的概念模式(Pattern),甚至是审美(Aesthetic)和禅(Zen)或道(Dao)。这些知识点作用到了工具和语言生态之中,意味着你需要使用合适工具、遵循生态的玩法才能从中受益。

工具

工欲善其事必先利其器,对于程序员而言,这个器是编辑器…吗?Emacs, Vim, VS Code or PyCharm?

解释器

当然不是,这个器应当是让你能立马运行程序并立刻看到结果的工具,在Python的上下文中,它是Python的解释器。一般情况下,我们会选择***版的解释器或者编译器,但是Python有一点点例外,因为Python3和2并不兼容,那么该选择哪个版本呢?寻找这类问题的答案其实就是融入Python社区的过程。幸运的是,社区出版了一本书   The Hitchhiker’s Guide to Python,里面诚恳地给出了建议。所以不出意外,Python3是比较合适的选择。

因为Python安装起来很简单,我们跳过…吧?不过,大侠留步,你可知道Python其实只是一个语言标准,它的实现程序不止一个,其中官方的实现是CPython,还有Jython和IronPython等。不过,CPython作为使用最为广泛的解释器当然是开发之***。

复制

$ python3 Python 3.6.5 (default, Jun 17 2018, 12:13:06) [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> print("hello world") hello world
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

编辑器

虽然面向REPL编程(Repl-Oriented  Programming)是一种比单元测试的反馈速度更快的编程方式,但是在REPL中编写应用程序并不合适,不合适的地方表现在代码不易组织(分模块)和代码没法记录(存盘)。所以我们需要可以编辑的源代码、目录和其它相关文件,这个时候就需要挑选趁手的编辑器。

神之编辑器Emacs中内置了python-mode,如果已经是Emacs用户,这款编辑器当是写Python的不二之选。编辑器之神的Vim排第二,如果你比较喜欢折腾Vim8.0的插件,或者想自己构建NeoVim的话。其它的编辑器,我不知道,不想用。不过PyCharm是Jetbrains家的IDE,靠谱。

有功夫在Terminal中装一个emacsclient,然后下载一个oh-my-zsh的插件emacsclient,就可以很愉悦地在Terminal中使用Emacs编辑文件了。

复制

$ te hello_world.py # te: aliased to /Users/qianyan/.oh-my-zsh/plugins/emacs/emacsclient.sh -nw """ hello_world.py Ctrl+x+c 退出emacs :) """ print("hello world") $ python3 hello_world.py hello world $ python3 -m hello_world #注意没有.py的后缀 hello world
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

生态

基本工具比较好把握,但是何时选择什么工具做什么样的事情就不好拿捏了,而且如何把事情做成Pythonic的模样也是对经验和能力的考验。

如果我们不是编程小白的话,就需要充分利用迁移学习的能力了。学习的***方法就是解决问题。不得不承认,在动手实践的过程,时间走得是最快的,在同一件事上花的时间越多也就越熟悉。

我们尝试用Python编写一个tree命令行(Command-Line  Application),顾名思义,打印目录层级结构的程序,详细描述参看这篇命令行中 tree 的多重实现。

测试模块

怎么写测试呢?多年养成的TDD习惯让我首先想要了解什么是Python中常用的测试工具。答案不难寻找,unittest是Python内置的测试模块,而pytest是比unittest更简洁和强大的选择,所以我选择后者。

这个程序的测试我使用pytest,但是它并不是所有项目测试的唯一选择,所以***能局部安装,尤其是限制在当前工程目录里。搜索查找的结果是,Python3内置的虚拟环境(Virtual  Environment)模块可以做到这点。

虚拟环境

在当前创建venv目录(python3 -m venv venv),然后用tree命令查看该目录的结构。

复制

$ python3 -m venv venv $ tree -L 4 venv venv ├── bin │   ├── activate │   ├── activate.csh │   ├── activate.fish │   ├── easy_install │   ├── easy_install-3.6 │   ├── pip │   ├── pip3 │   ├── pip3.6 │   ├── python -> python3 │   └── python3 -> /usr/local/bin/python3 ├── include ├── lib │   └── python3.6 │       └── site-packages │           ├── __pycache__ │           ├── easy_install.py │           ├── pip │           ├── pip-9.0.3.dist-info │           ├── pkg_resources │           ├── setuptools │           └── setuptools-39.0.1.dist-info └── pyvenv.cfg
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

进入虚拟环境,然后使用pip3安装pytest测试模块,会发现venv目录多了些东西。

复制

$  . venv/bin/activate venv ❯ pip3 install pytest Collecting pytest ... $ tree -L 4 venv venv ├── bin │   ├── py.test │   ├── pytest ├── include ├── lib │   └── python3.6 │       └── site-packages │           ├── __pycache__ │           ├── _pytest │           ├── atomicwrites-1.1.5.dist-info │           ├── attr │           ├── attrs-18.1.0.dist-info │           ├── more_itertools │           ├── more_itertools-4.2.0.dist-info │           ├── pluggy │           ├── pluggy-0.6.0.dist-info │           ├── py │           ├── py-1.5.3.dist-info │           ├── pytest-3.6.2.dist-info │           ├── pytest.py │           ├── six-1.11.0.dist-info │           └── six.py
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

此时,虚拟环境会在PATH变量中前置./bin目录,所以可以直接使用pytest命令进行测试。根据约定,测试文件的名称必须以test_开头,如test_pytree.py,测试方法也必须如此,如test_fix_me。遵循约定编写一个注定失败的测试如下:

复制

""" test_pytree.py """ def test_fix_me():     assert 1 == 0 $ pytest ...     def test_fix_me(): >       assert 1 == 0 E       assert 1 == 0 test_pytree.py:5: AssertionError
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

测试失败了,说明测试工具的打开方式是正确的。在进入测试、实现和重构(红-绿-黄)的心流状态之前,我们需要考虑测试和实现代码该放在哪里比较合适。

假设我们会把pytree作为应用程序分发出去供别人下载使用,那么标准的目录结构和构建脚本是必不可少的,Python自然有自己的一套解决方案。

目录结构

在Packaging Python Projects的指导下,我们略作调整,创建和源代码平级的测试目录(tests),得到的完整目录如下:

复制

. ├── CHANGES ├── LICENSE ├── README.md ├── docs ├── pytree │   ├── __init__.py │   ├── __version__.py │   ├── cli.py │   └── core.py ├── setup.cfg ├── setup.py ├── tests │   ├── fixtures │   └── test_pytree.py └── venv
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

这样的目录结构不仅可以清晰地模块化,隔离测试和实现,提供使用指导和版本更新记录,还可以很方便地做到包依赖管理和分发,这得归功于setup.py,它是Python项目中事实标准(de  facto standard)上的依赖和构建脚本,pytree下的setup.py内容如下:

复制

# setup.py # -*- coding: utf-8 -*- from setuptools import setup, find_packages from codecs import open import os here = os.path.abspath(os.path.dirname(__file__)) about = {} with open(os.path.join(here, 'pytree', '__version__.py'), 'r', 'utf-8') as f:     exec(f.read(), about)      with open('README.md') as f:     readme = f.read() with open('LICENSE') as f:     license = f.read() setup(     name='pytree',     version=about['__version__'],     description='list contents of directories in a tree-like format.',     long_description=readme,     author='Yan Qian',     author_email='qianyan.lambda@gmail.com',     url='https://github.com/qianyan/pytree',     license=license,     packages=find_packages(exclude=('tests', 'docs')),     classifiers=(         "Programming Language :: Python :: 3",         "License :: OSI Approved :: MIT License",         "Operating System :: OS Independent",     ),     setup_requires=['pytest-runner'],     tests_require=['pytest'],     entry_points = {         'console_scripts': [             'pytree = pytree.cli:main'         ]     },     install_requires=[] )
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

  • 16.

  • 17.

  • 18.

  • 19.

  • 20.

  • 21.

  • 22.

  • 23.

  • 24.

  • 25.

  • 26.

  • 27.

  • 28.

  • 29.

  • 30.

  • 31.

  • 32.

  • 33.

  • 34.

  • 35.

  • 36.

  • 37.

  • 38.

setup.py能帮助我们解决测试中依赖模块的问题,这样我们把pytree作为一个package引入到测试代码中。

复制

venv ❯ python3 Python 3.6.5 (default, Jun 17 2018, 12:13:06) [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import sys, pprint >>> pprint.pprint(sys.path) ['',  '/usr/local/Cellar/python/3.6.5_1/Frameworks/Python.framework/Versions/3.6/lib/python36.zip',  '/usr/local/Cellar/python/3.6.5_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6',  '/usr/local/Cellar/python/3.6.5_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/lib-dynload',  '/Users/qianyan/Projects/personal/public/pytree/venv/lib/python3.6/site-packages',  '/Users/qianyan/Projects/personal/public/pytree/venv/lib/python3.6/site-packages/docopt-0.6.2-py3.6.egg',  '/Users/qianyan/Projects/personal/public/pytree']
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

然后运行pytest或者python3 setup.py  pytest,此时pytest会把.pytree/tests前置到PATH变量中,验证如下:

复制

# test_pytree.py import sys def test_path():     assert sys.path == '' venv ❯ pytest -> AssertionError: assert ['/Users/qianyan/Projects/personal/public/pytree/tests',  '/Users/qianyan/Projects/personal/public/pytree/venv/bin', ...] == '' venv ❯ python3 setup.py pytest -> AssertionError: assert ['/Users/qianyan/Projects/personal/public/pytree/tests',  '/Users/qianyan/Projects/personal/public/pytree', ...] == ''
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

这里python3 setup.py pytest可以通过setup.cfg设置别名(alias):

复制

# setup.cfg [aliases] test=pytest
  • 1.

  • 2.

  • 3.

python3 setup.py test的效果和前面的命令等同。

使用TDD的方式实现了pytree核心的功能(源代码),然后考虑如何把它变成真正的命令行程序。首先要解决的问题是如何以用户友好的方式显示需要哪些传入参数,我们期待pytree  -h能提供一些帮助信息,为了不重复造轮子,挑选现成的Option解析库比较轻松。Python内置的argparse已经足够用了,不过docopt值得尝试。

依赖管理

setup.py提供了依赖管理功能,声明依赖及其版本号。

复制

# setup.py ... install_requires=[docopt==0.6.2]
  • 1.

  • 2.

  • 3.

然后运行python3 setup.py develop安装。就绪之后,编写cli.py作为命令行程序的入口。

复制

#!/usr/bin env python3 """list contents of directories in a tree-like format.   Usage:      pytree <dir>     pytree -h | --help | --version """ import pytree.core as pytree import pytree.__version__ as version def main():     from docopt import docopt     arguments = docopt(__doc__, version=version.__version__)     dir_name = arguments['<dir>']      print('\n'.join(pytree.render_tree(pytree.tree_format('', dir_name)))) if __name__ == "__main__":     main()
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

  • 9.

  • 10.

  • 11.

  • 12.

  • 13.

  • 14.

  • 15.

通过打印help信息的方式验证是否符合预期:

复制

$ python3 pytree/cli.py --help list contents of directories in a tree-like format.   Usage:     pytree <dir>     pytree -h | --help | --version
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

当然理想的结果是直接可以运行pytree --help,setup.py的console_scripts刚好派上用场。

复制

# setup.py     entry_points = {         'console_scripts': [             'pytree = pytree.cli:main' #以pytree作为命令行程序的调用名         ]     }
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

此时查看which  pytree显示/Users/qianyan/Projects/personal/public/pytree/venv/bin/pytree,说明pytree已经在路径变量当中,可以直接执行:

复制

$ pytree tests/fixtures tests/fixtures └── child
  • 1.

  • 2.

  • 3.

完成了命令行程序并通过测试,我们尝试发布到测试仓库(TestPyPI)供其他人下载使用。

包发布

依照文档描述,先去TestPyPI注册用户,本地打包成发行版,然后安装twine工具发布。

复制

$ python3 -m pip install --upgrade setuptools wheel $ python3 setup.py sdist bdist_wheel $ pytree dist # pytree查看dist目录 dist ├── pytree-1.0.2-py3-none-any.whl └── pytree-1.0.2.tar.gz $ python3 -m pip install --upgrade twine $ twine upload --repository-url https://test.pypi.org/legacy/ dist/* #or twine upload --repository testpypi dist/* 如果你配置了~/.pypirc
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

上传成功需要一段时间,等待服务完成同步才可以下载,我们在另一个虚拟环境中进行验证:

复制

$ python3 -m venv test $ . test/bin/activate test > python3 -m pip install --index-url https://test.pypi.org/simple/ pytree==1.0.2 test > ls venv/lib/python3.6/site-packages/ ... pytree pytree-1.0.2.dist-info ...
  • 1.

  • 2.

  • 3.

  • 4.

  • 5.

  • 6.

  • 7.

  • 8.

确保site-packages目录下有这两个目录:pytree和pytree-1.0.2.dist-info,然后我们就可以完成***的验证阶段了,如下:

复制

test > pytree tests/fixtures tests/fixtures └── child
  • 1.

  • 2.

  • 3.

这里版本号之所以是1.0.2,是因为已经上传过了0.0.1,  1.0.0, 1.0.1  等版本,TestPyPI不允许同名同版本的文件重复上传,即使删除原来的文件也不行。前面的版本都有一定的错误,错误的根因在于find_packages以及package_dir的配置项文档说明很模糊,而且只有到上传到TestPyPI然后下载下来,才能验证出来,这种缓慢的反馈是Python的应该诟病的地方。

注意

find_package()也是一个深坑,***个参数如果写成find_packages('pytree',  exclude=...),那么pytree下的所有Python文件都会被忽略。原因是pytree已经是package,所以不应该让setup去这个目录找其他的packages.

这个package_dir也是如此,我们如果设置package_dir={'':  'pytree'},setup.py就会将/Users/qianyan/Projects/personal/public/pytree/pytree前置到PATH中,这会导致console_scripts':  ['pytree = pytree.cli:main']抛出错误 ModuleNotFoundError: no module named  ‘pytree’,究其原因是pytree/pytree导致setup尝试在pytree/pytree这个package里头找自己(pytree),自然找不到。但是如果改成console_scripts':  ['pytree = cli:main'],因为cli在pytree/pytree底下,所以就能成功执行。当然这是一种错误的写法。

如果遇到了 ModuleNotFoundError: no module named ‘pytree’ 的错误,***的方式就是import sys,  pprint然后pprint.pprint(sys.path),很容易发现Python运行时的执行路径,这有助于排查潜在的配置错误。

16    2018-07-19 19:04:10    Python 编程 语言