tags:

views:

121

answers:

5

I am writing a small app that has to perform some 'sanity checks' before entering execution. (eg. of a sanity check: test if a certain path is readable / writable / exists)

The code:

import logging
import os
import shutil
import sys
from paths import PATH

logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger('sf.core.sanity')

def sanity_access(path, mode):
    ret = os.access(path, mode)
    logfunc = log.debug if ret else log.warning
    loginfo = (os.access.__name__, path, mode, ret)
    logfunc('%s(\'%s\', %s)==%s' % loginfo)
    return ret

def sanity_check(bool_func, true_func, false_func):
    ret = bool_func()
    (logfunc, execfunc) = (log.debug, true_func) if ret else \
        (log.warning, false_func)
    logfunc('exec: %s', execfunc.__name__)
    execfunc()

def sanity_checks():
    sanity_check(lambda: sanity_access(PATH['userhome'], os.F_OK), \
                 lambda: None, sys.exit)

My question is related to the sanity_check function.

This function takes 3 parameters (bool_func, true_func, false_func). If the bool_func (which is the test function, returning a boolean value) fails, true_func gets executed, else the false_func gets executed.

1) lambda: None is a little lame , because for example if the sanity_access returns True, lambda: None gets executed, and the output printed will be:

DEBUG:sf.core.sanity:access('/home/nomemory', 0)==True
DEBUG:sf.core.sanity:exec: <lambda>

So it won't be very clear in the logs what function got executed. The log will only contain <lambda> . Is there a default function that does nothing and can be passed as a parameter ? Is it a way to return the name of the first function that is being executed inside a lambda ?

Or a way not to log that "exec" if 'nothing' is sent as a paramter ?

What's the none / do-nothing equivalent for functions ?

sanity_check(lambda: sanity_access(PATH['userhome'], os.F_OK), \
                 <do nothing, but show something more useful than <lambda>>, sys.exit)

Additional question, why is lambda: pass instead of lambda: None not working ?

+6  A: 

update

I would normally delete this post because THC4k saw through all the complexity and rewrote your function correctly. However in a different context, the K combinator trick might come in handy, so I'll leave it up.


There is no builtin that does what you want AFIK. I believe that you want the K combinator (the link came up on another question) which can be encoded as

 def K_combinator(x, name):
     def f():
         return x
     f.__name__ = name
     return f

 none_function = K_combinator(None, 'none_function')

 print none_function()

of course if this is just a one off then you could just do

def none_function():
    return None

But then you don't get to say "K combinator". Another advantage of the 'K_combinator' approach is that you can pass it to functions, for example,

foo(call_back1, K_combinator(None, 'name_for_logging'))

as for your second statement, only expressions are allowed in lambda. pass is a statement. Hence, lambda: pass fails.

You can slightly simplify your call to sanity check by removing the lambda around the first argument.

def sanity_check(b, true_func, false_func):
    if b:
        logfunc = log.debug
        execfunc = true_func
    else:
        logfunc = log.warning
        execfunc = false_func
    logfunc('exec: %s', execfunc.__name__)
    execfunc()

def sanity_checks():
    sanity_check(sanity_access(PATH['userhome'], os.F_OK),
                 K_combinator(None, 'none_func'), sys.exit)

This is more readable (largely from expanding the ternary operator into an if). the boolfunc wasn't doing anything because sanity_check wasn't adding any arguments to the call. Might as well just call instead of wrapping it in a lambda.

aaronasterling
+1  A: 
>>> import dis
>>> f = lambda: None
>>> dis.dis(f)
  1           0 LOAD_CONST               0 (None)
              3 RETURN_VALUE    

>>> g = lambda: Pass 
>>> 
>>> 
>>> dis.dis(g)
  1           0 LOAD_GLOBAL              0 (Pass)
              3 RETURN_VALUE 


>>> g = lambda: pass 
  File "<stdin>", line 1
    g = lambda: pass 
                   ^
SyntaxError: invalid syntax
pyfunc
Can you please elaborate ?
Andrei Ciobanu
AaronMcSmooth has already explained why it wouldn't work. I thought not to replicate it and write it again in my reply.
pyfunc
+2  A: 

You might want to rethink this.

class SanityCheck( object ):
    def __call__( self ):
        if self.check():
            logger.debug(...)
            self.ok()
        else:
            logger.warning(...)
            self.not_ok()
    def check( self ):
        return True
    def ok( self ):
        pass
    def not_ok( self ):
        sys.exit(1)

class PathSanityCheck(SanityCheck):
    path = "/path/to/resource"
    def check( self ):
        return os.access( path, os.F_OK )

class AnotherPathSanityCheck(SanityCheck):
    path = "/another/path"

def startup():
    checks = ( PathSanityCheck(), AnotherPathSanityCheck() )
    for c in checks:
        c()

Callable objects can simplify your life.

S.Lott
I think you want to be extending `SanityCheck` instead of objects ;)
aaronasterling
@S.Lott : Your example is a good (readable) alternative. But one thing: isn't one subclass of SanityCheck enough, that receives path / mode as parameter ? Another thing: What if sometimes when not_ok() i want to perform a completely different tasks instead of sys.exit() . Same for ok()...
Andrei Ciobanu
@Andrei Ciobanu: You can always override `ok` and `not_ok` in a subclass.
mipadi
@mipadi That could be a solution. An OOP one.
Andrei Ciobanu
This is actually more complicated than what OP posted after you strip out all of the logging info. Or at least from my perspective.
aaronasterling
@AaronMcSmooth: That's a hard case to make. This uses simple inheritance and no "apply a random function-like thing to random arguments" mechanisms. Just simple inheritance and ordinary procedural programming. It takes up more lines of code for a reason -- to make the processing painfully obvious. Not subtle.
S.Lott
+1  A: 

Actually, what you want is a function which does nothing, but has a __name__ which is useful to the log. The lambda function is doing exactly what you want, but execfunc.__name__ is giving "<lambda>". Try one of these:

def nothing_func():
    return
def ThisAppearsInTheLog():
    return

You can also put your own attributes on functions:

def log_nothing():
       return
log_nothing.log_info = "nothing interesting"

Then change execfunc.__name__ to getattr(execfunc,'log_info', '')

greggo
+2  A: 

What's with all the lambdas that serve no purpose? Well, maybe optional arguments will help you a bit:

def sanity_check( test, name='undefined', ontrue=None, onfalse=None ):
    if test:
        log.debug(name)
        if ontrue is not None:
            ontrue()
    else:
        log.warn( name )
        if onfalse is not None:
            onfalse()

def sanity_checks():
    sanity_check(sanity_access(PATH['userhome'], os.F_OK), 'test home', 
        onfalse=sys.exit)

But you are really overcomplicating things.

THC4k
+1 this is very clean for what it is
aaronasterling