15

I want to hide some public methods during inheritance class B from class A:

class A(object):
  def someMethodToHide():
    pass

  def someMethodToShow():
    pass

class B(A):
  pass

len(set(dir(A)) - set(dir(B))) == 1

How to do it in python if it possible?

Chameleon
  • 9,722
  • 16
  • 65
  • 127
  • 1
    What's your use case for *deleting* methods for a subclass? Are you sure you want to use class inheritance in this case? – Ffisegydd Apr 20 '14 at 11:26
  • 4
    You can extend both A and B from a third class C which has only the methods to show. Then you can implement methodToHide in class A only. – Selcuk Apr 20 '14 at 11:29
  • @Selcuk Looks that is the simplest method to achieve it :) – Chameleon Apr 20 '14 at 11:35
  • @Ffisegydd Yes since not want duplicate code at all - not like Java way where you need to fight with code duplication. – Chameleon Apr 20 '14 at 11:36
  • 1
    But I suppose my point was "Why do you want to only partially inherit from a superclass?" Would it truly be thought of as a subclass if it only subscribed to part of the features? – Ffisegydd Apr 20 '14 at 11:38
  • @Ffisegydd There no answer on your question - since I want **partial inheritance** - I will use #Seluck pattern. Whatever it will be nice if question will be answered. – Chameleon Apr 20 '14 at 11:49
  • 1
    Did you do any research before asking this question? This is a duplicate of both [Is it possible to do partial inheritance with Python?](http://stackoverflow.com/q/5647118/7432) and [How to perform partial inheritance](http://stackoverflow.com/q/11204053/7432) – Bryan Oakley Apr 20 '14 at 13:54
  • @BryanOakley Yes I do some research and know some alternatives like `override` with exception and mixing classes. I've not found meta class alternative. I know very well polymorphic inheritance but not know meta class patterns or python specific idioms. This is very basic question so it is easy for human but very hard for Google or other searches. – Chameleon Apr 20 '14 at 23:43

3 Answers3

11

Here is the answer form of my comment: You can inherit both A and B from a third class, C. Like that:

class C(object):
  def someMethodToShow():
    pass

class A(C):
  def someMethodToHide():
    pass

class B(C):
  pass

As a side note, if what you wanted were possible, it would break the polymorphism. This one won't.

Selcuk
  • 57,004
  • 12
  • 102
  • 110
  • That is the most common solution and valid pattern in OO design - I will study CURSED one since what to hide some methods. – Chameleon Apr 20 '14 at 23:25
5

Selcuk's suggestion is best but sometimes the refactoring is too troublesome.

So instead, here's something that will cause your co-workers to scream like banshees and curse your name.

It works by using a metaclass, hide_meta, to add overloaded __getattribute__ and __dir__ methods. Its application to the desired class is done by simply setting __metaclass__ = hide_meta and __excluded__ = ["list", "of", "unwanted", "attributes/methods"].

class hide_meta(type):
    def __new__(cls, cls_name, cls_bases, cls_dict):
        cls_dict.setdefault("__excluded__", [])
        out_cls = super(hide_meta, cls).__new__(cls, cls_name, cls_bases, cls_dict)

        def __getattribute__(self, name):
            if name in cls_dict["__excluded__"]:
                raise AttributeError(name)
            else:
                return super(out_cls, self).__getattribute__(name)
        out_cls.__getattribute__ = __getattribute__

        def __dir__(self):
            return sorted((set(dir(out_cls)) | set(self.__dict__.keys())) - set(cls_dict["__excluded__"])) 
        out_cls.__dir__ = __dir__

        return out_cls

class A(object):
    def someMethodToHide(self):
        pass

    def someMethodToShow(self):
        pass

class B(A):
    __metaclass__ = hide_meta
    __excluded__  = ["someMethodToHide"]

a = A()
print dir(a)
b = B()
print dir(b)
b.someMethodToShow()
b.someMethodToHide()
Community
  • 1
  • 1
dilbert
  • 3,008
  • 1
  • 25
  • 34
  • 1
    This is remarkably complex for what little value it gives. I think this answer to an identical question gives a much cleaner solution: http://stackoverflow.com/a/11204743/7432 – Bryan Oakley Apr 20 '14 at 13:52
  • @BryanOakley, that answer is simpler however `NotImplementedError` doesn't 'hide' the method, it just makes it unusable. That answer also wouldn't work for an attribute (not without involving `@property` anyway). – dilbert Apr 20 '14 at 22:33
  • @dilbert Indeed `NotImplementedError` is not what I asked. You explanation is very good - looks that is the best answer! Sometimes is need to do CURSED CODE as you said which hides methods. BTW #Seluck solution should be used often but sometimes is need to hide methods - override with `NotImplementedError` is wrong pattern since error type is not valid and suggest something else - valid is `AttributeError`. I will mark answer soon if there will not better solution. – Chameleon Apr 20 '14 at 23:33
2

You can create a descriptor class for emulating "deleted" attributes. Then you can assign the "to-be-deleted" name an instance of that class.

Here's a full example, showing the errors / tracebacks emerging from access of that attribute on the subclass and an instance thereof. By raising a custom error the traceback explicitly indicates that this attribute has been intentionally "deleted".

In [1]: class DeletedAttributeError(AttributeError): 
   ...:     pass 
   ...:  
   ...:  
   ...: class DeletedAttribute: 
   ...:     def __set_name__(self, owner, name): 
   ...:         self.name = name 
   ...:  
   ...:     def __get__(self, instance, owner): 
   ...:         cls_name = owner.__name__ 
   ...:         accessed_via = f'type object {cls_name!r}' if instance is None else f'{cls_name!r} object' 
   ...:         raise DeletedAttributeError(f'attribute {self.name!r} of {accessed_via} has been deleted') 
   ...:  
   ...:  
   ...: class Foo: 
   ...:     def hide_me(self): 
   ...:         pass 
   ...:  
   ...:  
   ...: class Bar(Foo): 
   ...:     hide_me = DeletedAttribute() 
   ...:                                                                                       

In [2]: Foo.hide_me                                                                           
Out[2]: <function __main__.Foo.hide_me(self)>

In [3]: Bar.hide_me                                                                           
---------------------------------------------------------------------------
DeletedAttributeError                     Traceback (most recent call last)
<ipython-input-3-240c91cc1fc8> in <module>
----> 1 Bar.hide_me

<ipython-input-1-0f699423deb7> in __get__(self, instance, owner)
     10         cls_name = owner.__name__
     11         accessed_via = f'type object {cls_name!r}' if instance is None else f'{cls_name!r} object'
---> 12         raise DeletedAttributeError(f'attribute {self.name!r} of {accessed_via!r} has been deleted')
     13 
     14 

DeletedAttributeError: attribute 'hide_me' of type object 'Bar' has been deleted

In [4]: Bar().hide_me                                                                         
---------------------------------------------------------------------------
DeletedAttributeError                     Traceback (most recent call last)
<ipython-input-4-9653f0da628c> in <module>
----> 1 Bar().hide_me

<ipython-input-1-0f699423deb7> in __get__(self, instance, owner)
     10         cls_name = owner.__name__
     11         accessed_via = f'type object {cls_name!r}' if instance is None else f'{cls_name!r} object'
---> 12         raise DeletedAttributeError(f'attribute {self.name!r} of {accessed_via!r} has been deleted')
     13 
     14 

DeletedAttributeError: attribute 'hide_me' of 'Bar' object has been deleted

The above code works on Python 3.6+.

a_guest
  • 34,165
  • 12
  • 64
  • 118