尝试按照此处提供的示例使用transitions包https://github.com/pytransitions/transitions
出于某些原因,下面显示的两种方法都没有为已注册的evaporate()触发器提供键入建议(至少在Windows x64的PyCharm 2019.1.2中是这样)
同时,这些触发器仍然可以使用。
如何才能在我键入时提示这些触发器?
class Matter(Machine):
def say_hello(self): print("hello, new state!")
def say_goodbye(self): print("goodbye, old state!")
def __init__(self):
states = ['solid', 'liquid', 'gas']
Machine.__init__(self, states=states, initial='liquid')
self.add_transition('melt', 'solid', 'liquid')
testmatter= Matter()
testmatter.add_transition('evaporate', 'liquid', 'gas')
testmatter.evaporate()
Out: True
testmatter.get_model_state(testmatter)
Out: <State('gas')@14748976>class Matter2():
pass
testmatter2 = Matter2()
machine = Machine(model=testmatter2, states=['solid', 'liquid', 'gas', 'plasma'], initial='liquid')
machine.add_transition('evaporate', 'liquid', 'gas')
testmatter2.evaporate()
Out: True发布于 2020-02-13 16:11:43
transitions在运行时向模型(Matter)实例添加触发器。在实际执行初始化代码之前,IDE无法预测到这一点。我的天,这是transitions工作方式的最大缺点(但我再说一次,这也是它在处理动态状态机或在运行时创建/接收的状态机时的优势,但那是另一回事)
如果您使用带有代码完成功能的交互式shell (ipython),您将看到将建议使用evaporate (基于对模型的__dir__调用):
from transitions import Machine
class Model:
pass
model = Model()
>>> model.e # TAB -> nothing
# model will be decorated during machine initialization
machine = Machine(model, states=['A', 'B'],
transitions=[['evaporate', 'A', 'B']], initial='A')
>>> model.e # TAB -> completion! 但我假设这不是您计划编写代码的方式。那么,我们如何给自省提供提示呢?
最简单的解决方案:使用模型的文档字符串来通告触发器。
from transitions import Machine
class Model:
"""My dynamically extended Model
Attributes:
evaporate(callable): dynamically added method
"""
model = Model()
# [1]
machine = Machine(model, states=['A', 'B'],
transitions=[['evaporate', 'A', 'B']], initial='A')
model.eva # code completion! will also suggest 'evaporate' before it was added at [1]这里的问题是IDE将依赖于文档字符串的正确性。因此,当文档字符串方法(掩蔽为属性)是calles evaparate时,即使您后来添加了evaporate,它也将始终建议您这样做。
使用pyi文件和PEP484 (PyCharm解决方法)
不幸的是,正如您正确指出的那样,PyCharm不考虑文档字符串中的属性来完成代码(有关更多详细信息,请参阅this discussion )。我们需要使用另一种方法。我们可以创建所谓的pyi文件来为PyCharm提供提示。这些文件的名称与对应的.py文件相同,但仅用于IDE和其他工具,不得导入(请参见this post)。让我们创建一个名为sandbox.pyi的文件
# sandbox.pyi
class Model:
evaporate = None # type: callable现在让我们创建实际的代码文件sandbox.py (我没有将我的游乐场文件命名为'test‘,因为这总是让pytest感到惊讶……)
# sandbox.py
from transitions import Machine
class Model:
pass
## Having the type hints right here would enable code completion BUT
## would prevent transitions to decorate the model as it does not override
## already defined model attributes and methods.
# class Model:
# evaporate = None # type: callable
model = Model()
# machine initialization
model.ev # code completion

这样,您就有了代码完成,transitions将正确地修饰模型。缺点是您有另一个文件需要担心,它可能会使您的项目变得混乱。
如果你想自动生成pyi文件,你可以看看stubgen或者扩展Machine来为你生成模型的事件存根。
from transitions import Machine
class Model:
pass
class PyiMachine(Machine):
def generate_pyi(self, filename):
with open(f'{filename}.pyi', 'w') as f:
for model in self.models:
f.write(f'class {model.__class__.__name__}:\n')
for event in self.events:
f.write(f' def {event}(self, *args, **kwargs) -> bool: pass\n')
f.write('\n\n')
model = Model()
machine = PyiMachine(model, states=['A', 'B'],
transitions=[['evaporate', 'A', 'B']], initial='A')
machine.generate_pyi('sandbox')
# PyCharm can now correctly infer the type of success
success = model.evaporate()
model.to_A() # A dynamically added method which is now visible thanks to the pyi file另一种方法:从文档字符串生成机器配置
在过渡的问题跟踪器中已经讨论了类似的问题(参见https://github.com/pytransitions/transitions/issues/383)。您还可以从模型的文档字符串生成机器配置:
import transitions
import inspect
import re
class DocMachine(transitions.Machine):
"""Parses states and transitions from model definitions"""
# checks for 'attribute:value' pairs (including [arrays]) in docstrings
re_pattern = re.compile(r"(\w+):\s*\[?([^\]\n]+)\]?")
def __init__(self, model, *args, **kwargs):
conf = {k: v for k, v in self.re_pattern.findall(model.__doc__, re.MULTILINE)}
if 'states' not in kwargs:
kwargs['states'] = [x.strip() for x in conf.get('states', []).split(',')]
if 'initial' not in kwargs and 'initial' in conf:
kwargs['initial'] = conf['initial'].strip()
super(DocMachine, self).__init__(model, *args, **kwargs)
for name, method in inspect.getmembers(model, predicate=inspect.ismethod):
doc = method.__doc__ if method.__doc__ else ""
conf = {k: v for k, v in self.re_pattern.findall(doc, re.MULTILINE)}
# if docstring contains "source:" we assume it is a trigger definition
if "source" not in conf:
continue
else:
conf['source'] = [s.strip() for s in conf['source'].split(', ')]
conf['source'] = conf['source'][0] if len(conf['source']) == 1 else conf['source']
if "dest" not in conf:
conf['dest'] = None
else:
conf['dest'] = conf['dest'].strip()
self.add_transition(trigger=name, **conf)
# override safeguard which usually prevents accidental overrides
def _checked_assignment(self, model, name, func):
setattr(model, name, func)
class Model:
"""A state machine model
states: [A, B]
initial: A
"""
def go(self):
"""processes information
source: A
dest: B
conditions: always_true
"""
def cycle(self):
"""an internal transition which will not exit the current state
source: *
"""
def always_true(self):
"""returns True... always"""
return True
def on_exit_B(self): # no docstring
raise RuntimeError("We left B. This should not happen!")
m = Model()
machine = DocMachine(m)
assert m.is_A()
m.go()
assert m.is_B()
m.cycle()
try:
m.go() # this will raise a MachineError since go is not defined for state B
assert False
except transitions.MachineError:
pass这是一个非常简单的文档字符串到机器配置的解析器,它不会处理文档字符串中可能出现的所有可能性。它假定每个包含文档字符串("source:“)的方法都是触发器。然而,它也处理了文档的问题。使用这样的机器将确保至少存在所开发机器的一些文档。
https://stackoverflow.com/questions/60195188
复制相似问题