如何创建具有互斥参数的 Python 函数?

问题描述 投票:0回答:7

我有一个 Python 类,它需要接受两个互斥参数之一。如果参数不排他(即:如果两者都给出或都不给出),则应引发错误。

class OrgLocation:
    __init__(self, location_num=None, location_path=None):
        """location_num & location_path are mutually exclusive"""

在大多数情况下,最好的选择是创建两个单独的类。但是,我正在使用外部 API,它要求这两个属性是互斥的。

要求:

<OrgLocation LocationPathName="ROOT/BU/DIV/SL/DEPT/JOB" LocationNum="1234"/>

回应:

<Error Message="Use either LocationNum or LocationPathName but not both." ErrorCode="1186">

类似的问题似乎表明

argparse
可用于命令行界面中的互斥参数,但我不确定如何将其应用于类构造函数

如何创建具有互斥参数的 Python 函数?

python arguments mutual-exclusion
7个回答
9
投票

您可能想在

__init__
方法中创建一个测试,但更好的问题可能是......为什么?

if location_num is not None and location_path is not None:
    raise TheseParametersAreMutuallyExclusiveError()

为什么要创建一个具有多种用途的类?为什么不创建单独的类?


5
投票

除了@Ivonet的答案之外,Python 中的一种常见方法是接受单个参数,然后回避它:

class Location: __init__(self, location): """location_num & location_path are mutually exclusive""" try: x = self.locationArray[location] #location is a num? except TypeError: x = self.locationDict[location] #location is a string?

可能还有另一个例外。如果你想使用

argparse

,这对于只有两个参数来说可能有点过分,但可以很好地扩展:

import argparse class Bla: parser = argparse.ArgumentParser(prog='Class Bla init') path_group = parser.add_mutually_exclusive_group(required=True) path_group.add_argument('--num',nargs=1,type=int) path_group.add_argument('--path',nargs=1,type=str) def __init__(self,**kwargs): args=self.parser.parse_args(sum( zip(map( lambda x: '--'+x,kwargs.keys()), map(str,kwargs.values())),())) #Bla(x='abc') #Bla(num='abc') Bla(path='abc') Bla(path='abc',num=3)

从上到下的结果:

usage: Class Bla init [-h] (--num NUM | --path PATH) bla.py: error: one of the arguments --num --path is required usage: Class Bla init [-h] (--num NUM | --path PATH) bla.py: error: argument --num: invalid int value: 'abc' <__main__.Bla object at 0x7fd070652160> usage: Class Bla init [-h] (--num NUM | --path PATH) bla.py: error: argument --num: not allowed with argument --path

这也很酷,因为

Bla(help='anything')

 实际上会打印用法(并退出)。这是为了回答有关 
argparse
 的具体问题,但要明确的是,@Ivonet 有我实际用于您的确切示例的答案。


3
投票
你想做的事情很简单:

class Location: __init__(self, location_num=None, location_path=None): """location_num & location_path are mutually exclusive""" if location_num is not None and location_path is not None: raise ValueError("should have location_num or location_path, but not both") elif location_num: #create location from int elif location_str: #create location from str

但它不被认为是正确的Python。您应该创建替代构造函数作为类方法,而不是:

class Location: def __init__(self, parsed_location): #create location @classmethod def from_int(cls, location_int): return cls(parse_int(location_int)) @classmethod def from_str(cls, location_str): return cls(parse_str(location_str))

请参阅

在 Python 中拥有多个构造函数的干净、Python 方式是什么? 以获得更深入的示例。


1
投票
虽然有点 hacky,但您可以使用 XOR 运算符,如下所示:

class OrgLocation: def __init__(self, location_num=None, location_path=None): """location_num & location_path are mutually exclusive""" assert (location_num is None) ^ bool(location_path is None), "location_num and location_path are mutually exclussive" print("OK")
    

1
投票
这是我基于

https://stackoverflow.com/a/55156168/286807构建的互斥防护:

# Mutually Exclusive function predicate # Returns True if no. of args that are True or not None is > 1 def ismuex(*a): return not bool(sum(map(lambda v: bool(v if isinstance(v, bool) else not v is None), a)) > 1)
用途:

def my_func(arg_1, arg_2, arg3): assert ismuex(arg_1, arg_2, arg3), \ "arguments arg_1, arg_2 and arg_3 are mutually exclusive" #....
    

