sorin
sorin

Reputation: 170856

How to refactor methods in Python in order to make them private?

I know that there is no such thing as true private in Python for sometimes you need to following:

Please remember that this has to work with existing codebase, so it should support a gradually re factoring of the existing code.

The last point is almost solved, the only question about it is if I should use one underscore or two underscores?

Upvotes: 0

Views: 357

Answers (4)

tkone
tkone

Reputation: 22768

Hiding from autocomplete is going to completely depend on your editor and how it handles autocomplete. My editor does not do autocomplete so I wouldn't need to use it.

The standard python convention is to prefix and underscore to the method name. This tells the user that the method is private and shouldn't be used.

You can use double underscores before the method name; this will invoke name mangling.

See 9.7 on this page for details: http://docs.python.org/tutorial/classes.htm.

But it's still not private; it can be called.

As far raising exceptions look into the inspect object and frame info. There are a ton of q and as on this site about that.

tl;dr You can't make anything private but you can make it harder to find

Upvotes: 1

unutbu
unutbu

Reputation: 880987

You can decorate the private methods with a call to warnings.warn.

I'm not sure what you mean by preventing people but allowing modules to call methods. Is the distinction that one happens at an interactive prompt, but the other does not? If so, you can test if python is being run from an interactive prompt by checking the value of sys.path[0].

When python runs a script, sys.path[0] equals the directory of the script. When python runs an interactive session, sys.path[0] is set to the empty string ''. So, to warn people, but not scripts, you could do

import warnings
import functools

def warn_private(func):
    if not sys.path[0]:
        @functools.wraps(func)
        def wrapper(self,*args,**kwargs):
            warnings.warn('{f} is private'.format(f=func.__name__))
            return func(self,*args,**kwargs)
        return wrapper
    else:
        return func

class Foo(object):
    @warn_private
    def _bar(self):
        pass

Upvotes: 0

PaulMcG
PaulMcG

Reputation: 63802

Maybe define your private methods on an internal class, and access it internally using self._private.method:

class PublicObject(object):

    class PrivateObject(object):
        def __init__(self, public):
            self.public = public

        def private1(self):
            print "a private method, not visible outside the public wrapper on", id(self.public)

    def __init__(self):
        self._private = self.PrivateObject(self)

    def public1(self):
        print "a public method, which invokes a private one"
        return self._private.private1()

a = PublicObject()

print dir(a) # only shows public1, not private1; won't show private1 in most IDEs autocomplete

a.public1()
a.private1() # raises an exception
a._private.private1() # still possible to call private methods, but obvious when you are doing so

If you wanted ProtectedObject with visibility up the inheritance chain, create a similar _protected attribute and use __getattr__ to access attributes and methods on super(PublicObject,self.public)._protected.

By the way, what you are asking is really outside the Python philosophy, and adding this indirection through a composite object will incur a performance cost.

Upvotes: 0

Sven Marnach
Sven Marnach

Reputation: 602745

Python has the philosophy of "consenting adults": Prefix methods with an underscore to mark them as private. Don't call any methods with a leading underscore from the outside. If you do, you're on your own. You are free to do so, but you have been warned.

To adopt this convetion in an existing code base, rename the original methods to names with a leading underscore, and add a wrapper with the original name that throws a warning.

Your IDE should have configurable auto-completion. If not, use Emacs :)

Upvotes: 7

Related Questions