How to "fool" duck typing in Python

Viewed 332

Suppose I had a class A:

 class A:

     def __init__(self, x, y):
         self.x = x
         self.y = y

     def sum(self):
         return self.x + self.y

And I defined a factory method called factory:

def factory(x, y):

    class B: pass

    b = B()
    setattr(b, 'x', x)
    setattr(b, 'y', y)
    B.__name__ = 'A'
    return b

Now, If I do print(type(A(1, 2))) and print(type(factory(1, 2))) they will show that these are different types. And if I try to do factory(1, 2).sum() I'll get an exception. But, type(A).__name__ and type(factory(1, 2)).__name__ are equivalent and if I do A.sum(factory(1, 2)) I'll get 3, as if I was calling it using an A. So, my question is this:

What would I need to do here to make factory(1, 2).sum() work without defining sum on B or doing inheritance?

1 Answers

I think you're fundamentally misunderstanding the factory pattern, and possibly getting confused with how interfaces work in Python. Either that, or I am fundamentally confused by the question. Either way, there's some sorting out we need to do.

What would I need to do here to make factory(1, 2).sum() work without defining sum on B or doing inheritance?

Just return an A instead of some other type:

def factory(x, y):
    return A(x, y)

then

print(factory(1,2).sum())

will output 3 as expected. But that's kind of a useless factory...could just do A(x, y) and be done with it!

Some notes:

  1. You typically use a "factory" (or factory pattern) when you have easily "nameable" types that may be non-trivial to construct. Consider how when you use scipy.interpolate.interp1d (see here) there's an option for kind, which is basically an enum for all the different strategies you might use to do an interpolation. This is, in essence, a factory (but hidden inside the function for ease of use). You could imagine this could be standalone, so you'd call your "strategy" factory, and then pass this on to the interp1d call. However, doing it inline is a common pattern in Python. Observe: These strategies are easy to "name", somewhat hard to construct in general (you can imagine it would be annoying to have to pass in a function that does linear interpolation as opposed to just doing kind='linear'). That's what makes the factory pattern useful...

  2. If you don't know what A is up front, then it's definitely not the factory pattern you'd want to apply. Furthermore, if you don't know what you're serializing/deserializing, it would be impossible to call it or use it. You'd have to know that, or have some way of inferring it.

  3. Interfaces in Python are not enforced like they are in other languages like Java/C++. That's the spirit of duck typing. If an interface does something like call x.sum(), then it doesn't matter what type x actually is, it just has to have a method called sum(). If it acts like the "sum" duck, quacks like the "sum" duck, then it is the "sum" duck from Python's perspective. Doesn't matter if x is a numpy array, or A, it'll work all the same. In Java/C++, stuff like that wont compile unless the compiler is absolutely certain that x has the method sum defined. Fortunately Python isn't like that, so you can even define it on the fly (which maybe you were trying to do with B). Either way, interfaces are a much different concept in Python than in other mainstream languages.

P.S.

But, type(A).__name__ and type(factory(1, 2)).__name__ are equivalent

Of course they are, you explicitly do this when you say B.__name__ = 'A'. So I'm not sure what you were trying to get at there...

HTH!

Related