Gary Fixler
Gary Fixler

Reputation: 6028

Python: can a class instance evaluate to any type?

A class can act as a string through its __str__ method, or as a function via its __call__ method. Can it act as, say, a list, or a tuple?

class A (object):
    def __???__ (self):
        return (1, 2, 3)

>>> a = A()
>>> a * 3
(1, 2, 3, 1, 2, 3, 1, 2, 3)

EDIT...

Here's a better example to help clarify the above.

class Vector (object):
    def __init__ (self):
        self.vec = (1,2,3)
    def __???__ (self):
        # something like __repr__; non-string
        return self.vec

class Widget (object):
    def __init__ (self):
        self.vector = Vector()

>>> w = Widget()
>>> w.vector
(1, 2, 3) # not a string representation (at least, before being repr'd here)

Basically, I want something like __repr__ that doesn't return a string, but returns a tuple (or list) when I simply invoke the name pointing to the Vector instance, but I don't want to lose the rest of the abilities in the instance, like access to other properties and methods. I also don't want to have to use w.vector.vec to get to the data. I want vector to act like a tuple attribute of w, while still being able to do something like w.vector.whatever(), or overriding __mul__ so I can scale the vector via w.vector * 5. Possible?

Upvotes: 0

Views: 1028

Answers (3)

Russell Borogove
Russell Borogove

Reputation: 19037

Depending on what your goal is, you can create a class that inherits from built-in classes like list or tuple:

>>> class A(tuple):
...     def speak(self):
...         print "Bark!"
... 
>>> a = A((1,2,3)) # extra parens needed to distinguish single tuple arg from 3 scalar args
>>> a * 3
(1, 2, 3, 1, 2, 3, 1, 2, 3)
>>> a.speak()
Bark!

Given your Vector use case, subclassing tuple might well do the trick.

import math

class Vector(tuple):
    def magnitude(self):
        return math.sqrt( self[0]*self[0]+self[1]*self[1]+self[2]*self[2] )

Upvotes: 3

Li-aung Yip
Li-aung Yip

Reputation: 12486

For the specific behaviour in your example (A*3 gives three concatenated copies of the data in A) you want to implement the __mul__() operator.

For example, these are equivalent:

>>> a = [1,2,3]
>>> a*3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> a.__mul__(3)
[1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> 

More generally, if you want to implement a sequence type you have to implement all of the operations defined for sequence types. You have to define -

  • what A[3] means (__getitem__(), __setitem__())
  • what A[1:10] means (__getslice__())
  • what for item in A: means (__iter__())

and so on.

Here's the full list of methods defined on lists:

>>> pprint.pprint(dict(list.__dict__))
{'__add__': <slot wrapper '__add__' of 'list' objects>,
 '__contains__': <slot wrapper '__contains__' of 'list' objects>,
 '__delitem__': <slot wrapper '__delitem__' of 'list' objects>,
 '__delslice__': <slot wrapper '__delslice__' of 'list' objects>,
 '__doc__': "list() -> new empty list\nlist(iterable) -> new list initialized from iterable's items",
 '__eq__': <slot wrapper '__eq__' of 'list' objects>,
 '__ge__': <slot wrapper '__ge__' of 'list' objects>,
 '__getattribute__': <slot wrapper '__getattribute__' of 'list' objects>,
 '__getitem__': <method '__getitem__' of 'list' objects>,
 '__getslice__': <slot wrapper '__getslice__' of 'list' objects>,
 '__gt__': <slot wrapper '__gt__' of 'list' objects>,
 '__hash__': None,
 '__iadd__': <slot wrapper '__iadd__' of 'list' objects>,
 '__imul__': <slot wrapper '__imul__' of 'list' objects>,
 '__init__': <slot wrapper '__init__' of 'list' objects>,
 '__iter__': <slot wrapper '__iter__' of 'list' objects>,
 '__le__': <slot wrapper '__le__' of 'list' objects>,
 '__len__': <slot wrapper '__len__' of 'list' objects>,
 '__lt__': <slot wrapper '__lt__' of 'list' objects>,
 '__mul__': <slot wrapper '__mul__' of 'list' objects>,
 '__ne__': <slot wrapper '__ne__' of 'list' objects>,
 '__new__': <built-in method __new__ of type object at 0x1E1DACA8>,
 '__repr__': <slot wrapper '__repr__' of 'list' objects>,
 '__reversed__': <method '__reversed__' of 'list' objects>,
 '__rmul__': <slot wrapper '__rmul__' of 'list' objects>,
 '__setitem__': <slot wrapper '__setitem__' of 'list' objects>,
 '__setslice__': <slot wrapper '__setslice__' of 'list' objects>,
 '__sizeof__': <method '__sizeof__' of 'list' objects>,
 'append': <method 'append' of 'list' objects>,
 'count': <method 'count' of 'list' objects>,
 'extend': <method 'extend' of 'list' objects>,
 'index': <method 'index' of 'list' objects>,
 'insert': <method 'insert' of 'list' objects>,
 'pop': <method 'pop' of 'list' objects>,
 'remove': <method 'remove' of 'list' objects>,
 'reverse': <method 'reverse' of 'list' objects>,
 'sort': <method 'sort' of 'list' objects>}

Upvotes: 2

Joel Cornett
Joel Cornett

Reputation: 24788

The class doesn't act as a string when you call str. It creates and returns a NEW string object. Basically when you call str(something) on an object, this is what really happens:

a = str(someObject)

a = someObject.__str__()

So the str function can basically be thought of as doing this:

def str(variable):
    return variable.__str__()

The same is true when you call list(), tuple(), set() and so on. If what I think you're asking is correct:

tuple(), list(), and set() all call the __iter__() method of a class, so what you'd want to do is:

class MyClass(object):
    ...
    def __iter__(self):
        ...
        return myIterable

Upvotes: 0

Related Questions