Python class accessible by iterator and index - python

Might be a n00b question, but I currently have a class that implements an iterator so I can do something like
for i in class():
but I want to be able to access the class by index as well like
class()[1]
How can I do that?
Thanks!

The current accepted answer from #Ignacio Vazquez-Abrams is sufficient. However, others interested in this question may want to consider inheriting their class from an abstract base class (ABC) (such as those found in the standard module collections.abc). This does a number of things (there are probably others as well):
ensures that all of the methods you need to treat your object "like a ____" are there
it is self-documenting, in that someone reading your code is able to instantly know that you intend your object to "act like a ____".
allows isinstance(myobject,SomeABC) to work correctly.
often provides methods auto-magically so we don't have to define them ourselves
(Note that, in addition to the above, creating your own ABC can allow you to test for the presence of a specific method or set of methods in any object, and based on this to declare that object to be a subclass of the ABC, even if the object does not inherit from the ABCdirectly. See this answer for more information.)
Example: implement a read-only, list-like class using ABC
Now as an example, let's choose and implement an ABC for the class in the original question. There are two requirements:
the class is iterable
access the class by index
Obviously, this class is going to be some kind of collection. So what we will do is look at our menu of collection ABC's to find the appropriate ABC (note that there are also numeric ABCs). The appropriate ABC is dependent upon which abstract methods we wish to use in our class.
We see that an Iterable is what we are after if we want to use the method __iter__(), which is what we need in order to do things like for o in myobject:. However, an Iterable does not include the method __getitem__(), which is what we need in order to do things like myobject[i]. So we'll need to use a different ABC.
On down the collections.abc menu of abstract base classes, we see that a Sequence is the simplest ABC to offer the functionality we require. And - would you look at that - we get Iterable functionality as a mixin method - which means we don't have to define it ourselves - for free! We also get __contains__, __reversed__, index, and count. Which, if you think about it, are all things that should be included in any indexed object. If you had forgotten to include them, users of your code (including, potentially, yourself!) might get pretty annoyed (I know I would).
However, there is a second ABC that also offers this combination of functionality (iterable, and accessible by []): a Mapping. Which one do we want to use?
We recall that the requirement is to be able to access the object by index (like a list or a tuple), i.e. not by key (like a dict). Therefore, we select Sequence instead of Mapping.
Sidebar: It's important to note that a Sequence is read-only (as is a Mapping), so it will not allow us to do things like myobject[i] = value, or random.shuffle(myobject). If we want to be able do things like that, we need to continue down the menu of ABCs and use a MutableSequence (or a MutableMapping), which will require implementing several additional methods.
Example Code
Now we are able to make our class. We define it, and have it inherit from Sequence.
from collections.abc import Sequence
class MyClass(Sequence):
pass
If we try to use it, the interpreter will tell us which methods we need to implement before it can be used (note that the methods are also listed on the Python docs page):
>>> myobject = MyClass()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyClass with abstract methods __getitem__, __len__
This tells us that if we go ahead and implement __getitem__ and __len__, we'll be able to use our new class. We might do it like this in Python 3:
from collections.abc import Sequence
class MyClass(Sequence):
def __init__(self,L):
self.L = L
super().__init__()
def __getitem__(self, i):
return self.L[i]
def __len__(self):
return len(self.L)
# Let's test it:
myobject = MyClass([1,2,3])
try:
for idx,_ in enumerate(myobject):
print(myobject[idx])
except Exception:
print("Gah! No good!")
raise
# No Errors!
It works!

Implement both __iter__() and __getitem__() et alia methods.

Related

Python: How to annotate a variable number of iterable attributes?

