Как переопределить метод экземпляра на уровне экземпляра в Python, не заменяя его? - PullRequest
0 голосов
/ 01 мая 2019

Описание

Мой вопрос очень похож на вопрос, поднятый здесь , с одним отличием: я не хочу напрямую редактировать или заменять что-либо в исходном экземпляре класса.

Итак, представьте, что у нас есть класс, определенный как

class Class1(object):
    """
    This is Class1.

    """

    def __init__(self, name):
        self.name = name

    def a(self):
        "Method a"
        return("This is the Class1 a()-method!")

    def b(self):
        "Method b"
        return("This is the Class1 b()-method!")

    def bb(self):
        "Method bb"
        return(self.b())

Теперь я хочу создать функцию, которую пользователь может вызвать, где она предоставляет мне экземпляр Class1 (или любой производный подкласс). Затем я возвращаю объект, который ведет себя точно так же, как предоставленный экземпляр, за исключением того, что b() -метод был заменен

def b(self):
    return('This is the new Class1 b()-method!')

Все остальное в этом объекте должно быть точно таким же, как и в предоставленном экземпляре, чтобы новый объект мог использоваться в любом месте, где мог бы старый. По сути, это как если бы определение Class1 (или любой используемый подкласс) уже использовало это определение b() для начала.

Попытка

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


Я попытался использовать решение, которое было дано здесь , которое дало мне следующее:

from types import MethodType


# Define handy decorator for overriding instance methods
def override_method(instance):
    def do_override(method):
        setattr(instance, method.__name__, MethodType(method, instance))

    return(do_override)


# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
    # Override b-method
    @override_method(class1_obj)
    def b(self):
        return('This is the new Class1 b()-method!')

    # Return it
    return(class1_obj)

Это решение в основном делает все, что я хочу, за исключением того, что оно напрямую заменяет b() -метод в предоставленном class1_obj новым определением. Следовательно, после вызова функции get_new_Class1_obj() исходный код class1_obj больше не может использоваться в исходном состоянии. Это проблема, так как для приложения, которое у меня есть, я на самом деле требую, чтобы оригинальный метод b() был по-прежнему доступен (я просто не использовал такую ​​вещь в этом примере, чтобы сделать его немного проще).


Другой способ, которым я пытался это сделать, заключался в использовании фабрики классов (я пробовал несколько разных версий, причем ниже, вероятно, ближе всего к тому, что я хочу получить):

# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
    # Define list with all methods that are overridden
    override_attrs = ['__init__', '__getattribute__', '__dir__', 'b']

    # Make a subclass of the class used for class1_obj
    # This is required for functions like 'isinstance'
    class new_Class1(class1_obj.__class__, object):
        # Empty __init__ as no initialization is required
        def __init__(self):
            pass

        # Make sure only overridden attributes are used from this class
        def __getattribute__(self, name):
            if name in override_attrs:
                return(super().__getattribute__(name))
            else:
                return(getattr(class1_obj, name))

        # Use the __dir__ of class1_obj
        def __dir__(self):
            return(dir(class1_obj))

        # Override b-method
        def b(self):
            return("This is the new Class1 b()-method!")

    # Initialize new_Class1
    new_class1_obj = new_Class1()

    # Return it
    return(new_class1_obj)

Это также очень близко к тому, что я хочу (даже если постоянно обновлять список override_attrs), и теперь я могу использовать оригинальный class1_obj в new_class1_obj, если захочу. Однако проблема здесь в том, что bb() -метод new_class1_obj не будет работать должным образом, так как он будет использовать b() -метод class1_obj, а не метод new_class1_obj. Насколько я знаю, это невозможно сделать, не зная, что метод bb() существует в такой форме. Поскольку возможно, что кто-то подклассов Class1 и вводит c() -метод, который вызывает b(), это решение не будет работать должным образом (в то время как это будет работать должным образом с первым решением).

Отсутствие подкласса Class1 избавит вас от некоторых неприятностей, но это также будет означать, что такие функции, как isinstance, больше не работают должным образом, не решая проблему с bb() -методом.

Решение

Итак, в настоящее время я не могу придумать решение для этого (я пытался в течение нескольких дней). Я подумываю об использовании решения с первой попытки, но вместо немедленной замены b() -метода я сначала назначаю b() что-то вроде _old_b() или _b() (очевидно, убедившись, что он уже не существует), а затем заменяю b(). Мне не очень нравится это решение, так как оно мне покажется слишком грубым и грязным.

Так, у кого-нибудь есть идея для этого? На мой взгляд, это звучит как очень простая проблема: у меня есть экземпляр, и я хочу обновить один из его методов экземпляра новым, без изменения исходного экземпляра. Но, похоже, все не так просто.