0
投票
我认为装饰器是一种很好且富有表现力的方式来做到这一点。 我确信我的实现可以改进,但它有效,而且我认为它使用法非常可读:

class MutuallyExclusiveArgsError(Exception): def __init__(self, groups): err = f"These groups or arguments are mutually exclusive: {','.join(str(tuple(g)) for g in groups)}" super().__init__(err) def exclusive_args(*args): import attr import functools from typing import Callable,Set,Union,Iterable @attr.s class _inner: _arg_groups_conv = lambda val: {arg: group for group in {frozenset([s]) if isinstance(s, str) else s for s in val} for arg in group} func : Callable = attr.ib() arg_groups : Set[Union[str,Iterable]] = attr.ib(converter=_arg_groups_conv, kw_only=True) def __attrs_post_init_(self): functools.update_wrapper(self, self.func) def __call__(self, *args, **kwargs): groups = {self.arg_groups[kw] for kw in kwargs} if len(groups) > 1: raise MutuallyExclusiveArgsError(groups) self.func(*args, **kwargs) return functools.partial(_inner, arg_groups=args)
用法如下:

@exclusive_args("one", "two") def ex(*, one=None, two=None): print(one or two) ex(one=1, two=2) --------------------------------------------------------------------------- MutuallyExclusiveArgsError Traceback (most recent call last) <ipython-input-38-0f1d142483d2> in <module> ----> 1 ex(one=1, two=2) <ipython-input-36-c2ff5f47260f> in __call__(self, *args, **kwargs) 21 groups = {self.arg_groups[kw] for kw in kwargs} 22 if len(groups) > 1: ---> 23 raise MutuallyExclusiveArgsError(groups) 24 self.func(*args, **kwargs) 25 return functools.partial(_inner, arg_groups=args) MutuallyExclusiveArgsError: These groups or arguments are mutually exclusive: ('two',),('one',) ex(one=1) 1 ex(two=2) 2
或者像这样:

@exclusive_args("one", ("two","three")) def ex(*, one=None, two=None, three=None): print(one, two, three) ex(one=1) 1 None None ex(two=1) None 1 None ex(three=1) None None 1 ex(two=1, three=2) None 1 2 ex(one=1, two=2) --------------------------------------------------------------------------- MutuallyExclusiveArgsError Traceback (most recent call last) <ipython-input-46-0f1d142483d2> in <module> ----> 1 ex(one=1, two=2) <ipython-input-36-c2ff5f47260f> in __call__(self, *args, **kwargs) 21 groups = {self.arg_groups[kw] for kw in kwargs} 22 if len(groups) > 1: ---> 23 raise MutuallyExclusiveArgsError(groups) 24 self.func(*args, **kwargs) 25 return functools.partial(_inner, arg_groups=args) MutuallyExclusiveArgsError: These groups or arguments are mutually exclusive: ('one',),('two', 'three') ex(one=1,three=3) --------------------------------------------------------------------------- MutuallyExclusiveArgsError Traceback (most recent call last) <ipython-input-47-0dcb487cba71> in <module> ----> 1 ex(one=1,three=3) <ipython-input-36-c2ff5f47260f> in __call__(self, *args, **kwargs) 21 groups = {self.arg_groups[kw] for kw in kwargs} 22 if len(groups) > 1: ---> 23 raise MutuallyExclusiveArgsError(groups) 24 self.func(*args, **kwargs) 25 return functools.partial(_inner, arg_groups=args) MutuallyExclusiveArgsError: These groups or arguments are mutually exclusive: ('one',),('two', 'three')
    

0
投票
我知道这是一个老问题,但我还没有看到有人使用我采取的简单方法。这个例子是一个普通的旧方法,但它在类的

__init__

 方法中也同样有效:

def circle_area(radius=None, diameter=None, circumference=None): # check for mutually-exclusive parameters number_of_options_specified = len([opt for opt in [circumference, diameter, radius] if opt is not None]) if number_of_options_specified != 1: raise ValueError(f"Exactly one of radius ({radius}) / diameter ({diameter}) / circumference ({circumference}) must be specified") # calculate pi = 3.14 if radius is not None: area = pi * radius**2 if diameter is not None: area = pi * (diameter/2.0)**2 if circumference is not None: area = (circumference**2)/(4.0*pi) return area
    
© www.soinside.com 2019 - 2024. All rights reserved.