2010-01-20 35 views
39

Liên quan đến điều này SO question (C state-machine design), bạn có thể SO chia sẻ với tôi (và cộng đồng!) Các kỹ thuật thiết kế máy nhà nước Python của bạn không?Thiết kế máy trạng thái Python

UPDATE3: Tại thời điểm này, tôi sẽ cho một động cơ dựa trên những điều sau đây:

class TrackInfoHandler(object): 
    def __init__(self): 
     self._state="begin" 
     self._acc="" 

    ## ================================== Event callbacks 

    def startElement(self, name, attrs): 
     self._dispatch(("startElement", name, attrs)) 

    def characters(self, ch): 
     self._acc+=ch 

    def endElement(self, name): 
     self._dispatch(("endElement", self._acc)) 
     self._acc="" 

    ## =================================== 
    def _missingState(self, _event): 
     raise HandlerException("missing state(%s)" % self._state) 

    def _dispatch(self, event): 
     methodName="st_"+self._state 
     getattr(self, methodName, self._missingState)(event) 

    ## =================================== State related callbacks 

Nhưng tôi chắc chắn có tấn cách đi vào nó trong khi tận dụng tính chất năng động của Python (ví dụ điều động động).

Update2: Tôi sau khi thiết kế kỹ thuật cho "công cụ" nhận "sự kiện" và "gửi đi" dựa trên "trạng thái" của máy.

+0

Để diễn giải điểm của Adam, tôi nghĩ rằng một số thông tin cụ thể hơn về những gì bạn đang cố gắng hoàn thành sẽ giúp ích. –

+0

@Jason Orendorff: đủ công bằng. Tôi đã cập nhật câu hỏi cho phù hợp. – jldupont

Trả lời

35

Tôi không thực sự nhận được câu hỏi. Tiểu bang Mẫu thiết kế khá rõ ràng. Xem Design Patterns book.

class SuperState(object): 
    def someStatefulMethod(self): 
     raise NotImplementedError() 
    def transitionRule(self, input): 
     raise NotImplementedError() 

class SomeState(SuperState): 
    def someStatefulMethod(self): 
     actually do something() 
    def transitionRule(self, input): 
     return NextState() 

Đó là bản mẫu khá phổ biến, được sử dụng trong Java, C++, Python (và tôi cũng chắc chắn các ngôn ngữ khác).

Nếu các quy tắc chuyển tiếp trạng thái của bạn xảy ra không đáng kể, có một số tối ưu hóa để đẩy chính quy tắc chuyển đổi đó vào lớp cha.

Lưu ý rằng chúng ta cần phải có tham chiếu về phía trước, vì vậy chúng tôi gọi các lớp theo tên và sử dụng eval để dịch tên lớp thành một lớp thực tế. Cách khác là làm cho các biến quy tắc chuyển đổi thay vì các biến lớp và sau đó tạo các cá thể sau khi tất cả các lớp được định nghĩa.

class State(object): 
    def transitionRule(self, input): 
     return eval(self.map[input])() 

class S1(State): 
    map = { "input": "S2", "other": "S3" } 
    pass # Overrides to state-specific methods 

class S2(State): 
    map = { "foo": "S1", "bar": "S2" } 

class S3(State): 
    map = { "quux": "S1" } 

Trong một số trường hợp, sự kiện của bạn là không đơn giản như đối tượng thử nghiệm cho sự bình đẳng, do đó, một quy tắc chuyển tiếp tổng quát hơn là sử dụng một danh sách thích hợp của cặp hàm đối tượng.

class State(object): 
    def transitionRule(self, input): 
     next_states = [ s for f,s in self.map if f(input) ] 
     assert len(next_states) >= 1, "faulty transition rule" 
     return eval(next_states[0])() 

class S1(State): 
    map = [ (lambda x: x == "input", "S2"), (lambda x: x == "other", "S3") ] 

class S2(State): 
    map = [ (lambda x: "bar" <= x <= "foo", "S3"), (lambda x: True, "S1") ] 

Vì quy tắc được đánh giá tuần tự, điều này cho phép quy tắc "mặc định".

+0

+1: kỹ thuật thú vị ... cảm ơn! – jldupont

+1

@jldupont: Phải ra khỏi cuốn sách. Không phát minh ra nó, chỉ cần sao chép nó từ cuốn sách GoF Design Patterns. Đó là lý do tại sao tôi vẫn không nhận được câu hỏi của bạn - họ bao gồm điều này hoàn toàn. –

