传递“None”作为函数参数(其中参数是一个函数)

8
我是一个小型应用程序的编写者,在进入执行之前必须进行一些 "健全性检查"。(例如:测试某个路径是否可读/可写/存在)
代码如下:
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)

我的问题与sanity_check函数有关。
该函数有三个参数(bool_functrue_funcfalse_func)。如果bool_func(测试函数,返回布尔值)失败,则执行true_func,否则执行false_func1) lambda: None 有点简单,因为例如如果sanity_access返回True,则会执行lambda: None,并打印以下输出:
DEBUG:sf.core.sanity:access('/home/nomemory', 0)==True
DEBUG:sf.core.sanity:exec: <lambda>

因此,在日志中不会很清楚地显示执行了哪个函数。日志只包含<lambda>。是否有一个默认的函数可以什么都不做并作为参数传递?是否有一种方法可以返回在lambda内部执行的第一个函数的名称?

或者,如果将“nothing”作为参数发送,则不记录“exec”有什么方法吗?

函数的无操作/什么也不做的等效方式是什么?

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

还有一个问题,为什么使用lambda: pass而不是lambda: None会出现问题?


3
哇,那太复杂了。为什么这么复杂? - S.Lott
1
“Lambda”这个东西没有帮助,是吧?很多检查并不能证明它的复杂性。为什么不直接使用“if”语句呢? - S.Lott
2
你可以尝试写入路径,如果失败就记录日志。 - Jochen Ritzel
1
@Andrei 你正在尝试用Python编写Lisp。 - aaronasterling
1
函数式编程并不意味着要写很多函数或者传递函数!它的意思是编写简单、明显正确和可重用的函数,然后使用它们来组合更复杂的函数。FP 在 Python 中运行得非常好,但它更多地涉及使用 LCs、生成器和 itertools,而不是传递函数。 - Jochen Ritzel
显示剩余7条评论
5个回答

8

更新

通常我会删除这篇文章,因为THC4k看穿了所有的复杂性并正确重写了您的函数。然而在不同的情境下,K组合器技巧可能会派上用场,所以我会保留它。


据我所知,没有内置函数可以满足您的需求。我相信您需要K组合器(链接出现在另一个问题中),它可以被编码为

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

 none_function = K_combinator(None, 'none_function')

 print none_function()

当然,如果这只是一次性的操作,你可以直接执行以下命令:
def none_function():
    return None

但是这样你就不能说“K组合器”了。使用'K_combinator'方法的另一个好处是可以将其传递给函数,例如:

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

关于你的第二个声明,lambda中只允许表达式。 pass是一种语句。因此,lambda: pass会失败。

你可以通过删除围绕第一个参数的lambda轻微简化对sanity check的调用。

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)

这段代码更易读(主要是通过将三元操作符扩展为if语句实现的)。由于sanity_check没有向调用添加任何参数,因此boolfunc没有起到作用。最好直接调用而不是将其包装在lambda中。


我得说我曾经多次提到“K组合器”,然后我沿着SKI链接进入了一个组合逻辑、λ演算、非λ和应用编程语言的兔子洞,然后我完全忘记了我在SO上要找什么。 - Davos

8

为什么有那么多没有用途的lambda表达式?也许可选参数可以帮助你一些:

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)

但是你确实过于复杂化了事情。

3

你可能需要重新考虑这个问题。

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()

可调用对象可以简化您的生活。


我认为你想要扩展SanityCheck而不是对象 ;) - aaronasterling
@S.Lott:你的例子是一个不错的(易读的)替代品。但有一件事情:接收路径/模式作为参数的SanityCheck的一个子类是否足够?还有一件事情:如果有时候not_ok()我想执行完全不同的任务而不是sys.exit()怎么办?ok()也同样。 - Andrei Ciobanu
@Andrei Ciobanu:您可以在子类中始终覆盖 oknot_ok - mipadi
@mipadi 这可能是一个解决方案。一个面向对象的解决方案。 - Andrei Ciobanu
这实际上比 OP 发布的更复杂,如果你剥离所有的日志信息的话。至少从我的角度来看是这样的。 - aaronasterling
@AaronMcSmooth:这很难说服人。这只使用了简单的继承和没有“将类似于随机函数的东西应用于随机参数”的机制。只是简单的继承和普通的过程式编程。它占用更多的代码行是有原因的--让处理变得非常明显。不太微妙。 - S.Lott

1
>>> 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

AaronMcSmooth已经解释了为什么它不起作用。我想不复制它并在我的回复中重新编写它。 - pyfunc

1
其实,你需要的是一个什么都不做但对于日志有用的__name__函数。Lambda函数正好可以做到这一点,但是execfunc.__name__会返回"<lambda>"。尝试使用以下其中之一:
def nothing_func():
    return
def ThisAppearsInTheLog():
    return

你也可以在函数上放置自己的属性:

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

然后将 execfunc.__name__ 改为 getattr(execfunc,'log_info', '')


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接