transition 的两种写法
- 旧的写法(称为传统 transition 写法)是在
transition()函数里用if - elif判断场景、进行操作; - 新的写法(称为 TransitionOn 新写法)把每个场景的处理拆到单独的函数中,场景由
TransitionOn装饰器的参数指定,函数中针对指定场景进行操作。
经过改造,框架目前同时兼容两种写法,以后由传统 transition 写法逐渐向 TransitionOn 写法过渡。
如果 solver 容器类定义了 solver_default_scene 属性,则为新写法,否则为旧的写法。
TransitionOn 新写法的优点
- 少了一层缩进;
- 不同场景的处理使用函数隔离,不容易出错;
- 可以通过返回值类型推断直观地看出 solver 容器可能在哪个场景下退出。
对已有代码的影响
变化主要影响调用栈追踪。传统 transition 写法只需追踪名为 transition 的函数。TransitionOn 新写法再追踪 transition 函数在哪里返回没有意义。
为简便起见,约定 TransitionOn() 装饰的函数名只能是 _。
TransitionOn 装饰器的参数格式
- 接收某一个具体的场景作为参数(例如
TransitionOn(Scene.INDEX)),表示用它装饰的函数处理参数中指定的场景; - 接收场景列表(例如
TransitionOn(BaseSolver.waiting_scene)),表示它装饰的函数负责处理列表中所有的场景。
TransitionOn 装饰器的默认参数含义与默认场景
solver_default_scene 的值称为默认场景。
- 默认场景可以是一个具体的场景:
- 此时新 transition 遇到其它场景时,使用场景图导航跳转到默认场景;
TransitionOn()在处理默认场景时可以省略参数。
- 默认场景也可以是
None:- 此时
TransitionOn()不传参数时表示处理其它场景; - 必须有一个不传参数的
TransitionOn()装饰的函数。
- 此时
关于 TransitionOn 装饰器的实现
TransitionOn 要把函数与对应的场景关联起来。比较理想的办法是把函数存到 solver 容器的类属性里。虽然可以把装饰器写成 classmethod,但没法在类完成创建之前引用自己的类方法,所以不可行。如果尝试从被装饰的函数的参数列表入手,也不行:因为被装饰的是实例方法,对象实例化之后可以用 self.__class__ 获取类,但运行装饰器函数的时候,对象还没创建。好在可以用 __set_name__ 获取类。注意 __call__ 函数需要返回自身,这样 __set_name__ 才能被调用。
另一个问题是用 _ 作为函数名带来的。装饰多个名为 _ 的函数,会导致 __set_name__ 只在最后被调用一次,在此之前 __init__ 和 __call__ 都被调用了若干次。所以先用类属性存储场景和函数,在调用 __set_name__ 时再一起处理。
TransitionOn 将场景与函数的映射存储到solver容器的类属性 transition_func 中。这里要注意,所有solver容器的类属性指向的是同一个字典,所以在 __set_name__ 中让类属性指向新的字典。
TransitionOn 也可以通过类装饰器实现。虽然我们不容易在solver类的创建过程中对它进行修改,但是可以通过装饰器在solver类创建结束后修改它。代码如下:
class Transition:
scenes = []
funcs = []
default_scene = None
@classmethod
def on(cls, scene: int | list[int] | None = None):
def decorator(func):
if func.__name__ != "_":
cls.scenes = []
cls.funcs = []
raise Exception('TransitionOn装饰的函数名必须是"_"')
cls.scenes.append(scene)
cls.funcs.append(func)
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
return decorator
@classmethod
def default(cls, scene: int | list[int] | None = None):
cls.default_scene = scene
def decorator(solver):
solver.solver_default_scene = cls.default_scene
solver.transition_func = {}
for scene, func in zip(cls.scenes, cls.funcs):
if scene is None:
scene = solver.solver_default_scene
if not isinstance(scene, list):
scene = [scene]
for s in scene:
solver.transition_func[s] = func
cls.scenes = []
cls.funcs = []
return solver
return decorator
以及对应的solver类写法:
@Transition.default(Scene.MISSION_DAILY)
class MissionSolver(BaseSolver):
solver_name = "每日/每周任务"
solver_max_duration = timedelta(minutes=2)
def run(self) -> None:
self.daily = False
return super().run()
@Transition.on()
def _(self):
if self.daily:
self.tap("mission_weekly")
return
if pos := self.find("mission_collect"):
self.tap(pos)
return
if self.animation(((0, 0), (560, 1080))):
return
self.daily = True
self.solver_update_before_transition = False
@Transition.on(Scene.MISSION_WEEKLY)
def _(self):
if not self.daily:
self.tap("mission_daily")
return
if pos := self.find("mission_collect"):
self.tap(pos)
return
if self.animation(((0, 0), (560, 1080))):
return
return True
我们更喜欢目前的实现方法。这种类装饰器的方法记录备用。
另外还有两种方法:使用metaclass或__init_subclass__。