3

I'd like to create an Python class that superficially appears to be a subclass of another class, but doesn't actually inherit its attributes.

For instance, if my class is named B, I'd like isinstance(B(), A) to return True, as well as issubclass(B, A), but I don't want B to have the attributes defined for A. Is this possible?

Note: I don't control the implementation of A.

Why I care: The module I'm working with checks that a passed object is a subclass of A. I want to define the necessary attributes in B without inheriting the superfluous attributes defined in A (whose implementation I do not control) because I'm using __getattr__ to pass some attribute calls onto a wrapped class, and if these attributes are defined by inheritance from A, __getattr__ won't be called.

Luke Taylor
  • 8,631
  • 8
  • 54
  • 92

5 Answers5

3

In Python3, override the special method __getattribute__. This gives you almost complete control over attribute lookups. There are a few corner cases so check the docs carefully (it's section 3.3.2 of the Language Reference Manual).

Paul Cornelius
  • 9,245
  • 1
  • 15
  • 24
  • Moreso than `__getattr__`? iirc, `__getattr__` won't be called if the attribute in question is already defined – Luke Taylor Jul 08 '16 at 20:56
  • `__getattribute__`, unlike `__getattr__`, gets called for all defined and undefined attributes – λuser Jul 08 '16 at 21:03
  • 1
    @LukeTaylor See [this question](http://stackoverflow.com/q/3278077/216074), `__getattribute__` is the hardcore way to stop access to members. That being said, this is not really a good solution, as you need to opt out of every member individually (and also prevent subtypes from actually redefining those members). – poke Jul 08 '16 at 21:04
  • 1
    @LukeTaylor: The task at hand specifically requires the class B to inherit from A, yet override all the attributes that are already defined by A. Hence the use of `__getattribute__` instead of `__getattr__` is necessary. – Paul Cornelius Jul 08 '16 at 21:41
3

Use abstract base classes to make a semingly unrelated class B a subclass of A without inheriting from it:

from abc import ABCMeta
class A (metaclass=ABCMeta):
    def foo (self):
        print('foo')

class B:
    def bar (self):
        print('bar')

A.register(B)

Then used, it gives the desired results and appears as a subtype without actually having any of the base type’s members:

>>> issubclass(B, A)
True
>>> b = B()
>>> isinstance(b, A)
True
>>> b.foo()
Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    b.foo()
AttributeError: 'B' object has no attribute 'foo'

I'm using __getattr__ to pass some attribute calls onto a wrapped class, and if these attributes are defined by inheritance from A, __getattr__ won't be called.

__getattr__ is not invoked for members which are found using the normal attribute resolution. You can use __getattribute__ then instead.

However, if what you are doing is overwriting the behavior of the base class A, then I don’t see why simply overwriting the methods is not an option:

class A:
    def doSomething (self);
        print('Do not do this!')

class B:
    def __init__ (self, wrapper):
        self.wrapper = wrapper

    def doSomething (self):
        print('Doing something else instead!')
        self.wrapper.doSomething()
poke
  • 369,085
  • 72
  • 557
  • 602
3

You could implement __getattribute__ to raise AttributeErrors for the attributes that are not in B:

class A(object):
    def __init__(self):
        self.foo = 1

    def bar(self):
        pass

class B(A):
    def __init__(self):
        self.baz = 42

    def __getattribute__(self, attr):
        if attr in ('foo', 'bar'):
            raise AttributeError()
        return super(B, self).__getattribute__(attr)

I'm curious, why would you do this?

λuser
  • 893
  • 8
  • 14
  • I'm working with a library that checks whether something is a subclass before it will do anything with it, so I need to trick it into thinking that something with all the necessary methods (but not the superfluous ones) is a subclass when it's not. – Luke Taylor Jul 08 '16 at 21:03
  • @LukeTaylor In that case, why do you care whether those original members are actually there or not? If you are certain that they are not used (otherwise you couldn’t safely expect it to work without those members), then they probably won’t hurt anyone just *being* there. – poke Jul 08 '16 at 21:07
  • @poke Because it _uses_ them, but I'm implementing `__getattr__` to read them from a wrapped class. If the methods are defined, `__getattr__` isn't called. – Luke Taylor Jul 08 '16 at 21:08
1

As long as you're defining attributes in the __init__ method and you override that method, B will not run the code from A's __init__ and will thus not define attributes et al. Removing methods would be harder, but seem beyond the scope of the question.

Delioth
  • 1,564
  • 10
  • 16
1

I hope this satisfies you (I think it's a bit dirty hack):

class A:
    attribute = "Hello"
    pass

class B(A):
    def __getattribute__(self, name):
        if name == "__dict__":
            return super().__getattribute__(name)
        if name in type(self).__dict__:
            return type(self).__dict__[name]
        if name in self.__dict__:
            return self.__dict__[name]
        raise AttributeError("type object '{}' has no attribute '{}'".format(type(self).__name__, name))

Now let's test it:

>>> a = A()
>>> a.attribute
'Hello'
>>> b = B()
>>> b.attribute
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "file.py", line 13, in __getattribute__
    raise AttributeError("type object '{}' has no attribute '{}'".format(type(self).__name__, name))
AttributeError: type object 'B' has no attribute 'attribute'

Unfortunately class B itself inherited attributes so this happens:

>>> B.attribute
'Hello'

I hope it doesn't matters, if it does you need to use metaclasses (which can get quite nasty).

knowledge
  • 383
  • 3
  • 15