I want a class' __init__ to add the object to a list, but I want to prevent redundancy and “if self not in objectlist” isn't working

StackOverflow https://stackoverflow.com/questions/12571748

  •  03-07-2021
  •  | 
  •  

Question

I'm writing a script that checks multiple sources for weather data, then parses them for an some scripts on Scripting Layer for Android. The google API stopped working, so this is a hacked together replacement for the old weather module.

I made a class called "weatherdata", and I want to have all instances of the class add themselves to a list called "weatherobjects", for shenanigans like this:

    for source in weatherobjects:
        source.check()

Here's the catch: each time the function that fetches the weather is called, it causes the objects to run their __init__ method (I think this is technically called a constructor method?) without destroying the objects, or clearing the list. This is intentional. The problem comes when the function is called more than once in the lifetime of the module, and and the objects get redundantly added to the list. This seems like a potential source of a memory leak.

Here's the __init__ method:

class weatherdata():
    def __init__(self, url, unit = 'f'):
        self.url = url
        self.unit = unit
        print(self) #debug statement, please ignore
        if self not in weatherobjects:
            weatherobjects.append(self)
        if self.url.count("yahoo"):
            self.yahoo = True
        else:
            self.yahoo = False
        self.check()

And the troublesome function:

def fetch_weather(location=98661, hl='', weatherobjects= []):
    yahoo = weatherdata(yahoo_url, 'f')
    wunderground = weatherdata(wunderground_url, 'f')
    data = {}
    data['city'] = 'Vancouver'
    data['temperature'] = wunderground.temp
    data['conditions'] = 'foo'
    return data

Here's some shell output for context:

>>> weatherobjects
[<__main__.weatherdata object at 0x01F8BDF0>, <__main__.weatherdata object at 0x02035B70>]
>>> for i in range(3):
...     fetch_weather()
...
{'city': 'Vancouver', 'conditions': 'foo', 'temperature': '66.7'}
{'city': 'Vancouver', 'conditions': 'foo', 'temperature': '66.7'}
{'city': 'Vancouver', 'conditions': 'foo', 'temperature': '66.7'}
>>> weatherobjects
[<__main__.weatherdata object at 0x01F8BDF0>, <__main__.weatherdata object at 0x02035B70>, <__main__.weatherdata object at 0x01FA2E10>, <__main__.weatherdata object at 0x01FA2FB0>, <__main__.weatherdata object at 0x02035C30>, <__main__.weatherdata object at 0x02035E10>, <__main__.weatherdata object at 0x02035DF0>, <__main__.weatherdata object at 0x02035D10>]
>>> len(weatherobjects)
8

As you can see, there's a lot of redundancy in the list. Is it possible to do this in the __init__ method? Or do I need to have a main function do something like weatherobjects.append(foo)?

Was it helpful?

Solution 3

So two years later, I have found the answer while working on something else, and come back to this. Putting aside other issues with my code, I think I should have defined the list in the class (before instantiation), rather than as a global.

My desired behavior was like this example, taken from the Python Docs:

 class Dog:

    tricks = []             # mistaken use of a class variable

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

    def add_trick(self, trick):
        self.tricks.append(trick)

    >>> d = Dog('Fido')
    >>> e = Dog('Buddy')
    >>> d.add_trick('roll over')
    >>> e.add_trick('play dead')
    >>> d.tricks                
    ['roll over', 'play dead']

This is inappropriate because dogs don't have a hivemind, but the behavior is not explicitly disapproved of in the docs, so I'm going to roll with it. I WANT my weatherobjects to have a hivemind.

So instead of something like this,

global weatherobjectslist
class weathersource():
    def __init__(self, weatherobjectlist):
        weatherobjectlist.append(self)

My desired behavior is produced by this:

class weathersource():
    weathersourcelist = []

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

Now, I can check weathersource.weathersourcelist for a list of all my objects.

To put this more generically, here's an example with the same behavior, and some output:

>>> class person():
    people= []
    def __init__(self, name):
        self.people.append(self)
        self.name = name


>>> a,b,c = person('bob'), person('joe'), person('steve')

>>> for individual in person.people:
    print(individual.name)


bob
joe
steve

OTHER TIPS

Your custom classes do not define what it means to be equal. If you were to add a __eq__ method that tells python how to compare two instances, the in test would be able to find duplicates:

def __eq__(self, other):
    if not isinstance(other, self.__class__):
        return NotImplemented
    return self.url == other.url and self.unit == other.unit

To complement the method, you should add a __ne__ method as well:

def __ne__(self, other):
    return not self.__eq__(other)

If your weatherdata objects don't change (are not mutable) after creation, you can add a __hash__ method and store your instances in a set instead of a list. Using a set would speed up the in test. An example __hash__ method would be:

def __hash__(self):
    return hash((self.url, self.unit))

Instead of adding the sources to a list, you could consider a dictionary.

If the weatherdata class could then identify itself (def __repr__(): perhaps) uniquely, you can add the it to a dictionary..

weatherobjects[str(self)] = self
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top