Question

So as part of problem 17.6 in "Think Like a Computer Scientist", I've written a class called Kangaroo:

class Kangaroo(object):

    def __init__(self, pouch_contents = []):
        self.pouch_contents = pouch_contents

    def __str__(self):
        '''
        >>> kanga = Kangaroo()
        >>> kanga.put_in_pouch('olfactory')
        >>> kanga.put_in_pouch(7)
        >>> kanga.put_in_pouch(8)
        >>> kanga.put_in_pouch(9)
        >>> print kanga
        "In kanga's pouch there is: ['olfactory', 7, 8, 9]"
        '''

        return "In %s's pouch there is: %s" % (object.__str__(self), self.pouch_contents)

    def put_in_pouch(self, other):
        '''
        >>> kanga = Kangaroo()
        >>> kanga.put_in_pouch('olfactory')
        >>> kanga.put_in_pouch(7)
        >>> kanga.put_in_pouch(8)
        >>> kanga.put_in_pouch(9)
        >>> kanga.pouch_contents
        ['olfactory', 7, 8, 9]
        '''

        self.pouch_contents.append(other)

What's driving me nuts is that I'd like to be able to write a string method that would pass the unit test underneath __str__ as written. What I'm getting now instead is:

In <__main__.Kangaroo object at 0x4dd870>'s pouch there is: ['olfactory', 7, 8, 9]

Bascially, what I'm wondering if there is some function that I can perform on kanga = Kangaroo such that the output of the function is those 5 characters, i.e. function(kanga) -> "kanga".

Any ideas?

Edit: Reading the first answer has made me realize that there is a more concise way to ask my original question. Is there a way to rewrite __init__ such that the following code is valid as written?

>>> somename = Kangaroo()
>>> somename.name
'somename'
Was it helpful?

Solution

I wasn't going to post this but if you only want this for debugging then here you go:

import sys

class Kangaroo(object):
    def __str__(self):
        flocals = sys._getframe(1).f_locals
        for ident in flocals:
            if flocals[ident] is self:
                name = ident
                break
        else:   
            name = 'roo'
        return "in {0}'s pouch, there is {1}".format(name, self.pouch_contents)

kang = Kangaroo()
print kang

This is dependent on CPython (AFAIK) and isn't suitable for production code. It wont work if the instance is in any sort of container and may fail for any reason at any time. It should do the trick for you though.

It works by getting the f_locals dictionary out of the stack frame that represents the namespace where print kang is called. The keys of f_locals are the names of the variables in the frame so we just loop through it and test if each entry is self. If so, we break. If break is not executed, then we didn't find an entry and the loops else clause assigns the value 'roo' as requested.

If you want to get it out of a container of some sort, you need to extend this to look through any containers in f_locals. You could either return the key if it's a dictlike container or the index if it's something like a tuple or list.

OTHER TIPS

To put your request into perspective, please explain what name you would like attached to the object created by this code:

marsupials = []
marsupials.append(Kangaroo()) 

This classic essay by the effbot gives an excellent explanation.

To answer the revised question in your edit: No.

Now that you've come clean in a comment and said that the whole purpose of this naming exercise was to distinguish between objects for debugging purposes associated with your mutable default argument:

In CPython implementations of Python at least, at any given time, all existing objects have a unique ID, which may be obtained by id(obj). This may be sufficient for your debugging purposes. Note that if an object is deleted, that ID (which is a memory address) can be re-used by a subsequently created object.

class Kangaroo(object):

    def __init__(self, pouch_contents=None, name='roo'):
        if pouch_contents is None:
            self.pouch_contents = []  # this isn't shared with all other instances
        else:
            self.pouch_contents = pouch_contents
        self.name = name
    ...

kanga = Kangaroo(name='kanga')

Note that it's good style not to put spaces around = in the arguments

What you want is basically impossible in Python, even with the suggested "hacks". For example, what would the following code print?

>>> kanga1 = kanga2 = kanga3 = Kangaroo()
>>> kanga2.name 
???
>>> kanga3.name
???

or

>>> l = [Kangaroo()]
>>> l[0].name
???

If you want "named" objects, just supply a name to your object

def __init__(self, name):
    self.name = name

More explicit (which we like with Python) and consistent in all cases. Sure you can do something like

>>> foo = Kangaroo("bar")
>>> foo.name
'bar'

but foo will be just one of the possibly many labels the instance has. The name is explicit and permanent. You can even enforce unique naming if you want (while you can reuse a variable as much as you want for different objects)

I hadn't seen aaronasterling's hackish answer when I started working on this, but in any case here's a hackish answer of my own:

class Kangaroo(object):

    def __init__(self, pouch_contents = ()):
        self.pouch_contents = list(pouch_contents)

    def __str__(self):
        if not hasattr(self, 'name'):
            for k, v in globals().iteritems():
                if id(v) == id(self):
                    self.name = k
                    break
                else:
                    self.name = 'roo'                    
        return "In %s's pouch there is: %s" % (self.name, self.pouch_contents)

kanga = Kangaroo()
print kanga

You can break this by looking at it funny (it works as written, but it will fail as part of a doctest), but it works. I'm more concerned with what's possible than with what's practical at this point in my learning experience, so I'm glad to see that there are at least two different ways to do a thing I figured should be possible to do. Even if they're bad ways.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top