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.