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?