【问题标题】:Can I "detect" a slicing expression in a python class method?我可以在 python 类方法中“检测”切片表达式吗?
【发布时间】:2015-03-09 08:19:29
【问题描述】:

我正在开发一个应用程序,其中我定义了一个“变量”对象,其中包含 numpy 数组形式的数据。这些变量链接到(netcdf)数据文件,我想在需要时动态加载变量值,而不是一开始就从有时很大的文件中加载所有数据。

下面的 sn-p 演示了原理并且运行良好,包括使用切片访问数据部分。例如,你可以写:

a = var()   # empty variable
print a.values[7]   # values have been automatically "loaded"

甚至:

a = var()
a[7] = 0

但是,这段代码仍然迫使我一次加载整个变量数据。 Netcdf(带有 netCDF4 库)将允许我直接访问文件中的数据切片。示例:

f = netCDF4.Dataset(filename, "r")
print f.variables["a"][7]

我不能直接使用 netcdf 变量对象,因为我的应用程序绑定到无法记住 netcdf 文件处理程序的 Web 服务,还因为变量数据并不总是来自 netcdf 文件,但可能来自其他来源例如 OGC 网络服务。

有没有办法在属性或设置方法中“捕获”切片表达式并使用它们?我们的想法是这样写:

    @property
    def values(self):
        if self._values is None:
            self._values = np.arange(10.)[slice]  # load from file ...
        return self._values

而不是下面的代码。

工作演示:

import numpy as np

class var(object):

    def __init__(self, values=None, metadata=None):
        if values is None:
            self._values = None
        else:
            self._values = np.array(values)
        self.metadata = metadata  # just to demonstrate that var has mor than just values

    @property
    def values(self):
        if self._values is None:
            self._values = np.arange(10.)  # load from file ...
        return self._values

    @values.setter
    def values(self, values):
        self._values = values

首先想到:我是否应该将值创建为一个单独的类,然后使用__getitem__?见In python, how do I create two index slicing for my own matrix class?

