Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create instance method in metaclass using partial in Python 3

Using metaclasses, I am trying to create an instance method by simplifying an existing instance method. The problem is that partial does not work with instance method. This is a simple example of what I try to achieve:

from functools import partial

class Aclass(object):

    def __init__(self, value):
        self._value = value

    def complex(self, a, b):                                            
        return a + b + self._value

class Atype(type):

    def __new__(cls, name, bases, attrs):
        return super(Atype, cls).__new__(cls, name, (Aclass, ) + bases, attrs)

    def __init__(cls, name, bases, attrs):
        setattr(cls, 'simple', partial(cls.complex, b=1))

class B(metaclass=Atype):
    pass

b = B(10)

print(b.complex(1, 2))
print(b.simple(1))

and the output is:

13
Traceback (most recent call last):
  File "metatest.py", line 22, in <module>
    print(b.simple(1))
TypeError: complex() takes exactly 3 non-keyword positional arguments (1 given)

I have solved using lambda changing:

    setattr(cls, 'simple', partial(cls.complex, b=1))

to:

    setattr(cls, 'simple', lambda self, x: cls.complex(self, x, b=1))

but it is ugly and has problems with optional parameters.

I could create these method at the instance __init__ but I guess it makes more sense, and is more efficient to do it on class __init__using metaclasses.

Any ideas how to do it properly?

like image 729
Hernan Avatar asked Mar 01 '11 22:03

Hernan


Video Answer


2 Answers

Well, I am a bit unfamiliar with Python 3 method handling yet - the simplest thing I could think of is rewriting partial so that it preserves the first argument from the original call, then inserts the "partial" parameters.

It worked with your example, but it needs testing with more complex patterns.

from functools import wraps

class Aclass(object):
    def __init__(self, value):
        self._value = value

    def complex(self, a, b):                                            
        return a + b + self._value

def repartial(func, *parameters, **kparms):
    @wraps(func)
    def wrapped(self, *args, **kw):
        kw.update(kparms)
        return func(self, *(args + parameters), **kw)
    return wrapped

class Atype(type):
    def __new__(cls, name, bases, attrs):
        return super(Atype, cls).__new__(cls, name, (Aclass, ) + bases, attrs)

    def __init__(cls, name, bases, attrs):
        setattr(cls, 'simple', repartial(cls.complex, b=1))

class B(metaclass=Atype):
    pass

b = B(10)

print(b.complex(1, 2))
print(b.simple(1))
like image 95
jsbueno Avatar answered Oct 06 '22 02:10

jsbueno


Instead of using partial, I'd just define class Atype like this:

class Atype(type):

    def __new__(cls, name, bases, attrs):
        return super(Atype, cls).__new__(cls, name, (Aclass, ) + bases, attrs)

    def __init__(cls, name, bases, attrs):
        def simple(self, a):
            return cls.complex(self, a, 1)
        setattr(cls, 'simple', simple)

The __init__() method can also be written more compactly:

def __init__(cls, name, bases, attrs):
    setattr(cls, 'simple', lambda self, a: cls.complex(self, a, 1))
like image 21
martineau Avatar answered Oct 06 '22 01:10

martineau