Globals vs Parameters for Functions with the Same Parameters

£可爱£侵袭症+ 提交于 2019-12-24 00:13:56

问题


I am reading through "Making games with Python & Pygame" and I have noticed that because the author uses lots of functions with drawing in to break his code up, he uses lots of globals such as GRIDSIZE or BACKGROUNDCOLOR. I was always told that globals were generally bad, but without them, every drawing function would have ten more, repeating parameters - and I have also been told that repetition is bad. I wondered, then, is the author is correct in using globals for parameters that appear in most (drawing) functions, or should have just used more, repetitive parameters.


回答1:


Globals are generally bad because it is difficult to keep track of the values in a huge program. This is a bit less bad in Python because "global" variables are module-level, but even in this case you are well advised to avoid them: your modules can became huge (making global variables hard to track), module-level variables are prone to be updated by other modules etc.

I don't mean you should not use them - you should use them when they are needed. Two common cases when it is needed are sharing state and constants.

Module-level variables for sharing state

For example, module-level variables are the best way of storing a state shared through all the module, or even through all the program. Let's suppose we are going to implement the renderer of a game; it should store a map of all images that is shared by all the program. So at our module we can do something like this:

# map_renderer.py

_map = [
    [None, None, None, None],
    [None, None, None, None],
    [None, None, None, None],
    [None, None, None, None]
]

def put_sprint(sprint, x, y):
    _map[x][y] = sprint

def get_sprint(x, y):
    return _map[x][y]

Now you can even write a module like this:

# background.py
import map_renderer as mr

def draw_background(background_images):
   for i, row in enumerate(background_images):
       for j, image in enumerate(row):
           put_sprint(image, i, j)

...and in another module do something like this

# npc.py
import map_renderer as mr

def draw_npc(npc, x, y):
    image = npc.image
    put_sprint(image, x, y)

Since both modules should update the same map, you can use module-level variables to accomplish it.

NOTE: For those more design pattern-oriented, we can say that module-level variables are a very effective and simple way of implementing singletons.

Module-level variables as constants

The code in your book, however, are examples of another good way of using module level variables: constants. (I suppose these global variables are actually constants, because they follow the style for constants from PEP 8.)

Consider, for example, the BACKGROUNDCOLOR variable. I suppose it is a value that will be the same through all the module, maybe through all the program. Suppose this value is 0xFA2BEE. Then you have some options:

  • you could (in theory), write the value everywhere it is used:

    def paint_frame(image, x, y, h, w, fx, fy):
        paint_background(0xFA2BEE, fx, fy)
        spring = slice_image(image, x, y, x+w, y+h)
        paint(image, px, py)
    
    def clear_frame(fx, fy):
        paint_background(0xFA2BEE, fx, fy)
    

    That, however, is clearly a bad idea. First, what the frick is 0xFA2BEE? Anyone reading your code - even you in the future - will get confused by it. Also, what if the background should be changed to, let us say, 0xB2BAA3? Now you have to find all 0xFA2BEE in your code, make sure it is the value of the background instead of same other value and then replace it. Not nice, right?

  • If that is bad, you can think: well, since I should avoid "global" variables but the raw values are not good to use, I can pass them as parameters!

    def paint_frame(image, x, y, h, w, fx, fy, background):
        paint_background(background, fx, fy)
        spring = slice_image(image, x, y, x+w, y+h)
        paint(image, px, py)
    
    def clear_frame(fx, fy, background):
        paint_background(background, fx, fy)
    

    Well, at least now you 0xFA2BEE as the last argument of paint_frame/clear_frame is the value of the background. However, you put another argument in some already complicated functions. Also, if you change the background, you already have to find all calls to paint_frame/clear_frame in your entire codebase. Not cool, bro.

    Instead, you could...

  • ...create constants! In fact, it is what the author does:

    BACKGROUND = 0xFA2BEE
    
    def paint_frame(image, x, y, h, w, fx, fy):
        paint_background(BACKGROUND, fx, fy)
        spring = slice_image(image, x, y, x+w, y+h)
        paint(image, px, py)
    
    def clear_frame(fx, fy):
        paint_background(BACKGROUND, fx, fy)
    

    Now, you 1) don't need to guess if 0xFA2BEE is a background value or something else, 2) your functions are kept simpler and 3) you can easily change the background value at one place only. As you can see, the use of module-level variables payed well here. Even if you need to use the same background value in most but not all places, the constant will be helpful: just make it the default value of the background parameter:

    BACKGROUND = 0xFA2BEE
    
    def paint_frame(image, x, y, h, w, fx, fy, background=BACKGROUND):
        paint_background(background, fx, fy)
        spring = slice_image(image, x, y, x+w, y+h)
        paint(image, px, py)
    
    def clear_frame(fx, fy, background=BACKGROUND):
        paint_background(background, fx, fy)
    

    Now you call, let us say paint_frame(img, i, j, FRAME_SIZE, FRAME_SIZE, k*FRAME_SIZE, l*FRAME_SIZE) most of the time and only passes a background argument when needed

