-1

I have a pretty big project, due to complicity and privacy I would like to just post the meaning of my problem.

def H():
   B()
   ...
   # return with something else

def G():
   ...
   B()
   # return with something else

def F():
   dosomething(counter, ...)
   ...

def E():
   ...

def D():
   ...
   B()
   ...
   # return with something else

def C():
   ...

def B():
    ...
    if success:
        global counter += 1 
    ...
    

def A():
    B()
    C()
    D() # the list goes on...
    ...
    

def main():
    try:
        A()
    except Exception as e:
        ...
    else:
        ...

counter = 0


if __name__ == '__main__':
    main()

As you can see, the problem is that there is a global variable which is incremented each time a function called B() with a successful outcome.

I did my research on stackoverflow, most people are cursing at global variables that are not simply used as contants and that they are evil and they should be avoided at ALL COSTS.

Most people didn't really explain the whys and the main reason for that, but some people explained that global variables should be avoided because of increasing complexity, readability and confusion, which I absolutely understand and see if used improperly.

As a solution, most people said that it would be using returns, specifically with a tuple (returning with two values).

However in my case, the project is pretty big and I made a list of pros and cons of using a global variable and returns

Pros of using global variable in my case

  • Global variable is incremented by only one function, which is called by other functions, thus no complexity or confusion
  • costs me literally 2 lines of code

Cons of using global variable in my case

  • ?

Pros of using returns instead of global variable in my case

  • ?

Cons of using returns instead of global variable in my case

  • I have to rewrite basically everything, because I have to pass counter to main, then to D(), H(), B(), G(), A(). I have to rewrite in-function variables so that they used the correct returning element, I have to rewrite parameters, I have to rewrite arguments, I have to edit classes. It's a huge mess.

So in my case, I absolutely disagree with using returns instead of a global variable.

Using global variable -> literally two lines of code, simple and easy and easily understandable, intuitive.

Using returns -> probably 50 - 100 lines to be rewritten and spending time fixing classes and objects, increasing confusion and complexity.

