Как отличить объект в 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()
Это, очевидно, даст правильный возврат и желаемую распечатку.