如何在Python中更改作为类属性实现的列表的返回/输入类型?

0 投票
4 回答
1075 浏览
提问于 2025-04-17 10:57

编辑(对问题进行了全面的重新表述,因为原来的版本(见“原始版本”,稍后)容易引起误解):

这里的情况是:我有一个对象,它里面有一个类型为 <class 'One'> 的对象列表。我想访问这个列表,但希望使用类型为 <class 'Two'> 的对象,后者是 <class 'One'> 的升级版。

背景(1):

  • One 可能是一个可以通过 ORM(对象关系映射)轻松存储的对象。ORM 会根据数据模型处理这个列表。
  • Two 是一个类似于 One 的对象,但它增加了许多功能或者改变了访问方式。

背景(2):

  • 我正在尝试解决一个与 SQLAlchemy 相关的问题,我在 这里 提问过。因此,当前问题的答案也可能是解决那个问题的方案,涉及 SQLAlchemy 列表的返回/输入类型的改变。

这里有一些代码来说明:

import numpy as np

class One(object):
  """
  Data Transfere Object (DTO)
  """
  def __init__(self, name, data):
    assert type(name) == str
    assert type(data) == str
    self.name = name
    self.data = data

  def __repr__(self):
    return "%s(%r, %r)" %(self.__class__.__name__, self.name, self.data)

class Two(np.ndarray):
  _DTO = One
  def __new__(cls, name, data):
    dto = cls._DTO(name, data)
    return cls.newByDTO(dto)

  @classmethod
  def newByDTO(cls, dto):
    obj = np.fromstring(dto.data, dtype="float", sep=',').view(cls)
    obj.setflags(write=False) # Immutable
    obj._dto = dto
    return obj

  @property
  def name(self):
    return self._dto.name

class DataUI(object):
  def __init__(self, list_of_ones):
    for one in list_of_ones:
      assert type(one) == One
    self.list_of_ones = list_of_ones

if __name__ == '__main__':
  o1 = One('first object', "1, 3.0, 7, 8,1")
  o2 = One('second object', "3.7, 8, 10")
  my_data = DataUI ([o1, o2])

如何实现一个 list_of_twos,它基于 list_of_ones 进行操作,但给用户提供一个元素类型为 Two 的列表:

type (my_data.list_of_twos[1]) == Two
>>> True
my_data.list_of_twos.append(Two("test", "1, 7, 4.5"))
print my_data.list_of_ones[-1]
>>> One('test', '1, 7, 4.5')

问题的原始版本:

这里是问题的一个示例:

class Data(object):
    def __init__(self, name, data_list):
        self.name = name
        self.data_list = data_list

if __name__ == '__main__':
    my_data = Data ("first data set", [0, 1, 1.4, 5])

我想通过另一个列表(例如 my_data.data_np_list)访问 my_data.data_list,这个列表处理的元素类型不同(例如,作为 numpy.ndarray):

>>> my_data.data_np_list[1]
array(1)
>>> my_data.data_np_list.append(np.array(7))
>>> print my_data.data_list
[0, 1, 1.4, 5, 7]

4 个回答

0

不,想要这样做并不简单(或者说根本做不到,而且还会失去使用numpy.array时可能获得的性能提升)。你想要的其实是两种完全不同的结构相互对应,这就意味着需要同时存储这两种结构,并且在其中一个发生变化时,还要把这个变化同步到另一个结构上。要实现这一点,唯一的办法就是对listnumpy.array进行子类化,以便观察到它们的变化。

0

你应该使用一个属性

class Data(object):
    def __init__(self, name, data_list):
        self.name = name
        self.data_list = data_list

    @property
    def data_np_list(self):
        return numpy.array(self.data_list)

if __name__ == '__main__':
    my_data = Data ("first data set", [0, 1, 1.4, 5])
    print my_data.data_np_list

补充说明: numpy使用的是连续的内存区域,而Python的列表则是链表结构。你不能同时使用这两种结构,因为这样会影响性能,导致整个程序变得没用。它们是不同的数据结构。

0

我刚想到一个解决方案,可以通过一个叫做 ListView 的类来实现列表的视图,这个类需要以下几个参数:

  • raw_list: 这是一个包含 One 对象的列表
  • raw2new: 这是一个函数,用来把 One 对象转换成 Two 对象
  • new2raw: 这是一个函数,用来把 Two 对象转换回 One 对象

下面是代码:

class ListView(list):
  def __init__(self, raw_list, raw2new, new2raw):
    self._data = raw_list
    self.converters = {'raw2new': raw2new,
        'new2raw': new2raw}

  def __repr__(self):
    repr_list = [self.converters['raw2new'](item) for item in self._data]
    repr_str = "["
    for element in repr_list:
      repr_str += element.__repr__() + ",\n "
    repr_str = repr_str[:-3] + "]"
    return repr_str

  def append(self, item):
    self._data.append(self.converters['new2raw'](item))

  def pop(self, index):
    self._data.pop(index)

  def __getitem__(self, index):
    return self.converters['raw2new'](self._data[index])

  def __setitem__(self, key, value):
    self._data.__setitem__(key, self.converters['new2raw'](value))

  def __delitem__(self, key):
    return self._data.__delitem__(key)

  def __getslice__(self, i, j):
    return ListView(self._data.__getslice__(i,j), **self.converters)

  def __contains__(self, item):
    return self._data.__contains__(self.converters['new2raw'](item))

  def __add__(self, other_list_view):
    assert self.converters == other_list_view.converters
    return ListView(
        self._data + other_list_view._data,
        **self.converters
        )

  def __len__(self):
    return len(self._data)

  def __eq__(self, other):
    return self._data == other._data

  def __iter__(self):
    return iter([self.converters['raw2new'](item) for item in self._data])

