如何将布尔类型参数传递给Fabric命令

23

目前我正在使用fab -f check_remote.py func:"arg1","arg2"...来运行远程fab。

现在我需要发送一个布尔参数,但True/False会变成字符串参数,如何将其设置为布尔类型?

10个回答

40

我正在使用这个:

from distutils.util import strtobool

def func(arg1="default", arg2=False):
    if arg2:
        arg2 = bool(strtobool(arg2))

目前对我来说是有效的。 它将解析值(忽略大小写):

'y', 'yes', 't', 'true', 'on', '1'
'n', 'no', 'f', 'false', 'off', '0'

strtobool函数返回0或1,因此需要bool来将其转换为True/False布尔值。

为了完整起见,这里是strtobool的实现:

def strtobool (val):
    """Convert a string representation of truth to true (1) or false (0).

    True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
    are 'n', 'no', 'f', 'false', 'off', and '0'.  Raises ValueError if
    'val' is anything else.
    """
    val = val.lower()
    if val in ('y', 'yes', 't', 'true', 'on', '1'):
        return 1
    elif val in ('n', 'no', 'f', 'false', 'off', '0'):
        return 0
    else:
        raise ValueError("invalid truth value %r" % (val,))

稍微改进的版本(感谢mVChr的评论)
from distutils.util import strtobool

def _prep_bool_arg(arg): 
    return bool(strtobool(str(arg)))

def func(arg1="default", arg2=False):
    arg2 = _prep_bool_arg(arg2)

2
这是最简单和最佳的答案。可惜的是,strtobool实际上并没有返回一个“bool” - 它返回一个“int”! - wodow
1
认为这个语句很好地涵盖了所有情况:arg2 = True if arg2 == 'arg2' else bool(strtobool(str(arg2))) - 然后你可以使用 fab func:arg2=Truefab func:arg2=1 或者简单地使用 fab func:arg2 来打开它,而使用 fab func:arg2=Falsefab func:arg2=0 或者 fab func 来关闭它。最内层的 str() 表示你可以避免 if arg2: 的检查,并真正实现一行代码(因为它将默认值 False 转换为字符串 "False",再传递给 strtobool() 函数)。 - gimboland
1
谢谢!我制作了这个变量,这样你就不必做if检查了:_prep_arg(arg): return bool(strtobool(str(arg))) ,然后只需要arg2 = _prep_arg(arg2) - mVChr
@gimboland 如果您保留参数顺序并始终提供第一个参数,那么这将是可以的。在这个例子中,如果您执行:fab func:arg2,它实际上会给出arg1值“arg2”,并保持arg2为False,因此可能不是您想要的。 - Engrost

14

我会使用一个函数:

def booleanize(value):
    """Return value as a boolean."""

    true_values = ("yes", "true", "1")
    false_values = ("no", "false", "0")

    if isinstance(value, bool):
        return value

    if value.lower() in true_values:
        return True

    elif value.lower() in false_values:
        return False

    raise TypeError("Cannot booleanize ambiguous value '%s'" % value)

然后在任务中:

@task
def mytask(arg):
    arg = booleanize(arg)

5

如果你在所有任务中始终使用一致的模式('false','true'是布尔值),你可以将布料任务包裹起来并应用于所有任务

你可以使用这个由我编写的软件包:https://pypi.python.org/pypi/boolfab/

这里基本上是源代码:

from fabric.api import task as _task

def fix_boolean(f):
    def fix_bool(value):
        if isinstance(value, basestring):
            if value.lower() == 'false':
                return False
            if value.lower() == 'true':
                return True
        return value

    @wraps(f)
    def wrapper(*args, **kwargs):
        args_ = [fix_bool(arg) for arg in args]
        kwargs_ = {k: fix_bool(v) for k,v in kwargs.iteritems()}
        return f(*args_, **kwargs_)

    return wrapper


def task(f):
    return _task(fix_boolean(f))

So that it becomes:

@task
def my_task(flag_a, flag_b, flag_c)
   if flag_a:
       ....

避免用“布尔化”参数来污染每个任务。


这会破坏 @task(name='foo-bar')@task(default=True) - Marius Gedminas
这是一个修复后的版本(未经测试):https://gist.github.com/mgedmin/f832eed2ac0f3ce31edf - Marius Gedminas

5

arg1等于"False"时,它将被评估为True,这将是相当不直观的。 - charlax

3
更好的方法是使用ast.literal_eval函数进行解析:
from ast import literal_eval

def my_task(flag):
    if isinstance(flag, basestring): # also supports calling from other tasks
        flag = literal_eval(flag)

虽然它没有考虑'yes'或'no'等值,但比eval稍微更简洁、更安全...

