【问题标题】:Obscure behaviour of python in need of explanationpython的晦涩行为需要解释
【发布时间】:2020-12-21 13:20:00
【问题描述】:

我试图理解 python 的这种晦涩难懂的行为。我很确定这不是错误,但我无法真正解释为什么它会这样。 我正在尝试将一些图像加载到列表中,然后对其进行操作。

这是一个最小的例子:

import numpy as np
from PIL import Image
import os

DATA_URL = ('./images/')

def load_data():
    data_internal = list()
    for root, dirs, files in os.walk(DATA_URL, topdown=False):
        for name in files:
            with Image.open(DATA_URL + name) as f:

                data_internal.append(f)
                
    return data_internal


data = load_data()
print(data[0])
print(np.array(data[0]))

此代码产生以下内容:

> <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=2x2 at 0x1EDCC735708>
> <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=2x2 at 0x1EDCC735708>

所以 data[0]np.array(data[0]) 完全一样。

但是,如果我添加一行打印 f 作为一个 np.array() 在读入中,行为发生变化:

import numpy as np
from PIL import Image
import os

DATA_URL = ('./images/')

def load_data():
    data_internal = list()
    for root, dirs, files in os.walk(DATA_URL, topdown=False):
        for name in files:
            with Image.open(DATA_URL + name) as f:

                data_internal.append(f)
                print(np.array(data_internal[0]))
    return data_internal


data = load_data()
print(data[0])
print(np.array(data[0]))

该示例的输出是

> [[[146 135 129]
>  [145 134 128]]
>
> [[148 137 133]
>  [148 137 133]]]
>
><PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=2x2 at 0x261491DEF48>
>
>[[[146 135 129]
>  [145 134 128]]
>
> [[148 137 133]
>  [148 137 133]]]

正如我所料。

谁能告诉我,为什么仅仅访问变异(转换为 numpy 数组)列表条目就可以实现这一点?

非常感谢和最好的问候

【问题讨论】:

    标签: python arrays list numpy


    【解决方案1】:

    您正在使用上下文 (with) 打开图像文件以进行读取,并将 Image 对象附加到列表中。但是,一旦上下文关闭 - 当with 缩进块结束时 - Image 对象将关闭指向文件的指针。

    为了速度和效率,PIL 不会立即加载所有图像数据,而是仅在需要时加载,例如将其转换为 numpy 数组时。第一次请求数据时,它会被读入并保存到Image 对象中,以便再次重复使用。

    这就是区别,调用np.array(data_internal[0])上下文意味着指向文件的指针仍然打开以供读取。数据被读取并保存到Image对象,再次调用np.array只是返回之前读取的数据。

    这里有 3 个例子:

    1. 在没有上下文的情况下阅读
    x = Image.open('pic.jpg')
    
    np.array(x)
    # returns:
    array([[[ 55,  39,  42],
            [ 61,  45,  48],
            [ 55,  39,  42],
            ...,
            [110,  93,  83],
            [111,  94,  84],
            [121, 104,  94]],
    ...
    
    x.getdata()
    # returns:
    <ImagingCore at 0x2b21acd0950>
    
    1. 在上下文中打开,但不加载数据
    with open Image.open('pic.jpg') as f
        y = f
    
    np.array(y)
    # returns a zero-dimension array of the object:
    array(<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=2448x3264 at 0x2B21BA99A48>,
          dtype=object)
    
    y.getdata()
    # raises an error
    ...
    AttributeError: 'NoneType' object has no attribute 'seek'
    
    1. 使用上下文打开并加载数据
    with open Image.open('pic.jpg') as f
        z = f
        # force loading of the data here:
        np.array(z)
    
    np.array(z)
    # returns:
    array([[[ 55,  39,  42],
            [ 61,  45,  48],
            [ 55,  39,  42],
            ...,
            [110,  93,  83],
            [111,  94,  84],
            [121, 104,  94]],
    ...
    
    z.getdata()
    # returns:
    <ImagingCore at 0x2b21a74fb70>
    

    【讨论】:

    • 嘿,太好了,谢谢!还有一个问题:这里的最佳做法是什么?我必须非常谨慎地使用 with-context 吗?因为据我了解,在处理文件时使用它是明智的。使用接口时,是不是有一句你不应该了解底层实现的口头禅?
    • 加载元数据并仅在需要时延迟加载数据是一种非常常见的编码实践。在with 上下文中使用Image 接口意味着Image.openImage.close 都被调用,就像打开带有上下文的文件一样。一旦你想到这意味着什么:关闭文件;你可以理解它的操作非常像一个文件对象。就像你不能从上下文之外的文件对象中读取一样,你也不能从上下文之外的图像文件中提取数据:-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-07
    • 1970-01-01
    相关资源
    最近更新 更多