Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>The basic problem you're running into is that <code>__xxx__</code> methods are only looked up on the class, which means all instances of the same class will use the same <code>__xxx__</code> methods. This suggests using a method similar to what Cat Plus Plus suggested; however, you also don't want your users to have to worry about even more special names (such as <code>_call_impl</code> and <code>_negate</code>).</p> <p>If you don't mind the possibly mind-melting power of metaclasses, that is the route to take. A metaclass can add in the <code>_negate</code> attribute automatically (and name mangle it to avoid clashes), as well as take the <code>__call__</code> that your user wrote and rename it to <code>_call</code>, then create a new <code>__call__</code> that calls the old <code>__call__</code> (now called <code>_call</code> ;) and then negates the result, if necessary, before returning it.</p> <p>Here's the code:</p> <pre><code>import copy import inspect class MetaFunction(type): def __new__(metacls, cls_name, cls_bases, cls_dict): result_class = type.__new__(metacls, cls_name, cls_bases, cls_dict) if '__call__' in cls_dict: original_call = cls_dict['__call__'] args, varargs, kwargs, defaults = inspect.getargspec(original_call) args = args[1:] if defaults is None: defaults = [''] * len(args) else: defaults = [''] * (len(args) - len(defaults)) + list(defaults) signature = [] for arg, default in zip(args, defaults): if default: signature.append('%s=%s' % (arg, default)) else: signature.append(arg) if varargs is not None: signature.append(varargs) if kwargs is not None: signature.append(kwargs) signature = ', '.join(signature) passed_args = ', '.join(args) new_call = ( """def __call__(self, %(signature)s): result = self._call(%(passed_args)s) if self._%(cls_name)s__negate: result = -result return result""" % { 'cls_name':cls_name, 'signature':signature, 'passed_args':passed_args, }) eval_dict = {} exec new_call in eval_dict new_call = eval_dict['__call__'] new_call.__doc__ = original_call.__doc__ new_call.__module__ = original_call.__module__ new_call.__dict__ = original_call.__dict__ setattr(result_class, '__call__', new_call) setattr(result_class, '_call', original_call) setattr(result_class, '_%s__negate' % cls_name, False) negate = """def __neg__(self): "returns an instance of the same class that returns the negation of __call__" negated = copy.copy(self) negated._%(cls_name)s__negate = not self._%(cls_name)s__negate return negated""" % {'cls_name':cls_name} eval_dict = {'copy':copy} exec negate in eval_dict negate = eval_dict['__neg__'] negate.__module__ = new_call.__module__ setattr(result_class, '__neg__', eval_dict['__neg__']) return result_class class Base(object): __metaclass__ = MetaFunction class Power(Base): def __init__(self, power): "power = the power to raise to" self.power = power def __call__(self, number): "raises number to power" return number ** self.power </code></pre> <p>and an example:</p> <pre><code>--&gt; square = Power(2) --&gt; neg_square = -square --&gt; square(9) 81 --&gt; neg_square(9) -81 </code></pre> <p>While the metaclass code itself can be complex, the resulting objects can be very easy to use. To be fair, most of the code, and the complexity, in <code>MetaFunction</code> is due to re-writing <code>__call__</code> in order to preserve the call signature and make introspection useful... so instead of seeing <code>__call__(*args, *kwargs)</code> in help, you see this:</p> <pre><code>Help on Power in module test object: class Power(Base) | Method resolution order: | Power | Base | __builtin__.object | | Methods defined here: | | __call__(self, number) | raises number to power | | __init__(self, power) | power = the power to raise to | | __neg__(self) | returns an instance of the same class that returns the negation of __call__ </code></pre>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload