Python模块、包管理及网络编程实用指南
在Python编程中,模块和包的管理以及网络编程是非常重要的部分。下面将详细介绍相关的知识和操作方法。
模块与包的操作
读取包内数据文件
假设有一个名为
mypackage
的包,其文件结构如下:
mypackage/
__init__.py
somedata.dat
spam.py
若
spam.py
文件想要读取
somedata.dat
文件的内容,可以使用以下代码:
# spam.py
import pkgutil
data = pkgutil.get_data(__package__, 'somedata.dat')
这里得到的
data
变量是一个包含未处理文件内容的字节串。使用
pkgutil.get_data()
函数的好处是,它是一个高级工具,无论包安装在哪里或如何安装,都能获取文件数据。该函数的第一个参数是包含包名的字符串,可以直接提供,也可以使用特殊变量
__package__
;第二个参数是包内文件的相对名称。
向sys.path添加目录
当Python代码因不在
sys.path
列出的目录中而无法导入时,可以通过以下两种常见方法向
sys.path
添加新目录:
-
通过环境变量PYTHONPATH
:
bash % env PYTHONPATH=/some/dir:/other/dir python3
在自己的应用程序中,可以在程序启动时或通过脚本修改这个环境变量。
-
创建.pth文件
:
创建一个名为
myapplication.pth
的文件,内容如下:
/some/dir
/other/dir
将这个文件放在Python的
site-packages
目录之一,如
/usr/local/lib/python3.3/site-packages
或
~/.local/lib/python3.3/site-packages
。解释器启动时,文件中列出的目录会被添加到
sys.path
中。
不建议手动修改
sys.path
的值,因为这会使代码变得脆弱,难以维护。例如:
import sys
sys.path.insert(0, '/some/dir')
sys.path.insert(0, '/other/dir')
不过,可以使用模块级变量
__file__
来构造合适的绝对路径,避免硬编码目录名:
import sys
from os.path import abspath, join, dirname
sys.path.insert(0, abspath(dirname('__file__'), 'src'))
以字符串形式导入模块
如果有一个模块名存储在字符串中,想要导入该模块,可以使用
importlib.import_module()
函数。例如:
import importlib
math = importlib.import_module('math')
math.sin(2)
mod = importlib.import_module('urllib.request')
u = mod.urlopen('http://www.python.org')
对于包,
import_module()
也可用于相对导入,但需要提供额外的参数:
import importlib
# 等同于 'from . import b'
b = importlib.import_module('.b', __package__)
从远程计算机加载模块
要使Python的导入指令能够透明地从远程计算机加载模块,可以通过以下两种方法实现:
-
创建显式加载函数
:
import imp
import urllib.request
import sys
def load_module(url):
u = urllib.request.urlopen(url)
source = u.read().decode('utf-8')
mod = sys.modules.setdefault(url, imp.new_module(url))
code = compile(source, url, 'exec')
mod.__file__ = url
mod.__package__ = ''
exec(code, mod.__dict__)
return mod
使用示例:
fib = load_module('http://localhost:15000/fib.py')
fib.fib(10)
spam = load_module('http://localhost:15000/spam.py')
spam.hello('Guido')
这种方法适用于简单模块,但对于更复杂的结构(如包),需要额外的工作。
-
创建自定义导入器
:
可以编写“元路径导入器”,示例代码如下:
# urlimport.py
import sys
import importlib.abc
import imp
from urllib.request import urlopen
from urllib.error import HTTPError, URLError
from html.parser import HTMLParser
import logging
log = logging.getLogger(__name__)
# 获取链接
def _get_links(url):
class LinkParser(HTMLParser):
def handle_starttag(self, tag, attrs):
if tag == 'a':
attrs = dict(attrs)
links.add(attrs.get('href').rstrip('/'))
links = set()
try:
log.debug('Getting links from %s' % url)
u = urlopen(url)
parser = LinkParser()
parser.feed(u.read().decode('utf-8'))
except Exception as e:
log.debug('Could not get links. %s', e)
log.debug('links: %r', links)
return links
class UrlMetaFinder(importlib.abc.MetaPathFinder):
def __init__(self, baseurl):
self._baseurl = baseurl
self._links = { }
self._loaders = { baseurl : UrlModuleLoader(baseurl) }
def find_module(self, fullname, path=None):
log.debug('find_module: fullname=%r, path=%r', fullname, path)
if path is None:
baseurl = self._baseurl
else:
if not path[0].startswith(self._baseurl):
return None
baseurl = path[0]
parts = fullname.split('.')
basename = parts[-1]
log.debug('find_module: baseurl=%r, basename=%r', baseurl, basename)
# 检查链接缓存
if basename not in self._links:
self._links[baseurl] = _get_links(baseurl)
# 检查是否为包
if basename in self._links[baseurl]:
log.debug('find_module: trying package %r', fullname)
fullurl = self._baseurl + '/' + basename
# 尝试加载包
loader = UrlPackageLoader(fullurl)
try:
loader.load_module(fullname)
self._links[fullurl] = _get_links(fullurl)
self._loaders[fullurl] = UrlModuleLoader(fullurl)
log.debug('find_module: package %r loaded', fullname)
except ImportError as e:
log.debug('find_module: package failed. %s', e)
loader = None
return loader
# 普通模块
filename = basename + '.py'
if filename in self._links[baseurl]:
log.debug('find_module: module %r found', fullname)
return self._loaders[baseurl]
else:
log.debug('find_module: module %r not found', fullname)
return None
def invalidate_caches(self):
log.debug('invalidating link cache')
self._links.clear()
class UrlModuleLoader(importlib.abc.SourceLoader):
def __init__(self, baseurl):
self._baseurl = baseurl
self._source_cache = {}
def module_repr(self, module):
return '<urlmodule %r from %r>' % (module.__name__, module.__file__)
# 必需方法
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.rpartition('.')[0]
exec(code, mod.__dict__)
return mod
# 可选扩展
def get_code(self, fullname):
src = self.get_source(fullname)
return compile(src, self.get_filename(fullname), 'exec')
def get_data(self, path):
pass
def get_filename(self, fullname):
return self._baseurl + '/' + fullname.split('.')[-1] + '.py'
def get_source(self, fullname):
filename = self.get_filename(fullname)
log.debug('loader: reading %r', filename)
if filename in self._source_cache:
log.debug('loader: cached %r', filename)
return self._source_cache[filename]
try:
u = urlopen(filename)
source = u.read().decode('utf-8')
log.debug('loader: %r loaded', filename)
self._source_cache[filename] = source
return source
except (HTTPError, URLError) as e:
log.debug('loader: %r failed. %s', filename, e)
raise ImportError("Can't load %s" % filename)
def is_package(self, fullname):
return False
class UrlPackageLoader(UrlModuleLoader):
def load_module(self, fullname):
mod = super().load_module(fullname)
mod.__path__ = [ self._baseurl ]
mod.__package__ = fullname
def get_filename(self, fullname):
return self._baseurl + '/' + '__init__.py'
def is_package(self, fullname):
return True
# 安装/卸载加载器的辅助函数
_installed_meta_cache = { }
def install_meta(address):
if address not in _installed_meta_cache:
finder = UrlMetaFinder(address)
_installed_meta_cache[address] = finder
sys.meta_path.append(finder)
log.debug('%r installed on sys.meta_path', finder)
def remove_meta(address):
if address in _installed_meta_cache:
finder = _installed_meta_cache.pop(address)
sys.meta_path.remove(finder)
log.debug('%r removed from sys.meta_path', finder)
使用示例:
# 初始导入失败
import fib
# 加载导入器并再次尝试
import urlimport
urlimport.install_meta('http://localhost:15000')
import fib
import spam
import grok.blah
这种方法中,
UrlMetaFinder
实例作为“最后机会的导入器”,当模块在常规位置未找到时会被调用。它会根据提供的URL构建有效链接集合,当导入模块时,将模块名与这些已知链接进行比较,若匹配则使用
UrlModuleLoader
加载模块。链接会被缓存,以避免重复的HTTP请求。
还可以编写直接附加到
sys.path
的钩子,示例代码如下:
# urlimport.py
# ... 包含前面的代码 ...
class UrlPathFinder(importlib.abc.PathEntryFinder):
def __init__(self, baseurl):
self._links = None
self._loader = UrlModuleLoader(baseurl)
self._baseurl = baseurl
def find_loader(self, fullname):
log.debug('find_loader: %r', fullname)
parts = fullname.split('.')
basename = parts[-1]
# 检查链接缓存
if self._links is None:
self._links = []
self._links = _get_links(self._baseurl)
# 检查是否为包
if basename in self._links:
log.debug('find_loader: trying package %r', fullname)
fullurl = self._baseurl + '/' + basename
# 尝试加载包
loader = UrlPackageLoader(fullurl)
try:
loader.load_module(fullname)
log.debug('find_loader: package %r loaded', fullname)
except ImportError as e:
log.debug('find_loader: %r is a namespace package', fullname)
loader = None
return (loader, [fullurl])
# 普通模块
filename = basename + '.py'
if filename in self._links:
log.debug('find_loader: module %r found', fullname)
return (self._loader, [])
else:
log.debug('find_loader: module %r not found', fullname)
return (None, [])
def invalidate_caches(self):
log.debug('invalidating link cache')
self._links = None
# 检查路径是否为URL
_url_path_cache = {}
def handle_url(path):
if path.startswith(('http://', 'https://')):
log.debug('Handle path? %s. [Yes]', path)
if path in _url_path_cache:
finder = _url_path_cache[path]
else:
finder = UrlPathFinder(path)
_url_path_cache[path] = finder
return finder
else:
log.debug('Handle path? %s. [No]', path)
def install_path_hook():
sys.path_hooks.append(handle_url)
sys.path_importer_cache.clear()
log.debug('Installing handle_url')
def remove_path_hook():
sys.path_hooks.remove(handle_url)
sys.path_importer_cache.clear()
log.debug('Removing handle_url')
使用示例:
# 初始导入失败
import fib
# 安装路径钩子
import urlimport
urlimport.install_path_hook()
# 导入仍然失败(不在路径中)
import fib
# 添加记录到sys.path
import sys
sys.path.append('http://localhost:15000')
import fib
import grok.blah
导入模块时应用更改
若想在模块导入并被使用时更改模块中的函数或应用装饰器,可以使用以下方法:
# postimport.py
import importlib
import sys
from collections import defaultdict
_post_import_hooks = defaultdict(list)
class PostImportFinder:
def __init__(self):
self._skip = set()
def find_module(self, fullname, path=None):
if fullname in self._skip:
return None
self._skip.add(fullname)
return PostImportLoader(self)
class PostImportLoader:
def __init__(self, finder):
self._finder = finder
def load_module(self, fullname):
importlib.import_module(fullname)
module = sys.modules[fullname]
for func in _post_import_hooks[fullname]:
func(module)
self._finder._skip.remove(fullname)
return module
def when_imported(fullname):
def decorate(func):
if fullname in sys.modules:
func(sys.modules[fullname])
else:
_post_import_hooks[fullname].append(func)
return func
return decorate
sys.meta_path.insert(0, PostImportFinder())
使用示例:
from postimport import when_imported
@when_imported('threading')
def warn_threads(mod):
print('Threads? Are you crazy?')
import threading
更实际的例子是对现有定义应用装饰器:
from functools import wraps
from postimport import when_imported
def logged(func):
@wraps(func)
def wrapper(*args, **kwargs):
print('Calling', func.__name__, args, kwargs)
return func(*args, **kwargs)
return wrapper
# 示例
@when_imported('math')
def add_logging(mod):
mod.cos = logged(mod.cos)
mod.sin = logged(mod.sin)
包的安装与分发
为自己安装包
当没有权限将包安装到系统Python中,或只想为自己安装包时,可以使用以下命令:
python3 setup.py install --user
或
pip install --user packagename
用户特定的
site-packages
目录通常在
sys.path
中位于系统
site-packages
目录之前,因此使用此方法安装的包具有优先级。
创建新的Python环境
可以使用
pyvenv
命令创建新的“虚拟”环境:
bash % pyvenv Spam
创建后的
Spam
目录结构如下:
bin include lib pyvenv.cfg
在
bin
目录中可以找到Python解释器:
bash % Spam/bin/python3
虚拟环境的主要特点是其
site-packages
目录被设置为新环境,安装的第三方包将安装在此处,而不是系统的
site-packages
目录。默认情况下,虚拟环境是干净的,不包含第三方包。若想包含已安装的包,可以使用
--system-site-packages
选项创建:
bash % pyvenv --system-site-packages Spam
分发包
若想将自己编写的库分发给他人,首先要为其分配一个唯一的名称并清理目录结构。例如,一个典型的包结构如下:
projectname/
README.txt
Doc/
documentation.txt
projectname/
__init__.py
foo.py
bar.py
utils/
__init__.py
spam.py
grok.py
examples/
helloworld.py
...
然后编写
setup.py
文件:
# setup.py
from distutils.core import setup
setup(name='projectname',
version='1.0',
author='Your Name',
author_email='you@youraddress.com',
url='http://www.you.com/projectname',
packages=['projectname', 'projectname.utils'],
)
再创建
MANIFEST.in
文件,列出不包含源代码但想包含在包中的文件:
# MANIFEST.in
include *.txt
recursive-include examples *
recursive-include Doc *
确保
setup.py
和
MANIFEST.in
位于包的最高级目录。然后运行以下命令创建分发源:
% bash python3 setup.py sdist
这将创建
projectname-1.0.zip
或
projectname-1.0.tar.gz
文件,可以将其分发给他人或上传到Python Package Index。
网络编程:与HTTP服务交互
在Python中,作为客户端与各种Web服务进行HTTP交互有多种方法。
使用urllib.request进行简单请求
对于简单任务,
urllib.request
模块通常就足够了。
-
发送GET请求
:
from urllib import request, parse
# 基础URL
url = 'http://httpbin.org/get'
# 请求参数
parms = {
'name1' : 'value1',
'name2' : 'value2'
}
# 编码查询字符串
querystring = parse.urlencode(parms)
# 发送GET请求并读取响应
u = request.urlopen(url + '?' + querystring)
resp = u.read()
- 发送POST请求 :
from urllib import request, parse
# 基础URL
url = 'http://httpbin.org/post'
# 请求参数
parms = {
'name1' : 'value1',
'name2' : 'value2'
}
# 编码查询字符串
querystring = parse.urlencode(parms)
# 发送POST请求并读取响应
u = request.urlopen(url, querystring.encode('ascii'))
resp = u.read()
- 提供自定义HTTP头 :
from urllib import request, parse
# 基础URL
url = 'http://httpbin.org/post'
# 请求参数
parms = {
'name1' : 'value1',
'name2' : 'value2'
}
# 编码查询字符串
querystring = parse.urlencode(parms)
# 额外的头信息
headers = {
'User-agent' : 'none/ofyourbusiness',
'Spam' : 'Eggs'
}
req = request.Request(url, querystring.encode('ascii'), headers=headers)
# 发送请求并读取响应
u = request.urlopen(req)
resp = u.read()
使用requests库进行复杂交互
如果与服务的交互更复杂,
requests
库是更好的选择。
-
发送POST请求
:
import requests
# 基础URL
url = 'http://httpbin.org/post'
# 请求参数
parms = {
'name1' : 'value1',
'name2' : 'value2'
}
# 额外的头信息
headers = {
'User-agent' : 'none/ofyourbusiness',
'Spam' : 'Eggs'
}
resp = requests.post(url, data=parms, headers=headers)
# 解码后的响应文本
text = resp.text
- 发送HEAD请求并提取头信息 :
import requests
resp = requests.head('http://www.python.org/index.html')
status = resp.status_code
last_modified = resp.headers['last-modified']
content_type = resp.headers['content-type']
content_length = resp.headers['content-length']
- 使用基本认证登录 :
import requests
resp = requests.get('http://pypi.python.org/pypi?:action=login',
auth=('user','password'))
- 传递HTTP Cookie :
import requests
# 第一个请求
resp1 = requests.get(url)
# 第二个请求,使用第一个请求的Cookie
resp2 = requests.get(url, cookies=resp1.cookies)
- 上传数据到服务器 :
import requests
url = 'http://httpbin.org/post'
files = { 'file': ('data.csv', open('data.csv', 'rb')) }
r = requests.post(url, files=files)
在开发客户端HTTP代码时,测试可能会因为许多不明显的细节而变得困难。可以使用
httpbin
服务进行测试,它会以JSON响应的形式返回请求信息。例如:
import requests
r = requests.get('http://httpbin.org/get?name=Dave&n=37')
综上所述,Python在模块和包管理以及网络编程方面提供了丰富的工具和方法,通过合理运用这些知识,可以更高效地进行Python编程。
Python模块、包管理及网络编程实用指南
模块与包操作的深入理解
在前面介绍了模块与包的各种操作方法,这里进一步深入探讨其中的一些关键概念和细节。
模块缓存机制
Python解释器会对模块进行缓存,缓存的模块可以在
sys.modules
字典中找到。这意味着,如果一个模块已经被导入过,再次导入时不会重新加载,而是直接使用缓存中的模块。例如:
import sys
import imp
# 尝试从缓存中获取模块,如果不存在则创建新模块
m = sys.modules.setdefault('spam', imp.new_module('spam'))
print(m)
这种缓存机制可以提高模块导入的效率,但在某些情况下可能会导致问题,比如在开发过程中修改了模块代码,需要重新加载模块时,就需要手动处理缓存。
包的处理细节
在处理包时,需要特别注意
__init__.py
文件的作用。
__init__.py
文件可以为空,也可以包含初始化代码,它的存在告诉Python该目录是一个包。当导入一个包时,
__init__.py
文件会被执行。例如:
my_package/
__init__.py
module1.py
module2.py
在导入
my_package
时,
__init__.py
会被执行:
import my_package
另外,在处理包的子模块时,需要正确设置
__path__
属性。
__path__
属性是一个列表,包含了包的搜索路径。例如,在自定义的
UrlPackageLoader
类中,就需要设置
__path__
属性:
class UrlPackageLoader(UrlModuleLoader):
def load_module(self, fullname):
mod = super().load_module(fullname)
mod.__path__ = [ self._baseurl ]
mod.__package__ = fullname
return mod
导入钩子机制的原理与应用
导入钩子机制是Python中一个强大的特性,它允许我们自定义模块的导入过程。在前面介绍了两种导入钩子的实现方法,下面详细分析其原理和应用场景。
元路径导入器的原理
元路径导入器是通过修改
sys.meta_path
列表来实现的。
sys.meta_path
是一个包含元路径查找器的列表,当Python解释器执行
import
语句时,会依次调用
sys.meta_path
中的查找器的
find_module
方法,直到找到合适的加载器。例如,在自定义的
UrlMetaFinder
类中:
class UrlMetaFinder(importlib.abc.MetaPathFinder):
def __init__(self, baseurl):
self._baseurl = baseurl
self._links = { }
self._loaders = { baseurl : UrlModuleLoader(baseurl) }
def find_module(self, fullname, path=None):
# 查找模块的逻辑
pass
当
import
语句执行时,解释器会调用
UrlMetaFinder
的
find_module
方法,根据模块名和路径查找合适的加载器。
路径钩子的原理
路径钩子是通过修改
sys.path_hooks
列表来实现的。
sys.path_hooks
是一个包含路径查找函数的列表,当Python解释器处理
sys.path
中的每个目录时,会依次调用
sys.path_hooks
中的函数,根据目录的特点返回合适的查找器。例如,在自定义的
handle_url
函数中:
def handle_url(path):
if path.startswith(('http://', 'https://')):
# 如果路径是URL,则返回UrlPathFinder查找器
if path in _url_path_cache:
finder = _url_path_cache[path]
else:
finder = UrlPathFinder(path)
_url_path_cache[path] = finder
return finder
else:
return None
当解释器处理
sys.path
中的目录时,如果目录是一个URL,就会调用
handle_url
函数,返回
UrlPathFinder
查找器。
导入钩子的应用场景
导入钩子可以用于很多场景,比如:
-
动态加载模块
:根据不同的条件动态加载不同的模块。
-
模块监控
:在模块导入时进行监控,记录日志或执行一些额外的操作。
-
模块替换
:用自定义的模块替换系统中的模块。
虚拟环境的管理与使用
虚拟环境是Python开发中非常重要的工具,它可以帮助我们隔离不同项目的依赖,避免版本冲突。
虚拟环境的创建与激活
使用
pyvenv
命令可以创建虚拟环境:
bash % pyvenv myenv
创建完成后,需要激活虚拟环境。在不同的操作系统中,激活虚拟环境的方法不同:
-
Windows
:
myenv\Scripts\activate
- Linux/Mac :
source myenv/bin/activate
激活虚拟环境后,所有的Python命令和包安装都会在该虚拟环境中进行。
虚拟环境的管理
在虚拟环境中,可以使用
pip
命令安装和管理包。例如,安装
requests
包:
pip install requests
如果需要退出虚拟环境,可以使用
deactivate
命令:
deactivate
另外,还可以使用
--system-site-packages
选项创建虚拟环境,这样虚拟环境会包含系统中已经安装的包:
bash % pyvenv --system-site-packages myenv
网络编程的高级应用
在前面介绍了与HTTP服务交互的基本方法,下面介绍一些网络编程的高级应用。
异步HTTP请求
在处理大量的HTTP请求时,同步请求可能会导致性能问题。可以使用
asyncio
和
aiohttp
库实现异步HTTP请求。例如:
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch(session, 'http://www.example.com')
print(html)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
aiohttp
库提供了异步的HTTP客户端和服务器功能,可以大大提高网络请求的效率。
网络爬虫
网络爬虫是一种自动获取网页内容的程序。可以使用Python编写简单的网络爬虫。例如,爬取一个网页的标题:
import requests
from bs4 import BeautifulSoup
url = 'http://www.example.com'
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
title = soup.title.string
print(title)
BeautifulSoup
库可以帮助我们解析HTML和XML文档,提取所需的信息。
总结与展望
Python在模块和包管理以及网络编程方面提供了丰富的工具和方法。通过合理运用这些知识,可以更高效地进行Python编程。在模块和包管理方面,我们可以灵活地读取包内数据文件、向
sys.path
添加目录、以字符串形式导入模块、从远程计算机加载模块等。在网络编程方面,我们可以使用
urllib.request
和
requests
库与HTTP服务进行交互,还可以实现异步HTTP请求和网络爬虫等高级应用。
未来,随着Python的不断发展,模块和包管理以及网络编程的功能可能会更加完善和强大。例如,导入钩子机制可能会有更多的应用场景和优化,网络编程可能会支持更多的协议和功能。我们需要不断学习和探索,以跟上Python技术的发展步伐。
以下是网络编程中不同请求方式的对比表格:
| 请求方式 | 适用场景 | 代码示例 |
| ---- | ---- | ---- |
| GET | 获取资源 |
python<br>from urllib import request, parse<br><br>url = 'http://httpbin.org/get'<br>parms = {<br> 'name1' : 'value1',<br> 'name2' : 'value2'<br>}<br>querystring = parse.urlencode(parms)<br>u = request.urlopen(url + '?' + querystring)<br>resp = u.read()<br>
|
| POST | 提交数据 |
python<br>from urllib import request, parse<br><br>url = 'http://httpbin.org/post'<br>parms = {<br> 'name1' : 'value1',<br> 'name2' : 'value2'<br>}<br>querystring = parse.urlencode(parms)<br>u = request.urlopen(url, querystring.encode('ascii'))<br>resp = u.read()<br>
|
| HEAD | 获取资源元信息 |
python<br>import requests<br><br>resp = requests.head('http://www.python.org/index.html')<br>status = resp.status_code<br>last_modified = resp.headers['last-modified']<br>content_type = resp.headers['content-type']<br>content_length = resp.headers['content-length']<br>
|
以下是虚拟环境操作的流程图:
graph TD;
A[创建虚拟环境] --> B[激活虚拟环境];
B --> C[安装包];
C --> D[使用包];
D --> E[退出虚拟环境];
通过以上的学习和实践,相信大家对Python的模块和包管理以及网络编程有了更深入的理解和掌握。希望这些知识能够帮助大家在Python编程的道路上取得更好的成果。
超级会员免费看
1950

被折叠的 条评论
为什么被折叠?



