25

I have two classes (let's call them Working and ReturnStatement) which I can't modify, but I want to extend both of them with logging. The trick is that the Working's method returns a ReturnStatement object, so the new MutantWorking object also returns ReturnStatement unless I can cast it to MutantReturnStatement. Saying with code:

# 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

The expected result:
I am wrapping Working.
I am Working.
--
I'm wrapping ReturnStatement.
I'm a ReturnStatement.

Is it possible to solve the problem? I'm also curious if the problem can be solved in PHP, too. Unless I get a working solution I can't accept the answer, so please write working code to get accepted.

Visko
  • 423
  • 1
  • 5
  • 10
  • 7
    "Casting" does not exist in Python. – Ignacio Vazquez-Abrams Feb 02 '12 at 12:38
  • Also, you should not do "`ReturnStatement().act()`" - if you want the act method ont he other class to work on the current object, do `returnStatement.act(self)` - or just mark it as a classmethod or staticmethod - if there it does not need an instance of the current object. – jsbueno Feb 02 '12 at 12:41
  • Working.do() returns with a ReturnStatement. I want MutantWorking.do() to return with a MutantReturnStatement. I know casting does not exist in Python, but the problem does. Is there a solution? – Visko Feb 02 '12 at 12:43
  • It doesn't matter wether I override MutantReturnStatement's __init__() method to do the extra job and return with a ReturnStatement or override the MutantReturnStatement's act() method to do the job of the ReturnStatement's act() method. I can't reach the MutantReturnStatement because Working returns with ReturnStatement and not with a MutantReturnStatement. – Visko Feb 02 '12 at 12:49

5 Answers5

11

There is no casting as the other answers already explained. You can make subclasses or make modified new types with the extra functionality using decorators.

Here's a complete example (credit to How to make a chain of function decorators?). You do not need to modify your original classes. In my example the original class is called 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()

this will print

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

In addition, I would like to understand why you can not modify a class. Did you try? Because, as an alternative to making a subclass, if you feel dynamic you can almost always modify an old class in place:

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

Update: Apply logging to all methods of a class

As you now specifically asked for this. You can loop over all members and apply logging to them all. But you need to define a rule for what kind of members to modify. The example below excludes any method with __ in its name .

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

With this all you have to do to make a new logged version of a class is:

@loggify
class loggedWorker(Working): pass

Or modify an existing class in place:

loggify(Working)
Community
  • 1
  • 1
Johan Lundberg
  • 26,184
  • 12
  • 71
  • 97
  • 1
    I think you understood my problem and this far this seems to be THE answer. Thank you! – Visko Feb 02 '12 at 14:18
  • After my initial enthusiasm I sadly realized that this is not a solution for my problem. I can't modify the Working class using decorators to return MutantReturnStatement instead of ReturnStatement, because that was the initial condition (Working can't be modified). – Visko Feb 02 '12 at 15:45
  • @Visko, I think you misread my answer then. My 'loghello' is exactly the 'MutantWorking' you asked for. You do not have to modify 'Working' just as I did not modify 'hello'. If you think this is still unclear or if you don't agree I can try to update my answer. – Johan Lundberg Feb 02 '12 at 16:03
  • @Visko, I renamed my methods and classes to make it more like your example. – Johan Lundberg Feb 02 '12 at 16:13
  • Maybe I misunderstand something, but is it returning a MutantReturnState object? If not, it's not an acceptable solution. – Visko Feb 02 '12 at 16:37
  • @Visko It gives exactly the functionality you asked for. All you need to do is replace my Working with yours. Please try it. Or meet me in python chat later. – Johan Lundberg Feb 02 '12 at 16:42
  • So if I have 12 methods in ReturnStatement to override then I have to call 12 instructions like ReturnStatement.Act=logging(ReturnStatement.Act) ? That's why I don't like this solution, however this is the closest to what I want. I would rather say that it is impossible to do it in Python, the problem can be only solved by casting. – Visko Feb 03 '12 at 09:11
  • That's dirty hacking magic you used :). However it works, so I must accept it :). But it won't change my mind that Python lacks this feature, it is much much more complicated than a simple casting at the return statement in MutantWorker :). Anyway, thank you for the answer, you did a great job! :) – Visko Feb 03 '12 at 10:47
