Индексировать член класса как список в Python
Предположим, у меня есть простой класс вроде
class Foo:
def __init__(bar):
self.x = transform1(bar)
self.y = transform2(bar)
Теперь меня интересует создание класса, в котором я могу передать итерацию для
bar
в инициализатор и верните экземпляр
Foo
где я могу получить доступ к участникам
x
и
y
как итерации размером
bar
, т.е.
x = [1, 2, 3]
foo = Foo(x)
plt.plot(foo.x, foo.y)
Я знаю, что можно легко сделать
foo = [Foo(elem) for elem in x]
plt.plot([elem.x for elem in foo], [elem.y for elem in foo])
но это кажется многословным и, вероятно, не очень эффективным. Я могу примерно представить решение с множеством структурированных массивов, но мне просто любопытно, есть ли какие-либо стандартные решения для этого. Возможно, используя метаклассы. Поиск в Google в основном давал результаты о том, как получить список всех членов класса или чего-то подобного.
Если можно было бы даже придумать решение, которое позволяет для индексации либо объекта
foo
или его членов, это было бы грандиозно.
2 ответа
If I get this right, you just want to transform all elements in
bar
at once.just do it, instead of one scalar at a time. Just do it:
class Foo:
def __init__(bar):
self.x = [transform1(el) for el in bar]
self.y = [transform2(el) for el in bar]
It is really that simple. There are fancy things if you'd want to run transform1 and transform2 in parallel, using threads or processes, or if you'd like to have all the transforms calculated just as needed, in a lazy way.
But for plotting your graph, a list will do. And there are even no gains in doing it in a single
for
loop instead of two list comprehensions - the time taken in the iteration using
for
itself is negligible.
If you want to be able to index the instance itself, and get back objects that have the needed attributes, what is necessary is to write a class with the
__getitem__
method - and have the objects returned by getitem to have both attributes.
For that you could use a simpler class, representing your scalar, and depending on what you need, this simpler class can be a namedtuple:
from collections import namdtuple
ScalarFoo = namedtuple("ScalarFoo", "x y")
class Foo:
def __init__(bar):
self.x = [transform1(el) for el in bar]
self.y = [transform2(el) for el in bar]
def __getitem__(self, index):
return ScalarFoo(self.x[index], self.y[index])
def __len__(self):
return len(self.x)
(The
__len__
method, combined with the
__getitem__
allows Python to use instances of
Foo
automatically in for-loop iterations)
Now, if you want to get it really interesting, let's suppose your
Foo
class, with the scalar application of the transforms exists as in your question - it is possible to "transform" it so that it can operate with sequences.
Than we get closer to the original
metaclass
исследования - и может быть достигнуто с помощью декоратора классов. Декораторы классов были введены довольно давно, чтобы заменить некоторые виды использования метаклассов.
def autosequence(cls):
"""Transforms the received class into a factory,
so that if a sequence or iterator is passed as the first
argument to it, a new, sequence class is used. If the
resulting class is used in an iteration or as a sequence,
an instance of the original class is returned
"""
class AutoSequence:
def __init__(self, *args, **kw):
self.sequence = list(args[0])
self.other_args = args[1:]
self.kw = kw
def __getitem__(self, index):
return cls(self.sequence[index], *self.other_args, **self.kw)
def __len__(self):
return len(self.sequence)
def __repr__(self):
return f"Lazy sequence of f{cls.__name__} objects with {len(self)} elements"
def factory(*args, **kw):
if args and hasattr(args[0], "__len__") or hasattr(args[0], "__iter__"):
return AutoSequence(*args, **kw)
return cls(*args, **kw)
factory.__name__ = cls.__name__
return factory
def transform1(a):
return a
def transform2(a):
return a ** 2
@autosequence
class Foo:
def __init__(self, bar):
self.x = transform1(bar)
self.y = transform2(bar)
def __repr__(self):
return f"{self.__class__.__name__}({self.x}, {self.y})"
А вот как это ведет себя в интерактивном интерпретаторе:
In [24]: a = Foo([1,2,3])
In [25]: a[2]
Out[25]: Foo(3, 9)
In [26]: Foo(4)
Out[26]: Foo(4, 16)
In [27]: Foo(4).y
Out[27]: 16
In [28]: a[2].y
Out[28]: 9
Вышеупомянутую "фабричную" функцию можно превратить в
__new__
и вставлен в декорированный класс, а затем получившийся декорированный класс будет вести себя как настоящий класс, но особенно если у вас есть интроспективный код и вам нужен
Foo
class, чтобы быть реальным классом, работающим со скалярами, вам лучше вообще иметь два отдельных класса: один создает последовательности, а другой имеет дело со скалярами.
В этом случае вы могли бы просто отключить функцию "factory", настроить "autosequence" для возврата самого класса AutoSequence и использовать его следующим образом:
class Foo:
...
FooSequence = autosequence(Foo)
Вы можете сделать это с помощью одного цикла вместо двух:
class Foo:
def __init__(self, bar):
self.xs = []
self.ys = []
for elem in bar:
self.xs.append(elem.x)
self.ys.append(elem.y)
Вы можете скрыть цикл, используя
map
и
zip
:
class Foo:
def __init__(self, bar):
self.xs, self.ys = zip(*map(lambda e: (e.x, e.y), bar))