3

Full script: https://gist.github.com/4476526

The specific code in question is

# Cloud Files username & API key
username = ''
key = ''

# Source and destination container names
originContainerName = ''
targetContainerName = ''

...

def cloudConnect():
    global originContainer
    global targetContainer
    global connection
    print "Creating connection"
    connection = cloudfiles.get_connection(username,key,servicenet=True)
    print "-- [DONE]"
    print "Accessing containers"
    originContainer = connection.create_container(originContainerName)
    targetContainer = connection.create_container(targetContainerName)
    print "-- [DONE]"
    return

The script works perfectly fine, however I've read in multiple places that global variables should be used with hesitation and that there is almost always a better way to do the same thing without them. Is this true? And if so, how exactly should I fix this script? To me it seems much easier just to use global connection and container variables instead of passing those objects around as arguments in multiple functions.

Nordom Whistleklik
  • 251
  • 1
  • 5
  • 8

2 Answers2

5

You should create a class (called something like CloudContainer) that includes all of those global variables as members, and rewrite it as (just as a start):

class CloudContainers(object):
    def __init__(self, username, key, originContainerName, targetContainerName):
        self.username = username
        self.key = key     
        self.originContainerName = originContainerName
        self.targetContainerName = targetContainerName

    def cloudConnect(self):
        print "Creating connection"
        self.connection = cloudfiles.get_connection(self.username,self.key,servicenet=True)
        print "-- [DONE]"
        print "Accessing containers"
        self.originContainer = connection.create_container(self.originContainerName)
        self.targetContainer = connection.create_container(self.targetContainerName)
        print "-- [DONE]"
        return

    def uploadImg(self, new_name):
        new_obj = self.targetContainer.create_object(new_name)
        new_obj.content_type = 'image/jpeg'
        new_obj.load_from_filename("up/"+new_name)

    def getImg(name):
        obj = self.originContainer.get_object(name)
        obj.save_to_filename("down/"+name)

Thus, any function that uses these global variables (like getImg and uploadImg above) would be included as a method of the class.

David Robinson
  • 77,383
  • 16
  • 167
  • 187
  • I would argue a class might be overkill, unless there is other functionality to be baked in with it, a class with one function should probably just be a function. – Gareth Latty Jan 07 '13 at 17:27
  • @Lattyware: To the contrary, if you look at the script there are many other functions that use these global variables, like `uploadImg` or `containerDif`. – David Robinson Jan 07 '13 at 17:29
  • 1
    I've seen designs like this before where it is a class that has something like a get_connection() method. This would create one if it doesnt exist and save it as a private member. Future calls would return the existing connection or even a new one if the previous one timed out. – jdi Jan 07 '13 at 17:31
  • @DavidRobinson Unless those are inherently connected, why wrap them up in a class? – Gareth Latty Jan 07 '13 at 17:32
  • @Lattyware: They are inherently connected. That's why they use the same global variables like `targetContainer`. – David Robinson Jan 07 '13 at 17:32
  • @DavidRobinson I'm talking about being conceptually related - what object is being represented here? Your `CloudConnector` is *nounifying* (for the lack of a real word) and adjective. Python has modules and data structures to represent things that get used together, classes are to represent an object. – Gareth Latty Jan 07 '13 at 17:34
  • 1
    @Lattyware: No, what is contained in the class is precisely a noun, or rather a combination of nouns (which is precisely when you want to use a class). It is a combination of a connection, an origin container, and a target container. Note that those two containers are created at the exact same time as the connection is, and in this script are never even changed (to refer to different containers, for example). – David Robinson Jan 07 '13 at 17:36
  • Yes, but unless you have related functionality, a data structure is more apt than a class, and the only functions you are adding appears to be completely unrelated. I'm not saying it's horrific, just I don't see the benefit of making a class here. – Gareth Latty Jan 07 '13 at 17:43
  • @Lattyware: How is the functionality of `resizeImg`, `uploadImg`, and `getImg` not related (even just going by their names it's obvious, let alone their related and interconnected functionalities)? You're saying that there are about 5 functions here that all need to take as their first argument the exact same data structure, and you don't see why a class is appropriate? – David Robinson Jan 07 '13 at 17:46
  • @DavidRobinson Yeah, I do see your point, but there it would seem to make more sense to me to have an image class that needs a connection to function - so that functionality is grouped into the image class, and the connection (a tuple of the connection info) is returned by the function and given to the image class in the constructor. It's not a huge deal, so I'm going to stop spamming up the comment area. – Gareth Latty Jan 07 '13 at 17:47
  • Thank you! I didn't expect to get such a discussion on my question, but reading through all these comments has given me a lot of good information. – Nordom Whistleklik Jan 07 '13 at 19:45
1

Easier, yes, but it means that it's very hard to tell when and why those variables get changed. I think the best answer is the one you have given in your question - pass them around as an object. E.g:

def cloud_connect(origin_container_name, target_container_name):
    print "Creating connection"
    connection = cloudfiles.get_connection(username, key, servicenet=True)
    print "-- [DONE]"
    print "Accessing containers"
    origin_container = connection.create_container(origin_container_name)
    target_container = connection.create_container(target_container_name)
    print "-- [DONE]"
    return connection, origin_container, target_container

Then simply pass that tuple around.

Gareth Latty
  • 86,389
  • 17
  • 178
  • 183
  • 1
    Passing around a tuple seems like "do-it-yourself OO", when these could instead be members of an object. – David Robinson Jan 07 '13 at 17:30
  • @DavidRobinson I don't see the benefit of a class here over a tuple, dict, or maybe namedtuple if you really wanted. This isn't Java, not everything needs to be an object. – Gareth Latty Jan 07 '13 at 17:35
  • 1
    One benefit is encapsulation of implementation detail, no? Sure, you can look inside a C FILE struct, but 'tis far better to not. Passing a tuple means other functions need to know details of that tuple. – msw Jan 07 '13 at 17:42
  • 1
    The OP is working with a class of related functions (just to name a few: `cloudConnect`, `getImg`, `uploadImg`, `processImg`, and `containerDif`) that all require working with the same group of variables (*not* just the same general topic- the *exact* same variables, `targetContainer` and `originContainer`). You seriously want to pass a dictionary around to every one of those functions containing all those variables? That's no different than a class, except that it's much more unwieldy and explicitly ignores that that's what classes were designed to do. – David Robinson Jan 07 '13 at 17:42
  • I see your point, I guess I'm just being a bit of a purist when it comes to not using a class as a namespace. – Gareth Latty Jan 07 '13 at 17:46
  • 1
    This actually isn't using a class as a namespace- there's a very real kind of "object" that the class represents. It represents the pairing between two remote folders, where you want to convert the files to those in another. One method initializes the connection between them, and the others are concerned with transferring files back and forth, checking how much is left over, or cleaning up when you're done. It's very easy to imagine that you could want to open multiple such pairings at the same time, and it's easy to imagine many additional functionalities and methods that will be necessary. – David Robinson Jan 07 '13 at 17:54
  • Fair enough, maybe I just didn't read enough into the question then. – Gareth Latty Jan 07 '13 at 17:57