1
\$\begingroup\$

I am trying to implement an open source desktop application with python. I want to provide both a gui and an API. I know there is no private methods in python but i am trying to find the most elegant way to keep some methods private. I do not want my users to see all list of methods while using API. After some research, I come up with the implementation below and wondering if there is any problem with my implementation of this concept and if there is any room for improvement. Please provide me a review and do not hesitate to ask any questions you have. Here is my try:

from pprint import pprint


# CamelCase because it "acts" like a class
def CounterController():
    class CounterControllerPrivate(object):
        def __init__(self):
            self.counter = 0

        def add_one(self):
            self.counter += 1

        def reset(self):
            self.counter = 0

        def get_counter(self):
            return self.counter

    counter_controller = CounterControllerPrivate()

    class CounterControllerPublic(object):
        def add_one_endpoint(self):
            counter_controller.add_one()
            return counter_controller.get_counter()

        def reset_endpoint(self):
            counter_controller.reset()
            return counter_controller.get_counter()

    return CounterControllerPublic()


# counter attribute is not accessible from out here
controller = CounterController()

print(controller.add_one_endpoint())
print(controller.add_one_endpoint())
print(controller.reset_endpoint())

pprint(dir(controller))
\$\endgroup\$
9
  • \$\begingroup\$ When I run your code, I get: Traceback (most recent call last): File "cr105997.py", line 33, in <module>: controller.add_one_endpoint(): TypeError: add_one_endpoint() missing 1 required positional argument: 'self' \$\endgroup\$ Commented Oct 30, 2018 at 22:24
  • \$\begingroup\$ you are right. it gets that error with python3 but works with python2. I didnt realized that. thank you. now checking and will be updating the code. \$\endgroup\$
    – CanCode
    Commented Oct 30, 2018 at 22:29
  • \$\begingroup\$ For the record, I got the same error with both Python 2 and Python 3. \$\endgroup\$ Commented Oct 30, 2018 at 22:30
  • \$\begingroup\$ i figured that it was about static methods. changed it now. can you give it another chance? \$\endgroup\$
    – CanCode
    Commented Oct 30, 2018 at 22:35
  • \$\begingroup\$ What's the point of this code, if you cannot publicly query the counter value? If it's just a hypothetical example, then it's off-topic for Code Review. \$\endgroup\$ Commented Oct 30, 2018 at 22:41

2 Answers 2

6
\$\begingroup\$

The private objects and attributes are easily accessed using inspect.getclosurevars:

>>> c = CounterController()
>>> import inspect
>>> inspect.getclosurevars(c.add_one_endpoint)
ClosureVars(nonlocals={'counter_controller': <__main__.CounterController.<locals>.CounterControllerPrivate object at 0x10a047cf8>}, globals={}, builtins={}, unbound={'add_one'})
>>> _.nonlocals['counter_controller']
<__main__.CounterController.<locals>.CounterControllerPrivate object at 0x10a047cf8>
>>> _.counter
0

So you might consider whether it's worth spending a lot of effort on this approach. Personally I would spend the effort on API documentation instead.

\$\endgroup\$
3
  • \$\begingroup\$ Thank you for letting me know about inspect.getclosurecars. It seems powerful. Is there any work around to disable this too? \$\endgroup\$
    – CanCode
    Commented Oct 30, 2018 at 22:52
  • 3
    \$\begingroup\$ The approach that I would recommend is to write documentation for the public methods and add a note that everything not documented is subject to change without notice in future versions. It's not worth your time fighting with users to control access to the code. \$\endgroup\$ Commented Oct 31, 2018 at 10:58
  • \$\begingroup\$ You are right. I will be accepting your answer and focusing on docs. However, I still love to have decorators for classes and methods to make them private for example. I understand it is conflicting the design motivations of the language but love to suggest and try to implement that. Any suggestions about where I can discuss this issue? \$\endgroup\$
    – CanCode
    Commented Oct 31, 2018 at 18:48
2
\$\begingroup\$

Python classes operate under the principle of "We are all consenting adults here".

This means that there is no real way to encapsulate your methods completely, there will always be a way to access them regardless (except if you make your code a thin wrapper on a C++ class with private members, or similar, and even that might not be foolproof).

The convention is for method names with one leading underscore to be considered internal methods, probably not for public consumption and method names with two leading underscores to be the closest thing to private you will get without a great effort. Python does some name mangling on the latter (by inserting the class name).

What this means is that these methods will still show up when calling dir(obj), however, when working in an interactive session with tab completion, you need to explicitly start typing obj._ObjectClass__m[Tab] to access them, you will not get them with a simple obj.m[Tab]. It also means that when a subclass defines a method/attribute with the same name, there is no naming conflict with the base class.

In your documentation of the API you do not need to describe these methods at all, since it is the standard that they are not to be used. They should probably still have docstrings in the source code, though, if only for your own sake.

Some good further reading can be found here: https://stackoverflow.com/questions/3385317/private-variables-and-methods-in-python.

These conventions are also encoded in Python's official style-guide, PEP8:

  • _single_leading_underscore: weak "internal use" indicator. E.g. from M import * does not import objects whose name starts with an underscore.

  • single_trailing_underscore_: used by convention to avoid conflicts with Python keyword, e.g. Tkinter.Toplevel(master, class_='ClassName')

  • __double_leading_underscore: when naming a class attribute, invokes name mangling (inside class FooBar, __boo becomes _FooBar__boo; see below).

  • __double_leading_and_trailing_underscore__: "magic" objects or attributes that live in user-controlled namespaces. E.g. __init__, __import__ or __file__. Never invent such names; only use them as documented.

\$\endgroup\$
1
  • \$\begingroup\$ @GarethRees: You are right, I reworded that part. In any case it is described again in the part quoted from PEP8 \$\endgroup\$
    – Graipher
    Commented Oct 31, 2018 at 11:55

Your Answer

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

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