Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to "fool" duck typing in Python

Tags:

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?

like image 597
Woody1193 Avatar asked Nov 05 '18 16:11

Woody1193


People also ask

Does Python support duck typing?

Python is a duck typing language. It means the data types of variables can change as long as the syntax is compatible. Python is also a dynamic programming language.

Why is Python duck typed?

Duck typing is a concept related to dynamic typing, where the type or the class of an object is less important than the methods it defines. When you use duck typing, you do not check types at all. Instead, you check for the presence of a given method or attribute.

Is duck typing good?

With duck typing the type doesn't matter as long as it has the right method, so it really just eliminates a lot of the hassle of casting and conversions between types. There is plenty of anecdotal evidence suggesting that duck typing is significantly more productive than static typing.

Is duck typing a polymorphism?

Duck Typing is a term commonly related to dynamically typed programming languages and polymorphism. The idea behind this principle is that the code itself does not care about whether an object is a duck, but instead it does only care about whether it quacks.


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!

like image 75
Matt Messersmith Avatar answered Nov 15 '22 04:11

Matt Messersmith