Удаление определенных методов из дочернего класса, которые унаследованы от родительского класса - PullRequest
9 голосов
/ 14 июля 2011

Код, как показано ниже, только базовая структура:

class FooType(type):
    def __new__( cls, name, bases, classdict ):
        instance = type.__new__( cls, name, bases, classdict )
        # What can I do here?
        return instance

class FooBase( object, metaclass=FooType ):
    def __init__( self ):
        pass

class Foo( FooBase ):
    def __init__( self, name ):
        self.name = name

    def method1( self ):
        pass

    def method2( self ):
        pass

    def specialmethod( self ):
        pass

class A( Foo ):
    pass

class B( Foo ):
    pass

class C( Foo ):
    _disallowed_methods = ['specialmethod']

Я хочу, чтобы экземпляры класса C не имели specialmethod, но этот метод должен быть доступен для экземпляров A и B.

Я могу переопределить этот метод в классе C и вызвать ошибку, но я бы предпочел не делать этого.

Я понимаю, что могу добавить код для проверки _disallowed_methods в FooType и на основе этой проверки, есть ли у instance какие-либо из них в выводе dir(instance). Но я не могу удалить метод из __dict__ из C, используя любые методы, которые я пробовал до сих пор. Я попробовал следующие методы: delattr(instance, 'specialmethod') и del instance.__dict__['specialmethod'].

.

Метод delattr приводит к «AttributeError: specialmethod», а метод del приводит к «TypeError: объект« dict_proxy »не поддерживает удаление элемента»

В основном многие различные классы наследуются от Foo, но некоторые из них не должны иметь специальных методов, таких как C, для которых не должно быть specialmethod.

Что я делаю не так? Или как еще я могу это сделать?

Ответы [ 4 ]

5 голосов
/ 14 июля 2011

Ну, вы не можете сделать это таким образом, поскольку вам нужно изменить не класс C, а класс Foo, который действительно содержит specialmethod. Но на самом деле вы не можете сделать это, поскольку class является глобальным изменяемым объектом, и любые изменения в Foo затронут все дочерние классы.

Попробуйте думать по-другому. Например. Вы можете изменить логику доступа к атрибутам C класса:

class C( Foo ):
    def __getattribute__(self, name):
        if name in ['specialmethod']:
            raise AttributeError('no such method')
        return super(C, self).__getattribute__(name)

После этого C('a').specialmethod() производит трассировку:

Traceback (most recent call last):
  File "meta.py", line 37, in <module>
    C('a').specialmethod()
  File "meta.py", line 34, in __getattribute__
    raise AttributeError('no such method')
AttributeError: no such method
2 голосов
/ 14 июля 2011

Или как еще я могу это сделать?

Подобных результатов можно достичь, используя множественное наследование .

Переместите методы, которые вы хотели бы, чтобы только у некоторых детей было от Foo до ExtraFoo. Затем используйте class A(Foo, ExtraFoo) или class C(Foo). Таким образом, вы можете даже «прикрепить» данный метод дальше по иерархии дочерних элементов.

Если повторное присоединение метода не является чем-то, что вас интересует, тогда вы можете просто иметь ExtraFoo в качестве потомка Foo (то есть: добавлять методы, не отсоединяя их) и иметь class A(ExtraFoo) и class C(Foo) .

1 голос
/ 31 июля 2016

Если у вас есть родительский объект, который вы не хотите изменять, и дочерний элемент с одним или несколькими унаследованными методами, который вы хотите сделать недоступным, вы можете сделать это с помощью дескрипторов. Одним из самых простых подходов является использование property встроенного:

class Parent:
    def good_method(self):
        print('Good one')

    def bad_method(self):
        print('Bad one')

class Child(Parent):
    bad_method = property(doc='(!) Disallowed inherited')

one = Parent()
one.good_method()  # > 'Good one'
one.bad_method()   # > 'Bad one'

two = Child()
two.good_method()  # > 'Good one'
two.bad_method()   # > AttributeError: unreadable attribute
two.bad_method     # > AttributeError: unreadable attribute
two.bad_method = 'Test'  # > AttributeError: can't set attribute

Как справка (два) печатает это:

class Child(Parent)
 |  Method resolution order:
 |      Child
 |      Parent
 |      builtins.object
 |  
 |  Data descriptors defined here:
 |  
 |  bad_method
 |      (!) Disallowed inherited
 |  
 |  ----------------------------------------------------------------------
 |  Methods inherited from Parent:
 |  
 |  good_method(self)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from Parent:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

Довольно хорошо, на мой взгляд. Но вы должны быть осторожны, чтобы не определять наследуемые методы таким образом, если другие методы полагаются на них (этого можно избежать, используя прокси-класс, который наследует от родителя и переопределяет такие методы, чтобы использовать super().bad_method() вместо просто self.bad_method() и указывать на bad_method сам запретить дескриптор). При необходимости вы можете кодировать более сложную логику дескриптора

0 голосов
/ 08 сентября 2017

Я работал с тестами и наткнулся на точно такую ​​же проблему.

Я нашел только один верный способ удалить «ненужные методы» из унаследованного класса: удалить его из родительского класса. (Это плохая идея, поскольку она сломает ВСЕ экземпляры всех родительских классов и все экземпляры всех унаследованных классов, если эта функция вызывается хотя бы один раз).

Пример кода:

class Base(object):
    def excessive(self):
        pass

class Inher(Base):
    def __init__(self):
        #import pdb
        #pdb.set_trace()
        del Base.excessive

b=Base()
example = Inher()
try:
    example.excessive()
except  AttributeError:
    print("Removed!")
else:
    raise Exception("Not removed!")
try:
    b.excessive()
except AttributeError:
    print("Unfortunately, all instances of Base no longer have .excessive() method")

Причина в том, что «унаследованные» методы не хранятся в parent (как код или как ссылки), но хранятся внутри parent. Когда кто-то вызывает метод, python проходит через все родительские классы, пока не найдет один или не остановится.

В моем случае я смог использовать эту технику, потому что я использовал другие тесты парней для своих целей, и я сохранил их 'setUp / tearDown' и аксиллярные методы, но я удалил все их тесты.

Любое реальное приложение не должно использовать эту технику.

...