Python Cookbook第三版学习笔记【第10章】

本文详细介绍了Python中模块和包的高级使用技巧,包括模块的组织、符号导入控制、相对导入、模块分解、命名空间包、模块重载、包数据读取、sys.path的动态添加、字符串导入模块、用户级包安装、以及包的发布流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第10章 模块和包

10.1 把模块按层次结构组织成包

  • 把代码在文件系统上进行组织,确保每一个目录下定义一个__init__.py文件
  • 当模块被导入时,它所属的包的__init__.py就会被触发执行
  • init.py可以留空,也可以用来进行子模块的加载

10.2 对所有符号的导入进行精确控制

  • 使用from module import * 可以导入模块中的所有符号
  • 可以在模块中定义__all__变量(列表),就可以控制可被导入的符号,例如:
  • 在一个模块中:
def foo():
    print("Hello World")

def bar():
    print("Bye World")

__all__ = ["foo"]
  • 在另一个模块中使用*导入:
from test import *

bar()
  • 报错:
NameError: name 'bar' is not defined
  • 注意:只有在使用“*”导入时,__all__才能起到控制作用,如果直接以名称导入,是可以正常执行的:
# 以下代码正常执行
from test import bar

bar()

10.3 用相对名称来导入包中的子模块

  • 想从一个子模块中导入另一个子模块,又不想硬编码包的名称。
  • 例如,有下面的包:
- top
- __init__.py
	- testA
		- __init__.py
		- foo.py
		- bar.py
	- testB
		- __init__.py
		- hi.py
		- ho.py
  • 相同目录下的导入,例如在foo.py中导入bar.py:
from . import bar
  • 不同目录中的导入,例如在hi.py中导入bar.py:
from ..testA import bar
  • 上面的代码中使用了相对名称,没有包含顶层包top

10.4 将模块分解成多个文件

  • 例如有一个模块test.py是这样的:
class A:
    def __init__(self):
        print("Hey, I'm class A")

class B(A):
    def __init__(self):
        print("Hey, I'm class B")
  • 想将A和B类放到不同模块中定义,但是此时已经在其他模块中导入了它们,不想修改这些代码。
  • 可以把test改成这样:
- test
	- __init__.py
	- a.py
	- b.py
  • init.py:
from .a import A
from .b import B
  • 在模块a.py和b.py中分别就是A和B类的定义
  • 由此我们发现,之前对模块test的导入是不必作任何修改的:
from test import A, B
import test.A
import test.B
...
  • 惰性导入:在__init__.py中,我们一次性加载了所有子模块, 但如果模块很大的话,我们往往希望只有在实际需要的时候才加载,因此可以把__init__.py修改成:
def A():
	from .a import A
	return A()

def B():
	from .b import B
	return B()

10.5 让各个目录下的代码在统一的命名空间下导入

  • 一个代码库中不同的包是由不同开发人员开发维护的,我们希望把这些包用一个统一的前缀命名
  • 例如,在顶层目录下有两个包:
- foo/
	- spam/
		- hello.py
- bar/
	- spam/
		- hi.py
  • 注意到在spam下面都没有定义__init__.py
  • 下面把foo和bar都添加到Python模块查询路径中:
import sys
sys.path.extend(['foo', 'bar'])
  • 然后就可以对spam执行导入了:
from spam import hello, hi
  • 不同包中的模块可以通过同一条语句导入

  • 这种特性叫“命名控件包”,对于不同包中的代码起到了合并的作用,在统一的命名空间下进行管理。

  • 这样任何人创建的包下只要包含了“spam”, 并且把这个包添加到sys.path中,就可以用类似以上的方式导入这个包的内容

  • 检查一个包是否为命名空间包:

    1. 打印一个包的字符串表达:
    >>> spam
    <module 'spam' (namespace)>
    
    • 有namespace字样,就是命名空间包
    1. 查看它的__file__属性,如果缺失,那么也代表它是命名空间包

10.6 重新加载模块

  • 重新加载一个已加载果的模块,使用importlib.reload()实现(书中介绍的imp.reload()已过时):