Is there something I don't see? Do you have something in mind?

  • I see that `B()` sometimes runs: `counter += 1`. What else uses `counter`? – quamrana Aug 25 '21 at 16:00
  • I personally like to use classes. Any variable defined in the `__init__` method can be accessed globally throughout the class. In addition, classes make your code clearer and more organized – Roni Aug 25 '21 at 16:01
  • Can you post a [mre]? Emphasis on the ***minimal*** part. Doesn't seem necessary to define all those 6-8 functions to demonstrate your problem... – Tomerikoo Aug 25 '21 at 16:01
  • @quamrana Nothing else uses the counter variable, just B(), which is called by other methods – John McGreen Aug 25 '21 at 16:03
  • Anyway, you could put all those functions as class' methods and have that `counter` as an attribute for easy access – Tomerikoo Aug 25 '21 at 16:03
  • @Roni isn't that basically using global variables? I would just create a main class just for the variable to be shared? – John McGreen Aug 25 '21 at 16:05
  • 1
    But, if only `B()` is using `counter`, as you just said, then maybe [Static variable in Python?](https://stackoverflow.com/q/26630821/6045800) is your solution – Tomerikoo Aug 25 '21 at 16:05
  • @JohnMcGreen: Ok, so then its not really a global variable if one and only one function has access to it. It becomes part of the private state of the module where `B()` resides. Variables are only really global if they are accessible globally *and* they *are* accessed globally. – quamrana Aug 25 '21 at 16:06
  • @quamrana Sorry forgot to include, that there is error logging function which reads the counter value and logs it among other things. – John McGreen Aug 25 '21 at 16:08
  • @JohnMcGreen Maby a [closure](https://medium.com/python-features/introduction-to-closures-in-python-8d697ff9e44d) is what you're looking for? – soufiane yakoubi Aug 25 '21 at 16:08
  • As I said, a [mre] is really needed here. Try to reduce the amount of functions and remove all those `...`. Create a scenario that demonstrates all the code issues you want to handle - only `B` changes the variable, other functions *read* it, etc. – Tomerikoo Aug 25 '21 at 16:10
  • How much effort would it be to replace the read-only accesses of `counter` with `get_counter()`? – quamrana Aug 25 '21 at 16:11
  • @soufianeyakoubi Wow, that's interesting, I start looking into it. Sounds like a good solution! – John McGreen Aug 25 '21 at 16:11
  • It's not like using global variables. It's different. Check out @studenprogrammer's answer for an example – Roni Aug 25 '21 at 16:31
  • @Roni of course its not entirely like global variables, but in my case, creating a class JUST for one variable to be traced back is the same as using the global variable. The only difference is the variable will be "global" inside the class instead of the whole project. Creating a class just for one variable so that it can be traced back increases complexity because I have to rewrite tons of lines of code and referencing. Which is a waste of time because the outcome is the same as using global variables. Its just the scope difference – John McGreen Aug 25 '21 at 16:42
  • @Roni and then I would be using `self.counter` instead of `global counter` – John McGreen Aug 25 '21 at 16:43
  • @JohnMcGreen, I just think you should create a class, add the functions you showed in your question as methods and use `counter` as an instance variable – Roni Aug 25 '21 at 16:59
  • 1
    "creating a class JUST for one variable to be traced back is the same as using the global variable." this is patently false. "The only difference is the variable will be "global" inside the class instead of the whole project." lmao you're contradicting yourself. Why bother asking this question if you're just going to ignore everybody's advice? – ddejohn Aug 25 '21 at 22:07

2 Answers2

4

The issue with simply using a global counter, especially in large projects, is that it can easily become unclear what happens to the variable. Using return allows for easier debugging and tracing, as you can take return statements indvidually and look at them, as well as easier reusability, as you can take the return statement (or the value thereof) and reuse it, even reassign it if you need/want.

Now, since we don't know what exactly your functions do and there is little insight into the code except the basic structure, it's hard to give clear advice.

Another option can be putting the variable into a class, since an object is globally callable as well but way more robust. An example could be:

class B:
    def __init__(self):
        self.counter = 0
    def increment_counter(self):
        self.counter +=1
    def get_counter(self):
        return self.counter

Then create an object and call the objects functions within your other functions.

Finally, for some general advice, don't be afraid to rewrite code, even though it may take alot of effort, if it provides a substantial improvement in code quality. One can learn a lot in the process and it may help you avoiding the mistake in the future.

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
  • 1
    This is a good answer that actually addresses OP's real question, "Why not use globals?" – ddejohn Aug 25 '21 at 16:17
  • Creating a class just for one variable so that it can be traced back increases complexity because I have to rewrite tons of lines of code and referencing. Which is a waste of time because the outcome is the same as using global variables. Its just the scope difference and that I would be using` self.counter` instead of `global counter`. – John McGreen Aug 25 '21 at 16:46
  • I would have to rewrite basically everything, because I have to pass counter to main, then to D(), H(), B(), G(), A(). I have to rewrite in-function variables so that they use the correct returning element, I have to rewrite parameters, I have to rewrite arguments, I have to edit classes. It's a huge, unnecessary mess. – John McGreen Aug 25 '21 at 16:49
  • 1
    From the way you write, I can't really understand why you asked the question in the first place. I explained to you, why using global variables is bad practice and others seem to share that view based on their comments. If you don't want to change your code, that's fine, but why ask then in the first place. We know very little of your project code, so, again, it is hard to give specific advice outside the generic we already gave. – studenprogrammer Aug 25 '21 at 20:27
0

If I understood your problem, then You might want to use a closure.

Since the B function conditionally increments the counter, and the the counter is used somewhere else, you'll probably need something like a factory function that returns a tuple of two functions, one that executes B and another one which returns the current value of counter.

Something like this:

def factory():
    counter = 0

    def B():
        ...
        if success:
            counter += 1
        ...

    def get_counter():
        return counter

    return (B, get_counter)


B, get_counter = factory()

// execute B
B()

// get the value of counter
current_counter = get_counter()
soufiane yakoubi
  • 861
  • 11
  • 31