1
我使用装饰器解决了这个问题。使用装饰器可以获得灵活性和明确性。
以下是代码的核心部分:
import ast
from fabric import utils
from fabric.api import task
from functools import wraps


def params(*types, **kwtypes):
    def decorator(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            new_args = ()
            for index, arg in enumerate(args):
                new_args += __cast(arg, types[index]),
            for kwarg in kwargs:
                kwargs[kwarg] = __cast(kwargs[kwarg], kwtypes[kwarg])
            return function(*new_args, **kwargs)
        return wrapper
    return decorator


def __evaluate(arg):
    try:
        return ast.literal_eval(arg)
    except:
        return str(arg)


def __cast(arg, arg_type):
    try:
        return arg_type(__evaluate(arg))
    except:
        utils.abort("Unable to cast '{}' to {}".format(arg, arg_type))

这是在代码中使用它的样子:

@task
@params(int, bool, arg1=int, arg2=bool)
def test(arg1, arg2):
    print type(arg1), arg1
    print type(arg2), arg2

以下是使用良好参数通过 fab 调用的外观:
```python 以下是使用良好参数通过 fab 调用的外观: ```
fab test:0.1,1
<type 'int'> 0
<type 'bool'> True

fab test:5,arg2=False
<type 'int'> 5
<type 'bool'> False

fab test:arg1=0,arg2=false
<type 'int'> 5
<type 'bool'> True
注意: 在最后一个例子中,"false" 是 True,在Python中这是预期的行为,但可能自然上不直观。类似于将False作为整数传递,它将转换为0,因为在Python中int(False) == 0。
以下是使用错误参数通过fab调用它的样子:
fab test:Test,False
Fatal error: Unable to cast 'Test' to <type 'int'>

Aborting.

1
Craig和Ari的答案将会在用户传递“False”时返回True值(Ari的回答更清晰明了)。
如果您使用eval()函数,字符串“True”和“False”将会被正确地转换为布尔值。但是,如果您使用默认值,您需要确保它们是字符串而不是布尔值。
def myfunc(arg1="True", arg2=False):
    arg1 = eval(arg1)
    arg2 = eval(arg2) #error

评估用户输入是危险的。 - charlax
1
假设这是在开发者环境中,那么如果他们想要摧毁自己的代码库/服务器,有更简单的方法可以实现。对答案进行如此多的负评是愚蠢的。 - AJP

1
在我的fabfiles中,我只是这样做:
TRUTHY = [True, 1, '1', 'true', 't', 'yes', 'y']

@task
def my_task(my_arg=True):
    if my_arg in TRUTHY:
         # do stuff
    else:
         # do other stuff

当然,这意味着任何不在真值中的值都是有效的False,但到目前为止,我没有需要更复杂的内容。

0

这是一个基于https://gist.github.com/mgedmin/f832eed2ac0f3ce31edf的工作版本。与旧版本不同,它实际上尊重所有可能的装饰器参数和任务别名:

from functools import wraps
from fabric import tasks

def fix_boolean(f):

    true_values = ("yes", "true", "1")
    false_values = ("no", "false", "0")

    def fix_bool(value):
        if isinstance(value, basestring):
            if value.lower() in false_values:
                return False
            if value.lower() in true_values:
                return True
        return value

    @wraps(f)
    def wrapper(*args, **kwargs):
        args_ = [fix_bool(arg) for arg in args]
        kwargs_ = {k: fix_bool(v) for k,v in kwargs.iteritems()}
        return f(*args_, **kwargs_)

    return wrapper


def task(*args, **kwargs):
    """
    The fabric.decorators.task decorator which automatically converts command line task arguments
    to a boolean representation if applicable.
    :param args:
    :param kwargs:
    :return: wrapped
    """
    invoked = bool(not args or kwargs)
    task_class = kwargs.pop("task_class", tasks.WrappedCallableTask)

    def wrapper(f):
        return task_class(fix_boolean(f), *args, **kwargs)

    return wrapper if invoked else wrapper(args[0])

代码片段:https://gist.github.com/eltismerino/a8ec8584034c8a7d087e

-3

fabric文档中所述,所有参数最终都会变成字符串。在这里最简单的做法就是检查参数:

def myfunc(arg1, arg2):
  arg1 = (arg1 == 'True')

括号不是必需的,但有助于提高可读性。

编辑:显然我之前的答案并没有真正尝试;已更新。(两年后。)


1
但是无论如何,如果在执行任何任务时将True或False作为参数发送,它都将评估为True,因为bool('True')评估为True,bool('False')也是如此。更好的解决方案可能是不发送参数或者如果参数等于'False'则将变量设置为False。 - Ricardo Murillo
4
正如前面的评论所述,这个答案不能被接受,因为它不起作用,或者至少是误导性的。bool结果对于任何字符串都将为True。 - idanzalz

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