在 Python 脚本中嵌入资源

3
我希望能够在Python脚本中嵌入二进制内容。例如,我不想在外部留下任何文件(图片,声音等),我希望所有这些内容都存在于我的Python脚本中。
以下是一个简单的示例来说明:
from StringIO import StringIO
from PIL import Image, ImageFilter

embedded_resource = StringIO(open("Lenna.png", "rb").read())
im = Image.open(embedded_resource)
im.show()

im_sharp = im.filter(ImageFilter.SHARPEN)
im_sharp.show()

从示例中可以看到,它正在读取外部文件 'Lenna.png'

enter image description here

问题

如何将 "Lenna.png" 嵌入到我的 Python 脚本中作为一个资源(变量)? 用 Python 实现这个简单任务的最快方法是什么?


我能想到的唯一方法就是将图像转换为“原始”数据并将其存储在变量中。不确定这是否符合Pythonic的风格。 - UnholySheep
2个回答

4

最好的方法是将您的图片转换为Python字符串,并将其放在一个名为 resources.py 的单独文件中,然后简单地解析它。

如果您想将整个东西嵌入到单个二进制文件中,则需要使用 py2exe 等工具。这里 提供了一个外部文件嵌入的示例。

在第一种情况下,您甚至可以使用 base64 对图片进行编码和解码,类似于以下内容:

import base64
file = open('yourImage.png'); 
encoded = base64.b64encode(file.read())
data = base64.b64decode(encoded) # Don't forget to file.close() !

1
Av4t4r,我正要发布base64的解决方案 :) 。是的,这就是我在寻找的,谢谢。 - BPL

4
您可能会发现以下类对于在程序中嵌入资源非常有用。要使用它,请使用路径调用package方法,以便将您想要嵌入的文件添加进去。该类将打印出一个 DATA 属性,并用其替换已经存在于类中的属性。如果您想要将文件添加到预构建数据中,请改用add方法。要在程序中使用该类,请使用上下文管理器语法调用load方法。返回值是一个Path对象,可用作其他函数的文件名参数或直接用于加载重组后的文件。请参见此SMTP客户端,了解具体的使用示例。
import base64
import contextlib
import pathlib
import pickle
import pickletools
import sys
import zlib


class Resource:

    """Manager for resources that would normally be held externally."""

    WIDTH = 76
    __CACHE = None
    DATA = b''

    @classmethod
    def package(cls, *paths):
        """Creates a resource string to be copied into the class."""
        cls.__generate_data(paths, {})

    @classmethod
    def add(cls, *paths):
        """Include paths in the pre-generated DATA block up above."""
        cls.__preload()
        cls.__generate_data(paths, cls.__CACHE.copy())

    @classmethod
    def __generate_data(cls, paths, buffer):
        """Load paths into buffer and output DATA code for the class."""
        for path in map(pathlib.Path, paths):
            if not path.is_file():
                raise ValueError('{!r} is not a file'.format(path))
            key = path.name
            if key in buffer:
                raise KeyError('{!r} has already been included'.format(key))
            with path.open('rb') as file:
                buffer[key] = file.read()
        pickled = pickle.dumps(buffer, pickle.HIGHEST_PROTOCOL)
        optimized = pickletools.optimize(pickled)
        compressed = zlib.compress(optimized, zlib.Z_BEST_COMPRESSION)
        encoded = base64.b85encode(compressed)
        cls.__print("    DATA = b'''")
        for offset in range(0, len(encoded), cls.WIDTH):
            cls.__print("\\\n" + encoded[
                slice(offset, offset + cls.WIDTH)].decode('ascii'))
        cls.__print("'''")

    @staticmethod
    def __print(line):
        """Provides alternative printing interface for simplicity."""
        sys.stdout.write(line)
        sys.stdout.flush()

    @classmethod
    @contextlib.contextmanager
    def load(cls, name, delete=True):
        """Dynamically loads resources and makes them usable while needed."""
        cls.__preload()
        if name not in cls.__CACHE:
            raise KeyError('{!r} cannot be found'.format(name))
        path = pathlib.Path(name)
        with path.open('wb') as file:
            file.write(cls.__CACHE[name])
        yield path
        if delete:
            path.unlink()

    @classmethod
    def __preload(cls):
        """Warm up the cache if it does not exist in a ready state yet."""
        if cls.__CACHE is None:
            decoded = base64.b85decode(cls.DATA)
            decompressed = zlib.decompress(decoded)
            cls.__CACHE = pickle.loads(decompressed)

    def __init__(self):
        """Creates an error explaining class was used improperly."""
        raise NotImplementedError('class was not designed for instantiation')

谢谢!我已经更改了被接受的答案并给予了赞。虽然我曾经接受了其他答案,但是您的答案对我的目的非常有用。 - BPL
感谢对我的信任!希望参考程序能够提供足够的示例来说明如何使用类。如果你想让资源文件在被加载后继续存在,你可以在load方法中将delete=True改为delete=False,或者调用该方法并将False作为其第二个参数传递。这个类的一个有创意的用法是将你编写的其他模块嵌入到需要作为依赖项的程序中。SMTP客户端就是这样做的,它在tkinter类周围加载了一个线程安全的包装器。总的来说,它运行得相当不错。 - Noctis Skytower

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接