0

I ran into a weird problem today, here is some example code

from collections import defaultdict

class Counter:
    hits = 0
    visitors = set()

    def addHit(self, ip):
        self.hits += 1
        self.visitors.add(ip)

d = defaultdict(Counter)
d['a'].addHit('1.1.1')
d['a'].addHit('2.2.2')
d['b'].addHit('3.3.3')

print d['a'].hits, d['a'].visitors
print d['b'].hits, d['b'].visitors

Expected Result:

2 set(['1.1.1', '2.2.2'])
1 set(['3.3.3'])

Actual Result:

2 set(['1.1.1', '3.3.3', '2.2.2'])
1 set(['1.1.1', '3.3.3', '2.2.2'])

Why are the visitor sets sharing data between what I thought should be separate instances of the Counter class. Shouldn't each input point to a specific instance?

What makes this more difficult to understand is that the hit counter seems to work fine and keep things separate.

Can anyone help me understand what's going on here or how to fix it?

Mark Dunne
  • 401
  • 2
  • 6
  • 9
  • differently phrased, but same problem as http://stackoverflow.com/questions/14667465/multiple-instances-of-a-python-object-are-acting-like-the-same-instance – tacaswell Feb 03 '13 at 03:21

2 Answers2

5

I suspect your visitors set is a class variable and not an instance variable.

Nothing to do with defaultdicts behaviour.

Try:

class Counter:
    def __init__(self):
        self.hits = 0
        self.visitors = set()

    def addHit(self, ip):
        self.hits += 1
        self.visitors.add(ip)

EDIT: Nothing to do with your questions, but just some ideas how to expand your counter:

#! /usr/bin/python3.2

class Counter:
    def __init__(self):
        self.__hits = 0
        self.__visitors = {}

    def addHit(self, ip):
        self.__hits += 1
        if ip not in self.__visitors:
            self.__visitors [ip] = 0
        self.__visitors [ip] += 1

    @property
    def hits (self):
        return self.__hits

    @property
    def uniqueHits (self):
        return len (self.__visitors)

    @property
    def ips (self):
        return (ip for ip in self.__visitors)

    def __getitem__ (self, ip):
        return 0 if ip not in self.__visitors else self.__visitors [ip]

c = Counter ()

c.addHit ('1.1.1.1')
c.addHit ('1.1.1.1')
c.addHit ('1.1.1.1')
c.addHit ('1.1.1.1')
c.addHit ('1.1.1.2')
c.addHit ('1.1.1.2')
c.addHit ('1.1.1.3')

print (c.hits)
print (c.uniqueHits)
for ip in c.ips:
    print (ip, c [ip] )
Hyperboreus
  • 31,997
  • 9
  • 47
  • 87
  • Thanks for that, the solution works. I'm not sure I need to count the number of hits from each unique visitor but thanks for expanding out a little – Mark Dunne Feb 03 '13 at 13:41
2

This is the exact same problem as Multiple Instances of a Python Object are acting like the same instance

You are using a class level variable. Change it to

class Counter:
    def __init__(self):
        self.hits = 0
        self.visitors = set()

    def addHit(self, ip):
        self.hits += 1
        self.visitors.add(ip)
Community
  • 1
  • 1
tacaswell
  • 84,579
  • 22
  • 210
  • 199
  • 1
    Haha. Two stupids, one thought. You and me have posted exactely the same code. – Hyperboreus Feb 03 '13 at 03:09
  • @Hyperboreus Well, there is the bit of the manifesto that says there should be only one way do things in python ;) – tacaswell Feb 03 '13 at 03:11
  • And for those wondering how they can read the "pythonist manifesto", simply `import this` ... And then, for a little extra fun, read the source :) – mgilson Feb 03 '13 at 03:28