I have a class family for which I need to be able to iterate through attributes of type: Metric.
The family consists of an abstract base class parent and child classes. The child classes will all have varying number of class attributes of type Metric, and they inherit an __iter__ method from the parent class that allows me to iterate through the attributes.
I am using iterable attributes rather than a dict because I want my objects to be typed, but I need to be able to call metrics in sequence, and by name.
So I need to be able to do:
Metrics.metric_1
and
for metric in Metrics:
My question is, how do I correctly hint in the base class that there are a variable number of attributes of the same type?
I'm currently using a couple of attribute hints with an ellipsis:
class MetricsBase(ABC):
metric_1: Metric
metric_2: Metric
...
#classmethod
def __iter__(cls):
for attr, value in cls.__dict__.items():
if not attr.startswith("__"):
yield value
class MetricChild(MetricsBase):
metric_1 = Metric(x)
metric_2 = Metric(y)
metric_3 = Metric(z)
But I'm not sure if this is pythonic or correct, and wondering if there is a neater way of doing this.
Many thanks for any input!
I am not answering how to "fix" static type checking on that.
That said, this is ok as Python code, hence "pythonic" . the problem is that you want to use static type checking on it - and you are using a dynamic meta programming technique there. Static type checking is not meant to check this (in a way of saying, it can only handle a small subset of what would be "pythonic"). Maybe there is a way to "solve" this - but if you can, just mark the static type checkers to skip that, and spare hours yourself hours of meaningless work (since it won't change how the code works)
More important than that, that __iter__ method won't work for the class itself, regardless of you marking it as a #classmethod. (It will work fot the instances, despite you doing so, though). If you want to iterate on the class, you will have to resort to a metaclass:
import abc
class MetricMeta(abc.ABCMeta):
def __iter__(cls):
# this will make _instances of this metaclass__ iterable
for attr, value in cls.__dict__.items():
if not attr.startswith("__"):
yield value
class MetricsBase(metaclass=MetricsMeta):
metric_1: Metric
metric_2: Metric
...
Type chekers actually, should, supposedly, not need one to expliclty annotate all variables, reducing Python to a subset of Pascal or the like. If you simply type in your class attributes in each subclass, attributing a Metric instance to then it should work, without the need to explictly annotate each one with a :Metric.
They will certainly complain when you try to iterate over a class with a statement like for metric in Metrics:, but that is easily resolvable by asserting to it explicitly that the class is iterable, using typing.cast. No tool (at least not yet) will be able to "see" that the metaclass you are using feature an __iter__ method that enables the class itself to be iterable.
from typing import cast
from collections.abc import Iterable
...
for metric in cast(Iterable, metrics):
...

Is it possible to type hint exclusively a class object but exclude subclass objects?

I would like to exclusively type hint an argument to a specific class but exclude any subclasses.
class A:
pass
class B(A):
pass
def foo(obj: A):
pass
foo(B()) # I'd like the type checker to warn me here that it expects A, not B
Is this possible? and if so, how?
(bonus points if you can tell me what I would call this. Googling wasn't helpful, but I'm afraid I'm using the wrong terminology to describe this)
No, this is not possible to do.
Fundamentally, the Python typing ecosystem assumes that you are following the Liskov substitution principle -- assumes that it is always safe to substitute a subclass in places designed to handle the parent.
The fact that it permits you to pass in instances of B in addition to instances of A in your code snippet is just one example of this principle in play.
So if your subclass B is designed not to follow the Liskov substitution principle, that probably it wasn't ever really a "kind of" A to begin with and shouldn't be subclassing it.
You could fix this by either adjusting your code so B does properly follow Liskov or by making B stop subclassing A and instead use composition instead of inheritance as a mechanism for code reuse. That is, make B keep an instance of A as a field and use it as appropriate.
And if you run into a rare case where it's legitimately not possible to ever subclass A without breaking Liskov, something you could do to prevent people from accidentally subclassing it would be to explicitly mark A as being final:
from typing import final
# If you want to support Python 3.7 or earlier, pip-install 'typing_extensions'
# and do 'from typing_extensions import final' instead
#final
class A: pass
class B(A): pass
This should make your type checker report a "B cannot subclass A" error on the definition of B. And if you fix that error by changing/deleting B, the call to foo(B()) should also naturally fail to type-check.

Python collections.MappingView

I was checking out the very nice collections library and more specific the Abstract Base Classes (ABC). One I could not get my head around: the MappingView.
What is its use? What is its advantage over Sized? An example perhaps?
Documentation says its base class is Sized, but on the other hand there is a len mixin... So do we have to implement len, or not?
For the documentation, see collections
MappingView is essentially the base class for user defined views. It implements the Sized interface by providing the __len__ attribute which is the length of its _mapping member, so if this implementation is fine for you, you do not need to implement your own __len__.
It holds common code for KeysView, ItemsView and ValuesView. These last classes can be used wherever a view like my_dict.keys(), my_dict.items() or my_dict.values() would be expected. If you create a new user defined kind of data and want to create a view that can be neither compared to keys, values or items, then you could subclass MappingView directly and implement differently the __contains__ and __iter__ functions.

Creating a Java-like interface in Python

I have multiple classes that have common functionality, which is implemented differently for various reasons. I'm using all these classes in the same context, but I don't know which one will be used until run time. I want to instantiate different classes, but use the same line of code to invoke the common functionality.
So, I've though of using inheritance (obviously), and now my code looks like this:
class Base():
def common(self):
return "abstract"
class A(Base):
def common(self):
return "A"
class B(Base):
def common(self):
return "B"
I want to be able to instantiate any of the derived classes as Base (so that I don't have to make a special case and checks for every new class I add) and call it's common method and get the desired "A" or "B" result.
Python is a dynamically typed language with duck typing. Unlike statically typed languages like Java, there is no need to have interfaces. If you have an object, you can just call any method of it in any way you want. The interpreter will try to find such a method and call it and the worst that can happen is that you get an exception at run time. That’s how dynamically typed languages work.
With duck typing, just having the methods you want in an object, you can expect the object to be of a type you expect it to be. There is no need to check the inheritance of it.
So in your case, you can even get rid of Base (unless you of course want to provide a default implementation). And then you just get an object an call obj.common(). If you want to be on the very safe side, you can also check if the method exists first:
if hasattr(obj, 'common'):
obj.common()
Alternatively, if you keep your base type around, you could also do the check for inheritance if you want:
if isinstance(obj, Base):
obj.common()
But in general, you would just call the method and check if it works: It’s easier to ask for forgiveness than permission. So you would do this:
try:
obj.common()
except AttributeError:
print('Oops, object wasn’t right after all')

Emulating membership-test in Python: delegating __contains__ to contained-object correctly

I am used to that Python allows some neat tricks to delegate functionality to other objects. One example is delegation to contained objects.
But it seams, that I don't have luck, when I want to delegate __contains __:
class A(object):
def __init__(self):
self.mydict = {}
self.__contains__ = self.mydict.__contains__
a = A()
1 in a
I get:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: argument of type 'A' is not iterable
What I am making wrong? When I call a.__contains __(1), everything goes smooth. I even tried to define an __iter __ method in A to make A more look like an iterable, but it did not help. What I am missing out here?
Special methods such as __contains__ are only special when defined on the class, not on the instance (except in legacy classes in Python 2, which you should not use anyway).
So, do your delegation at class level:
class A(object):
def __init__(self):
self.mydict = {}
def __contains__(self, other):
return self.mydict.__contains__(other)
I'd actually prefer to spell the latter as return other in self.mydict, but that's a minor style issue.
Edit: if and when "totally dynamic per-instance redirecting of special methods" (like old-style classes offered) is indispensable, it's not hard to implement it with new-style classes: you just need each instance that has such peculiar need to be wrapped in its own special class. For example:
class BlackMagic(object):
def __init__(self):
self.mydict = {}
self.__class__ = type(self.__class__.__name__, (self.__class__,), {})
self.__class__.__contains__ = self.mydict.__contains__
Essentially, after the little bit of black magic reassigning self.__class__ to a new class object (which behaves just like the previous one but has an empty dict and no other instances except this one self), anywhere in an old-style class you would assign to self.__magicname__, assign to self.__class__.__magicname__ instead (and make sure it's a built-in or staticmethod, not a normal Python function, unless of course in some different case you do want it to receive the self when called on the instance).
Incidentally, the in operator on an instance of this BlackMagic class is faster, as it happens, than with any of the previously proposed solutions -- or at least so I'm measuring with my usual trusty -mtimeit (going directly to the built-in method, instead of following normal lookup routes involving inheritance and descriptors, shaves a bit of the overhead).
A metaclass to automate the self.__class__-per-instance idea would not be hard to write (it could do the dirty work in the generated class's __new__ method, and maybe also set all magic names to actually assign on the class if assigned on the instance, either via __setattr__ or many, many properties). But that would be justified only if the need for this feature was really widespread (e.g. porting a huge ancient Python 1.5.2 project that liberally use "per-instance special methods" to modern Python, including Python 3).
Do I recommend "clever" or "black magic" solutions? No, I don't: almost invariably it's better to do things in simple, straightforward ways. But "almost" is an important word here, and it's nice to have at hand such advanced "hooks" for the rare, but not non-existent, situations where their use may actually be warranted.

Categories

Resources