+1

@scott: Tôi không có cuốn sách này. Bằng cách đặt câu hỏi, tôi hy vọng sẽ khai thác sự khôn ngoan của cộng đồng SO. Tất nhiên bạn có thể chọn bỏ qua câu hỏi hoặc đóng góp: đó là lựa chọn của bạn. – jldupont

2

Nó có thể phụ thuộc vào độ phức tạp của máy trạng thái của bạn. Đối với các máy trạng thái đơn giản, một dict của dicts (của các event-keys tới các khóa trạng thái cho DFA, hoặc các event-keys cho các danh sách/bộ/các khóa trạng thái cho NFA) có lẽ sẽ là thứ đơn giản nhất để viết và hiểu. Đối với các máy trạng thái phức tạp hơn, tôi đã nghe những điều tốt đẹp về SMC, có thể biên dịch các mô tả máy trạng thái khai báo thành nhiều ngôn ngữ, bao gồm cả Python.

+0

+1 cho liên kết thú vị này. – neuro

8

this design pattern để sử dụng trang trí để triển khai máy trạng thái. Từ mô tả trên trang:

Trang trí được sử dụng để chỉ định phương thức nào là trình xử lý sự kiện cho lớp học.

Có mã ví dụ trên trang cũng vậy (nó khá dài nên tôi sẽ không dán nó ở đây).

+0

+1 cho liên kết hữu ích. – jldupont

+0

Đề cập tuyệt vời. Luôn tự hỏi làm thế nào tôi sẽ thực hiện chính xác điều đó! – erikbwork

3

Tôi nghĩ S.Câu trả lời của Lott là một cách tốt hơn để thực hiện một máy trạng thái, nhưng nếu bạn vẫn muốn tiếp tục với cách tiếp cận của bạn, sử dụng (state,event) làm chìa khóa cho dict của bạn là tốt hơn. Sửa đổi mã của bạn:

class HandlerFsm(object): 

    _fsm = { 
    ("state_a","event"): "next_state", 
    #... 
    } 
+0

+1: cảm ơn, tôi cũng đang dự tính khả năng này. Tôi chưa giải quyết xong việc triển khai. – jldupont

1

Tôi sẽ không nghĩ đến được một máy trạng thái hữu hạn để xử lý XML. Cách thông thường để làm được điều này, tôi nghĩ, là sử dụng một chồng:

class TrackInfoHandler(object): 
    def __init__(self): 
     self._stack=[] 

    ## ================================== Event callbacks 

    def startElement(self, name, attrs): 
     cls = self.elementClasses[name] 
     self._stack.append(cls(**attrs)) 

    def characters(self, ch): 
     self._stack[-1].addCharacters(ch) 

    def endElement(self, name): 
     e = self._stack.pop() 
     e.close() 
     if self._stack: 
      self._stack[-1].addElement(e) 

Đối với mỗi loại nguyên tố, bạn chỉ cần một lớp học có hỗ trợ addCharacters, addElement, và close phương pháp.

CHỈNH SỬA: Để làm rõ, có ý tôi cho rằng các máy trạng thái hữu hạn thường là câu trả lời sai, đó là kỹ thuật lập trình có mục đích chung là rác và bạn nên tránh xa.

Có một số vấn đề thực sự được hiểu rõ ràng, được phân biệt rõ ràng mà FSM là một giải pháp tốt. Ví dụ: lex là nội dung hay.

Điều đó nói rằng, FSM thường không đối phó tốt với thay đổi. Giả sử một ngày nào đó bạn muốn thêm một chút trạng thái, có lẽ là "chúng ta đã thấy phần tử X chưa?" cờ. Trong đoạn mã trên, bạn thêm thuộc tính boolean vào lớp phần tử thích hợp và bạn đã hoàn thành. Trong một máy trạng thái hữu hạn, bạn tăng gấp đôi số trạng thái và chuyển tiếp.

vấn đề đòi hỏi phải có trạng thái hữu hạn lúc đầu rất thường xuyên phát triển để đòi hỏi nhà nước hơn nữa, giống như có thể là một số, tại thời điểm đó một trong hai chương trình FSM của bạn là bánh mì nướng, hoặc tệ hơn, bạn phát triển nó thành một số loại máy nhà nước tổng quát và vào thời điểm đó, bạn thực sự gặp rắc rối. Bạn càng đi xa, càng nhiều quy tắc của bạn bắt đầu hành động như mã — nhưng mã trong ngôn ngữ diễn giải chậm bạn đã phát minh ra rằng không ai khác biết, mà không có trình gỡ lỗi và không có công cụ.

