Python equivalent of C++ member pointer

3.3k views Asked by At

What would be the equivalent of a C++ member pointer in Python? Basically, I would like to be able to replicate similar behavior in Python:

// Pointer to a member of MyClass
int (MyClass::*ptMember)(int) = &MyClass::member; 

// Call member on some instance, e.g. inside a function to 
// which the member pointer was passed 
instance.*ptMember(3)

Follow up question, what if the member is a property instead of a method? Is it possible to store/pass a "pointer" to a property without specifying the instance?

One way would obviously be to pass a string and use eval. But is there a cleaner way?

EDIT: There are now several really good answers, each having something useful to offer depending on the context. I ended up using what is described in my answer, but I think that other answers will be very helpful for whoever comes here based on the topic of the question. So, I am not accepting any single one for now.

5

There are 5 answers

1
Kevin J. Chase On

I'm not a C++ programmer, so maybe I'm missing some detail of method pointers here, but it sounds like you just want a reference to a function that's defined inside a class. (These were of type instancemethod in Python 2, but are just type function in Python 3.)

The syntax will be slightly different --- instead of calling it like a method with object.reference(args), you'll call it like a function: reference(object, args). object will be the argument to the self parameter --- pretty much what the compiler would have done for you.

Despite the more C-like syntax, I think it still does what you wanted... at least when applied to a callable member like in your example. It won't help with a non-callable instance field, though: they don't exist until after __init__ runs.

Here's a demonstration:

#!/usr/bin/env python3


import math


class Vector(object):

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

    def __str__(self):
        return '(' + str(self.x) + ', ' + str(self.y) + ')'

    def __repr__(self):
        return self.__class__.__name__ + str(self)

    def magnitude(self):
        return math.sqrt(self.x ** 2 + self.y ** 2)


def print_dict_getter_demo():
    print('Demo of member references on a Python dict:')
    dict_getter = dict.get
    d = {'a': 1, 'b': 2, 'c': 3, 'z': 26}
    print('Dictionary d      :  ' + str(d))
    print("d.get('a')        :  " + str(d.get('a')))
    print("Ref to get 'a'    :  " + str(dict_getter(d, 'a')))
    print("Ref to get 'BOGUS':  " + str(dict_getter(d, 'BOGUS')))
    print('Ref to get default:  ' + str(dict_getter(d, 'BOGUS', 'not None')))
    return


def print_vector_magnitude_demo():
    print('Demo of member references on a user-defined Vector:')
    vector_magnitude = Vector.magnitude
    v = Vector(3, 4)
    print('Vector v        :  ' + str(v))
    print('v.magnitude()   :  ' + str(v.magnitude()))
    print('Ref to magnitude:  ' + str(vector_magnitude(v)))
    return

def print_vector_sorting_demo():
    print('Demo of sorting Vectors using a member reference:')
    vector_magnitude = Vector.magnitude
    v0 = Vector(0, 0)
    v1 = Vector(1, 1)
    v5 = Vector(-3, -4)
    v20 = Vector(-12, 16)
    vector_list = [v20, v0, v5, v1]
    print('Unsorted:  ' + str(vector_list))
    sorted_vector_list = sorted(vector_list, key=vector_magnitude)
    print('Sorted:    ' + str(sorted_vector_list))
    return


def main():
    print_dict_getter_demo()
    print()
    print_vector_magnitude_demo()
    print()
    print_vector_sorting_demo()
    return


if '__main__' == __name__:
    main()

When run with Python 3, this produces:

Demo of member references on a Python dict:
Dictionary d      :  {'a': 1, 'c': 3, 'b': 2, 'z': 26}
d.get('a')        :  1
Ref to get 'a'    :  1
Ref to get 'BOGUS':  None
Ref to get default:  not None

Demo of member references on a user-defined Vector:
Vector v        :  (3, 4)
v.magnitude()   :  5.0
Ref to magnitude:  5.0

Demo of sorting Vectors using a member reference:
Unsorted:  [Vector(-12, 16), Vector(0, 0), Vector(-3, -4), Vector(1, 1)]
Sorted:    [Vector(0, 0), Vector(1, 1), Vector(-3, -4), Vector(-12, 16)]

