1

I've read this SO discussion about factory methods, and have an alternate constructor use case.

My class looks like this:

class Foo(object):
    def __init__(self, bar):
        self.bar = bar

    @classmethod
    def from_data(cls, datafile):
        bar = datafile.read_bar()
        # Now I want to process bar in some way
        bar = _process_bar(bar)
        return cls(bar)

    def _process_bar(self, bar)
        return bar + 1

My question is, if a @classmethod factory method wants to use a function in its code, should that function (_proces_bar) be:

  1. A @classmethod, which seems a bit weird because you won't ever call it like Foo._process_bar()
  2. A method outside of the class Foo but in the same .py file. I'd go with this, but it seems kind of weird. Will those methods always be available to an instance of Foo, regardless of how it was instantiated? (e.g. what if it's saved to a Pickle then reloaded? Presumably methods outside the class will then not be available!)
  3. A @staticmethod? (see 1. This seems weird)
  4. Something else? (but not this!)
2
  • 2
    If process is only used in from_data, make it a local function. Otherwise, I'd go with a module-level func. Could you elaborate on the import problem? Commented Oct 8, 2014 at 12:07
  • Why do you think making it a @staticmethod would be weird? Just declare it with def _process_bar(bar): and call it in from_data() with cls._process_bar(bar). Commented Oct 8, 2014 at 12:54

1 Answer 1

1

The "right solution" depends on your needs...

  • If the function (_process_bar) needs an access to class Foo (or the current subclass of...) then you want a classmethod - which should be then called as cls._process_bar(), not Foo._process_bar().

  • If the function doesn't need an access to the class itself but you still want to be able to override it in subclasses (IOW : you want class-based polymorphism), you want a staticmethod

  • Else you just want a plain function. Where this function's code lives is irrelevant, and your import problems are othogonal.

Also, you may (or not, depending on your concrete use case) want to allow for more flexiblity using a callback function (possibly with a default), ie:

def process_bar(bar):
   return bar + 1

class Foo(object):
    @classmethod
    def from_data(self, datafile, processor=process_bar):
        bar = datafile.read_bar()
        bar = processor(bar)
        return cls(bar)
Sign up to request clarification or add additional context in comments.

5 Comments

Your last example is quite different from what the OP has. He clearly shows that _process_bar is a private function, and as such the users need not have access to it at all. In other words your third case should just have _process_bar defined as a local function inside from_data to completely hide it. What you proposed would be yet an other scenario that changes the function to a public one.
I wouldn't define it inside from_data, because even though that is the only place it is used, it would be redefined every time you call from_data. Better to define it once, probably as a "private" static method inside Foo. Functionally, that's identical to a module-level function, just namespaced inside Foo to indicate that is primarily where it is used.
@Bakuriu: yes, it's different. wrt/ making the function local to from_data, it just makes the function untestable for no good reason.
@chepner Well, function definition isn't a costly operation (since the code is already compiled. It simply has to create one struct and increase the reference count of the code object) so that point doesn't really hold most of the time. Anyway the most important point I wanted to make is that if the OP used a private function you should keep it private and only add extension afterwards.
@brunodesthuilliers With very simple assumptions you can test it, even if its locally defined. The assuptions are that it doesn't access other local variables defined by the top-level function (and if this were the case you simply couldn't define it elsewhere anyway, without significantly changing its logic). Surely it's not something you generally want to do but saying that it renders the function untestable is false.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.