epiphany

Steven D'Aprano steve+comp.lang.python at pearwood.info
Wed Apr 24 21:35:07 EDT 2013


On Wed, 24 Apr 2013 19:50:33 -0400, Roy Smith wrote:

> I discovered something really neat today.
> 
> We've got a system with a bunch of rules.  Each rule is a method which
> returns True or False.  At some point, we need to know if all the rules
> are True.  Complicating things, not all the rules are implemented. Those
> that are not implemented raise NotImplementedError.


NotImplementedError is intended to be raised by abstract base classes to 
indicate a method that must be overridden. I also use it as a place-
holder for functions or methods I haven't actually written yet. I'm not 
sure what semantics you're giving NotImplementedError in your code, but I 
wonder whether a neater solution might be to just use rule = None for 
unimplemented rules, rather than:

def unimplemented():
    raise NotImplementedError

rule = unimplemented

Then your logic for seeing if all rules return true would become:

all(r() for r in rules if r is not None)

and for seeing if all rules return true or are unimplemented:

all(r is None or r() for r in rules)



> We used to have some ugly logic which kept track of which rules were
> active and only evaluated those.

I don't see why you would need anything like that. Reading further on, I 
see that you are counting unimplemented rules as true, for some reason 
which I don't understand. (Knowing nothing of your use-case, I would have 
expected intuitively that unimplemented rules count as not true.) A 
simple helper function will do the job:


def eval(rule):
    try:
        return rule()
    except NotImplementedError:
        return True

everything_is_true = all(eval(r) for r in rules)



No need for complicated ugly logic keeping track of what rules are 
implemented. But if you're worried about the cost of catching those 
exceptions (you've profiled your code, right?) then that's easy with a 
decorator:


def not_implemented(func):
    @functools.wraps(func)
    def inner(*args, **kw):
        raise NotImplementedError
    inner.ni = True
    return inner


# Decorate only the rules you want to be unimplemented.

@not_implemented
def my_rule():
    pass


everything_is_true = all(r() for r in rules if not hasattr(r, 'ni'))



Note that if you could reverse the logic so that unimplemented rules 
count as not true, this will also work:

try:
    everything_is_true = all(r() for r in rules)
except NotImplementedError:
    everything_is_true = False



> So, here's the neat thing.  It turns out that bool(NotImplemented)
> returns True.  By changing the unimplemented rules from raising
> NotImplementedError to returning NotImplemented, the whole thing
> becomes:
> 
>     return all(r() for r in rules)

Objects are supposed to return NotImplemented from special dunder methods 
like __add__, __lt__, etc. to say "I don't know how to implement this 
method for the given argument". Python will then try calling the other 
object's special method. If both objects return NotImplemented, Python 
falls back on whatever default behaviour is appropriate.

So, knowing nothing of your application, I fear that this is an abuse of 
NotImplemented's semantics. If a rule returns NotImplemented, I would 
expect your application to fall back on a different rule. If that's not 
the case, you're using it in a non-standard way that will cause confusion 
for those with expectations of what NotImplemented means.



-- 
Steven



More information about the Python-list mailing list