【问题讨论】:

    标签: python numpy indexing slice


    【解决方案1】:

    不,您无法检测从.values 返回后将对对象执行的操作。结果可以存储在一个变量中,并且仅(很久以后)被切片,或在不同的地方切片,或整体使用,等等。

    您确实应该返回一个 包装对象 并挂钩到 object.__getitem__;它可以让您检测切片并根据需要加载数据。切片时,Python 传入slice() object

    【讨论】:

    • 谢谢!经过进一步阅读,我发现__getattribute__ 而不是__getattr__(仅在定义属性时调用)。那你为什么要指出__getattr__
    • @maschu 我实际上打错了。我的意思是在这里指向__getitem__。过错!
    • 好的。我相信我现在正在接近解决方案。不过还有一件事:使用__getitem__ 意味着我总是必须索引我的“值”,即我应该使用“[:]”(或“[...]”)来获取完整的数据。没有任何切片,__getitem__ 不会被调用,我确实必须调整 __getattribute__ 这有点讨厌。有没有一种好方法来确定切片或切片元组是否应该索引整个数据数组?以下表达式都会在__getitem__ 中产生不同的参数:[:]、[:,:]、[...]、[:,...] 等。
    • @maschu:一旦你得到一个元组,你就会处理各个维度;您需要加载元组第一个维度中的所有元素,以便其余元素正常工作; [:,...] 是一个完整的切片 (slice(None, None, None)),无论如何您都需要加载所有内容。
    • @maschu:您可以尝试使用__getattr__ 来检测您的对象的所有其他用途并在那时加载整个东西;只要对象被视为鸭子而不是应用isinstance(),您就应该能够摆脱这样的代理。
    【解决方案2】:

    感谢 Martijn Pieters 的指导和更多阅读,我想出了以下代码作为演示。请注意,Reader 类使用 netcdf 文件和 netCDF4 库。如果您想自己尝试此代码,您将需要一个带有变量“a”和“b”的 netcdf 文件,或者将 Reader 替换为其他可以返回数据数组或数据数组切片的东西。

    此解决方案定义了三个类:Reader 执行实际的文件 I/O 处理,Values 管理数据访问部分,如果内存中没有存储数据,则调用 Reader 实例,var 是实际的最终“变量”生活将包含更多的元数据。该代码包含一些用于教育目的的额外打印语句。

    """Implementation of a dynamic variable class which can read data from file when needed or
    return the data values from memory if they were read already. This concepts supports
    slicing for both memory and file access.""" 
    
    import numpy as np
    import netCDF4 as nc
    
    FILENAME = r"C:\Users\m.schultz\Downloads\data\tmp\MACC_20141224_0001.nc"
    VARNAME = "a"
    
    
    class Reader(object):
        """Implements the actual data access to variable values. Here reading a
        slice from a netcdf file.
        """
    
        def __init__(self, filename, varname):
            """Final implementation will also have to take groups into account...
            """
            self.filename = filename
            self.varname = varname
    
        def read(self, args=slice(None, None, None)):
            """Read a data slice. Args is a tuple of slice objects (e.g.
            numpy.index_exp). The default corresponds to [:], i.e. all data
            will be read.
            """
            with nc.Dataset(self.filename, "r") as f:
                values = f.variables[self.varname][args]
            return values
    
    
    class Values(object):
    
        def __init__(self, values=None, reader=None):
            """Initialize Values. You can either pass numerical (or other) values,
            preferrably as numpy array, or a reader instance which will read the
            values on demand. The reader must have a read(args) method, where
            args is a tuple of slices. If no args are given, all data should be
            returned.
            """
            if values is not None:
                self._values = np.array(values)
            self.reader = reader
    
        def __getattr__(self, name):
            """This is only be called if attribute name is not present.
            Here, the only attribute we care about is _values.
            Self.reader should always be defined.
            This method is necessary to allow access to variable.values without
            a slicing index. If only __getitem__ were defined, one would always
            have to write variable.values[:] in order to make sure that something
            is returned.
            """
            print ">>> in __getattr__, trying to access ", name
            if name == "_values":
                print ">>> calling reader and reading all values..."
                self._values = self.reader.read()
            return self._values
    
        def __getitem__(self, args):
            print "in __getitem__"
            if not "_values" in self.__dict__:
                values = self.reader.read(args)
                print ">>> read from file. Shape = ", values.shape
                if args == slice(None, None, None):
                    self._values = values  # all data read, store in memory
                return values
            else:
                print ">>> read from memory. Shape = ", self._values[args].shape
                return self._values[args]
    
        def __repr__(self):
            return self._values.__repr__()
    
        def __str__(self):
            return self._values.__str__()
    
    
    class var(object):
    
        def __init__(self, name=VARNAME, filename=FILENAME, values=None):
            self.name = name
            self.values = Values(values, Reader(filename, name))
    
    
    if __name__ == "__main__":
        # define a variable and access all data first
        # this will read the entire array and save it in memory, so that
        # subsequent access with or without index returns data from memory
        a = var("a", filename=FILENAME)
        print "1: a.values = ", a.values
        print "2: a.values[-1] = ", a.values[-1]
        print "3: a.values = ", a.values
        # define a second variable, where we access a data slice first
        # In this case the Reader only reads the slice and no data are stored
        # in memory. The second access indexes the complete array, so Reader
        # will read everything and the data will be stored in memory.
        # The last access will then use the data from memory.
        b = var("b", filename=FILENAME)
        print "4: b.values[0:3] = ", b.values[0:3]
        print "5: b.values[:] = ", b.values[:]
        print "6: b.values[5:8] = ",b.values[5:8]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-12-15
      • 2010-10-19
      • 2022-11-16
      • 2017-04-12
      • 2018-01-17
      • 1970-01-01
      相关资源
      最近更新 更多