方法只能从 python 中的 class 个后代访问

Method accessible only from class descendants in python

假设我有以下两个 类

class A:
    def own_method(self):
        pass
    def descendant_method(self):
        pass

class B(A):
    pass

并且我希望 descendant_method 可以从 B 的实例调用,而不是 A 的实例,并且 own_method 可以从任何地方调用。

我想了几个办法,都不满意:

  1. 检查一些字段并手动加注 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

但这是在修改方法的运行时行为,我不想这样做

  1. 使用混入:
class A:
    def own_method(self):
        pass

class AA:
    def descendant_method(self):
        pass

class B(AA, A):
    pass

这很好,只要 descendant_method 不使用 A 的太多内容,否则我们将不得不继承 AA(A),这违背了整点

  1. A 中使方法私有并在元类中重新定义它:
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__()

这行得通,但显然看起来很脏,就像在 B 本身中写 self.descendant_method = self._A__descendant_method 一样。

实现此行为的正确 "pythonic" 方法是什么?

UPD:当然,将方法直接放在 B 中是可行的,但我希望 A 将有许多后代将使用此方法并且不想在每个中定义它子类。

据我所知,这可能是最Python完成您想要的事情的方式:

class A:
    def own_method(self):
        pass
    def descendant_method(self):
        raise NotImplementedError

class B(A):
    def descendant_method(self):
        ...

另一个选项可能是:

class A:
    def own_method(self):
        pass
    def _descendant_method(self):
        pass

class B(A):
    def descendant_method(self):
        return self._descendant_method(self)

它们都是 Pythonic 因为它明确、可读、清晰和简洁

  • 它是明确的,因为它没有做任何不必要的魔术。
  • 它是可读的,因为 一个人可以准确地说出你在做什么,你的意图是什么 乍一看。
  • 很清楚,因为前导单下划线是 Python 社区中广泛使用的私有约定 (非魔术)方法——任何使用它的开发人员都应该知道 小心。

在这些方法中做出选择将取决于您打算如何处理您的用例。你的问题中的一个更具体的例子会有所帮助。

尝试使用 __class__.__name__ 检查 class 名称。

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

AA 继承自 A 有什么不好?它基本上是一个抽象基础 class,添加了 A 中不可用的附加功能。如果你真的不想 AA 被实例化,那么 pythonic 的答案就是不用担心它,只是记录用户不打算这样做。不过,如果您真的坚持,您可以定义 __new__ 以在用户尝试实例化 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

另一种选择可能是将 AA 设为 Abstract Base Class。为此,您需要将至少一种方法定义为抽象方法——__init__ 如果您不想说其他方法是抽象的,则可以这样做。

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)

最后,在 A 上提供后代方法有什么不好,只是不使用它。您正在为 A 编写代码,所以不要使用该方法...您甚至可以记录该方法,它不打算由 A 直接使用,而是意味着供 classes 儿童使用。这样未来的开发人员就会知道您的意图。