4

Suppose we have the following code. I want to swap out the inner parse_place function. How do you do this without replacing the whole method?

class GoogleV3Place(GoogleV3):
    """Simply extends the GoogleV3 to bucket the object into a place"""

    def parse_json(self, page, exactly_one=True):
        """Returns location, (latitude, longitude) from json feed."""
        if not isinstance(page, basestring):
            page = util.decode_page(page)
        self.doc = json.loads(page)
        places = self.doc.get('results', [])

        if not places:
            check_status(self.doc.get('status'))
            return None
        elif exactly_one and len(places) != 1:
            raise ValueError(
                "Didn't find exactly one placemark! (Found %d)" % len(places))

        def parse_place(place):
            """This returns an object how we want it returned."""
            location.formatted_address = place.get('formatted_address')
            location.latitude = place['geometry']['location']['lat']
            location.longitude = place['geometry']['location']['lng']

            latitude = place['geometry']['location']['lat']
            longitude = place['geometry']['location']['lng']
            return (location, (latitude, longitude))

        if exactly_one:
            return parse_place(places[0])
        else:
            return [parse_place(place) for place in places]
rh0dium
  • 6,811
  • 4
  • 46
  • 79
  • 1
    Yes this will work but (as with my __limited__ understanding of super) it defeats my question. I want to simply swap out the parse place method. If I use super I need to redefine the class. Perhaps I'm overthinking this can you give me an example? – rh0dium Feb 24 '12 at 16:36

4 Answers4

3

If you have control over the GoogleV3Place code (ie it's code you've written and isn't in a library), then I'd refactor parse_json to take a "parse_place_fn" argument, and move parse_place to be a top-level function (if accessibility is an issue you can always prefix it with double-underscore):

def parse_place(place):
    """This returns an object how we want it returned."""
    location.formatted_address = place.get('formatted_address')
    location.latitude = place['geometry']['location']['lat']
    location.longitude = place['geometry']['location']['lng']

    latitude = place['geometry']['location']['lat']
    longitude = place['geometry']['location']['lng']
    return (location, (latitude, longitude))

class GoogleV3Place(GoogleV3):
    """Simply extends the GoogleV3 to bucket the object into a place"""

    def parse_json(self, page, exactly_one=True, parse_place_fn = parse_place):
        """Returns location, (latitude, longitude) from json feed."""
        if not isinstance(page, basestring):
            page = util.decode_page(page)
        self.doc = json.loads(page)
        places = self.doc.get('results', [])

        if not places:
            check_status(self.doc.get('status'))
            return None
        elif exactly_one and len(places) != 1:
            raise ValueError(
                "Didn't find exactly one placemark! (Found %d)" % len(places))

        if exactly_one:
            return parse_place_fn(places[0])
        else:
            return [parse_place_fn(place) for place in places]

Now any function you create which takes a place and returns a tuple of the form (location, (latitude, latitude)) can be passed to parse_json, and if no function is specified it uses the default of parse_place.

Adam Parkin
  • 17,891
  • 17
  • 66
  • 87
  • To be clear created the class to replace the inner method. Now I have another class (Bing) which I need to do the same thing. Now I'm wondering if there is a better way to do it.. – rh0dium Feb 24 '12 at 16:40
  • I might not fully understand what you're trying to do, but it sounds like you want a "SearchPlace" base class, from which you can derive "GoogleV3Place" and "BingPlace". If the concern is that the base SearchPlace class would have no functionality, you could make it an [abstract base class](http://docs.python.org/library/abc.html) – Adam Parkin Feb 24 '12 at 16:53
  • Right - Initially I just subclassed GoogleV3 and modified the `parse_place` method. Now I'm realizing I will need to do the same for Bing.. Since there is now a pattern whereby I'm simply modifying the `parse_place` method I'm left wondering is there an easier way.. – rh0dium Feb 24 '12 at 17:01
1

The proper way to do this is at compile time. You need to make an abstract base class (the ABC) with the abc module. This will not allow the client code to instantiate the class unless the virtual functions are implemented whic is caught at compile time. Good for polymorphic programming. The virtual functions are the ones that have the @abstractmethod decorator. You can define real functions in the ABS too.

import abc
class MyABC(object):
    __metaclass__ ABCMeta

    @abstractmethod
    def my_virtual_function(self):
        pass

    def real_function(self):
        print "ABClass function"

The derived class now inherits the ABC and implements the virtual functions.

class MyDerived(MyABC):

    def my_virtual_function(self):
        print "ok"

You must have python 2.6 or later for this.

For more on the topic... http://docs.python.org/library/abc.html

if you are stuck with python 2.4 then you have to contend with a much less desirable situation. You must have a base class that throws a runtime exception.

class MyBaseClass(object):
    def my_redefinable(self):
        raise NotImplementedError("You must implement my_redefinable() ")

class MyDerivedClass(MyBaseClass):
    def __init__(self):
        pass

    def my_redefinable(self):
        print "ok"

test = MyBaseClass()
test.my_redifinable() # throws error

test = MyDerivedclass()
test my_redefinable() #  is ok
Peter Moore
  • 1,632
  • 1
  • 17
  • 31
0

If you really want to make some method polymorphic, you can use callable objects instead of functions. You could delegate call to private class-level callable or implement binding to instances in that callable objects.

But in that concrete example there is no reasons to embed parse_place into parse_json

Odomontois
  • 15,918
  • 2
  • 36
  • 71
  • FWIW - I completely agree with your second comment. In fact the the inner method simply screams that it wants the ability to be free of the out method for replacement. I figured that since the originator did that there must be a clean way to "replace" it later on.. – rh0dium Feb 24 '12 at 16:42
  • @Odomontois: I'm not sure what callable objects would buy here over just using higher order functions. The point is the existing code would have to be modified to make the `parse_place` functionality be a parameter to `parse_json` so why not just use normal functions rather than callable objects? (a function "is a" callable object, but less verbose to define). Or perhaps I'm misunderstanding your intention – Adam Parkin Feb 24 '12 at 16:58
0

You can not access nested functions, these are only visible inside the function, like local variables. However, you may put parse_place into separate method and then override with regular inheritance.

Samvel
  • 182
  • 2
  • 6