>>> import importlib
>>> importlib.reload(a)
  • 这种方法多用于开发和调试阶段,应尽量避免在生产环境中重新加载模块
  • reload模块会擦除模块的__dict__中的内容,并重新执行模块的源代码
  • 如果使用from module import name这样的语句,reload不会生效;
  • 只有import module.name 的语句reload才有效

10.7 让目录或zip文件成为可运行的脚本

  • 如果项目规模庞大,涉及多个目录,可以使用__main__.py, Python解释器会把这个文件当成主程序来执行:
- app
	- foo.py
	- bar.py
	- hello.py
	- __main__.py
  • 在顶层目录,执行:
python3 app
  • 就可以运行整个应用
  • 如果将一个应用放到压缩包.zip文件中,上述方法依然可行:
python3 app.zip

10.8 读取包中的数据文件

  • 有两个问题:
    1. 使用内建的IO函数(如open())无法读取保存在归档(archive)中的数据文件,因为包通常被安装为.zip或.egg文件
    2. 对open()函数,我们要提供包的绝对路径,或是相对解释器的路径,我们不想硬编码绝对路径,也无法控制解释器的所在路径,这么做使代码的可移植性较低。
  • pkgutil.get_data()函数接收一个包含包名的字符串,第二个参数是要获取的数据文件相对于包的名称

10.9 添加目录到sys.path中

  • Python解释器会在sys.path中查找我们导入的包,一些包没有在其中列出,因此想在代码中把它们添加进去
  • 以下列出了各种方法:
# 1. 设置系统的环境变量:PYTHONPATH
# 对于MacOS,在~/.bash_profile 中修改并使之生效

# 2. 创建一个.pth文件, 放到site-packages目录下

# 3. 代码中动态添加
import sys
sys.path.insert(0, '/some/dir')
  • 前两种方法在运行程序前就设置,第3种方法在程序运行时设置并且用了硬编码路径的方式
  • 应当使用一些策略避免使用这种硬编码的方式,如:

import sys
from os.path import abspath, join, dirname
sys.path.insert(0, abspath(dirname('__file__'), 'src'))

10.10 使用字符串中给定的名称来导入模块

  • 使用importlib.import_module():
import importlib

math = importlib.import_module("math")
math.sin(2)

10.13 安装只为自己所用的包

  • 有以下两种情形:
    • 想安装一个第三方的包,但是没有权限在系统Python中安装其他的包
    • 安装一个给自己使用的包,而不是给系统中其他用户使用
  • Python有一个用户级的目录,仅属于当前用户所有。要把包强制安装到这个目录下,只要在安装命令后添加–user:
pip install --user some-package

python3 setup.py install -user
  • 一般而言,包会被安装在系统级的site-package目录下,利用–user选项可以把包安装到自己的目录中
  • 使用虚拟环境也可以完成类似的事情

10.15 发布自己定义的包

  • 要发布一个自定义的包,要经过以下几个步骤:
    1. 为自己的库起一个独一无二的名字,清理目录结构:
    - project/
    	- README.txt
    	- Doc/
    		- doc.txt
    	- project/
    		- __init__.py
    		- foo.py
    		- bar.py
    		- utils/
    			- __init__.py
    			- spam.py
    			- grok.py
    	- examples/
    		- hello.py
    	...
    
    1. 编写一个setup文件,放置于顶层目录
    from setuptools import setup, find_packages 
      
    setup( 
     name = "test", 
     version = "1.0", 
     keywords = ("test", "xxx"), 
     description = "eds sdk", 
     long_description = "eds sdk for python", 
     license = "MIT Licence", 
      
     url = "http://test.com", 
     author = "test", 
     author_email = "test@gmail.com", 
      
     packages = find_packages(), 
     include_package_data = True, 
     platforms = "any", 
     install_requires = [], 
      
     scripts = [], 
     entry_points = { 
      'console_scripts': [ 
       'test = test.help:main'
      ] 
     } 
    )
    
    1. 创建一个MANIFEST.in文件,在其中列出希望被包含在包中的非源码文件,放置于顶层目录:
    # MANIFEST.in
    include *.txt
    recursive-include examples *
    recursive-include Doc *
    
    1. 创建一个源码级的分发包(生成一个压缩文件):
    python3 setup.py sdist
    
    1. 在PyPi上发布!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值