Пример

Полный пример использования этого будет:

# Define original Class1 class
class Class1(object):
    """
    This is Class1.

    """

    def __init__(self, name):
        self.name = name

    def a(self):
        "Method a"
        return("This is the Class1 a()-method!")

    def b(self):
        "Method b"
        return("This is the Class1 b()-method!")

    def bb(self):
        "Method bb"
        return(self.b())


# Define new b()-method
def b(self):
    # Return both old and new b() output
    return(class1_obj.b(), "This is the new Class1 b()-method!")


# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
    <code involving getting new_class1_obj>
# Use with expected outputs
>>> class1_obj = Class1('TEST')
>>> new_class1_obj = get_new_Class1_obj(class1_obj)
>>> class1_obj is new_class1_obj
False
>>> class1_obj.name
'TEST'
>>> class1_obj.a()
"This is the Class1 a()-method!"
>>> class1_obj.b()
"This is the Class1 b()-method!"
>>> class1_obj.bb()
"This is the Class1 b()-method!"
>>> new_class1_obj.name
'TEST'
>>> new_class1_obj.a()
"This is the Class1 a()-method!"
>>> new_class1_obj.b()
("This is the Class1 b()-method!", "This is the new Class1 b()-method!")
>>> new_class1_obj.bb()
("This is the Class1 b()-method!", "This is the new Class1 b()-method!")
>>> class1_obj.name = 'TEST2'
>>> class1_obj.name
'TEST2'
>>> new_class1_obj.name
'TEST2'

Ответы [ 3 ]

0 голосов
/ 01 мая 2019

Я не знаю, хотите ли вы, но приведенный ниже код имеет те же результаты, что и ваш пример:

import copy

class ComplexObject:   # Equivalent of old Class1, but with no methods
    def __init__(self, name):
        self.name = name
        self.other_member = 'other, but could be intracomm'    

class Class1(object):
    def __init__(self, fwd):
        self.fwd = fwd  # All the interesting data is in this instance

    def get_name(self):
        return self.fwd.name  # Need to go through fwd to get at data
    def a(self):
        "Method a"
        return("This is the Class1 a()-method!")

    def b(self):
        "Method b"
        return("This is the Class1 b()-method!")

    def bb(self):
        "Method bb"
        return(self.b())

# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
    def b(self):
            # Return both old and new b() output
            return(class1_obj.b(), "This is the new Class1 b()-method!")
    new_instance = copy.copy(class1_obj)
    def b_wrapper():
        return b(new_instance)
    new_instance.b = b_wrapper

    return new_instance

complex=ComplexObject('TEST')
class1_obj = Class1(complex)
new_class1_obj = get_new_Class1_obj(class1_obj)
print("are insts same? ",class1_obj is new_class1_obj)
print("class1_obj.name",class1_obj.get_name())
print("class1_obj.a():",class1_obj.a())
print("class1_obj.b():",class1_obj.b())
print("class1_obj.bb():",class1_obj.bb())
print("new_class1_obj.name",new_class1_obj.get_name())
print("new_class1_obj.a():",new_class1_obj.a())
print("new_class1_obj.b():",new_class1_obj.b())
print("new_class1_obj.bb():",new_class1_obj.bb())
#Change some of the interesting data
class1_obj.fwd.name='FOO'
print("class1_obj.name",class1_obj.get_name())
print("new_class1_obj.name",new_class1_obj.get_name())

Выход:

are insts same?  False
class1_obj.name TEST
class1_obj.a(): This is the Class1 a()-method!
class1_obj.b(): This is the Class1 b()-method!
class1_obj.bb(): This is the Class1 b()-method!
new_class1_obj.name TEST
new_class1_obj.a(): This is the Class1 a()-method!
new_class1_obj.b(): ('This is the Class1 b()-method!', 'This is the new Class1 b()-method!')
new_class1_obj.bb(): ('This is the Class1 b()-method!', 'This is the new Class1 b()-method!')
class1_obj.name FOO
new_class1_obj.name FOO

Вы можете видеть, что изменения в интересных данных влияют на оба класса.

0 голосов
/ 02 мая 2019

Возможное решение

Посмотрев на ответ, данный quamrana , я нашел возможное решение этого вопроса, но я хотел бы получить отзыв об этом:

from types import MethodType


# Define original Class1 class
class Class1(object):
    """
    This is Class1.

    """

    def __init__(self, name):
        self.name = name

    def a(self):
        "Method a"
        return("This is the Class1 a()-method!")

    def b(self):
        "Method b"
        return("This is the Class1 b()-method!")

    def bb(self):
        "Method bb"
        return(self.b())


