8

I tried a bit of code but it seems to cause issues:

class Page:
    cache = []


    """ Return cached object """
    def __getCache(self, title):
        for o in Page.cache:
            if o.__searchTerm == title or o.title == title:
                return o
        return None


    """ Initilize the class and start processing """
    def __init__(self, title, api=None):
        o = self.__getCache(title)
        if o:
            self = o
            return
        Page.cache.append(self)

        # Other init code
        self.__searchTerm = title
        self.title = self.someFunction(title)

Then I try:

a = Page('test')
b = Page('test')

print a.title # works
print b.title # AttributeError: Page instance has no attribute 'title'

Whats wrong with this bit of code? Why wont it work? Is there a way to make it work? If not how do I go about easily and transparently to the end user caching objects?

Justin808
  • 20,859
  • 46
  • 160
  • 265

3 Answers3

10

if you want to manipulate the creation, you need to change __new__.

>>> class Page(object):
...     cache = []
...     """ Return cached object """
...     @classmethod
...     def __getCache(cls, title):
...         for o in Page.cache:
...             if o.__searchTerm == title or o.title == title:
...                 return o
...         return None
...     """ Initilize the class and start processing """
...     def __new__(cls, title, api=None):
...         o = cls.__getCache(title)
...         if o:
...             return o
...         page = super(Page, cls).__new__(cls)
...         cls.cache.append(page)
...         page.title = title
...         page.api = api
...         page.__searchTerm = title
...         # ...etc
...         return page
... 
>>> a = Page('test')
>>> b = Page('test')
>>> 
>>> print a.title # works
test
>>> print b.title
test
>>> 
>>> assert a is b
>>> 

EDIT: using __init__:

>>> class Page(object):
...     cache = []
...     @classmethod
...     def __getCache(cls, title):
...         """ Return cached object """
...         for o in Page.cache:
...             if o.__searchTerm == title or o.title == title:
...                 return o
...         return None
...     def __new__(cls, title, *args, **kwargs):
...         """ Initilize the class and start processing """
...         existing = cls.__getCache(title)
...         if existing:
...             return existing
...         page = super(Page, cls).__new__(cls)
...         return page
...     def __init__(self, title, api=None):
...         if self in self.cache:
...             return
...         self.cache.append(self)
...         self.title = title
...         self.api = api
...         self.__searchTerm = title
...         # ...etc
... 
>>> 
>>> a = Page('test')
>>> b = Page('test')
>>> 
>>> print a.title # works
test
>>> print b.title
test
>>> assert a is b
>>> assert a.cache is Page.cache
>>> 
dnozay
  • 23,846
  • 6
  • 82
  • 104
  • @BrendanLong, you can use `__init__` if you like if you have a way to find out when to skip it. – dnozay Oct 24 '12 at 23:12
4

You cannot really change the instance of a created object once it has been created. When setting self to something else, all you do is change the reference that variable points to, so the actual object is not affected.

This also explains why the title attribute is not there. You return as soon as you change the local self variable, preventing the current instance to get the title attribute initialized (not to mention that self at that point wouldn’t point to the right instance).

So basically, you cannot change the object during its initialization (in __init__) as at that point it has already been created, and assigned to the variable. A constructor call like a = Page('test') is actually the same as:

a = Page.__new__('test')
a.__init__('test')

So as you can see, the __new__ class constructor is called first, and that’s actually who’s responsible for creating the instance. So you could overwrite the class’ __new__ method to manipulate the object creation.

A generally preferred way however is to create a simple factory method, like this:

@classmethod
def create (cls, title, api = None):
    o = cls.__getCache(title)
    if o:
        return o
    return cls(title, api)
poke
  • 369,085
  • 72
  • 557
  • 602
1

self is a normal local variable, so setting self = .. just changes what the self variable points to in that function. It doesn't change the actual object.

See: Is it safe to replace a self object by another object of the same type in a method?

To do what you want, you could use a static function as a factory:

class Page:
    cache = []


    """ Return cached object """
    @staticmethod
    def create(title):
        for o in Page.cache:
            if o.__searchTerm == title or o.title == title:
                return o
        return Page(title)

    """ Initilize the class and start processing """
    def __init__(self, title, api=None):
        Page.cache.append(self)

        # Other init code
        self.__searchTerm = title
        self.title = title


a = Page.create('test')
b = Page.create('test')

print a.title
print b.title
Community
  • 1
  • 1
Brendan Long
  • 53,280
  • 21
  • 146
  • 188
  • Is there a way to make it work? If not how do I go about easily and transparently to the end user caching objects? – Justin808 Oct 24 '12 at 17:18
  • Rather than setting self, tried `return o` but then I get yelled at by python that __init__ should return None – Justin808 Oct 24 '12 at 17:19
  • @Justin808 Apparently not. I was looking at using `__new__`, but apparently that won't work (see poke's answer). – Brendan Long Oct 24 '12 at 17:24
  • Thanks, I guess I'll have to go with a factory. I was hoping I could be transparent to the user, but doesn't look like python will allow that. – Justin808 Oct 24 '12 at 17:30
  • 1
    Is there any way to make `__init__` private so `o = Page('test')` called by the user would fail? – Justin808 Oct 24 '12 at 17:31
  • @Justin808 See the new answer. They get around how `__init__()` always gets called by just not having one. I wish I had looked into it more. – Brendan Long Oct 24 '12 at 17:34