Как привести объект в Python - PullRequest
       4

Как привести объект в Python

14 голосов
/ 02 февраля 2012

У меня есть два класса (назовем их Working и ReturnStatement), которые я не могу изменить, но я хочу расширить оба из них с помощью регистрации.Хитрость заключается в том, что метод Working возвращает объект 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.Пока я не получу рабочее решение, я не смогу принять ответ, поэтому, пожалуйста, напишите рабочий код, чтобы его приняли.

Ответы [ 4 ]

7 голосов
/ 02 февраля 2012

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

Вот полный пример (кредит Как создать цепочку декораторов функций? ).Вам не нужно изменять свои оригинальные классы.В моем примере исходный класс называется 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 голосов
/ 02 февраля 2012

В 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

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

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

1 голос
/ 02 февраля 2012

Прямого пути нет.

Вы можете определить MutantReturnStatement's 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()
0 голосов
/ 02 февраля 2012

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...