9

There is no "casting" in Python. Any subclass of a class is considered an instance of its parents. Desired behavior can be achieved by proper calling the superclass methods, and by overriding class attributes.

update: with the advent of static type checking, there is "type casting" - check bellow.

What you can do on your example, is to have to have a subclass initializer that receives the superclass and copies its relevant attributes - so, your MutantReturnstatement could be written thus:

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()

And then change your MutantWorking class to:

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

There are Pythonic ways for not having a lot of self.attr = other.attr lines on the __init__method if there are lots (like, more than 3 :-) ) attributes you want to copy - the laziest of which wiuld be simply to copy the other instance's __dict__ attribute.

Alternatively, if you know what you are doing, you could also simply change the __class__ attribute of your target object to the desired class - but that can be misleading and carry you to subtle errors (the __init__ method of the subclass would not be called, would not work on non-python defined classes, and other possible problems), I don't recomment this approach - this is not "casting", it is use of introspection to bruteforce an object change and is only included for keeping the answer complete:

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

Again - this should work, but don't do it - use the former method.

By the way, I am not too experienced with other OO languages, that allow casting - but is casting to a subclass even allowed in any language? Does it make sense? I think casting s only allowed to parentclasses.

update: When one works with type hinting and static analysis in the ways describd in PEP 484, sometimes the static analysis tool can't figure out what is going on. So, there is the typing.cast call: it does absolutely nothing in runtime, just return the same object that was passed to it, but the tools then "learn" that the returned object is of the passed type, and won't complain about it. It will remove typing errors in the helper tool, but I can't emphasise enough it does not have any effect in runtime:

In [18]: from typing import cast                                                                                                   

In [19]: cast(int, 3.4)                                                                                                            
Out[19]: 3.4
jsbueno
  • 99,910
  • 10
  • 151
  • 209
1

No direct way.

You may define MutantReturnStatement's init like this:

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

and then use it like this:

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())

And you should get rid from inheriting ReturnStatement in your wrapper, like this

class MutantReturnStatement(object):
    def act(self):
        print "I'm wrapping ReturnStatement."
        return self.retStatement.act()
Jurlie
  • 1,014
  • 10
  • 27
  • I'm new in python, there may be some mistakes in the code, but it shows the idea – Jurlie Feb 02 '12 at 12:49
  • I like your answer! If nobody tells another (better) technique, I'll accept this :). – Visko Feb 02 '12 at 12:52
  • +1. @Visko, +1 if you like something. (you can do it more than once.) Also, with decorators you can do this without the need to make one such block of code for each method. – Johan Lundberg Feb 02 '12 at 13:04
  • This solution is still not perfect. I missed to mention that I need MutantWorking must return with a MutantReturnStatement which works almost like ReturnStatement, only it's act() method is overloaded. In this solution I can access returnStatement as a property which changes the way I can use act() (rs.returnStatement.act() instead of rs.act() ). – Visko Feb 02 '12 at 14:10
0

What you do is not a casting, it is a type conversion. Still, you could write something like

def cast_to(mytype: Type[any], obj: any):
    if isinstance(obj, mytype):
        return obj
    else:
        return mytype(obj)

class MutantReturnStatement(ReturnStatement):
    def __init__(self, *args, **kwargs):
        if isinstance(args[0], Working):
            pass  
            # your custom logic here 
            # for the type conversion.

Usage:

cast_to(MutantReturnStatement, Working()).act()
# or simply
MutantReturnStatement(Working()).act()

(Note that in your example MutantReturnStatement does not have .do() member function.)

Barney Szabolcs
  • 11,846
  • 12
  • 66
  • 91
0

You don't need casting here. You just need

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

This will obviously give the correct return and desired printout.

Falcon
  • 1,317
  • 1
  • 13
  • 30
  • It is not a good solution because I have to return with a same state object (properties are not included in my example to preserve simplicity). This returns a new instance, which is not what I wanted. – Visko Feb 02 '12 at 13:49