2

Say I have a class definition which takes some arguments and creates additional data with it, all within the __init__ method:

class Foo():

    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
        self.bar = generate_bar(x, y, z)

    def generate_bar(self, x, y, z):
        return x+y+z

I only want to run the generate_bar() method once, when an instance of the class is created, so it wouldn't make sense for that method to be callable on the instance. Is there a sleeker way to ensure that a method is only available to __init__, or do I just have to assume anyone who looks at my code will understand that there's never a situation in which it would be appropriate to call generate_bar() on an instance of the class?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343

2 Answers2

5

If you are not using any instance state, just make it a separate function:

def _generate_bar(x, y, z):
    return x + y + z

class Foo():
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
        self.bar = _generate_bar(x, y, z)

The leading underscore, by convention, signals it is an internal function not to be used by external consumers of your module.

You could nest the function inside the __init__ but this doesn't really help with readability:

class Foo():
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

        def generate_bar():
            return x + y + z

        self.bar = generate_bar()

Here generate_bar() doesn't even need arguments, it could access x, y and z from the enclosing scope.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Ah, that works, thanks! Is there any style preference between defining it outside or inside the class? – SadKingBilly Sep 15 '15 at 19:26
  • @SadKingBilly: if you define it outside the class, you can still unittest it separately. I rarely see a need to nest functions, I only use that to factor out parts of a larger expression such as a list comprehension, and only for *small* functions. – Martijn Pieters Sep 15 '15 at 19:27
  • Is the convention then to only include methods that you'll want to be callable in the class? Meaning anything I'd want to put an underscore in front of, I'd also want to leave out of the class definition? – SadKingBilly Sep 15 '15 at 19:37
  • @SadKingBilly: anything that needs to access to the class state makes sense to keep on the class, bundling all functionality that operates on the state together. If you rather write the function as `def _generate(self): return self.x + self.y + self.z` (and using it as `self.bar = self.generate()`), then it might make sense to keep it on the class. – Martijn Pieters Sep 15 '15 at 19:39
  • @SadKingBilly: we recently refactored a huge reporting query-generating API object into separate functions in separate modules; bundling splits, filters and metrics into a module each; this let the API object focus on modeling the outward API appearance, while the separation into modules facilitated factoring out unittests into separate modules too which in turn let us run more tests in parallel and reduce test runs from 5+ minutes to 30 seconds. So there are many more factors at play here than just 'convention'. :-) – Martijn Pieters Sep 15 '15 at 19:43
  • @SadKingBilly: (there were more benefits; fewer merge conflicts in Perforce as disparate teams can focus on specific functionality areas, query refactoring and testing is easier, etc.) – Martijn Pieters Sep 15 '15 at 19:45
  • Excellent. It definitely feels better to split things off into smaller, neater packages. Makes sense that it's useful for group projects like that. – SadKingBilly Sep 15 '15 at 19:50
1

For "hidden" functions, it is customary to use a single underscore to signify your intent:

def _generate_bar(self, x, y, z): ...

These are still accessible, but are not visible via tab completion.

See this SO explanation:

class foo:
    def __init__(self):
        self.response = self._bar()
    def _bar(self):
        return "bar"

f = foo()
>>> f.response
bar

You can verify yourself that the function _bar is not visible to the object via tab completion

Community
  • 1
  • 1
Alexander
  • 105,104
  • 32
  • 201
  • 196
  • A single underscore, to indicate that the method is "private", suffices. Double underscores are to protect a function from accidentally being overriden in a child class. – chepner Sep 15 '15 at 19:22