如何重构多个big if语句?

问题描述

我在Python中有一个协程,它接受许多对象,确定每个对象的属性,然后根据这些结果加载正确的资源。问题是,可以提供数量可变的对象(1、2、3 ..),每个对象具有N个可用状态{A,B,C,D,E ...}中的1个。因此,对于一个对象,我们可以具有A,B,C ...类型的交互,对于2个对象,可以具有AB,AA,BA ...类型的交互(允许订购,允许重复),对于3个或更多对象,依此类推。天真的解决方案(我的)是检查我们有多少个对象,然后用YandereDev样式检查每个可能的组合:

std::cout

此协程不完整,我省略了2个人的情况,但它遵循相同的逻辑,尽管更为复杂。 (我很抱歉)

作为解释,这是怎么回事,该协程是不和谐的命令,其中一个成员提到另一个成员,并且根据其性别角色执行某些操作。如果重要的话,将支持多个性别角色,而不仅是男性角色(尽管在此示例中已将其省略)。

我的问题是,我该如何精确重构@client.command() async def hug(ctx,members: commands.Greedy[discord.Member]): count = len(members) if count == 1: if ctx.author.id == members[0].id: author = await get_roles_single(ctx.author) if author == 'Male': os.chdir(base + '\\hug\\SM') elif author == 'Female': os.chdir(base + '\\hug\\SF') (file,embed) = await prepare_embed("Self Hug",f"{ctx.author.mention} hugs themselves.",'hug.gif',ctx.author.color,False) await ctx.send(file=file,embed=embed) return author = await get_roles_single(ctx.author) recepient = await get_roles_single(members[0]) if author == recepient == 'Male': os.chdir(base + '\\hug\\men') elif author == recepient == 'Female': os.chdir(base + '\\hug\\women') elif (author,recepient) in (('Male','Female'),('Male','Female')): os.chdir(base + '\\hug\\mix') else: os.chdir(base + '\\hug\\default') (file,embed) = await prepare_embed("Hug",f"{ctx.author.mention} hugs {members[0].mention}.",False) await ctx.send(file=file,embed=embed) os.chdir(base) elif count == 2: pass ,而我们还认为我们可以让成员ping 1、2、3或更多成员,而其顺序需要保留。我们如何排除这种巨大的条件逻辑?

解决方法

对于2个可用状态(A,B)的2个对象:

interaction of A -> B:  A's reaction of B
interaction of B -> A:  B's reaction of A
interaction of A -> A:  A's reaction of another A
interaction of B -> B:  B's reaction of another B

如果上面的示例是每次交互具有不同响应的最终目标,但是没有if链接-将每个州创建为class,并为用户分配States类。通过目标的状态class分配交互目标将使扩展更加容易。

要扩展状态类型,只需创建更多的状态类,并为您拥有的每种类型的状态在每个类中注册交互。

如果要将交互的对象总数扩展到2以上,则必须修改或添加新的Decorator来注册/分派具有更多自变量的对象。

这里是基本的调度逻辑-定义了每个州的响应,而没有单个if分支。将获得以上交互结果进行演示。

from functools import wraps


def state_deco(func_main):
    """
    Decorator that mimics singledispatch for ease of interaction expansions.
    """

    # assuming no args are needed for interaction functions.
    func_main.dispatch_list = {}  # collect decorated functions

    @wraps(func_main)
    def wrapper(target):
        # dispatch target to destination interaction function.
        nonlocal func_main
        try:
            # find and run callable for target
            return func_main.dispatch_list[type(target)]()
        except KeyError:
            # If no matching case found,main decorated function will run instead.
            func_main()

    def register(target):
        # A decorator that register decorated function to main decorated function.
        def decorate(func_sub):
            nonlocal func_main
            func_main.dispatch_list[target] = func_sub

            def register_wrapper(*args,**kwargs):
                return func_sub(*args,**kwargs)

            return register_wrapper
        return decorate

    wrapper.register = register
    return wrapper


# Abstract class of reactions
class StateBase:
    # Implement per states
    def interaction(self,target) -> str:
        raise NotImplementedError


class StateA(StateBase):
    def interaction(self,target):
        # if interaction target is not registered,general() will run instead.

        @state_deco
        def general():
            # Add some necessary setups like os.chdir here,and below functions.
            # return is not needed,just for demonstration.
            return "A's reaction to undefined others."

        @general.register(StateA)
        def _():
            # Function name is not required,up to you whether name it or not.
            return "A's reaction of another A"

        @general.register(StateB)
        def _():
            return "A's reaction of B"

        return general(target)


