Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to have a class return a list when list() is called on an instance of it

Tags:

python

I am trying to have a list returned when I call list() on a class. What's the best way to do this.

class Test():
    def __init__(self):
        self.data = [1,2,3]
    def aslist(self):
        return self.data
a = Test()
list(a)
[1,2,3]

I want when list(a) is called for it to run the aslist function and ideally I'd like to implement asdict that works when dict() is called

I'd like to be able to do this with dict, int and all other type casts

like image 234
Marc Frame Avatar asked Jul 07 '18 02:07

Marc Frame


People also ask

How do you access a class list in Python?

Method 2: Another way of finding a list of attributes is by using the module inspect . This module provides a method called getmemebers() that returns a list of class attributes and methods. Method 3: To find attributes we can also use magic method __dict__ . This method only returns instance attributes.

Can a list be a class?

To be more concrete, list is a class object (remember that “class” and “type” are synonymous) - it is the same sort of object that is produced when a class definition is executed.

Can a class method be called on an instance?

To call a class method, put the class as the first argument. Class methods can be can be called from instances and from the class itself. All of these use the same method. The method can use the classes variables and methods.

How do you print a class list in Python?

Printing a list in python can be done is following ways: Using for loop : Traverse from 0 to len(list) and print all elements of the list one by one using a for loop, this is the standard practice of doing it.


2 Answers

Unlike many other languages you might be used to (e.g., C++), Python doesn't have any notion of "type casts" or "conversion operators" or anything like that.

Instead, Python types' constructors are generally written to some more generic (duck-typed) protocol.


The first thing to do is to go to the documentation for whichever constructor you care about and see what it wants. Start in Builtin Functions, even if most of them will link you to an entry in Builtin Types.

Many of them will link to an entry for the relevant special method in the Data Model chapter.


For example, int says:

… If x defines __int__(), int(x) returns x.__int__(). If x defines __trunc__(), it returns x.__trunc__()

You can then follow the link to __int__, although in this case there's not much extra information:

Called to implement the built-in functions complex(), int() and float(). Should return a value of the appropriate type.

So, you want to define an __int__ method, and it should return an int:

class MySpecialZero:
    def __int__(self):
        return 0 

The sequence and set types (like list, tuple, set, frozenset) are a bit more complicated. They all want an iterable:

An object capable of returning its members one at a time. Examples of iterables include all sequence types (such as list, str, and tuple) and some non-sequence types like dict, file objects, and objects of any classes you define with an __iter__() method or with a __getitem__() method that implements Sequence semantics.

This is explained a bit better under the iter function, which may not be the most obvious place to look:

object must be a collection object which supports the iteration protocol (the __iter__() method), or it must support the sequence protocol (the __getitem__() method with integer arguments starting at 0) …

And under __iter__ in the Data Model:

This method is called when an iterator is required for a container. This method should return a new iterator object that can iterate over all the objects in the container. For mappings, it should iterate over the keys of the container.

Iterator objects also need to implement this method; they are required to return themselves. For more information on iterator objects, see Iterator Types.

So, for your example, you want to be an object that iterates over the elements of self.data, which means you want an __iter__ method that returns an iterator over those elements. The easiest way to do that is to just call iter on self.data—or, if you want that aslist method for other reasons, maybe call iter on what that method returns:

class Test():
    def __init__(self):
        self.data = [1,2,3]
    def aslist(self):
        return self.data
    def __iter__(self):
        return iter(self.aslist())

Notice that, as Edward Minnix explained, Iterator and Iterable are separate things. An Iterable is something that can produce an Iterator when you call its __iter__ method. All Iterators are Iterables (they produce themselves), but many Iterables are not Iterators (Sequences like list, for example).


dict (and OrderedDict, etc.) is also a bit complicated. Check the docs, and you'll see that it wants either a mapping (that is, something like a dict) or an iterable of key-value pairs (those pairs themselves being iterables). In this case, unless you're implementing a full mapping, you probably want the fallback:

class Dictable:
    def __init__(self):
        self.names, self.values = ['a', 'b', 'c'], [1, 2, 3]
    def __iter__(self):
        return zip(self.names, self.values)

Almost everything else is easy, like int—but notice that str, bytes, and bytearray are sequences.


Meanwhile, if you want your object to be convertible to an int or to a list or to a set, you might want it to also act a lot like one in other ways. If that's the case, look at collections.abc and numbers, which not provide helpers that are not only abstract base classes (used if you need to check whether some type meets some protocol), but also mixins (used to help you implement the protocol).

For example, a full Sequence is expected to provide most of the same methods as a tuple—about 7 of them—but if you use the mixin, you only need to define 2 yourself:

class MySeq(collections.abc.Sequence):
    def __init__(self, iterable):
        self.data = tuple(iterable)
    def __getitem__(self, idx):
        return self.data[idx]
    def __len__(self):
        return len(self.data)

Now you can use a MySeq almost anywhere you could use a tuple—including constructing a list from it, of course.

For some types, like MutableSequence, the shortcuts help even more—you get 17 methods for the price of 5.


If you want the same object to be list-able and dict-able… well, then you run into a limitation of the design. list wants an iterable. dict wants an iterable of pairs, or a mapping—which is a kind of iterable. So, rather than infinite choices, you only really have two:

  • Iterate keys and implement __getitem__ with those keys for dict, so list gives a list of those keys.
  • Iterate key-value pairs for dict, so list gives a list of those key-value pairs.

Obviously if you want to actually act like a Mapping, you only have one choice, the first one.

The fact that the sequence and mapping protocols overlap has been part of Python from the beginning, inherent in the fact that you can use the [] operator on both of them, and has been retained with every major change since, even though it's made other features (like the whole ABC model) more complicated. I don't know if anyone's ever given a reason, but presumably it's similar to the reason for the extended-slicing design. In other words, making dicts and other mappings a lot easier and more readable to use is worth the cost of making them a little more complicated and less flexible to implement.

like image 128
abarnert Avatar answered Nov 15 '22 15:11

abarnert


This can be done with overloading special methods. You will need to define the __iter__ method for your class, making it iterable. This means anything expecting an iterable (like most collections constructors like list, set, etc.) will then work with your object.

class Test:
    ...

    def __iter__(self):
        return iter(self.data)

Note: You will need to wrap the returned object with iter() so that it is an iterator (there is a difference between iterable and iterator). A list is iterable (can be iterated over), but not an iterator (supports __next__, raises StopIteration when done etc.)

like image 41
Edward Minnix Avatar answered Nov 15 '22 14:11

Edward Minnix