Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test that functools.partial produces the expected function object

When going from one API to another it can sometimes be helpful to map between similar keywords in each API, allowing one controller API to flexibly dispatch to other libraries without needing the user to fuss around with the different API's under the hood.

Assume some library, other_api, has a method called "logarithm", and the keyword argument for the base is something I need to factor out of my code, like "log_base_val"; so that to use it from other_api I need to type (for example):

other_api.logarithm(log_base_val=math.e)

Consider a toy class like this:

import other_api
import math
import functools

class Foo(object):
    _SUPPORTED_ARGS = {"base":"log_base_val"}

    def arg_binder(self, other_api_function_name, **kwargs):
        other_api_function = getattr(other_api, other_api_function_name)
        other_api_kwargs = {_SUPPORTED_ARGS[k]:v for k,v in kwargs.iteritems()}
        return functools.partial(other_api_function, **other_api_kwargs)

With Foo, I can map some other API, where this argument is always called base, like this:

f = Foo()
ln = f.arg_binder("logarithm", base=math.e)

and ln is logically equivalent to (with log_base_val=math.e in kwargs, from functools):

other_api.logarithm(*args, **kwargs)

However, manually making the same argument bind by invoking functools will lead to different function objects:

In [10]: import functools

In [11]: def foo(a, b):
   ....:     return a + b
   ....: 

In [12]: f1 = functools.partial(foo, 2)

In [13]: f2 = functools.partial(foo, 2)

In [14]: id(f1)
Out[14]: 67615304

In [15]: id(f2)
Out[15]: 67615568

So testing for f1 == f2 won't succeed as intended:

In [16]: f1 == f2
Out[16]: False

So the question is: what is the prescribed way to test whether the argument binding function has resulted in the correct output function object?

like image 490
ely Avatar asked Mar 27 '14 16:03

ely


1 Answers

The func attribute on the partial() object is a reference to the original function object:

f1.func is f2.func

Function objects themselves don't implement a __eq__ method, so you may as well just use is to test for identity.

Similarly, the partial().args and partial().keywords contain the arguments and keyword arguments to be passed to the function when called.

Demo:

>>> from functools import partial
>>> def foo(a, b):
...     return a + b
... 
>>> f1 = partial(foo, 2)
>>> f2 = partial(foo, 2)
>>> f1.func is f2.func
True
>>> f1.args
(2,)
>>> f2.args
(2,)
>>> f1.keywords is None
True
>>> f2.keywords is None
True
like image 187
Martijn Pieters Avatar answered Oct 22 '22 21:10

Martijn Pieters