# Define function that returns new Class1 object
def get_new_Class1_obj(class1_obj):
    # Obtain list of all properties that class1_obj has that are not methods
    props = [prop for prop in dir(class1_obj)
             if not isinstance(getattr(class1_obj, prop), MethodType)]

    # Make a subclass of the class used for class1_obj
    class new_Class1(class1_obj.__class__, object):
        # Empty __init__ as no initialization is required
        def __init__(self):
            pass

        # If requested attribute is not a method, use class1_obj for that
        def __getattribute__(self, name):
            if name in props:
                return(getattr(class1_obj, name))
            else:
                return(super().__getattribute__(name))

        # If requested attribute is not a method, use class1_obj for that
        def __setattr__(self, name, value):
            if name in props:
                setattr(class1_obj, name, value)
            else:
                super().__setattr__(name, value)

        # Use the __dir__ of class1_obj
        def __dir__(self):
            return(dir(class1_obj))

        # Define new b()-method
        def b(self):
            # Return both old and new b() output
            return(class1_obj.b(), "This is the new Class1 b()-method!")

    # Initialize new_Class1
    new_class1_obj = new_Class1()

    # Return it
    return(new_class1_obj)
# Do testing
if __name__ == '__main__':
    # Initialize instances
    class1_obj = Class1('TEST')
    new_class1_obj = get_new_Class1_obj(class1_obj)

    # Check that instances are not the same
    print(class1_obj is new_class1_obj)

    # Check outputs of class1_obj
    print(class1_obj.name)
    print(class1_obj.a())
    print(class1_obj.b())
    print(class1_obj.bb())

    # Check outputs of new_class1_obj
    print(new_class1_obj.name)
    print(new_class1_obj.a())
    print(new_class1_obj.b())
    print(new_class1_obj.bb())

    # Check that non-method changes in class1_obj affect new_class1_obj
    class1_obj.name = 'TEST2'
    print(class1_obj.name)
    print(new_class1_obj.name)

    # Check that non-method changes in new_class1_obj affect class1_obj
    new_class1_obj.name = 'TEST3'
    print(class1_obj.name)
    print(new_class1_obj.name)

Все выходы здесь являются выходами, которые я хочу. Я все еще не уверен, хочу ли я, чтобы изменения, не относящиеся к методам в new_class1_obj, повлияли на class1_obj, но я могу легко удалить это, просто не переопределяя __setattr__(). Вышеприведенное также гарантирует, что добавление нового атрибута в new_class1_obj не повлияет на class1_obj. Я мог бы добавить определение props к __getattribute__, если я хочу, чтобы противоположное сработало (добавление нового атрибута к class1_obj влияет на new_class1_obj).

0 голосов
/ 01 мая 2019

Я не совсем уверен, что вы хотите, но может ли это быть ниже?(файл test.py протестирован нормально с python 3.7.3)

class Class1(object):
    """
    This is Class1.

    """

    def __init__(self, name):
        self.name = name

    def a(self):
        "Method a"
        return("This is the Class1 a()-method!")

    def b(self):
        "Method b"
        return("This is the Class1 b()-method!")

    def bb(self):
        "Method bb"
        return(self.b())

def get_new_Class1_obj(class1_obj):

    new_Class1_obj = Class1(class1_obj.name)

    def b():
        return(class1_obj.b(), "This is the new Class1 b()-method!")

    new_Class1_obj.b = b
    return new_Class1_obj

if __name__ == "__main__":
    class1_obj = Class1('TEST')
    new_class1_obj = get_new_Class1_obj(class1_obj)
    print("are insts same? ",class1_obj is new_class1_obj)
    print("class1_obj.name?",class1_obj.name)
    print("class1_obj.a():",class1_obj.a())
    print("class1_obj.b():",class1_obj.b())
    print("class1_obj.bb():",class1_obj.bb())
    print("new_class1_obj.name?",new_class1_obj.name)
    print("new_class1_obj.a():",new_class1_obj.a())
    print("new_class1_obj.b():",new_class1_obj.b())
    print("new_class1_obj.bb():",new_class1_obj.bb())

Этот код вернет:

$ python test.py
are insts same?  False
class1_obj.name? TEST
class1_obj.a(): This is the Class1 a()-method!
class1_obj.b(): This is the Class1 b()-method!
class1_obj.bb(): This is the Class1 b()-method!
new_class1_obj.name? TEST
new_class1_obj.a(): This is the Class1 a()-method!
new_class1_obj.b(): ('This is the Class1 b()-method!', 'This is the new Class1 b()-method!')
new_class1_obj.bb(): ('This is the Class1 b()-method!', 'This is the new Class1 b()-method!')

Это то, что вы хотите?

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

...