开发者

Create instance method in metaclass using partial in Python 3

开发者 https://www.devze.com 2023-02-14 17:57 出处:网络
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

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?


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))


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))


The problem is that the object returned by functools.partial() is a callable object, not a function. So apparently Python doesn't care for a non-function trying to act like one in this context. One solution is to create a function as a wrapper for the partial object.

class Atype(type):

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

jsbueno's solution (a reimplementation of partial that returns a real function) is good though. I really don't know why functools.partial() doesn't work that way; not being able to use it in this context is a surprising pitfall.

0

精彩评论

暂无评论...
验证码 换一张
取 消