raratiru
raratiru

Reputation: 9616

Python: How to update the calls of a third class to the overriden method of the original class?

class ThirdPartyA(object):
    def __init__(self):
        ...
    def ...():
    ...
-------------------
from xxx import ThirdPartyA

class ThirdPartyB(object):
    def a(self):
        ...
        #call to ThirdPartyA
        ....

    def b(self):
        ...
        #call to ThirdPartyA
        ...

    def c(self):
        ...
        #call to ThirdPartyA
        ...
-----------------------------------
from xxx import ThirdPartyA

class MyCodeA(ThirdPartyA):
    def __init__(self):
        # overriding code

When overriding the __init__ method of A class, how could I instruct B class that it should call MyCodeA instead of ThirdPartyA in all its methods?

The real code is here:

CLass Geoposition: ThirdPartyA

Class GeopositionField: ThirdPartyB

My override to class Geoposition so it returns max 5 decimal digits:

class AccuracyGeoposition(Geoposition):

    def __init__(self, latitude, longitude):
        if isinstance(latitude, float) or isinstance(latitude, int):
            latitude = '{0:.5f}'.format(latitude)
        if isinstance(longitude, float) or isinstance(longitude, int):
            longitude = '{0:.5f}'.format(longitude)

        self.latitude = Decimal(latitude)
        self.longitude = Decimal(longitude)

Upvotes: 0

Views: 47

Answers (2)

abarnert
abarnert

Reputation: 365687

From your updated code, I think what you're trying to do is change GeopositionField. to_python() so that it returns AccuracyGeoposition values instead of Geoposition values.

There's no way to do that directly; the code in GeopositionField explicitly says it wants to construct a Geoposition, so that's what happens.

The cleanest solution is to subclass GeopositionField as well, so you can wrap that method:

class AccuracyGeopositionField(GeopositionField):
    def topython(self, value):
        geo = super(AccuracyGeopositionField, self).topython(value)
        return AccuracyGeoposition(geo.latitude, geo.longitude)

If creating a Geoposition and then re-wrapping the values in an AccuracyGeoposition is insufficient (because accuracy has already been lost), you might be able to pre-process things before calling the super method as well/instead. For example, if the way it deals with list is not acceptable (I realize that's not true here, but it serves as a simple example), but everything else you can just let it do its thing and wrap the result, you could do this:

class AccuracyGeopositionField(GeopositionField):
    def topython(self, value):
        if isinstance(value, list):
            return AccuracyGeoposition(value[0], value[1])
        geo = super(AccuracyGeopositionField, self).topython(value)
        return AccuracyGeoposition(geo.latitude, geo.longitude)

If worst comes to worst, you may have to reimplement the entire method (maybe by copying, pasting, and modifying its code), but hopefully that will rarely come up.


There are hacky alternatives to this. For example, you could monkeypatch the module to globally replace the Geoposition class with your AccuracyGeoposition class But, while that may save some work up front, you're almost certain to be unhappy with it when you're debugging things later. Systems that are designed for aspect-oriented programming (which is basically controlled monkeypatching) are great, but trying to cram it into systems that were designed to resist it will give you headaches.

Upvotes: 1

abarnert
abarnert

Reputation: 365687

Assuming your real code works like your example—that is, every method of B creates a new A instance just to call a method on it and discard it—well, that's a very weird design, but if it makes sense for your use case, you can make it work.

The key here is that classes are first-class objects. Instead of hardcoding A, store the class you want as a member of the B instance, like this:

class B(object):
    def __init__(self, aclass=A):
        self.aclass = aclass

    def a(self):
        self.aclass().a()

Now, you just create a B instance with your subclass:

b = B(OverriddenA)

Your edited version does a different strange thing: instead of constructing a new A instance each time to call methods on it, you're calling class methods on A itself. Again, this is probably not what you want—but, if it is, you can do it:

class B(object):
    def __init__(self, aclass=A):
        self.aclass = aclass

    def a(self):
        self.aclass.a()

However, more likely you don't really want either of these. You want to take an A instance at construction time, store it, and use it repeatedly. Like this:

class B(object):
    def __init__(self, ainstance):
        self.ainstance = ainstance

    def a(self):
        self.ainstance.a()

b1 = B(A())
b2 = B(OverriddenA())

If this all seems abstract and hard to understand… well, that's because we're using meaningless names like A, B, and OverriddenA. If you tell us the actual types you're thinking about, or just plug those types in mechanically, it should make a lot more sense.

For example:

class Vehicle(object):
    def move(self):
        print('I am a vehicle, and I am moving')

class Operator(object):
    def __init__(self, vehicle):
        self.vehicle = vehicle
    def move(self):
        print('I am moving my vehicle')
        self.vehicle.move()

class Car(object):
    def move(self):
        print('I am a car, and I am driving')

driver = Operator(Car())
driver.move()

Upvotes: 1

Related Questions