Home > Net >  Import small files as "normal" code as if it was all the same file
Import small files as "normal" code as if it was all the same file

Time:12-06

The idea I had about imports until now is that, at least if you used the from x import * the file you are importing would just be completely copied into your code.

In order to help myself organize my code better, I started deviding it into different files, but, when it came to global variables at least, they dont seem to work anymore.

I have something like this:

import threading

run = False

def do_something():
    global run
    while True:
        if run == False:
            break
        print("Anything")
    return 0

def start_do_something():
    global run 
    x = threading.Thread(target=do_something, args=(,))
    x.start()
    run = True
    return 0

def start():
    global run
    while True:
        print("1 - Do something\n2 - Stop something")
        global run
        n = int(input())
        if n == 1:
            start_do_something()
        elif n == 2:
            run = False
        else:
            return 0

start()

The code is obviously more complex but the point is that I separated it into two files like in the following example

Do_something.py:

run = False

def do_something():
    global run
    while True:
        if run == False:
            break
        print("Anything")
    return 0

def start_do_something():
    global run 
    x = threading.Thread(target=do_something, args=(,))
    x.start()
    run = True
    return 0

Start.py:

import threading
from Do_something import *

def start():
    global run
    while True:
        print("1 - Do something\n2 - Stop something")
        global run
        n = int(input())
        if n == 1:
            start_do_something()
        elif n == 2:
            run = False
        else:
            return 0

start()

The code works as intended in the first example, when i input the value 2, the do_something thread/function stops outputting, but in the second, it seems like the start function is not being able to change the run variable and thus, remains unable to finish the do_something function as it keeps printing.

Is there a way to just import everything as it is on the file? It isn't really necessary but it would help a lot when it comes to organization and readability of my code, at least for me. Also, I understand its bad practice, I'm just trying a couple of stuff, and kind of teaching myself and I believe this way I could organize myself better

CodePudding user response:

You're combining two things that are generally bad ideas:

  • from module import *, you should generally avoid this.
  • using global from a function to bind global variables, particularly a variable that was imported.

Your reasoning seems to be:

  • define a global variable run in file1.py
  • import that global variable from file2.py
  • modify that global variable from a function start() in file2.py, using global
  • then access the same global variable in another function in file1.py, also using global

That seems like it might work on the surface, but that's not how names work in Python. (they might have, but they don't - this is not how you're supposed to be passing values around)

What really happens:

  • define a global variable run in file1.py
    this part is OK, although using a global variable already has a bad code smell.
  • import that global variable from file2.py
    this is also OK, you'd find that if you only accessed the value anywhere in file2 (assuming it's not shadowed anywhere), that it works as expected. However, since you used import *, nobody will be able to say where exactly that variable was defined.
  • with global run and run = False, you rebind run within the file2 namespace. It no longer refers to the same value and run in file2 and run in file1 now refer to different objects.
  • with global run in start_do_something(), you tell Python that you want to be able to rebind the global variable run, but you have no intention of doing so as the function doesn't (re)define run.

Note that removing the global run from start_do_something() doesn't help, because run = False for the global run in start() still rebinds the name and you're dealing with 2 separate variables after that.

You could define an object that defined whether you should run or not in file1 and call a method on it to switch it. That would work, but still be very bad code:

file1.py:

class Run():
    value = True

run = Run()

def do_something():
    # no "global" needed to refer to a global variable
    if not run.value:
        return
    print("Anything")
    return 0

file2.py:

from file1 import *

def start():
    # no "global" needed to refer to a global variable
    n = int(input())
    if n == 0:
        run.value = False  # avoid rebinding global run
    return 0


start()
do_something()

However, you should just steer clear of globals, for many obvious reasons and non-obvious ones like the one I explained above.

A language like Python has global because there are rare cases where it is needed, and it would be a worse language without it. It does not have global as the preferred way of passing data around. Many new programmers make the mistake of viewing global as the be all end all of making things available in their code, but it's a dead end. Perhaps this is the result of coming from other languages where using globals makes more sense, but in Python you should try to define things in appropriate modules. Then just use what you defined, without trying to redefine it. Generally pass explicitly to a function what it needs, instead of the function relying on it being available in the current namespace.

CodePudding user response:

1) Put all global properties into a class

...and reference them with dot notation. The class variables are singular and instead of reassigning a variable, you mutate the state of an object, so you may find it useful, though that is not a very pythonic thing to do.

globs.py

class GlobalVars:
    foo = 0
    bar = False
    fizz = "hello"

main.py

from globs import GlobalVars

print(GlobalVars.foo)

GlobalVars.foo = "New value"

print(GlobalVars.foo)

2) Keep global variables hidden for other files and use getters and setters

That keeps the global variables protected from other modules, but you will have to call getters and setters a lot, so I find the first way better in terms of readability.

globs.py

some_global_var_x = 100


def set_global_var_x(new_value):
    global some_global_var_x
    some_global_var_x = new_value


def get_global_var_x():
    return some_global_var_x

main.py

from globs import set_global_var_x, get_global_var_x

print(get_global_var_x())

set_global_var_x("Hello")

print(get_global_var_x())

3) However, You can still also combine several small files into one. (Not recommended)

That is not how things are done in Python, but if that's what you want...

Consider the following project structure:

 -src
|   -snippet_1.py
|   -snippet_2.py
|   -snippet_3.py
|   -snippet_4.py
|   -...
 -gen.py

The folder src contains your project broken into small and manageable pieces. gen.py is the file that generates the final file with the code you will run.

gen.py

import os

with open('combined_code.py', 'w') as destination:
    for filename in os.listdir('src'):

        code_file = os.path.join('src', filename)

        # we do not consider directories
        if not os.path.isfile(code_file):
            continue

        with open(code_file) as source:
            # Extra newline just in case you forget to put newline at the end of the file
            destination.write(source.read()   "\n")

snippet_1.py

x = 1

snippet_2.py

print(x)

snippet_3.py

x  = 1

snippet_4.py

print(x)

Running

> python gen.py
> ls
src combined_code.py gen.py
> python combined_code.py
1
2

Generated combined_code.py

x = 1
print(x)
x  = 1
print(x)

As you can see, there are no imports, but there are definitely problems. In this approach, you have to ensure the order of the code if that matters. Also may be hard to debug and you have to re-"compile" everything every time you use it. Just for your information and curiosity.

  • Related