+0

@ Jason: theo như tôi lo ngại, bất kỳ phần mềm nào (nhưng tầm thường) hoạt động như một "máy trạng thái" ở dạng này hay dạng khác. Tuyên bố rằng FSM bị hạn chế sử dụng rơi vào hố không đáy trên lãnh thổ của tôi. Tất nhiên, nếu bạn đang đề cập đến một mẫu FSM cụ thể, điều đó có thể đúng, nhưng tôi có khuynh hướng linh hoạt khi áp dụng các mẫu. – jldupont

+1

Chỉ là về bất kỳ thuật toán * có thể * được thực hiện như một máy nhà nước. Rất ít người trong số họ * nên *. –

+1

@jldupont: Câu trả lời của tôi đề cập đến một kỹ thuật cụ thể nơi bạn thực sự thiết kế một phần chương trình của bạn như một máy trạng thái hữu hạn theo nghĩa thông thường của từ, trạng thái và chuyển tiếp rõ ràng trong mã, và một số logic được mã hóa trong bảng chuyển tiếp trạng thái. Giống như ví dụ của bạn và tất cả các câu trả lời khác. :) –

11

Trong ấn bản tháng 4 năm 2009 của tạp chí Python, tôi đã viết một bài viết về nhúng một DSL nhà nước trong Python, sử dụng pyparsing và imputil. Mã này sẽ cho phép bạn viết trafficLight.pystate mô-đun:

# trafficLight.pystate 

# define state machine 
statemachine TrafficLight: 
    Red -> Green 
    Green -> Yellow 
    Yellow -> Red 

# define some class level constants 
Red.carsCanGo = False 
Yellow.carsCanGo = True 
Green.carsCanGo = True 

Red.delay = wait(20) 
Yellow.delay = wait(3) 
Green.delay = wait(15) 

và trình biên dịch DSL sẽ tạo ra tất cả các TrafficLight cần thiết, đỏ, vàng, và các lớp màu xanh lá cây, và các phương pháp chuyển trạng thái thích hợp. Mã có thể gọi đó là các lớp học sử dụng một cái gì đó như thế này:

import statemachine 
import trafficLight 

tl = trafficLight.Red() 
for i in range(6): 
    print tl, "GO" if tl.carsCanGo else "STOP" 
    tl.delay() 
    tl = tl.next_state() 

(Thật không may, imputil đã được giảm xuống bằng Python 3.)

+0

+1: thú vị dù sao ... cảm ơn! – jldupont

+3

