Как отличить объект в Python

У меня есть два класса (назовите их Working and ReturnStatement), которые я не могу изменить, но я хочу расширить их оба с помощью журнала. Хитрость заключается в том, что рабочий метод возвращает объект ReturnStatement, поэтому новый объект MutantWorking также возвращает ReturnStatement, если я не могу применить его к MutantReturnStatement. Высказывание с помощью кода:

# these classes can't be changed
class ReturnStatement(object):
    def act(self):
        print "I'm a ReturnStatement."

class Working(object):
    def do(self):
        print "I am Working."
        return ReturnStatement()

# these classes should wrap the original ones
class MutantReturnStatement(ReturnStatement):
    def act(self):
        print "I'm wrapping ReturnStatement."
        return ReturnStatement().act()

class MutantWorking(Working):
    def do(self):
        print "I am wrapping Working."
        # !!! this is not working, I'd need that casting working !!!
        return (MutantReturnStatement) Working().do()

rs = MutantWorking().do() #I can use MutantWorking just like Working
print "--" # just to separate output
rs.act() #this must be MutantReturnState.act(), I need the overloaded method

Ожидаемый результат:
Работаю.
Я работаю.
-
Я обертываю ReturnStatement.
Я - ReturnStatement.

Можно ли решить проблему? Мне также интересно, если проблема может быть решена и на PHP. Если я не получу рабочее решение, я не могу принять ответ, поэтому, пожалуйста, напишите рабочий код, чтобы принять его.

Ответ 1

Нет никаких кастингов, как уже объясняли другие ответы. Вы можете создавать подклассы или модифицировать новые типы с помощью дополнительных функций с помощью декораторов.

Вот полный пример (кредит Как создать цепочку декораторов функций?). Вам не нужно изменять исходные классы. В моем примере исходный класс называется Working.

# decorator for logging
def logging(func):
    def wrapper(*args, **kwargs):
        print func.__name__, args, kwargs
        res = func(*args, **kwargs)
        return res
    return wrapper

# this is some example class you do not want to/can not modify
class Working:
    def Do(c):
        print("I am working")
    def pr(c,printit):   # other example method
        print(printit)
    def bla(c):          # other example method
        c.pr("saybla")

# this is how to make a new class with some methods logged:
class MutantWorking(Working):
    pr=logging(Working.pr)
    bla=logging(Working.bla)
    Do=logging(Working.Do)

h=MutantWorking()
h.bla()
h.pr("Working")                                                  
h.Do()

это напечатает

h.bla()
bla (<__main__.MutantWorking instance at 0xb776b78c>,) {}
pr (<__main__.MutantWorking instance at 0xb776b78c>, 'saybla') {}
saybla

pr (<__main__.MutantWorking instance at 0xb776b78c>, 'Working') {}
Working

Do (<__main__.MutantWorking instance at 0xb776b78c>,) {}
I am working

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

Working.Do=logging(Working.Do)
ReturnStatement.Act=logging(ReturnStatement.Act)

Обновление: Применить протоколирование ко всем методам класса

Как вы сейчас специально просили об этом. Вы можете перебрать все элементы и применить к ним все записи. Но вам нужно определить правило для каких членов изменять. В приведенном ниже примере исключается любой метод с именем __ в его имени.

import types
def hasmethod(obj, name):
    return hasattr(obj, name) and type(getattr(obj, name)) == types.MethodType

def loggify(theclass):
  for x in filter(lambda x:"__" not in x, dir(theclass)):
     if hasmethod(theclass,x):
        print(x)
        setattr(theclass,x,logging(getattr(theclass,x)))
  return theclass

При этом все, что вам нужно сделать, чтобы создать новую версию класса, это:

@loggify
class loggedWorker(Working): pass

Или измените существующий класс на месте:

loggify(Working)

Ответ 2

В Python нет "кастинга". Любой подкласс класса считается экземпляром его родителей. Желаемое поведение может быть достигнуто путем правильного вызова методов суперкласса и переопределения атрибутов класса.

Что вы можете сделать в своем примере, так это иметь инициализатор подкласса, который получает суперкласс и копирует его соответствующие атрибуты, поэтому ваше MutantReturnstatement можно записать так:

class MutantReturnStatement(ReturnStatement):
    def __init__(self, previous_object=None):
        if previous_object:
            self.attribute = previous_object.attribute
            # repeat for relevant attributes
    def act(self):
        print "I'm wrapping ReturnStatement."
        return ReturnStatement().act()

И затем измените класс MutantWorking на:

class MutantWorking(Working):
    def do(self):
        print "I am wrapping Working."
        return MutantReturnStatement(Working().do())

Есть способы Pythonic для того, чтобы не иметь много строк self.attr = other.attr в методе __init__, если есть много (например, более 3:-)) атрибутов, которые вы хотите скопировать - самым ленивым из которых будет просто копирование атрибута другого экземпляра __dict__.

В качестве альтернативы , если вы знаете, что вы делаете, вы также можете просто изменить атрибут __class__ целевого объекта на нужный класс, но это может ввести в заблуждение и привести вас к тонким ошибкам (метод __init__ подкласса не будет вызываться, не будет работать на не-python определенных классах и других возможных проблемах), я не рекомментирую этот подход - это не "литье", это использование интроспекции для принудительного изменения объекта и включается только для завершения ответа:

class MutantWorking(Working):
    def do(self):
        print "I am wrapping Working."
        result = Working.do(self)
        result.__class__ = MutantReturnStatement
        return result

Снова - это должно работать, но не делайте этого - используйте прежний метод.

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

Ответ 3

Нет прямого пути.

Вы можете определить MutantReturnStatement init следующим образом:

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

а затем используйте его следующим образом:

class MutantWorking(Working):
    def do(self):
        print "I am wrapping Working."
        # !!! this is not working, I'd need that casting working !!!
        return MutantReturnStatement(Working().do())

И вы должны избавиться от унаследования ReturnStatement в вашей обертке, например,

class MutantReturnStatement(object):
    def act(self):
        print "I'm wrapping ReturnStatement."
        return self.retStatement.act()

Ответ 4

Вам не нужно кастинг здесь. Вам просто нужно

class MutantWorking(Working):
def do(self):
    print "I am wrapping Working."
    Working().do()
    return MutantReturnStatement()

Это, очевидно, даст правильный возврат и желаемую распечатку.