Python: Modifying passed arguments before class instance initialization

ⅰ亾dé卋堺 提交于 2019-12-11 03:52:42

问题


I am experimenting with ways to implement a simplified Term Rewriting System (TRS)/Symbolic Algebra System in Python. For this I would really like to be able to be able to intercept and modify the operands in particular cases during the class instance instantiation process. The solution I came up with, was to create a metaclass that modifies the typical call behavior of a class object (of type 'type').

class Preprocess(type):
    """
    Operation argument preprocessing Metaclass.
    Classes using this Metaclass must implement the 
        _preprocess_(*operands, **kwargs)
    classmethod.
    """

    def __call__(cls, *operands, **kwargs):
        pops, pargs = cls._preprocess_(*operands, **kwargs)
        return super(Preprocess, cls).__call__(*pops, **pargs)

An example case would be to expand out nested operations F(F(a,b),c)-->F(a,b,c)

class Flat(object):
    """
    Use for associative Operations to expand nested 
    expressions of same Head: F(F(x,y),z) => F(x,y,z)
    """
    __metaclass__ = Preprocess
    @classmethod
    def _preprocess_(cls, *operands, **kwargs):
        head = []
        for o in operands:
            if isinstance(o, cls):
                head += list(o.operands)
            else:
                head.append(o)
        return tuple(head), kwargs

So, now this behavior can be realized through inheritance:

class Operation(object):
    def __init__(self, *operands):
        self.operands = operands

class F(Flat, Operation):
    pass

This leads to the desired behavior:

print F(F(1,2,3),4,5).operands
(1,2,3,4,5)

However, I would like to combine several such preprocessing classes and have them process the operands sequentially according to the natural class mro.

class Orderless(object):
    """
    Use for commutative Operations to bring into ordered, equivalent 
    form: F(*operands) => F(*sorted(operands))
    """
    __metaclass__ = Preprocess

    @classmethod
    def _preprocess_(cls, *operands, **kwargs):

        return sorted(operands), kwargs

And this does not seem to work as wanted. Defining a Flat and Orderless Operation type

class G(Flat, Orderless, Expression):
    pass

results in only the first Preprocessing superclass being 'active'.

print G(G(3,2,1),-1,-3).operands
(3,2,1,-1,-3)

How can I ensure that all Preprocessing classes' preprocess methods are called before class instantiation?

Update:

I can't seem to formally answer my question yet due to my status as new stackoverflow user. So, I believe this is probably the best solution I can come up with:

class Preprocess(type):
    """
    Abstract operation argument preprocessing class.
    Subclasses must implement the 
        _preprocess_(*operands, **kwargs)
    classmethod.
    """

    def __call__(cls, *operands, **kwargs):
        for cc in cls.__mro__:
            if hasattr(cc, "_preprocess_"):
                operands, kwargs = cc._preprocess_(*operands, **kwargs)

        return super(Preprocess, cls).__call__(*operands, **kwargs)

I guess the problem is that super(Preprocess, cls).__call__(*operands, **kwargs) does not traverse the mro of cls as expected.


回答1:


I think you're going about this the wrong way; drop the metaclass, use class and method decorators instead.

For example, define your flat as:

@init_args_preprocessor
def flat(operands, kwargs): # No need for asterisks
    head = []
    for o in operands:
        if isinstance(o, cls):
            head += list(o.operands)
        else:
            head.append(o)
    return tuple(head), kwargs

With the init_args_preprocessor decorator turning that function into a class decorator:

def init_args_preprocessor(preprocessor):
    def class_decorator(cls):
        orig_init = cls.__init__
        def new_init(self, *args, **kwargs):
            args, kwargs = preprocessor(args, kwargs)
            orig_init(self, *args, **kwargs)
        cls.__init__ = new_init
        return cls
   return class_decorator

And now, instead of mixins use decorators:

class Operation(object):
    def __init__(self, *operands):
        self.operands = operands

@flat
class F(Operation):
    pass

And you should have no problem combining the class modifiers cleanly:

@init_args_preprocessor
def orderless(args, kwargs):
    return sorted(args), kwargs

@orderless
@flat
class G(Expression):
   pass

Caveat Emptor: All code above strictly untested.



来源:https://stackoverflow.com/questions/10522431/python-modifying-passed-arguments-before-class-instance-initialization

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!