class StateB(StateBase):
    def interaction(self,target):

        @state_deco
        def general():
            return "B's reaction to undefined others."

        @general.register(StateA)
        def _():
            return "B's reaction of A"

        @general.register(StateB)
        def _():
            return "B's reaction of another B"

        return general(target)

# Expand States responses further for more interactions choices.


if __name__ == '__main__':
    # pretending users got their roles via get_roles_single()
    user_A = StateA()
    user_B = StateB()

    print(f"interaction of A -> B:  {user_A.interaction(user_B)}")
    print(f"interaction of B -> A:  {user_B.interaction(user_A)}")
    print(f"interaction of A -> A:  {user_A.interaction(user_A)}")
    print(f"interaction of B -> B:  {user_B.interaction(user_B)}")

,

我们可以简化这一部分:

    if count == 1:
        if ctx.author.id == members[0].id:
            author = await get_roles_single(ctx.author)
            if author == 'Male':
                os.chdir(base + '\\hug\\SM')
            elif author == 'Female':
                os.chdir(base + '\\hug\\SF')
            (file,embed) = await prepare_embed("Self Hug",f"{ctx.author.mention} hugs themselves.",'hug.gif',ctx.author.color,False)
            await ctx.send(file=file,embed=embed)
            return
        author = await get_roles_single(ctx.author)
        recepient = await get_roles_single(members[0])

与:

    if count == 1:
        author = await get_roles_single(ctx.author)
        if ctx.author.id == members[0].id:
            os.chdir(base + '\\hug\\S'+ author[0])
            (file,embed=embed)
            return
        # author = await get_roles_single(ctx.author)
        recipient = await get_roles_single(members[0])

对于这部分:

        if author == recepient == 'Male':
            os.chdir(base + '\\hug\\men')
        elif author == recepient == 'Female':
            os.chdir(base + '\\hug\\women')
        elif (author,recepient) in (('Male','Female'),('Male','Female')):
            os.chdir(base + '\\hug\\mix')
        else:
            os.chdir(base + '\\hug\\default')

具有:

        _dict = {'MM' : 'men','FF' : 'women','MF' : 'mix','FM' : 'mix'}
        _key  = author[0] + recipient[0]
        os.chdir(base + '\\hug\\' + _dict.get(_key,'default'))
,

如果不对count> 1进行泛化,要掌握整个逻辑会有些困难,但有一些想法:

  • 将逻辑拆分为尽可能多的功能。例如,get_path_for_members会根据性别返回要移动的路径。

  • 在可能的情况下尝试找到一条通用规则,而不是到处都是特殊情况(一个逻辑表示任意数量的ex成员)。

  • 如果仍然需要分别处理特殊情况,请将每个特殊情况移到一个单独的函数中,并排除所有常见的情况(例如get_roles_single?)

author = await get_roles_single(ctx.author)
roles = [await get_roles_single(m) for m in members]
rel_path = get_path_for_members(author,roles)    # "\\hug\\SM","\\hug\\women" etc
title,msg = get_message(author,roles)    # "Hug",".. hugs .." or "Self Hug",".. hugs themselves"
os.chdir(base + rel_path)
(file,embed) = await prepare_embed(title,msg,False)
await ctx.send(file=file,embed=embed)
os.chdir(base)
,

您可以首先构造一个字符串,表示发生的确切交互,该字符串由唯一的符号组成。例如,如下所示:

# "Am" = Author,male. "Mf" = Member,female...
interaction = ""
for member in members:
    if ctx.author.id == member.id:
        interaction += "A"
    else:
        interaction += "M"

    role = await get_roles_single(member)
    if role == 'Male':
        interaction += "m"
    elif.... # other roles

return interaction # e.g. "AmMfMf"

现在我们有了确切的互动。如果每种可能的交互都需要独特的处理(这就是我的理解),则可以为每种交互定义命令并将它们映射到交互字符串。作为命令,您可以使用函数,lambda函数或类。例如:

# just as an example,what will be called
def selfhug_male(author,members):
    os.chdir(base + '\\hug\\SM')
    (file,f"{author.mention} hugs themselves.",author.color,False)
    await ctx.send(file=file,embed=embed)

# all the commands
commands = {
    "AmAm": selfhug_male,"AfAf": selfhug_female,}

#usage (I ignored concurrency here,you might have to modify):
command = commands[interaction]
command(author,members)

您可能需要为每个交互编写一个单独的命令,但是可能会重用或巧妙地构造它们,因为我怀疑某些用于交互的系统。

,

您的意思是:

for member in members:
    if ctx.author.id == member.id:
        ...

是否用members[0]取代member