1

This is the class definition of a Counter(file name: counter.py) which is to be used in a complex application,

class Counter:
    def __init__(self):
        self.count = 0
    def incr(self):
        self.count += 1
    def decr(self):
        self.count -= 1

It turns out that this Class will be instantiated only twice. The two instances will be used by multiple modules.

  1. When a file is read, counter 1 will be incremented
  2. When a HTTP call is made, counter 2 will be incremented

What is the correct way to create 2 instances of the Counter class, so that the state of the two instances can be shared across the application?

Shown below is the solution I came up with,

class Counter:
    def __init__(self):
        self.count = 0
    def incr(self):
        self.count += 1
    def decr(self):
        self.count -= 1

fileCounter = Counter()
httpCounter = Counter()

So from another module, I would do the following,

from counter import fileCounter, httpCounter

def readMoxyConfig():
    # Do read the file here
    fileCounter.incr()

def callMoxyAPI():
    # Make the HTTP rest call here
    httpCounter.incr()

Are there any loopholes to this approach? If yes, what is the correct way to achieve the same result?

Note: I am not explicitly interested to know how global variables are shared as in this question. What I want to know is the correct way to instantiate multiple objects from the same class and access the instances from any where in the application.

Pubudu Dodangoda
  • 2,742
  • 2
  • 22
  • 38
  • What sort of app is this? Is it running in a single process? – Daniel Roseman Oct 23 '17 at 10:23
  • This application is supposed to be similar to a web application distributed across the world. The app connects to a central API using websockets. Upon websocket requests from the API, the methods as explained in the question will be called – Pubudu Dodangoda Oct 23 '17 at 13:41
  • I would approach this in an entirely different way, using an external key-value store like Redis (which has atomic increments) to store the counter. – Daniel Roseman Oct 23 '17 at 13:45
  • @DanielRoseman That's a nice approach. But we are working in a very restricted environment and python and bash are the only technologies we are able to use – Pubudu Dodangoda Oct 23 '17 at 13:50

3 Answers3

2

The only loophole is that you would be mutating global state, which is usually frowned upon for a number of reasons. You might instantiate the counters in your main function and pass them to the objects/methods that need them to avoid this issue. Or you might decide that global mutable state is fine for your use case but this is at least something you should think about.

Read the linked artcle to learn about the problems associated to global mutable state and how to avoid it.

How is this global mutable state?

Module-level variables like fileCounter, httpCounter are global in the sense that any part of the code can access them just by importing the module (note that this is not the same as the global keyword in Python). And they hold state in .count which can be mutated by calling incr() or .decr() or just assigning a value.

By making the instances local to a function other parts of the code cannot access them anymore, unless you pass them explicitly.

The correct way

Your solution is "correct" in the sense that it will work. But it can lead to some problems that you must be aware of. That's what my answer is about.

Actually the the very "access the instances from any where in the application" thing is problematic. It can make complexity grow as your objects get more complex and are accessed in more ways from more parts of the application.

Stop harming Monica
  • 12,141
  • 1
  • 36
  • 56
  • Can u explain a bit on how global state is mutated here? Are the fileCounter, httpCounter instances global? How does that problem solve when they are instantiated from a main function? – Pubudu Dodangoda Oct 23 '17 at 13:44
1

I would do this a little differently. This so you can add code to the two different counter classes when the need arises. In your counter.py:

class Counter():

    def incr(self, increment = 1):
        self.count += increment

    def decr(self, decrement = 1):
        self.count -= decrement

class httpCounter(Counter):

    def __init__(self, start = 0):
        self.count = start

class fileCounter(Counter):

    def __init__(self, start = 0):
        self.count = start

This way, if you have a specific additional need to go into one of the classes, you can add it to httpCounter or fileCounter. Or if you have additional code for both classes, it can go into the Counter class.

Additionally you can change the increment/decrement/start value if you wishes to do so.

Edwin van Mierlo
  • 2,398
  • 1
  • 10
  • 19
  • I understand the benefits in your approach. But how would you instantiate httpCounter and fileCounter and how would you use them in other modules? And how does this avoid globals? – Pubudu Dodangoda Oct 23 '17 at 13:37
0

I think you could have used the Multiton pattern(here multiton decorator has been used) to avoid the Global Mutable State.In this example the class "MyClass" itself behaves as its Multiton

Multiton

def multiton(cls):
    instances = {}

    def getinstance(name):
        if name not in instances:
            instances[name] = cls()
        return instances[name]

    return getinstance


@multiton
class MyClass:
    pass

a = MyClass("MyClass0")
b = MyClass("MyClass0")
c = MyClass("MyClass1")
print a is b  # True
print a is c  # False
Daham
  • 11
  • 1