12

The code is as below, just the basic structure:

class FooType(type):
    def __new__( cls, name, bases, classdict ):
        instance = type.__new__( cls, name, bases, classdict )
        # What can I do here?
        return instance

class FooBase( object, metaclass=FooType ):
    def __init__( self ):
        pass

class Foo( FooBase ):
    def __init__( self, name ):
        self.name = name

    def method1( self ):
        pass

    def method2( self ):
        pass

    def specialmethod( self ):
        pass

class A( Foo ):
    pass

class B( Foo ):
    pass

class C( Foo ):
    _disallowed_methods = ['specialmethod']

What I want to do is that instances of class C should not have specialmethod, but that method should be available to instances A and B.

I can override this method in class C and raise an error, but I would prefer not to do this.

I realize I can add in code to check for _disallowed_methods in the FooType and on the basis of that check if the instance has any of them in the output of dir(instance). But I cannot seem to remove the method from __dict__ of C using any methods I have tried so far. The methods I tried are delattr(instance, 'specialmethod'), and del instance.__dict__['specialmethod'].

The delattr method results in "AttributeError: specialmethod", and the del method results in "TypeError: 'dict_proxy' object does not support item deletion"

Basically many different classes will inherit from Foo, but some of them should not have specific methods available to them like C which should not have specialmethod available to it.

What am I doing wrong? Or how else can I accomplish this?

skulled
  • 133
  • 1
  • 6
  • 2
    BTW: This would violate the [Liskov Substitution Principle](http://en.wikipedia.org/wiki/Liskov_substitution_principle). – pillmuncher Jul 14 '11 at 15:08

4 Answers4

7

If you have a parent, which you don't want to be modified, and a child with one or more inherited methods you want to be unaccessible, you could do so with descriptors. One of the simplest approach is to use property built-in:

class Parent:
    def good_method(self):
        print('Good one')

    def bad_method(self):
        print('Bad one')

class Child(Parent):
    bad_method = property(doc='(!) Disallowed inherited')

one = Parent()
one.good_method()  # > 'Good one'
one.bad_method()   # > 'Bad one'

two = Child()
two.good_method()  # > 'Good one'
two.bad_method()   # > AttributeError: unreadable attribute
two.bad_method     # > AttributeError: unreadable attribute
two.bad_method = 'Test'  # > AttributeError: can't set attribute

How help(two) prints it:

class Child(Parent)
 |  Method resolution order:
 |      Child
 |      Parent
 |      builtins.object
 |  
 |  Data descriptors defined here:
 |  
 |  bad_method
 |      (!) Disallowed inherited
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from Parent:
 |  
 |  good_method(self)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from Parent:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Pretty good, in my opinion. But you should be careful not to define inherited methods this way if other methods rely on them (this could be avoided by using proxy class, which inherits from parent and redefines such methods to use super().bad_method() instead of just self.bad_method() and points the bad_method itself to disallowing descriptor). You could code more complicated descriptor logic if needed

thodnev
  • 1,564
  • 16
  • 20
5

Well, you can't accomplish this in such a way, since you have to modify not C class, but Foo class, which really contains specialmethod. But actually you can't do it since class is the global mutable object and any changes to Foo will affect all child classes.

Try to think in another way. E.g. you can modify logic of accessing attributes of C class:

class C( Foo ):
    def __getattribute__(self, name):
        if name in ['specialmethod']:
            raise AttributeError('no such method')
        return super(C, self).__getattribute__(name)

After that C('a').specialmethod() produces a traceback:

Traceback (most recent call last):
  File "meta.py", line 37, in <module>
    C('a').specialmethod()
  File "meta.py", line 34, in __getattribute__
    raise AttributeError('no such method')
AttributeError: no such method
Roman Bodnarchuk
  • 29,461
  • 12
  • 59
  • 75
  • I tried this out (Apologies for not mentioning that in my initial post), and plan on going this route as the last resort. From my understanding this approach means that every time an attribute of instances of class `C` are used it goes through this method and thus my apprehension was its effect on speed/performance. Is that negligible enough that I don't have to worry about it? – skulled Jul 14 '11 at 14:59
  • @skulled Yes, it might affect performance. But I can't believe that it will significant in your case. If you are worrying about it just `timeit`. – Roman Bodnarchuk Jul 14 '11 at 15:04
4

Or how else can I accomplish this?

You could achieve similar results by using multiple inheritance.

Move the methods that you would like only some of the children to have from Foo to ExtraFoo. Then use class A(Foo, ExtraFoo) or class C(Foo). This way you could even "reattach" a given method further down the children hierarchy.

If reattaching the method is not something you are interested to, then you could simply have ExtraFoo as a child of Foo (so: adding the methods, not detaching them) and have class A(ExtraFoo) and class C(Foo).

mac
  • 42,153
  • 26
  • 121
  • 131
  • I realized I could this by creating the extra class and subclass using both `Foo` and `FooExtra` to classes that need the `specialmethod` and just `Foo` to the others. I just thought I perhaps could remove the method from dict when the instance is being created using the metaclass. Guess I was wrong ... – skulled Jul 14 '11 at 14:55
  • 1
    @skulled - Python is very flexible, and there is a good chance that you could achieve that somehow playing with the internals. Yet, the most important point is that it would be bad design. One of the OOP tenets is that a subclass "inherit" from its parent, so "disinherit" a subclass would be like building wings on a plane designed **not to** make it fly! :) BTW: you should upvote the questions that you found useful, and - if any solved your problem - you could mark it as "accepted" too (Just mentioning as I noticed you are new on the site, forgive me if you already knew that). – mac Jul 17 '11 at 10:27
  • Thanks! I kind of realized that perhaps I was taking on the wrong approach once the responses confirmed my suspicions. – skulled Jul 18 '11 at 15:56
  • @mac - re "One of the OOP tenets..." I can build a [stack](https://en.wikipedia.org/wiki/Stack_(abstract_data_type)) class from scratch, or I can get close to a stack by inheriting from `list`, then discarding some methods. I prefer the latter. "The strongest test of any system is not how well its features conform to anticipated needs but how well it performs when one wants to do something the designer did not forsee" (Alan Kay, 1984, "Computer Software" _Scientific American_ **251**:4--9). A simple form of doing something unforseen is not doing something foreseen. – Ana Nimbus Oct 16 '18 at 22:54
  • @AnaNimbus - Maybe in some casesl. In this specific instance probably just bad design, given that tera are foressen, more robust, more elegant and resilient and well established patterns available. – mac Oct 30 '18 at 20:49
0

I've worked with tests and stumble upon exactly same problem.

I found only one true way to remove 'unnecessary methods' from inherited class: remove it from parent. (This is bad idea, as it will break ALL instances of all parent class and all instances of all inherited classes if that function is called at least once).

Example code:

class Base(object):
    def excessive(self):
        pass

class Inher(Base):
    def __init__(self):
        #import pdb
        #pdb.set_trace()
        del Base.excessive

b=Base()
example = Inher()
try:
    example.excessive()
except  AttributeError:
    print("Removed!")
else:
    raise Exception("Not removed!")
try:
    b.excessive()
except AttributeError:
    print("Unfortunately, all instances of Base no longer have .excessive() method")

The reason is that 'inherited' methods aren't stored in parent (as code or as links), but are kept inside parent. When someone calls a method, python travel through all parent classes until it find one or stops.

In my case I was able to use this technique because I used other guys tests for my purposes and I kept their 'setUp/tearDown' and axillary methods, but I've removed all their tests.

Any real-life application shouldn't use this technique.

George Shuklin
  • 6,952
  • 10
  • 39
  • 80