2008-11-26 12 views
10

Sé que las funciones de python son virtuales por defecto. Digamos que tengo esto:Haciendo funciones no reemplazables

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

yo no quiero que sean capaces de hacer esto:

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

¿Hay una manera de evitar que los usuarios sobrecarga Roo()?

+2

¿por qué quieres una cosa así? ¿Tienes miedo de que alguien lo anule y no funcionará para ellos? es su problema Pero a veces, ellos saben lo que están haciendo y solo necesitan hacerlo. He gastado tres días para solucionar este tipo de limitación en Java, en Python fueron 20 segundos. – Pablo

Respuesta

32

Se puede utilizar una metaclase:

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

del metatipo nueva se llama cada vez que se crea una subclase; esto causará un error en el caso que presente. Aceptará una definición de roo solo si no hay clases base.

Puede hacer que el enfoque sea más elegante usando anotaciones para declarar qué métodos son finales; entonces necesita inspeccionar todas las bases y calcular todos los métodos finales, para ver si alguno de ellos es anulado.

Esto todavía no evita que alguien aplique un parche a un método en una clase después de que se haya definido; puede tratar de detectarlos utilizando un diccionario personalizado como el diccionario de las clases (que podría no funcionar en todas las versiones de Python, ya que las clases pueden requerir que el diccionario de clase sea del tipo exacto de dict).

+0

+1 por un BUEN ejemplo de metaclass __new__ –

+2

te odiaría, si tuviese que usar tu clase – hop

+0

¿No pudiste proporcionar tu método como un descriptor que evitó que se eliminara o se anulara? Presumiblemente, los descriptores solo se activan en la búsqueda de atributos y no si se buscan directamente en el diccionario de clases. – fuzzyman

8

Como Python tiene parches de mono, no solo no puedes hacer nada "privado". Incluso si pudieras, alguien podría poner parche en una nueva versión de la función del método.

Puede utilizar este tipo de nombre como una advertencia de "no acercarse".

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

Ese es el enfoque habitual. Todos pueden leer tu fuente. Ellos fueron advertidos. Si van audazmente adonde fueron advertidos de no ir, obtienen lo que merecen. No funciona y no puedes ayudarlos.

Puede tratar de hacer esto de forma intencionada con clases internas y módulos de implementación "ocultos" que son llamados por los métodos "privados". Pero ... todos tienen tu fuente. No puede prevenir nada. Solo puedes avisar a la gente de las consecuencias de sus acciones.

6
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" 

# -------------------- 
0

tarde a la fiesta, pero no todos los métodos de pitón son "virtual" por defecto - considerar:

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

golpes debido al nombre mangling:

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' 

Así que si quieres Obtenga ayuda del idioma para prohibir que las personas anulen un poco de la funcionalidad que necesita dentro de su clase. Este es el camino a seguir. Si el método que quiere hacer final es parte de su API de clase, sin embargo, está atascado con el enfoque de comentarios (o hacks de metaclass).Mi opinión personal es que una palabra clave final es muy útil para la herencia, ya que se puede evitar romper clases de manera insidiosa cuando se anula (considere usar el método "final" en la implementación súper por ejemplo y luego alguien anula - boom, súper roto) - y para fines de documentación (no hay documentos son mejores que un error de sintaxis en tiempo de compilación) - pero la naturaleza dinámica de Python no lo permitiría y hacks son frágiles - por lo que añadir una cadena de documentación:

"""DON'T OVERRIDE THIS METHOD""" 
Cuestiones relacionadas