[译]python代码入口点

842 阅读2分钟
原文链接: zhuanlan.zhihu.com
原文链接:
amir.rachum.com/blog/2017/0…
(随心所欲翻译.)

这篇文章将会聊聊python代码中的入口点.很多人可能知道,入口点一般会放在setup.py 文件中,经过打包后可以作为命令行使用,不过很少有人会去用它.接下来,我将会介绍如何使用入口点生成python包,之后就可以让别人在代码中或者命令行下使用.

安装过python包的都知道使用pip install <somepackages> , setup.py就是定义了你自己开发的包的相关信息,然后可以打包上传到python的包管理中心供别人下载.而入口点entry_points可以让包作为命令行工具使用.

Snek,Inc.

恭喜你!你刚刚被任命为"snek半导体和软件公司"的CEO,作为CEO,你的第一个工作是让员工开发出snek的原型系统,很快,工程师就做出来了,它是一个python脚本,snek.py:

  ascii_snek = """\
      --..,_                     _,.--.
         `'.'.                .'`__ o  `;__.
            '.'.            .'.'`  '---'`  `
              '.`'--....--'`.'
                `'--....--'`
  """
  ​
  def main():
      print(ascii_snek)
      
  if __name__ == '__main__':
      main()

在公司产品发布的那一天,你演示了这个原型系统,运行很顺利:

  $ python snek.py
      --..,_                     _,.--.
         `'.'.                .'`__ o  `;__.
            '.'.            .'.'`  '---'`  `
              '.`'--....--'`.'
                `'--....--'`

SaaS--Snek as a Service

不幸的是,用户不会使用python,他们只想在电脑的命名行下直接使用Snek的服务,于是工程师们加班加点作出了下一个版本,让snek在安装时自动生成控制台脚本,这样就可以在命令行下运行了.他们是怎么做的?只需要在setup.py 脚本中包含包名,依赖等信息,像下面这样:

下面的代码就是指定了应该从snek脚本的main函数开始执行.
  from setuptools import setup
  ​
  setup(
      name='snek',
      entry_points={
          'console_scripts': [
              'snek = snek:main',
          ],
      }
  )

工程师说,控制台脚本就是特殊的入口点,setuptools 在你的包被别人安装时,读取其中的内容并生成某些脚本文件.现在,让我们从源代码中安装(当然你可以发布为python库,让别人使用pip下载安装):

  $ python setup.py develop
  ​
  running develop
  running egg_info
  writing snek.egg-info\PKG-INFO
  writing dependency_links to snek.egg-info\dependency_links.txt
  writing entry points to snek.egg-info\entry_points.txt
  writing top-level names to snek.egg-info\top_level.txt
  reading manifest file 'snek.egg-info\SOURCES.txt'
  writing manifest file 'snek.egg-info\SOURCES.txt'
  running build_ext
  Creating c:\program files (x86)\py36-32\lib\site-packages\snek.egg-link (link to .)
  snek 0.0.0 is already the active version in easy-install.pth
  Installing snek-script.py script to C:\Program Files (x86)\Py36-32\Scripts
  Installing snek.exe script to C:\Program Files (x86)\Py36-32\Scripts
  Installing snek.exe.manifest script to C:\Program Files (x86)\Py36-32\Scripts
  ​
  Installed c:\users\rachum\notebooks
  Processing dependencies for snek==0.0.0
  Finished processing dependencies for snek==0.0.0

在公司的年度产品会上,你展示了这个amazing的产品:

  $ snek
      --..,_                     _,.--.
         `'.'.                .'`__ o  `;__.
            '.'.            .'.'`  '---'`  `
              '.`'--....--'`.'
                `'--....--'`

Snek for Everyone

没有人不喜欢Snek,公司的IPO超过了60亿美元.有些高级用户希望为他们量身定制高级的版本.工程师又加班加点做了出来:

  """Print an ASCII Snek.
  ​
  Usage:
      snek [--type=TYPE]
      
  """
  import docopt
  ​
  normal_snek = """\
      --..,_                     _,.--.
         `'.'.                .'`__ o  `;__.
            '.'.            .'.'`  '---'`  `
              '.`'--....--'`.'
                `'--....--'`
  """
  ​
  fancy_snek = """\
                            _,..,,,_
                       '``````^~"-,_`"-,_
         .-~c~-.                    `~:. ^-.
     `~~~-.c    ;                      `:.  `-,     _.-~~^^~:.
           `.   ;      _,--~~~~-._       `:.   ~. .~          `.
            .` ;'   .:`           `:       `:.   `    _.:-,.    `.
          .' .:   :'    _.-~^~-.    `.       `..'   .:      `.    '
         :  .' _:'   .-'        `.    :.     .:   .'`.        :    ;
         :  `-'   .:'             `.    `^~~^`   .:.  `.      ;    ;
          `-.__,-~                  ~-.        ,' ':    '.__.`    :'
                                       ~--..--'     ':.         .:'
                                                       ':..___.:'
  """
  ​
  def get_sneks():
      return {
          'normal': normal_snek,
          'fancy': fancy_snek,
      }
  ​
  ​
  def main():
      args = docopt.docopt(__doc__)
      snek_type = args['--type'] or 'normal'
      print(get_sneks()[snek_type])
      
  if __name__ == '__main__':
      main()

