Reputation: 427
Let's say I have the following two classes
class A:
def own_method(self):
pass
def descendant_method(self):
pass
class B(A):
pass
and I want descendant_method
to be callable from instances of B
, but not of A
, and own_method
to be callable from everywhere.
I can think of several solutions, all unsatisfactory:
NotImplementedError
:class A:
def __init__(self):
self.some_field = None
def own_method(self):
pass
def descendant_method(self):
if self.some_field is None:
raise NotImplementedError
class B(A):
def __init__(self):
super(B, self).__init__()
self.some_field = 'B'
pass
But this is modifying the method's runtime behaviour, which I don't want to do
class A:
def own_method(self):
pass
class AA:
def descendant_method(self):
pass
class B(AA, A):
pass
This is nice as long as descendant_method
doesn't use much from A
, or else we'll have to inherit AA(A)
and this defies the whole point
A
and redefine it in a metaclass:class A:
def own_method(self):
pass
def __descendant_method(self):
pass
class AMeta(type):
def __new__(mcs, name, parents, dct):
par = parents[0]
desc_method_private_name = '_{}__descendant_method'.format(par.__name__)
if desc_method_private_name in par.__dict__:
dct['descendant_method'] = par.__dict__[desc_method_private_name]
return super(AMeta, mcs).__new__(mcs, name, parents, dct)
class B(A, metaclass=AMeta):
def __init__(self):
super(B, self).__init__()
This works, but obviously looks dirty, just like writing self.descendant_method = self._A__descendant_method
in B
itself.
What would be the right "pythonic" way of achieving this behaviour?
UPD: putting the method directly in B
would work, of course, but I expect that A
will have many descendants that will use this method and do not want to define it in every subclass.
Upvotes: 4
Views: 455
Reputation: 40703
What is so bad about making AA
inherit from A
? It's basically an abstract base class that adds additional functionality that isn't meant to be available in A
. If you really don't want AA
to ever be instantiated then the pythonic answer is not to worry about it, and just document that the user isn't meant to do that. Though if you're really insistent you can define __new__
to throw an error if the user tries to instantiate AA
.
class A:
def f(self):
pass
class AA(A):
def g(self):
pass
def __new__(cls, *args, **kwargs):
if cls is AA:
raise TypeError("AA is not meant to be instansiated")
return super().__new__(cls)
class B(AA):
pass
Another alternative might be to make AA
an Abstract Base Class. For this to work you will need to define at least one method as being abstract -- __init__
could do if there are no other methods you want to say are abstract.
from abc import ABCMeta, abstractmethod
class A:
def __init__(self, val):
self.val = val
def f(self):
pass
class AA(A, metaclass=ABCMeta):
@abstractmethod
def __init__(self, val):
super().__init__(val)
def g(self):
pass
class B(AA):
def __init__(self, val):
super().__init__(val)
Very finally, what's so bad about having the descendant method available on A
, but just not using it. You are writing the code for A
, so just don't use the method... You could even document the method that it isn't meant to be used directly by A
, but is rather meant to be available to child classes. That way future developers will know your intentions.
Upvotes: 2
Reputation: 7404
As far as I can tell, this may be the most Pythonic way of accomplishing what you want:
class A:
def own_method(self):
pass
def descendant_method(self):
raise NotImplementedError
class B(A):
def descendant_method(self):
...
Another option could be the following:
class A:
def own_method(self):
pass
def _descendant_method(self):
pass
class B(A):
def descendant_method(self):
return self._descendant_method(self)
They're both Pythonic because it's explicit, readable, clear and concise.
Choosing between one of these approaches will depend on how you intend on your use case. A more concrete example in your question would be helpful.
Upvotes: 2
Reputation: 51
Try to check the class name using __class__.__name__
.
class A(object):
def descendant_method(self):
if self.__class__.__name__ == A.__name__:
raise NotImplementedError
print 'From descendant'
class B(A):
pass
b = B()
b.descendant_method()
a = A()
a.descendant_method()
Upvotes: 0