As you can see, it works with both builtins and user-defined classes.

Edit:

The huge demo above was based on an assumption: that you had a reference to the class, and that your goal was to "hold on to" to one of the class's methods for use on whatever instances of that class showed up sometime later.

If you already have a reference to the instance, it's much simpler:

d = {'a': 1, 'b': 2, 'c': 3, 'z': 26}
d_getter = d.get
d_getter('z')  # returns 26

This is basically the same thing as above, only after the transformation from a function into a method has "locked in" the argument to self, so you don't need to supply it.

1
user2357112 On

The closest fit would probably be operator.attrgetter:

from operator import attrgetter
foo_member = attrgetter('foo')
bar_member = attrgetter('bar')
baz_member = attrgetter('baz')

class Example(object):
    def __init__(self):
        self.foo = 1

    @property
    def bar(self):
        return 2

    def baz(self):
        return 3

example_object = Example()
print foo_member(example_object) # prints 1
print bar_member(example_object) # prints 2
print baz_member(example_object)() # prints 3

attrgetter goes through the exact same mechanism normal dotted access goes through, so it works for anything at all you'd access with a dot. Instance fields, methods, module members, dynamically computed attributes, whatever. It doesn't matter what the type of the object is, either; for example, attrgetter('count') can retrieve the count attribute of a list, tuple, string, or anything else with a count attribute.


For certain types of attribute, there may be more specific member-pointer-like things. For example, for instance methods, you can retrieve the unbound method:

unbound_baz_method = Example.baz
print unbound_baz_method(example_object) # prints 3

This is either the specific function that implements the method, or a very thin wrapper around the function, depending on your Python version. It's type-specific; list.count won't work for tuples, and tuple.count won't work for lists.

For properties, you can retrieve the property object's fget, fset, and fdel, which are the functions that implement getting, retrieving, and deleting the attribute the property manages:

example_bar_member = Example.bar.fget
print example_bar_member(example_object) # prints 2

We didn't implement a setter or deleter for this property, so the fset and fdel are None. These are also type-specific; for example, if example_bar_member handled lists correctly, example_bar_member([]) would raise an AttributeError rather than returning 2, since lists don't have a bar attribute.

2
Ulrich Eckhardt On

Assuming a Python class:

class MyClass:
    def __init__(self):
        self.x = 42

    def fn(self):
        return self.x

The equivalent of a C++ pointer-to-memberfunction is then this:

fn = MyClass.fn

You can take a method from a class (MyClass.fn above) and it becomes a plain function! The only difference between function and method is that the first parameter is customarily called self! So you can call this using an instance like in C++:

o = MyClass()
print(fn(o)) # prints 42

However, an often more interesting thing is the fact that you can also take the "address" of a bound member function, which doesn't work in C++:

o = MyClass()
bfn = o.fn
print(bfn()) # prints 42, too

Concerning the follow-up with the properties, there are plenty answers here already that address this issue, provided it still is one.

1
Christopher Ian  Stern On

The way I would approach this in python is to use __getattribute__. If you have the name of an attribute, which would be the analog of the c++ pointer-to-member, you could call a.__getattribute__(x) to get the attribute whose name is stored in x. It's strings and dicts instead of offsets & pointers, but that's python.

6
Andrzej Pronobis On

I was not satisfied with the string approach and did some testing. This seems to work pretty well and avoids passing strings around:

import types

# Our test class
class Class:

    def __init__(self, val):
        self._val = val

    def method(self):
        return self._val

    @property
    def prop(self):
        return self._val

# Get the member pointer equivalents
m = Class.method
p = Class.prop

# Create an instance
c1 = Class(1)

# Bind the method and property getter to the instance
m1 = types.MethodType(m, c1)
p1 = types.MethodType(p.fget, c1)

# Use
m1()  # Returns 1
p1()  # Returns 1

# Alternatively, the instance can be passed to the function as self
m(c1)  # Returns 1
p.fget(c1)  # Returns 1