他们加了一个豪华版的Snek,这让高级用户特别开心.

  $ snek
      --..,_                     _,.--.
         `'.'.                .'`__ o  `;__.
            '.'.            .'.'`  '---'`  `
              '.`'--....--'`.'
                `'--....--'`
  ​
  $ snek --type fancy
                            _,..,,,_
                       '``````^~"-,_`"-,_
         .-~c~-.                    `~:. ^-.
     `~~~-.c    ;                      `:.  `-,     _.-~~^^~:.
           `.   ;      _,--~~~~-._       `:.   ~. .~          `.
            .` ;'   .:`           `:       `:.   `    _.:-,.    `.
          .' .:   :'    _.-~^~-.    `.       `..'   .:      `.    '
         :  .' _:'   .-'        `.    :.     .:   .'`.        :    ;
         :  `-'   .:'             `.    `^~~^`   .:.  `.      ;    ;
          `-.__,-~                  ~-.        ,' ':    '.__.`    :'
                                       ~--..--'     ':.         .:'
                                                       ':..___.:'

Snek International Community

全球数百万人都在使用snek,人们对snek不同版本的需求越来越高,而且一些snek的专业用户(程序员)要求snek可以定制,他们想开发自己的snek版本.

  """Print an ASCII Snek.
  ​
  Usage:
      snek [--type=TYPE]
      
  """
  import docopt
  import pkg_resources
  ​
  normal_snek = """\
      --..,_                     _,.--.
         `'.'.                .'`__ o  `;__.
            '.'.            .'.'`  '---'`  `
              '.`'--....--'`.'
                `'--....--'`
  """
  ​
  fancy_snek = """\
                            _,..,,,_
                       '``````^~"-,_`"-,_
         .-~c~-.                    `~:. ^-.
     `~~~-.c    ;                      `:.  `-,     _.-~~^^~:.
           `.   ;      _,--~~~~-._       `:.   ~. .~          `.
            .` ;'   .:`           `:       `:.   `    _.:-,.    `.
          .' .:   :'    _.-~^~-.    `.       `..'   .:      `.    '
         :  .' _:'   .-'        `.    :.     .:   .'`.        :    ;
         :  `-'   .:'             `.    `^~~^`   .:.  `.      ;    ;
          `-.__,-~                  ~-.        ,' ':    '.__.`    :'
                                       ~--..--'     ':.         .:'
                                                       ':..___.:'
  """
  ​
  def get_sneks():
      sneks = {
          'normal': normal_snek,
          'fancy': fancy_snek,
      }
      for entry_point in pkg_resources.iter_entry_points('snek_types'):
          sneks[entry_point.name] = entry_point.load()
      return sneks
  ​
  ​
  def main():
      args = docopt.docopt(__doc__)
      snek_type = args['--type'] or 'normal'
      print(get_sneks()[snek_type])
      
  if __name__ == '__main__':
      main()

他们增加了snek的基础架构,当snek运行时,使用被称作snek_types 的入口点注册其他类型的snek,这样就能动态的在控制台输出不同的snek版本.

具体来说,get_sneks 中的pkg_resources.iter_entry_points('snek_types') 遍历所有注册到入口点的snek名,然后加入snek版本中.于是,开发者们加入了一个可爱版本的snek,命名为cute_snek.py

  cute_snek = r"""
                      /^\/^\
                    _|__|  O|
           \/     /~     \_/ \
            \____|__________/  \
                   \_______      \
                           `\     \                 \
                             |     |                  \
                            /      /                    \
                           /     /                       \
                         /      /                         \ \
                        /     /                            \  \
                      /     /             _----_            \   \
                     /     /           _-~      ~-_         |   |
                    (      (        _-~    _--_    ~-_     _/   |
                     \      ~-____-~    _-~    ~-_    ~-_-~    /
                       ~-_           _-~          ~-_       _-~ 
                          ~--______-~                ~-___-~
  """

如何找到这个可爱版本的snek呢?像下面这样定义setup.py

代码中指定了从cute_snek.py脚本寻找cute_snek.
  from setuptools import setup
  ​
  setup(
      name='cute_snek',
      entry_points={
          'snek_types': [
              'cute = cute_snek:cute_snek',
          ],
      }
  )

