18
\$\begingroup\$

I try to avoid using classes in Python as much as possible; if I don't plan on building on it, I don't build it in the first place. It helps me avoid Java-like classes like FileDownloader(), when I could build download_file() instead.

That being said, I wanted to discuss getter and setter methods -- particularly getter methods. Instead of using class methods, I typically make them module methods (and use the module as others might use a class).

For example:

package/
|
| __init__.py
| constants.py
| main.py
| helpers.py

How this might be used:

#constants.py
import os
def get_default_directory():
    return os.path.join(os.expanduser('~'), 'Desktop', 'files')
def get_server_name():
    return '127.0.0.1'
def get_url_template(site, timezone):
    ...

And so on. This is the go here and change stuff if something changes area of the code.

For helpers.py, you might have some convenience functions that work well as one liners here and there, but duplicating them in each script might be a stretch while you're still formulating a final draft.

For instance, I had something like this, but ended up copying it into a second file later on:

def filename_from_path(path):
    return os.path.split(path)[-1]

def trimext(filename):
    return os.path.splitext(filename)[0]

def raw_filename(path):
    return trimext(filename_from_path(path))

This replaced return os.path.splitext(os.path.split(path)[-1])[0], which ended up in a couple of places (and looked ugly).

main() simply holds all of my high level convenience functions, my "dumb functions", as they were. They just carry out a task, and make it take the least amount of keystrokes possible. Like a main method, but you have many different arrangements available to you.

#main.py
import constants
import helpers

def admin_dry_run(safe_mode=True):
    do_main_a(safe_mode)
    do_main_b(safe_mode)
    do_main_c(safe_mode)

def dry_run(safe_mode=False):
    if safe_mode:
        #even this code could be defined in `helpers` to use 
        #the login credentials in `constants`, saving space here
        if helpers.login(constants.get_credentials()):
            do_main_a()
        else:
            do_main_b()
    else:
        do_main_c()

def do_main_a():
    pass #...

Is this typical? Is there anyone who does this, or might be able to suggest a different approach to structuring programs that stretch across multiple files in a similar manner?

\$\endgroup\$

closed as off-topic by Sᴀᴍ Onᴇᴌᴀ, t3chb0t, Billal Begueradj, Stephen Rauch, Dannnno May 31 '18 at 18:15

This question appears to be off-topic. The users who voted to close gave this specific reason:

  • "Lacks concrete context: Code Review requires concrete code from a project, with sufficient context for reviewers to understand how that code is used. Pseudocode, stub code, hypothetical code, obfuscated code, and generic best practices are outside the scope of this site." – Sᴀᴍ Onᴇᴌᴀ, t3chb0t, Billal Begueradj, Stephen Rauch, Dannnno
If this question can be reworded to fit the rules in the help center, please edit the question.

10
\$\begingroup\$

So one thing about not using objects is that it makes getters/setters a bit less flexible in that you can't start them out as pure properties and then move them to property() values later.

Also, by using something like helpers.login(..), you're implicitly limiting yourself to one login at a time since the helpers namespace isn't managed like an object namespace would be. That may or may not be a problem for you now, but it's definitely a corner you may not be aware you're coding yourself into. If in the future you had two things you wanted to log into using the same code, you'd have to restructure quite a bit.

\$\endgroup\$
  • \$\begingroup\$ What about assigning the @property decorator to the getters in helpers.py? Would that ease any of your concerns? Your second point is definitely a concern I hadn't thought of. \$\endgroup\$ – Droogans May 22 '12 at 17:37
  • \$\begingroup\$ Will @property work for variables in a module namespace? I don't know - I've only ever used them on object-variables. It's nice to be able to start with 'somevalue = 1' and then later be able to attach behavior to the assignment event. \$\endgroup\$ – pjz May 23 '12 at 15:22
  • \$\begingroup\$ Yeah I tried it...it doesn't work. I'll try rolling out my own decorator that assigns the function to some junk class, and see if I can get the effect. \$\endgroup\$ – Droogans May 24 '12 at 11:37
  • \$\begingroup\$ At the risk of being obvious: If you're having to use a 'junk class', you're working against the language, which is almost never what you want. If you don't want to use objects, use another language. Something functional, perhaps? \$\endgroup\$ – pjz May 24 '12 at 14:02
  • \$\begingroup\$ I think you already know what I'm going to say: it didn't work. I think I have a better idea of what your point is, but for this specific script I'm writing, it's not an issue. I will add that, in the future, I will probably use a very bland class to represent constants...based on the task they are a part of. \$\endgroup\$ – Droogans May 25 '12 at 3:01
4
\$\begingroup\$

Well, this kind of stuff is usually context dependent and all, but in general your approach is pretty good. Many times I'd put my package's top level functions in it's __init__.py (if it's small) or reimport them there from the package submodules, like your main.py (only, the names are content dependent, again).

I'm having that helpers-like file for functions that don't belong to any other particular file yet, and take them out when I figure where to put them. Only it's called utils.py in my case, usually.

As for the code you provided, couple of notes:

  • constants.py:

    I'd make it look like this:

    import os
    
    get_default_directory = lambda: os.path.join(os.expanduser('~'), 'Desktop', 'files')
    get_server_name = lambda: '127.0.0.1'
    get_url_template = lambda site, timezone: ...
    

    Replacing the functions with lambdas reduces overall code noise and the constants look more like constants, not some functions. :-)

    Talking about functional programming, the get_default_directory function is not really a good constant, because it has a side effect of interacting with outside world by getting $HOME environment, but we can leave it this way, I guess.

  • helpers.py:

    def filename_from_path(path):
        return os.path.split(path)[-1]
    

    This functions is exactly os.path.basename(path).

\$\endgroup\$
  • 1
    \$\begingroup\$ I like your point about lambdas, however, in my examples I'd like to take advantage of docstrings...is it possible to self-document a lambda expression? \$\endgroup\$ – Droogans May 21 '12 at 3:38
  • 1
    \$\begingroup\$ Well yeah, it is possible, though it looks a bit hacky: some = lambda: 1; some.__doc__ = """ Do some serious stuff. """ (Damn these comments, I can't have newlines here) \$\endgroup\$ – dmedvinsky May 21 '12 at 4:32

Not the answer you're looking for? Browse other questions tagged or ask your own question.