0

I have a Python system consisting of around 9-10 classes all implementing most of a fat duck-typed interface and used interchangeably by a large collection of modules. I'm trying to refactor the classes into a core, explicit (i.e. ABC) interface and peripheral functionality, following separation of responsibility, but in order to do that I need to be able to tell when the consumer modules are calling methods outside the core interface.

Suppose I have an ABC with abstract methods:

from abc import ABCMeta, abstractmethod

class MyABC:
    __metaclass__ = ABCMeta
    @abstractmethod 
    def foo(self):
        pass

I also have a class implementing those abstract methods as well as other methods:

class MyClass(MyABC):
    def foo(self):
        pass
    def bar(self):
        pass

instance = MyClass()

>>> isinstance(instance, MyABC)
True

How can I ensure that when I pass instance to a method do_something it only uses the methods that are part of MyABC (in this case foo) and not any other methods (bar)? In a static-typed language (e.g. C++) I could pass do_something a pointer of the ABC type; is there some sort of wrapper available in Python that will restrict method access similarly?

ecatmur
  • 137,771
  • 23
  • 263
  • 343

2 Answers2

0

Short and simple answer: no

There is no concept of private methods/variables in Python that would be enforced, as described here in detail.

In Python this is handeld by convention.

And if you really want to go into the deep internals checkout this thread.

Community
  • 1
  • 1
schacki
  • 9,176
  • 4
  • 23
  • 28
0

This is what I came up with:

class ABCGuard(object):
    def __init__(self, obj, *abcs):
        if any(not isinstance(obj, abc) for abc in abcs):
            raise ValueError("{0} must implement {1}"
                             .format(obj.__class__.__name__,
                                     ', '.join(abc.__name__ for abc in abcs
                                               if not isinstance(obj, abc))))
        self.__obj = obj
        self.__abcs = abcs
        classname = '{0}{{{1}}}'.format(obj.__class__.__name__,
                                        ', '.join(abc.__name__ for abc in abcs))
        self.__class__ = type(classname, (ABCGuard, ) + abcs, {})

    def __getattribute__(self, name):
        if name.startswith('_ABCGuard__') or (name.startswith('__') and
                                              name.endswith('__')):
            return super(ABCGuard, self).__getattribute__(name)
        elif any(name in abc.__abstractmethods__ for abc in self.__abcs):
            return getattr(self.__obj, name)
        else:
            raise AttributeError("%r object has no attribute %r" %
                                 (self.__class__.__name__, name))

    def __dir__(self):
        return [x for abc in self.__abcs for x in abc.__abstractmethods__]
ecatmur
  • 137,771
  • 23
  • 263
  • 343