然后像之前一样打包:

  $ cd cute_snek && python setup.py develop
  running develop
  running egg_info
  writing cute_snek.egg-info\PKG-INFO
  writing dependency_links to cute_snek.egg-info\dependency_links.txt
  writing entry points to cute_snek.egg-info\entry_points.txt
  writing top-level names to cute_snek.egg-info\top_level.txt
  reading manifest file 'cute_snek.egg-info\SOURCES.txt'
  writing manifest file 'cute_snek.egg-info\SOURCES.txt'
  running build_ext
  Creating c:\program files (x86)\py36-32\lib\site-packages\cute-snek.egg-link (link to .)
  cute-snek 0.0.0 is already the active version in easy-install.pth
  ​
  Installed c:\users\rachum\cute_snek
  Processing dependencies for cute-snek==0.0.0
  Finished processing dependencies for cute-snek==0.0.0

现在我们就能在命令行下成功的运行这个可爱版本的snek,这是从cute_snek中动态加载的:

  $ snek --type cute
                      /^\/^\
                    _|__|  O|
           \/     /~     \_/ \
            \____|__________/  \
                   \_______      \
                           `\     \                 \
                             |     |                  \
                            /      /                    \
                           /     /                       \
                         /      /                         \ \
                        /     /                            \  \
                      /     /             _----_            \   \
                     /     /           _-~      ~-_         |   |
                    (      (        _-~    _--_    ~-_     _/   |
                     \      ~-____-~    _-~    ~-_    ~-_-~    /
                       ~-_           _-~          ~-_       _-~ 
                          ~--______-~                ~-___-~
  ​

既然cute_snek可以动态的加载,那么所有类型的snek应该都可以动态加载,于是工程师们又修改了代码:

所有的snek都从snek_types中加载.
  from setuptools import setup
  ​
  setup(
      name='cute_snek',
      entry_points={
          'snek_types': [
              'cute = cute_snek:cute_snek',
              'normal = snek:normal_snek',
              'fancy = snek:fancy_snek',
          ],
      }
  )
  ​
  # 同时修改之前文件中的get_sneks()函数:
  def get_sneks():
      #sneks = {
      #   'normal': normal_snek,
      #    'fancy': fancy_snek,
      #}
      # 将snek修改为空字典
      snek = {}
      for entry_point in pkg_resources.iter_entry_points('snek_types'):
          sneks[entry_point.name] = entry_point.load()
      return sneks

现在重新打包snek:

  $ python setup.py develop
  running develop
  running egg_info
  writing snek.egg-info\PKG-INFO
  writing dependency_links to snek.egg-info\dependency_links.txt
  writing entry points to snek.egg-info\entry_points.txt
  writing top-level names to snek.egg-info\top_level.txt
  reading manifest file 'snek.egg-info\SOURCES.txt'
  writing manifest file 'snek.egg-info\SOURCES.txt'
  running build_ext
  Creating c:\program files (x86)\py36-32\lib\site-packages\snek.egg-link (link to .)
  snek 0.0.0 is already the active version in easy-install.pth
  Installing snek-script.py script to C:\Program Files (x86)\Py36-32\Scripts
  Installing snek.exe script to C:\Program Files (x86)\Py36-32\Scripts
  Installing snek.exe.manifest script to C:\Program Files (x86)\Py36-32\Scripts
  ​
  Installed c:\users\rachum\notebooks
  Processing dependencies for snek==0.0.0
  Finished processing dependencies for snek==0.0.0

大功告成!!!

  $ snek
      --..,_                     _,.--.
         `'.'.                .'`__ o  `;__.
            '.'.            .'.'`  '---'`  `
              '.`'--....--'`.'
                `'--....--'`
  ​
  $ snek --type fancy
                            _,..,,,_
                       '``````^~"-,_`"-,_
         .-~c~-.                    `~:. ^-.
     `~~~-.c    ;                      `:.  `-,     _.-~~^^~:.
           `.   ;      _,--~~~~-._       `:.   ~. .~          `.
            .` ;'   .:`           `:       `:.   `    _.:-,.    `.
          .' .:   :'    _.-~^~-.    `.       `..'   .:      `.    '
         :  .' _:'   .-'        `.    :.     .:   .'`.        :    ;
         :  `-'   .:'             `.    `^~~^`   .:.  `.      ;    ;
          `-.__,-~                  ~-.        ,' ':    '.__.`    :'
                                       ~--..--'     ':.         .:'
                                                       ':..___.:'
  ​
  $ snek --type cute
                      /^\/^\
                    _|__|  O|
           \/     /~     \_/ \
            \____|__________/  \
                   \_______      \
                           `\     \                 \
                             |     |                  \
                            /      /                    \
                           /     /                       \
                         /      /                         \ \
                        /     /                            \  \
                      /     /             _----_            \   \
                     /     /           _-~      ~-_         |   |
                    (      (        _-~    _--_    ~-_     _/   |
                     \      ~-____-~    _-~    ~-_    ~-_-~    /
                       ~-_           _-~          ~-_       _-~ 
                          ~--______-~                ~-___-~

到此为止,你应该大概知道了如何使用python中的入口点了吧.