开发者

Removing specific methods from child class which are inherited from parent class

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 开发者_如何学Goshould 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?


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


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


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).


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