Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Python的module机制与最佳实践

233 views

Published on

Pycon China 2015

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Python的module机制与最佳实践

  1. 1. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! Python Module 引⼊入机 制与最佳实践 —刘畅
  2. 2. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! 演讲内容 1. 为什么会发⽣生ImportError? 2. 如何避免 sys.path.append 的hack⾏行为? 3. python -m 是怎么回事? 4. python项⺫⽬目的⺫⽬目录结构如何组织? 5. import 语句是如何实现的? 6. 如何定制化⾃自⼰己的import?
  3. 3. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! Python的包引⼊入机制 • 
 
 
 import string
 print string.a a = 2 pkg/
 pkg/__init__.py
 pkg/main.py pkg/string.py • 如果运⾏行 pkg/main.py ⽂文件的话,会发⽣生什么事情? • import的string 是标准库的string还是当前⺫⽬目录的string呢? • 如果明确的指明⾃自⼰己要引⼊入的module?
  4. 4. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! Python的包引⼊入机制 • 相对引⽤用 implicit/explicit relative import • 绝对引⽤用 absolute import • 推荐阅读:https://docs.python.org/2/whatsnew/ 2.5.html#pep-328-absolute-and-relative-imports
  5. 5. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! relative_import 代码⽰示例: from .foo import bar
 from ..foo import bar • 在 py2.7 之前是python的默认的module引⼊入⽅方式 • 推荐阅读:PEP0328
  6. 6. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! absolute Import 代码⽰示例: from __future__ import absolute_import from API.foo import bar from API.API2.foo import bar • 在py2.5实现,需要明确的声明从 __future__ 中引⼊入该机制。在py2.7和 py3中成为默认的引⼊入机制。
  7. 7. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! 相对引⽤用!出错!
  8. 8. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! 绝对引⽤用!出错!
  9. 9. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! 如何解决引⽤用⽤用出错的问题?
  10. 10. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! import sys sys.path.append(‘..’) 不推荐!
  11. 11. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! ⼩小知识:加载python的⼆二 种⽅方式 • as the top-level script
 python foo/myfile.py • as a module:
 python -m foo.myfile
  12. 12. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! ⼆二种加载⽅方式的不同之 处 When a file is loaded, it is given a name (which is stored in its __name__ attribute). If it was loaded as the top-level script, its name is __main__. If it was loaded as a module, its name is the filename, preceded by the names of any packages/ subpackages of which it is a part, separated by dots. • 推荐阅读:http://stackoverflow.com/questions/ 14132789/pythonrelative-imports-for-the-billionth- time
  13. 13. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! 为什么这个例⼦子出错?
  14. 14. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! 出错的原因 • 当使⽤用⽤用python subpackage1/foo.py时,foo.py 被当成了 top level script,它的__name__属性被设置为__main__。 • relative import则是根据__name__来确定被引⼊入⼊入包的位 置。 Relative imports use a module's __name__ attribute to determine that module's position in the package hierarchy. If the module’s name does not contain any package information (e.g. it is set to '__main__') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.
  15. 15. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! 解决办法 • 让运⾏行⾏行的⽂文⽂文件有完整的 __name__,也就是使⽤用⽤用 module 的 load⽅方⽅方式 • 推荐阅读:PEP 0366
  16. 16. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! ⼩小知识 • python在执⾏行⾏行程序时,会⾃自⾃自动的把CWD添加到 sys.path中。
  17. 17. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! 为什么这个例⼦子出错?
  18. 18. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! 出错的原因 • 例⼦子从subpackage1.bar中引⼊入hello函数。但是 sys.path中找不到subpackage1这个包。
  19. 19. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! 解决办法 • python -m subpackage1.foo2 • 将cwd修改为合适的⺫⽬目录 • 将subpackage1添加到 sys.path
  20. 20. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! 我们应该使⽤用哪种引⽤用⽅方式? pep8推荐absolute import 在特别复杂的包结构时, 也可以使⽤用明确的相对引⽤用 相对引⽤用(不带.号)在python3被移除
  21. 21. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! facebook/tornado 的代码⽰示例
  22. 22. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! Python项⺫⽬目的推荐⺫⽬目录结构
  23. 23. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! • 每个项⺫⽬目⼀一个⺫⽬目录(不是python package,没有 __init__.py) • 顶层⺫⽬目录下⾯面⼀一个同名(也可以不同名)的python package。 • ⽂文档⺫⽬目录在顶层⺫⽬目录下 • 各种脚本,配置⽂文件,描述⽂文件在顶层⺫⽬目录下(好处? 想⼀一想sys.path的进制)
  24. 24. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! import 实现 > from package import module as mymodule 1. 查找相应的module 2. 加载module到local namespace
  25. 25. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! 查找过程 1. 检查 sys.modules (保存了之前import的类库的缓存),如 果module被找到,则⾛走到第⼆二步。 2. 检查 sys.meta_path。meta_path 是⼀一个 list,⾥里⾯面保存着 ⼀一些  finder对象,如果找到该module的话,就会返回⼀一个 finder对象。 3.检查⼀一些隐式的finder对象,不同的python实现有不同的隐 式finder,但是都会有 sys.path_hooks, sys.path_importer_cache 以及 sys.path。 4.抛出 ImportError
  26. 26. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! finder 对象 • finder 对象⽤用来查找module的 loader对象 • finder 必须实现⼀一个叫作 find_module 的⽅方法,当 find_module成功,返回 loader,否则返回None • 可以 python -m modulefinder filename.py,打印出 finder的具体信息 • 推荐阅读 https://docs.python.org/2/library/ modulefinder.html 和 PEP302
  27. 27. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! loader对象 loader对象要实现load_module⽅方法 load_module(module)⽅方法会执⾏行: • 检查sys.module,如果有则返回,否则添加新 的。如果load失败,还要删除之前添加的module object • 设置__file__ __name__ __path__ __loader__ __package__
  28. 28. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! ⼀一个meta_path的例⼦子 import sys class Watcher(object): @classmethod def find_module(cls, name, path, target=None): print('Importing', name, path, target) return None sys.meta_path.insert(0, Watcher) import socket Importing socket None None Importing _socket None None Importing enum None None Importing collections None None Importing _collections None None Importing operator None None Importing _operator None None Importing keyword None None Importing heapq None None Importing itertools None None Importing _heapq None None Importing reprlib None None Importing types None None
  29. 29. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! sys.path • sys.path 由三个部分组成
  30. 30. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! sys.prefix python 的安装⺫⽬目录
  31. 31. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! PYTHONPATH 环境变量,类似于PATH
  32. 32. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! site.py • 定义了 site-package 的⺫⽬目录 • 可以python -S关闭site.py的加载
  33. 33. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! import hooks • __import__:内建函数 • imputil:py2.6开始被声明废弃,py3彻底移除 • importlib:py3中添加,同时添加到py2.7 • 推荐阅读:https://docs.python.org/3.5/library/ importlib.html
  34. 34. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! import hook⽤用法 __import__ 的⽤用法,不推荐: In [1]: spam = __import__('sys')
 In [2]: spam
 Out[2]: <module 'sys' (built-in)> importlib 的⽤用法,推荐: # Same as: import spam
 In [1]: spam = importlib.import_module(‘spam') 
 # Same as: from . import spam
 In [2]: spam = importlib.import_module('.spam', __package__)
  35. 35. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! Hook Example >>> def my_import(modname *args, imp=__import__): ... print('importing', modname) ... return imp(modname, *args) ... >>> import builtins >>> builtins.__import__ = my_import >>> >>> import socket importing socket importing _socket … builtins 只⽀支持py3,上⾯面的例⼦子要在py3下运⾏行
  36. 36. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! Hook Example2 # spam.py
 from importlib.util import find_spec
 if find_spec('foo'):
 import foo
 else:
 import simplefoo 更加直观的⽅方式import包,出错信息更丰富
  37. 37. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! Hook Example3 • Lazy Importer • https://pypi.python.org/pypi/Importing
  38. 38. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! Hook Example4 • ⾃自定义⼀一个python package proxy • flask 的插件机制,当引⼊入flask.ext.api等包时⾃自动 import相应的包。
  39. 39. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! flask 实现 def load_module(self, fullname): if fullname in sys.modules: return sys.modules[fullname] modname = fullname.split('.', self.prefix_cutoff)[self.prefix_cutoff] for path in self.module_choices: realname = path % modname try: __import__(realname) except ImportError: exc_type, exc_value, tb = sys.exc_info() sys.modules.pop(fullname, None) if self.is_important_traceback(realname, tb): reraise(exc_type, exc_value, tb.tb_next) continue module = sys.modules[fullname] = sys.modules[realname] if '.' not in modname: setattr(sys.modules[self.wrapper_module], modname, module) return module raise ImportError('No module named %s' % fullname)
  40. 40. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! Hook Example5 >>> import requests Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named ‘requests' >>> import autoinstall >>> import requests Installing requests >>> requests <module 'requests' from '...python3.4/site-packages/requests/ __init__.py’> 在import第三⽅方包时,⾃自动安装相应的依赖
  41. 41. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! import sys import subprocess import importlib.util class AutoInstall(object): _loaded = set() @classmethod def find_spec(cls, name, path, target=None): if path is None!and name not in! cls._loaded: cls._loaded.add(name) print("Installing",! name) try: out = subprocess.check_output( [sys.executable, '-m', 'pip', 'install', name]) return importlib.util.find_spec(name) except Exception as e: print("Failed") return None sys.meta_path.append(AutoInstall)
  42. 42. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! Refrences • Modules and Packages:Live and Let Die! —David Beazley • http://stackoverflow.com/questions/14132789/pythonrelative-imports-for-the-billionth-time • https://github.com/tornadoweb/tornado • https://github.com/mitsuhiko/flask • PEP 0328 • PEP 0273 • PEP 0338 • PEP 0008 • PEP 0302 • PEP 0366 • PEP 0451
  43. 43. 北京/上海/⼲⼴广州 0xFF Life's pathetic, go Pythonic! Thanks • 刘畅 • GeneDock Senior Software Engineer • http://liuchang0812.com • RocksDB/Spark/Tornado Contributor • https://genedock.com/joinus/

×