0

I have a class with two methods A and B. The class will be subclassed. Is there an elegant way to enforce that B() is only ever called on an object of the class from within the A() method?

Let's constrain it and say that A() is only called in one place but subclasses implement A() and can optionally call B() within that. One way I thought of doing this was to wrap the A() call with setting a global variable that says it's ok to call B(), and B() would check this variable when it's invoked. This doesn't seem elegant though.

Any suggestions?

Heinrich Schmetterling
  • 6,614
  • 11
  • 40
  • 56
  • 1
    Just make `B` private by adding a leading underscore so it's `_B`. That tells people it's a private method. I don't think there's an easy or even reliable way to do what you want in Python. – martineau Jan 26 '11 at 11:07
  • 1
    It also sounds like, from an OOP design point-of-view, like your base class knows too much about the class(es) that will be derived from it. – martineau Jan 26 '11 at 13:13
  • 1
    Prepend an underscore to signify "private", and assume people who use tnis object are intelligent, then move on to more important problems. – Bryan Oakley Jan 26 '11 at 13:33

2 Answers2

5

Actual private methods is an evil. Mark your method as internal by adding a leading underscore. This tells programmers not to use it unless they know what they are doing.

Lennart Regebro
  • 167,292
  • 41
  • 224
  • 251
  • Could you elaborate on this? What's wrong with private methods? – Makis Jan 26 '11 at 14:03
  • @Makis: You are preventing people from calling them, and if they then need to call them, you are just making life difficult for them for no reason. For a longer explanation, I have this answer: http://stackoverflow.com/questions/4683937/the-use-of-getters-and-setters-for-different-programming-languages/4684469#4684469 – Lennart Regebro Jan 26 '11 at 15:48
  • Why should others be able to call internal functions? Are static functions in C bad as well? – Makis Jan 27 '11 at 06:15
  • @Makis: The necessity to call internal functions pop up quite often in the real world, when a library you are using isn't doing just exactly what you want. – Lennart Regebro Jan 27 '11 at 08:03
  • To me that sounds like poor design more than anything. I'm still interested to hear if you think static is a completely useless keyword. But in general I just think you encourage poor design ("I don't have to think too hard about the API, I'll just make everything public"). – Makis Jan 29 '11 at 13:57
  • @Makis: If you think less about your API design just because you develop in Python, then you shouldn't design API's in the first place. But I can't convince you, only experience can. One day you'll end up having to copy half of the code in a library just so you can override one protected method, and then the penny will drop. – Lennart Regebro Jan 29 '11 at 14:28
  • To me your reasoning is a bit shall we say rocky. On the other hand you think people are so good at programming that they don't need this sort of stuff and in the next sentence you tell how you have to circumvent poor programming. I have had many experiences where the library didn't quite do what I wanted - not easily anyways. I try my damnest to come up with alternative solutions, because using the library through non-API calls is just stupid. The API is usually pretty stable in stable libraries, inner functions aren't. So you are just increasing your maintenance burden. – Makis Jan 31 '11 at 18:35
  • @Makis: It has nothing to do with being a good or bad programmers. Good programmers make API's which other good programmers then find limited, it's prefectly normal. And private methods does not in any way protect bad programmers from misusing or misunderstanding your module. "because using the library through non-API calls is just stupid." Except when it isn't. Like when you end up having to duplicate half of the implementation, just because one method was private so you couldn't call it from your subclass. But as mentioned, I can't convince you, only experience can. – Lennart Regebro Jan 31 '11 at 21:55
  • "So you are just increasing your maintenance burden." Sure. But that is *your* problem, right? If you choose to use a method that may change, that is *your* decision and *your* problem. Not the problem of the guy who wrote the library. Why should *he* make that decision for *you*? Right, he shouldn't. Because when he takes that decision instead of you, he just creates more problems for you, because now you have to duplicate that "inner function" and keep it up to date with his bugfixes. Less maintenance burden? No, more, in fact. – Lennart Regebro Jan 31 '11 at 21:56
  • If that's not the problem, why not then change the library to better suit you? You break compatibility just the same way, except that if you change the library, you can try to send the author a patch and an explanation - maybe he then makes the change to his library and you can just update it, and everything is fine again. After that, zero maintenance burden. And even if he doesn't, it's much easier to update the library (diff the new version and your branch). In your case you might find that the private function no longer even exists! What do you do then? – Makis Feb 01 '11 at 11:37
  • @Lennart: And do you really think a good tactic to tell you are more experienced than someone else when you know nothing about that person's experience? IMO, I've been in those situations and have been able to get around them without having to use private methods. – Makis Feb 01 '11 at 11:40
  • @Makis: "Tactics"!? You either listen, or you don't. My "tactics" can't change that. If you got around your problems without having to use private methods you were not in those situations, per definition, as the definition is that you needed to call the private/protected method, and you could not, as it was private/protected. The way around it in those cases is to duplicate the code, which creates *more* maintenance burden than using an internal API does. But I already said that. But you didn't listen, did you? Now spend years developing Python and then explain why it needs private methods. – Lennart Regebro Feb 01 '11 at 11:48
  • @Lennart: I call it a tactic that you constantly belittle my experience and elevate yours. What you are saying is that since you can't come up with a good solution, no-one else can either. I find that stance insulting to others. And this has nothing to do with just Python, just because you can do stupid things in Python doesn't mean the problem you mention is unique to it. And as for reading what others write: I already wrote how to go along with *less* (often zero) maintenance, which you obviously didn't read. – Makis Feb 02 '11 at 10:21
  • Your solution requires *more* maintenance. Which I now pointed out several times. More repetition here is obviously not going to help. I have lead you to the water, it's up to you to drink. You are free not to drink, just as I should be free to "misuse" your module if I see fit to do so. In programming we are all consenting adults, and if I use the internals of your modules, it's my problem, not yours. And maybe I'm wrong, and maybe you never will realize this, but repeating it forever is hardly going to help. Private/protected methods have no benefit, and only cause problems. EOD. – Lennart Regebro Feb 02 '11 at 11:11
2

Although I don't recommend the practice, here's a way it could be done using sys._getframe():

import sys

class Base(object):
    def A(self):
        print '  in method A() of a {} instance'.format(self.__class__.__name__)

    def B(self):
        print '  in method B() of a {} instance'.format(self.__class__.__name__)
        if sys._getframe(1).f_code.co_name != 'A':
            print '    caller is not A(), aborting'
            return
        print '    called from A(), continuing execution...'

class Derived(Base):
    def A(self):
        print "  in method A() of a {} instance".format(self.__class__.__name__)
        print '    calling self.B() from A()'
        self.B()

print '== running tests =='
base = Base()
print 'calling base.A()'
base.A()
print 'calling base.B()'
base.B()
derived = Derived()
print 'calling derived.A()'
derived.A()
print 'calling derived.B()'
derived.B()

The output:

== running tests ==
calling base.A()
  in method A() of a Base instance
calling base.B()
  in method B() of a Base instance
    caller is not A(), aborting
calling derived.A()
  in method A() of a Derived instance
    calling self.B() from A()
  in method B() of a Derived instance
    called from A(), continuing execution...
calling derived.B()
  in method B() of a Derived instance
    caller is not A(), aborting
martineau
  • 119,623
  • 25
  • 170
  • 301