Matt Anderson đã gửi phiên bản mã statemachine này cho wiki pyparsing (http://pyparsing.wikispaces.com/message/view/Publications/18439845) tương thích với Py2.7 trở lên. – PaulMcG

1

Các mã sau đây là một giải pháp thực sự đơn giản. Phần thú vị duy nhất là:

def next_state(self,cls): 
     self.__class__ = cls 

Tất cả logic cho mỗi trạng thái được chứa trong một lớp riêng biệt. 'Trạng thái' được thay đổi bằng cách thay thế '__class__' của cá thể đang chạy.

#!/usr/bin/env python 

class State(object): 
    call = 0 # shared state variable 
    def next_state(self,cls): 
     print '-> %s' % (cls.__name__,), 
     self.__class__ = cls 

    def show_state(self,i): 
     print '%2d:%2d:%s' % (self.call,i,self.__class__.__name__), 

class State1(State): 
    __call = 0 # state variable 
    def __call__(self,ok): 
     self.show_state(self.__call) 
     self.call += 1 
     self.__call += 1 
     # transition 
     if ok: self.next_state(State2) 
     print '' # force new line 

class State2(State): 
    __call = 0 
    def __call__(self,ok): 
     self.show_state(self.__call) 
     self.call += 1 
     self.__call += 1 
     # transition 
     if ok: self.next_state(State3) 
     else: self.next_state(State1) 
     print '' # force new line 

class State3(State): 
    __call = 0 
    def __call__(self,ok): 
     self.show_state(self.__call) 
     self.call += 1 
     self.__call += 1 
     # transition 
     if not ok: self.next_state(State2) 
     print '' # force new line 

if __name__ == '__main__': 
    sm = State1() 
    for v in [1,1,1,0,0,0,1,1,0,1,1,0,0,1,0,0,1,0,0]: 
     sm(v) 
    print '---------' 
    print vars(sm 

Kết quả:

0: 0:State1 -> State2 
1: 0:State2 -> State3 
2: 0:State3 
3: 1:State3 -> State2 
4: 1:State2 -> State1 
5: 1:State1 
6: 2:State1 -> State2 
7: 2:State2 -> State3 
8: 2:State3 -> State2 
9: 3:State2 -> State3 
10: 3:State3 
11: 4:State3 -> State2 
12: 4:State2 -> State1 
13: 3:State1 -> State2 
14: 5:State2 -> State1 
15: 4:State1 
16: 5:State1 -> State2 
17: 6:State2 -> State1 
18: 6:State1 
--------- 
{'_State1__call': 7, 'call': 19, '_State3__call': 5, '_State2__call': 7} 
+0

Tôi đã viết một vài máy trạng thái python bằng cách sử dụng thiết kế này, và tôi phải nói rằng tôi nghĩ rằng đó là một trong sạch nhất trong số tất cả các tùy chọn ở đây. Nó không đòi hỏi một thư viện bên ngoài, cơ chế nội bộ khá đơn giản và súc tích, và nó hoạt động rất tốt. –

2

Tôi nghĩ rằng công cụ PySCXML cần có một cái nhìn gần hơn nữa. dự án này sử dụng định nghĩa của W3C: State Chart XML (SCXML): Nhà nước Máy Notation Kiểm Soát Abstraction

SCXML cung cấp một generic nhà máy dựa trên môi trường thực hiện dựa trên CCXML và Bàn Harel Nhà nước

Hiện nay, SCXML là một dự thảo làm việc; nhưng rất có khả năng là nó sẽ sớm được giới thiệu W3C (Đây là bản thảo thứ 9). Một điểm thú vị khác để làm nổi bật là có một dự án Apache Commons nhằm tạo và duy trì một công cụ Java SCXML có khả năng thực thi một máy trạng thái được xác định bằng một tài liệu SCXML, trong khi trừu tượng hóa các giao diện môi trường ... Và cho một số công cụ khác hỗ trợ công nghệ này sẽ xuất hiện trong tương lai khi SCXML được rời khỏi nó draft-trạng thái ...

5

Tôi cũng không hài lòng với các tùy chọn hiện tại cho state_machines vì ​​vậy tôi đã viết thư viện state_machine

bạn có thể cài đặt nó bằng cách pip install state_machine và sử dụng nó như vậy:

@acts_as_state_machine 
class Person(): 
    name = 'Billy' 

    sleeping = State(initial=True) 
    running = State() 
    cleaning = State() 

    run = Event(from_states=sleeping, to_state=running) 
    cleanup = Event(from_states=running, to_state=cleaning) 
    sleep = Event(from_states=(running, cleaning), to_state=sleeping) 

    @before('sleep') 
    def do_one_thing(self): 
     print "{} is sleepy".format(self.name) 

    @before('sleep') 
    def do_another_thing(self): 
     print "{} is REALLY sleepy".format(self.name) 

    @after('sleep') 
    def snore(self): 
     print "Zzzzzzzzzzzz" 

    @after('sleep') 
    def big_snore(self): 
     print "Zzzzzzzzzzzzzzzzzzzzzz" 

person = Person() 
print person.current_state == person.sleeping  # True 
print person.is_sleeping       # True 
print person.is_running        # False 
person.run() 
print person.is_running        # True 
person.sleep() 

# Billy is sleepy 
# Billy is REALLY sleepy 
# Zzzzzzzzzzzz 
# Zzzzzzzzzzzzzzzzzzzzzz 

print person.is_sleeping       # True 
2

Tôi chắc chắn không khuyên bạn nên triển khai mô hình nổi tiếng như vậy. Chỉ cần thực hiện một triển khai mã nguồn mở như transitions và bọc một lớp khác xung quanh nó nếu bạn cần các tính năng tùy chỉnh. Trong this post Tôi giải thích lý do tại sao tôi thích triển khai cụ thể này và các tính năng của nó.

Các vấn đề liên quan