问题
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