现在,DataUI 应该看起来像这样:

class DataUI(object):
  def __init__(self, list_of_ones):
    for one in list_of_ones:
      assert type(one) == One
    self.list_of_ones = list_of_ones
    self.list_of_twos = ListView(
        self.list_of_ones,
        Two.newByDTO,
        Two.getDTO
        )

为此,Two 需要以下方法:

def getDTO(self):
  return self._dto

整个示例现在看起来如下:

import unittest
import numpy as np

class ListView(list):
  def __init__(self, raw_list, raw2new, new2raw):
    self._data = raw_list
    self.converters = {'raw2new': raw2new,
        'new2raw': new2raw}

  def __repr__(self):
    repr_list = [self.converters['raw2new'](item) for item in self._data]
    repr_str = "["
    for element in repr_list:
      repr_str += element.__repr__() + ",\n "
    repr_str = repr_str[:-3] + "]"
    return repr_str

  def append(self, item):
    self._data.append(self.converters['new2raw'](item))

  def pop(self, index):
    self._data.pop(index)

  def __getitem__(self, index):
    return self.converters['raw2new'](self._data[index])

  def __setitem__(self, key, value):
    self._data.__setitem__(key, self.converters['new2raw'](value))

  def __delitem__(self, key):
    return self._data.__delitem__(key)

  def __getslice__(self, i, j):
    return ListView(self._data.__getslice__(i,j), **self.converters)

  def __contains__(self, item):
    return self._data.__contains__(self.converters['new2raw'](item))

  def __add__(self, other_list_view):
    assert self.converters == other_list_view.converters
    return ListView(
        self._data + other_list_view._data,
        **self.converters
        )

  def __len__(self):
    return len(self._data)

  def __iter__(self):
    return iter([self.converters['raw2new'](item) for item in self._data])

  def __eq__(self, other):
    return self._data == other._data


class One(object):
  """
  Data Transfere Object (DTO)
  """
  def __init__(self, name, data):
    assert type(name) == str
    assert type(data) == str
    self.name = name
    self.data = data

  def __repr__(self):
    return "%s(%r, %r)" %(self.__class__.__name__, self.name, self.data)


class Two(np.ndarray):
  _DTO = One
  def __new__(cls, name, data):
    dto = cls._DTO(name, data)
    return cls.newByDTO(dto)

  @classmethod
  def newByDTO(cls, dto):
    obj = np.fromstring(dto.data, dtype="float", sep=',').view(cls)
    obj.setflags(write=False) # Immutable
    obj._dto = dto
    return obj

  @property
  def name(self):
    return self._dto.name

  def getDTO(self):
    return self._dto


class DataUI(object):
  def __init__(self, list_of_ones):
    for one in list_of_ones:
      assert type(one) == One
    self.list_of_ones = list_of_ones
    self.list_of_twos = ListView(
        self.list_of_ones,
        Two.newByDTO,
        Two.getDTO
        )


class TestListView(unittest.TestCase):
  def testProperties(self):
    o1 = One('first object', "1, 3.0, 7, 8,1")
    o2 = One('second object', "3.7, 8, 10")
    my_data = DataUI ([o1, o2])

    t1 = Two('third object', "4.8, 8.2, 10.3")
    t2 = Two('forth object', "33, 1.8, 1.0")
    # append:
    my_data.list_of_twos.append(t1)
    # __getitem__:
    np.testing.assert_array_equal(my_data.list_of_twos[2], t1)
    # __add__:
    np.testing.assert_array_equal(
        (my_data.list_of_twos + my_data.list_of_twos)[5], t1)
    # __getslice__:
    np.testing.assert_array_equal(
        my_data.list_of_twos[1:],
        my_data.list_of_twos[1:2] + my_data.list_of_twos[2:]
        )
    # __contains__:
    self.assertEqual(my_data.list_of_twos.__contains__(t1), True)
    # __setitem__:
    my_data.list_of_twos.__setitem__(1, t1),
    np.testing.assert_array_equal(my_data.list_of_twos[1], t1)
    # __delitem__:
    l1 = len(my_data.list_of_twos)
    my_data.list_of_twos.__delitem__(1)
    l2 = len(my_data.list_of_twos)
    self.assertEqual(l1 - 1, l2)
    # __iter__:
    my_data_2 = DataUI ([])
    for two in my_data.list_of_twos:
      my_data_2.list_of_twos.append(two)


if __name__ == '__main__':
  unittest.main()

撰写回答