【问题标题】:Making functions non override-able使函数不可覆盖
【发布时间】:2008-11-26 15:08:29
【问题描述】:

我知道 python 函数默认是虚拟的。假设我有这个:

class Foo:
    def __init__(self, args):
        do some stuff
    def goo():
        print "You can overload me"
    def roo():
        print "You cannot overload me"

我不希望他们这样做:

class Aoo(Foo):
    def roo():
        print "I don't want you to be able to do this"

有没有办法防止用户重载roo()?

【问题讨论】:

  • 你为什么想要这样的东西?您是否害怕有人会覆盖它并且它对他们不起作用?这是他们的问题。但有时,他们知道自己在做什么,他们只需要这样做。我花了三天时间解决 Java 中的这种限制,在 Python 中是 20 秒。
  • @pupeno 干净的编码可能需要不应该被意外覆盖破坏的不变量。此外,您的论点仅在您无法访问超类代码的情况下才成立(例如在第 3 方库中)。更冗长:考虑一个具有多个开发人员的复杂 Python 项目,并且您是一个类的设计者/代码所有者,该类依赖于您的函数实现确保的某些不变量。您希望避免在惰性子类实现中破坏不变量。如果有人需要更改,他可以在超类代码中进行更改或发起设计讨论。

标签: python


【解决方案1】:

Python 3.8(2019 年 10 月发布)在输入中添加了 final 限定符。

final 限定符被添加到类型模块中——以 final 装饰器和 Final 类型注释的形式——用于三个相关目的:

  • 声明不应重写方法
  • 声明一个类不应被子类化
  • 声明不应重新分配变量或属性
from typing import final

class Base:
    @final
    def foo(self) -> None:
        ...

class Derived(Base):
    def foo(self) -> None:  # Error: Cannot override final attribute "foo"
                            # (previously declared in base class "Base")
        ...

它符合您的要求,并且现在得到核心 Python 的支持。

查看PEP-591了解更多详情。

【讨论】:

  • 遗憾的是没有办法有两个被接受的答案:)
  • 请注意,它不是在运行时强制执行的,它需要一个静态类型检查器:“有可能在类上使用 @final 装饰器在运行时动态防止子类化。不过,typing 中的任何其他内容都不会执行任何运行时强制,因此 final 也不会。'
【解决方案2】:

您可以使用元类:

class NonOverridable(type):
    def __new__(self, name, bases, dct):
        if bases and "roo" in dct:
            raise SyntaxError, "Overriding roo is not allowed"
        return type.__new__(self, name, bases, dct)

class foo:
    __metaclass__=NonOverridable
    ...

每当创建子类时,都会调用元类型的 new;这将在您提出的情况下导致错误。仅当没有基类时,它才会接受 roo 的定义。

您可以通过使用注解来声明哪些方法是最终的,从而使方法更加花哨;然后,您需要检查所有基础并计算所有最终方法,以查看它们中的任何一个是否被覆盖。

这仍然不能阻止某些人在定义方法后将其修补到一个类中;您可以尝试通过使用自定义字典作为类的字典来捕获这些(这可能不适用于所有 Python 版本,因为类可能要求类字典具有确切的 dict 类型)。

【讨论】:

  • 如果我不得不使用你的课程,我会恨你
  • 您不能将您的方法作为描述符提供,以防止自身被删除或覆盖吗?据推测,描述符仅在属性查找时触发,而不是直接在类字典中查找时触发。
  • 它需要新型类,即 Python 2.2 或更高版本。
  • 我们如何才能使NonOverridable 类足够通用,以供不同的类使用?目前它似乎只与foo 类紧密耦合。
  • 它根本没有与foo 耦合(尽管它确实有一个无法重新定义的硬编码方法列表); NonOverridable 可以用作任何类的元类(直到关于元类不组成的常见警告)。
【解决方案3】:

既然 Python 有猴子补丁,你不仅不能将任何东西“私有化”。即使可以,仍然有人可以在新版本的方法函数中进行猴子补丁。

您可以将此类名称用作“请勿靠近”的警告。

class Foo( object ):
    def _roo( self ):
       """Change this at your own risk."""

这是通常的方法。每个人都可以阅读您的来源。他们被警告了。如果他们勇敢地去他们被警告不要去的地方,他们就会得到他们应得的。它不起作用,你无法帮助他们。

您可以尝试使用内部类和由“私有”方法调用的“隐藏”实现模块故意使这一点变得模糊。但是……每个人都有你的来源。你不能阻止任何事情。您只能告知人们他们行为的后果。

【讨论】:

    【解决方案4】:
    def non_overridable(f):
        f.non_overridable = True
        return f
    
    class ToughMeta(type):
        def __new__(cls, name, bases, dct):
            non_overridables = get_non_overridables(bases)
            for name in dct:
                if name in non_overridables:
                    raise Exception ("You can not override %s, it is non-overridable" % name)
            return type.__new__(cls, name, bases, dct)
    
    def get_non_overridables(bases):
        ret = []
        for source in bases:
            for name, attr in source.__dict__.items():
                if getattr(attr, "non_overridable", False):
                    ret.append(name)
            ret.extend(get_non_overridables(source.__bases__))
        return ret
    
    class ToughObject(object):
        __metaclass__ = ToughMeta
        @non_overridable
        def test1():
            pass
    
    # Tests ---------------
    class Derived(ToughObject):
        @non_overridable
        def test2(self):
            print "hello"
    
    class Derived2(Derived):
        def test1(self):
            print "derived2"
    
    # --------------------
    

    【讨论】:

      【解决方案5】:

      迟到但并非所有 python 方法默认都是“虚拟的” - 考虑一下:

      class B(object):
          def __priv(self): print '__priv:', repr(self)
      
          def call_private(self):
              print self.__class__.__name__
              self.__priv()
      
      class E(B):
          def __priv(self): super(E, self).__priv()
      
          def call_my_private(self):
              print self.__class__.__name__
              self.__priv()
      
      B().call_private()
      E().call_private()
      E().call_my_private()
      

      由于名字修改而受到打击:

      B
      __priv: <__main__.B object at 0x02050670>
      E
      __priv: <__main__.E object at 0x02050670>
      E
      Traceback (most recent call last):
        File "C:/Users/MrD/.PyCharm2016.3/config/scratches/test_double__underscore", line 35, in <module>
          E().call_my_private()
        File "C:/Users/MrD/.PyCharm2016.3/config/scratches/test_double__underscore", line 31, in call_my_private
          self.__priv()
        File "C:/Users/MrD/.PyCharm2016.3/config/scratches/test_double__underscore", line 27, in __priv
          def __priv(self): super(E, self).__priv()
      AttributeError: 'super' object has no attribute '_E__priv'
      

      因此,如果您想从该语言中获得一些帮助,以禁止人们覆盖您在课堂上需要的一些功能,那么这就是您要走的路。如果您想要最终确定的方法是您的类 API 的一部分,但是您会被 cmets 方法(或元类黑客)所困。我个人的观点是,final 关键字对于继承非常有用——因为你可以避免类在被覆盖时以阴险的方式破坏(例如,考虑在超级实现中使用“final”方法,然后有人覆盖——繁荣,超级破坏)——并且出于文档目的(没有文档比编译时语法错误更好) - 但 Python 的动态特性不允许这样做,并且黑客很脆弱 - 所以添加一个文档字符串:

      """DON'T OVERRIDE THIS METHOD"""
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-02
        • 2019-04-21
        • 1970-01-01
        相关资源
        最近更新 更多