How can I change in Python the return/input type o

2020-08-01 06:34发布

EDIT (complete rephrase of the problem as the original version (see "original version", later) is misleading):

Here is the setting: I have a object which has a list of objects of type <class 'One'>. I would like to access this list but rather work with objects of type <class 'Two'> which is an enriched version of <class 'One'>.

Background (1):

  • One could be an object that can be stored easily via a ORM. The ORM would handle the list depending on the data model
  • Two would be an object like One but enriched by many features or the way it can be accessed

Background (2):

  • I try to solve a SQLAlchemy related question that I asked here. So, the answer to the present question could be also a solution to that question changing return/input type of SQLAlchemy-lists.

Here is some code for illustration:

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])

How to implement a list_of_twos which operates on list_of_ones but provides the user a list with elements of type 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')

Original version of the question:

Here is an illustration of the problem:

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])

I would like to access my_data.data_list via another list (e.g. my_data.data_np_list) that handles list-elements as a different type (e.g. as 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条回答
别忘想泡老子
2楼-- · 2020-08-01 07:04

One solution I just came up with would be to implement a View of the list via class ListView which takes the following arguments:

  • raw_list: a list of One-objects
  • raw2new: a function that converts One-objects to Two-objects
  • new2raw: a function that converts Two-objects to One-objects

Here is a the code:

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])

Now, DataUI has to look something like this:

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
        )

With that, Two needs the following method:

def getDTO(self):
  return self._dto

The entire example would now look like the following:

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()
查看更多
神经病院院长
3楼-- · 2020-08-01 07:07

No, you can't do it easily (or at all without losing any performance gain you might get in using numpy.array). You're wanting two fundamentally different structures mirroring one another, this will mean storing the two and transferring any modifications between the two; subclassing both list and numpy.array to observe modifications will be the only way to do that.

查看更多
男人必须洒脱
4楼-- · 2020-08-01 07:13

Not sure whether your approach is correct.

A property getter would help achieve what you're doing. Here's something similar using arrays instead of numpy.

I've made the array (or in your case numpy data type) the internal representation, with the conversion to list only done on demand with a temporary object returned.

import unittest
import array

class GotAGetter(object):
  """Gets something.
  """
  def __init__(self, name, data_list):
    super(GotAGetter, self).__init__()
    self.name = name
    self.data_array = array.array('i', data_list)

  @property
  def data_list(self):
    return list(self.data_array)


class TestProperties(unittest.TestCase):
  def testProperties(self):
    data = [1,3,5]
    test = GotAGetter('fred', data)
    aString = str(test.data_array)
    lString = str(test.data_list) #Here you go.
    try:
      test.data_list = 'oops'
      self.fail('Should have had an attribute error by now')
    except AttributeError as exAttr:
      self.assertEqual(exAttr.message, "can't set attribute")
    self.assertEqual(aString, "array('i', [1, 3, 5])",
                     "The array doesn't look right")
    self.assertEqual(lString, '[1, 3, 5]',
                     "The list property doesn't look right")

if __name__ == "__main__":
  unittest.main()
查看更多
smile是对你的礼貌
5楼-- · 2020-08-01 07:15

You should use a property

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

edit: numpy use a continous memory area. python list are linked list. You can't have both at the same time without paying a performance cost which will make the whole thing useless. They are different data structures.

查看更多
登录 后发表回答