问题描述
我在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
?