Constants are suitable to be global variables/module-level variables also because they do not change. Global variables are specially nasty because you should keep track of their values. If the values does not change, however, this will not be a problem, right?

NOTE: these variables are constants by convention and can be updated. Python has no kind of read-only variable but you can avoid problems by following the code standard.)

The only universal rule is "it depends"

You will find a lot of people saying "don't use X", "don't do Y", "always use Z" etc. Well, most of the time these suggestions are correct, but you probably will always find contexts where they do not apply. This is the case with the "do not use globals" rule. There are situations where the use of global is the best choice. Even in C there are such cases, and Python have many other situations where you can do it since module-level variables are still safer than globals.

The suggestion of avoiding globals (or module-level variables) is a good one, specially for a novice. However, as you can see, there are cases where it is better to not follow it.




回答2:


In Python you don't have as big a problem with globals, because Python globals are at the module level. (So global is a bit of a misnomer anyway.) It's fine to use module-level globals under many circumstances where true globals would be inappropriate.




回答3:


Example code in books is not the same as production code

Code in books and articles explaining things do things to keep to the point and not inundate the reader with what might be noise or tangents to the specific topic.

The author is taking short cuts to keep the code as specific to the problem they are explaining as possible.

That said, there are better ways to do what they are doing than using module level variables. Even in books and tutorials.

Huge State Machines are Bad

Variables that encompass too much scope, no matter what level they are at, tend to have the side effect of creating invisible side effects that manifest themselves from the huge state machine that the application becomes.

Excessive parameters to functions is a code smell

It should tell you that you probably need to encapsulate your data better. Multiple related parameters should be grouped into a single data structure and just pass that single data structure as a parameter.




回答4:


Constant globals can be OK, despite whatever general warnings you may have heard.

In Python "constant" means "named in capital letters as a hint that nobody should modify them", and "globals" means "module-level variables", but the principle still applies. So hopefully GRIDSIZE and BACKGROUNDCOLOR are constant for the duration of the program, but it's possible they are not: I haven't seem the code.

There are plenty of examples of module-level constants in the standard Python libraries. For example errno contains E2BIG, EACCESS, etc. math contains math.pi.

However, those examples are even more constant than GRIDSIZE. Presumably it could change between different runs of the same program, which math.pi will not. So you need to assess the consequences of this particular global, and decide whether to use it or not.

I was always told that globals were generally bad

Bad things are bad for reasons, not "generally bad". In order to decide what to do you need to understand the reasons. Using globals (or, in Python, module-scope objects) causes certain problems:

  • dependencies on previously-executed code: this is the worst problem, but it applies only to mutable globals.

If you use a global that can be modified elsewhere, then in order for someone reading the code to reason about its current value they might need to know about the whole program, because any part of the program might access the global. If you keep your mutables in an object that only parts of your code ever get to see, then you only have to reason about those parts of your code that interact with that object.

This might not sound like a big difference if your program is one file that's 100 lines long, but it becomes one eventually. Making your code easy to work with is almost all about making it easy for a reader to reason about what the code does. What else can affect it is a big part of that, so this is important. And "a reader" is you, next time you want to change something, so helping out "readers" of your code is pure self-interest.

  • dependencies on concurrently-executing code: mutable globals + threads = needs locks = effort.

  • hidden dependencies: this is applicable even for constants.

You have to decide whether it's OK for this bit of code to have some dependencies that are not supplied as parameters ("injected") by the caller of that code.

Almost always the answer to this question is "yes". If it is "absolutely not" then you're in a tight spot if you dislike lots of parameters, because you wouldn't even use Python builtins by name, let alone names from modules the code imports. Having decided that it's OK to have hidden dependencies, you have to think about the consequences of GRIDSIZE being one of them, on the way you use and especially test the code. Provided that you don't want to draw on different-sized grids in the same program you're basically fine until you want to write tests that cover lots of different values of GRIDSIZE. You'd do that to give yourself confidence now that when you change GRIDSIZE later nothing will break.

At that point you'd probably want to be able to provide it as a parameter. For this reason you might find it more useful to have parameters with defaults rather than global values. To prevent long repetitive parameter lists you might pass around one object that combines all of these settings rather than each separately. Just beware of creating an object with multiple unrelated purposes, because the others are distracting if you want to reason about just one.

  • namespace pollution: relevant in C, if you don't use static then two completely unrelated files cannot have different functions of the same name. Not so relevant in Python where everything is in namespaces.



回答5:


It depends on the situation.

Globals are not generally bad, but should be avoided if not absolutely necessary.

For example, constants are perfectly ok as globals, while configuration data should be bundled (encapsulated) as much as possible.

If you concentrate all such data onto one (or few) objects, you either might want to have the named functions as methods of the objects's class(es), or you might want to access these objects from within these functions.

At the end, it is a matter of taste. If you think that things are handlable in a convenient way, let it be as it is. If it looks like confusion, better change it.



来源:https://stackoverflow.com/questions/21054775/globals-vs-parameters-for-functions-with-the-same-parameters

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!