1

I have a dictionary like class with methods that return views of its keys, values and items. The methods for doing this are about as simple as you can get:

class MyType(collections.MutableMapping):

    def keys(self):
        return collections.KeysView(self)

However, this seems pointless to create a method for something so simple; all I am doing is passing self onto yet another callable. I would prefer to simply treat the class constructor for KeysView as a bound method. Doing the following creates a nested class (which is good because sometimes that is exactly what you want), but it looks closer to what I would want to do:

class MyType(collections.MutableMapping):

    keys = collections.KeysView

Is there anything builtin to Python or its standard library to do this? Maybe something like this:

class MyType(collections.MutableMapping):

    keys = bind_constructor_as_method(collections.KeysView)

I feel like there should be something in functools that would do the job, but there isn't anything that pops out as the right answer at first look. Maybe functools.partial, but the name isn't very descriptive of what I'm trying to do.

Will I just need to hand roll a custom descriptor class to make something like this work?

NOTE:

For work I often need to use Python 2.7, so although Python 3.x answers are still appreciated (and useful), they probably won't totally mitigate the issue for me personally. However, they may help someone else with this question, so please still include them!

eestrada
  • 1,575
  • 14
  • 24

2 Answers2

1

functools.partialmethod() is what you are looking for. This creates a method where self is the first argument.

import functools

class MyType(collections.MutableMapping):

    keys = functools.partialmethod(collections.KeysView)

You can also specify other arguments, like if you wanted to pass self, 1, 2, key=5 to func(), you would do functools.partialmethod(func, 1, 2, key=5).

But do note this is only available in Python 3.4 and above.

Artyer
  • 31,034
  • 3
  • 47
  • 75
  • I should probably tag my question a bit better. Most of my code needs to run in Python 2.7 for work environments and `partialmethod` is only available starting in 3.4. Even on personal stuff, I like to make it as backward compatible as possible, especially if it is open source (many people are still constrained to the 2.x line of Python). However, this answer is still useful. I will look at the standard library to see what they are doing for this and I can see if it is worth backporting or modifying for my own needs. – eestrada Jan 07 '16 at 20:40
  • @eestrada Well, you can always try to import it, and when you fail, implement your own version of functools.partialmethod (You can find it by running `import inspect; import functools; print(inspect.getsource(functools.partialmethod))`) in any Python 3.4+ interpreter. – Artyer Jan 07 '16 at 20:44
  • Yes, importing with `try/except` is a good idea; I have often done that in the past for other things that differ between versions (like `functools.lru_cache`, or `unicode`). The source code for [`partialmethod`](https://hg.python.org/cpython/file/3.5/Lib/functools.py#l267) isn't super long, but it is long enough that it would probably need to be kept in a separate module. I was hoping I could fake something in a handful of lines that I could just include as boilerplate when needed, but it seems that making a full descriptor class is necessary for the right behavior. – eestrada Jan 07 '16 at 22:41
0

After some thought (and some help from this SO post here), I have come up with another solution that works across Python versions 2 and 3, although it is less idiomatic. It definitely isn't very clear what is happening like it is when using partialmethod as @Artyer suggests. Really, this is what Python is doing under the hood whenever a new instance is created, but made more explicit; you're seeing how the sausage is made. :/

import types
import collections

class MyType(collections.MutableMapping):
    def __init__(self, *args, **kwargs):
        # do class specific stuff here

        # create bound method per instance
        self.keys = types.MethodType(collections.KeysView, self)

Better yet, we can do it in __new__ to be more certain that inherited classes get it:

import types
import collections

class MyType(collections.MutableMapping):
    def __new__(cls, *args, **kwargs):
        self = super(MyType, cls).__new__(cls)

        # create bound method per instance
        self.keys = types.MethodType(collections.KeysView, self)
        return self

If you don't like the relatively undocumented nature of types.MethodType, you can use functools.partial instead:

import functools
import collections

class MyType(collections.MutableMapping):
    def __new__(cls, *args, **kwargs):
        self = super(MyType).__new__(cls)

        # create bound method per instance
        self.keys = functools.partial(collections.KeysView, self)
        return self

NOTE: For Python 2/3 compatibility, you should not use the third optional class argument for types.MethodType; it is exists in Python 2, but is removed in Python 3 apparently.

Community
  • 1
  • 1
eestrada
  • 1,575
  • 14
  • 24