This is a follow on from this review. Where I am attempting to improve the performance of my rest client.
I have created an container type (as suggested in my previous post) that lazily instantiates object instances from a JSON Array. (Actually a python list of dict's)
The principle behind the class is to store the raw data in the instance.__dict__
as instance._attribute
. When the class' __getattribute__
fails __getattr__
is called which replaces instance._attribute
with instance.attribute
and returns the corresponding item
The __init__
method creates _attribute
's by enumerating the supplied *items
I'm able to simulate a sequence container by overwriting __getitem__
which turns the index into a getattr
call. (It also works with slice's)
I have purposely left of __reversed__
because I believe python will automatically use reversed(range(len(instance)))
to generate reversed index's
I have also left off __bool__
as __len__
is defined
Methods get_id
, get_instrument
, get_instruments
are domain specific to my application.
One caveat is that a helper function create_attribute
must be defined. Which is the function that will 'expand' the data into instances
EDIT
I forgot to mention that the class is meant to be immutable
The code:
class Array(object): """Mixin to denote objects that are sent from OANDA in an array. Also used to correctly serialize objects. """ def __init_subclass__(cls, **kwargs): # Denotes the type the Array contains cls._contains = kwargs.pop('contains') # True get_instrument/s() returns an Array of items. False returns single item cls._one_to_many = kwargs.pop('one_to_many', True) def __init__(self, *items): for index, item in enumerate(items): object.__setattr__(self, f'_{index}', item) def __getattr__(self, item): result = create_attribute(self._contains, self.__getattribute__('_' + item)) object.__setattr__(self, item, result) object.__delattr__(self, '_' + item) return result def __len__(self): return len(self.__dict__) def __iter__(self): def iterator(): for index in range(len(self)): try: yield getattr(self, str(index)) except AttributeError: raise StopIteration return iterator() def __add__(self, other): return self.__class__(*self.__dict__.values(), *other) __radd__ = __add__ def __getitem__(self, item): if isinstance(item, slice): return self.__class__(*[self[index] for index in range(len(self))[item]]) return getattr(self, str(item)) def __delattr__(self, item): raise NotImplementedError def __setattr__(self, key, value): raise NotImplementedError def get_id(self, id_, default=None): try: for value in self: if value.id == id_: return value except AttributeError: pass return default def get_instruments(self, instrument, default=None): # ArrayPosition can only have a One to One relationship between an instrument # and a Position. Though ArrayTrades and others can have a Many to One relationship try: matches = self.__class__(*[value for value in self if value.instrument == instrument]) if matches: return matches except AttributeError: pass return default def get_instrument(self, instrument, default=None): try: for value in self: try: if value.instrument == instrument: return value except AttributeError: if value.name == instrument: return value except AttributeError: pass return default def dataframe(self, json=False, datetime_format=None): """Create a pandas.Dataframe""" return pd.DataFrame(obj.data(json=json, datetime_format=datetime_format) for obj in self)
Console Example:
>>> class LazyLists(Array, contains=list): ... pass ... >>> # must define create_attribute >>> def create_attribute(typ, data): ... return typ(data) ... >>> lazy_lists = LazyLists(*[range(10) for _ in range(2)]) >>> lazy_lists <LazyLists object at 0x000002202BE335F8> >>> len(lazy_lists) 2 >>> lazy_lists.__dict__ {'_0': range(0, 10), '_1': range(0, 10)} >>> lazy_lists[1] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> lazy_lists.__dict__ {'_0': range(0, 10), '1': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]} >>> for i in lazy_lists: print(i) ... [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> lazy_lists.__dict__ {'1': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], '0': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}
I wrote a benchmark to asses if this was worth the effort.
Before previous post
Entire implementation can be found here. I am interested in what you think about the Array
class. How would you have done it better?