ระบบเหตุการณ์ใน Python


196

คุณใช้ระบบกิจกรรมใดใน Python ฉันรับทราบpydispatcher อยู่แล้ว แต่ฉันสงสัยว่าจะมีอะไรอีกบ้างที่สามารถพบได้หรือใช้กันทั่วไป

ฉันไม่สนใจผู้จัดการเหตุการณ์ที่เป็นส่วนหนึ่งของกรอบงานขนาดใหญ่ฉันควรใช้โซลูชันกระดูกเปลือยขนาดเล็กที่ฉันสามารถขยายได้อย่างง่ายดาย

คำตอบ:


181

แพ็คเกจ PyPI

ตั้งแต่เดือนมิถุนายน 2563 แพ็คเกจเหล่านี้เป็นแพคเกจที่เกี่ยวข้องกับเหตุการณ์ที่มีใน PyPI เรียงตามวันที่วางจำหน่ายล่าสุด

ยังมีอีก

มีไลบรารีให้เลือกมากมายโดยใช้คำศัพท์ที่แตกต่างกันมาก (เหตุการณ์, สัญญาณ, ตัวจัดการ, การจัดส่งเมธอด, hooks, ... )

ฉันกำลังพยายามเก็บภาพรวมของแพ็คเกจข้างต้นรวมถึงเทคนิคต่าง ๆ ที่กล่าวถึงในคำตอบที่นี่

ก่อนอื่นมีคำศัพท์บางคำ ...

รูปแบบการสังเกตการณ์

รูปแบบพื้นฐานที่สุดของระบบเหตุการณ์คือ 'วิธีการจัดการถุง' ซึ่งเป็นการใช้งานอย่างง่ายของรูปแบบการสังเกตการณ์รูปแบบการสังเกตการณ์

โดยทั่วไปวิธีการจัดการ (callables) จะถูกเก็บไว้ในอาร์เรย์และแต่ละคนจะเรียกว่าเมื่อเหตุการณ์ 'ไฟ'

เผยแพร่-สมัครสมาชิก

ข้อเสียของระบบเหตุการณ์ Observer คือคุณสามารถลงทะเบียนตัวจัดการบนวัตถุเหตุการณ์จริง (หรือรายการตัวจัดการ) เท่านั้น ดังนั้นในเวลาลงทะเบียนเหตุการณ์จำเป็นต้องมีอยู่แล้ว

นั่นเป็นเหตุผลที่รูปแบบที่สองของระบบการจัดกิจกรรมที่มีอยู่ที่: เผยแพร่สมัครรูปแบบ ที่นี่ตัวจัดการไม่ได้ลงทะเบียนในวัตถุเหตุการณ์ (หรือรายการตัวจัดการ) แต่อยู่ในโปรแกรมเลือกจ่ายส่วนกลาง นอกจากนี้ตัวแจ้งเตือนจะคุยกับดิสแพตเชอร์เท่านั้น สิ่งที่จะฟังหรือสิ่งที่จะเผยแพร่จะพิจารณาจาก 'สัญญาณ' ซึ่งไม่มีอะไรมากไปกว่าชื่อ (สตริง)

รูปแบบคนกลาง

อาจจะเป็นที่น่าสนใจเช่นกันคือรูปแบบการไกล่เกลี่ย

ตะขอ

ระบบ 'hook' ถูกใช้ในบริบทของปลั๊กอินแอปพลิเคชัน แอปพลิเคชันมีจุดรวมการแก้ไข (hooks) และแต่ละปลั๊กอินอาจเชื่อมต่อกับ hook นั้นและดำเนินการบางอย่าง

'กิจกรรม' อื่น ๆ

หมายเหตุ: เธรด.เหตุการณ์ไม่ใช่ 'ระบบเหตุการณ์' ในแง่ข้างต้น เป็นระบบการซิงโครไนซ์เธรดที่เธรดหนึ่งรอจนกระทั่งเธรด 'สัญญาณ' อื่น ๆ ของวัตถุเหตุการณ์

ไลบรารีข้อความเครือข่ายมักใช้คำว่า 'events' ด้วยเช่นกัน บางครั้งสิ่งเหล่านี้คล้ายกันในแนวคิด บางครั้งไม่ พวกเขาสามารถข้ามเส้นเขตแดนกระบวนการและขอบเขตของคอมพิวเตอร์ได้ ดูเช่น pyzmq , pymq , บิด , ทอร์นาโด , gevent , eventlet

ข้อมูลอ้างอิงที่อ่อนแอ

ใน Python การอ้างอิงถึงเมธอดหรือวัตถุทำให้มั่นใจได้ว่าจะไม่ถูกลบโดยตัวเก็บขยะ สิ่งนี้สามารถเป็นที่ต้องการได้ แต่มันยังสามารถนำไปสู่การรั่วไหลของหน่วยความจำ: ตัวจัดการที่เชื่อมโยงจะไม่ทำความสะอาด

ระบบเหตุการณ์บางระบบใช้การอ้างอิงที่อ่อนแอแทนระบบปกติเพื่อแก้ปัญหานี้

บางคำเกี่ยวกับห้องสมุดต่างๆ

ระบบเหตุการณ์สไตล์ผู้สังเกตการณ์:

  • zope.eventแสดงให้เห็นถึงกระดูกที่เปลือยเปล่าของวิธีการทำงานนี้ (ดูคำตอบของ Lennart ) หมายเหตุ: ตัวอย่างนี้ไม่สนับสนุนอาร์กิวเมนต์ตัวจัดการ
  • LongPoke ของ 'รายการ callable'แสดงให้เห็นว่าการดำเนินงานระบบดังกล่าวเหตุการณ์ที่สามารถดำเนินการได้มาก minimalistically โดย listsubclassing
  • EventHook การเปลี่ยนแปลงของFelkยังช่วยให้แน่ใจว่าลายเซ็นของ callees และผู้โทร
  • spasig's EventHook (รูปแบบเหตุการณ์ของ Michael Foord) เป็นการใช้งานที่ไม่ซับซ้อน
  • คลาสของบทเรียนที่มีค่าของ Josipนั้นเหมือนกัน แต่ใช้setแทน a listเพื่อเก็บกระเป๋าและนำไปใช้__call__ซึ่งเป็นส่วนเพิ่มเติมที่สมเหตุสมผล
  • PyNotifyคล้ายกันในแนวคิดและยังให้แนวคิดเพิ่มเติมเกี่ยวกับตัวแปรและเงื่อนไข ('เหตุการณ์ที่เปลี่ยนแปลงของตัวแปร') โฮมเพจไม่ทำงาน
  • Axelนั้นเป็น bag-of-hander ที่มีคุณสมบัติเพิ่มเติมที่เกี่ยวข้องกับเธรด, การจัดการข้อผิดพลาด, ...
  • หลามจัดส่งpydispatch.Dispatcherต้องเรียนแหล่งที่มาแม้จะได้รับจาก
  • Buslaneเป็นแบบพื้นฐานรองรับการจัดการเดี่ยวหรือหลายและอำนวยความสะดวกคำแนะนำประเภทที่กว้างขวาง
  • ผู้สังเกตการณ์ / เหตุการณ์ของ Pithikos คือการออกแบบที่มีน้ำหนักเบา

ไลบรารีที่เผยแพร่โดยสมัครสมาชิก:

  • ไฟกระพริบมีคุณสมบัติที่ดีบางอย่างเช่นการตัดการเชื่อมต่ออัตโนมัติและการกรองตามผู้ส่ง
  • PyPubSubเป็นแพคเกจที่เสถียรและสัญญาว่า "คุณสมบัติขั้นสูงที่อำนวยความสะดวกในการดีบักและบำรุงรักษาหัวข้อและข้อความ"
  • pymitterเป็นพอร์ต Python ของ Node.js EventEmitter2 และนำเสนอเนมสเปซไวด์การ์ดและ TTL
  • PyDispatcherดูเหมือนว่าจะเน้นความยืดหยุ่นโดยคำนึงถึงสิ่งพิมพ์หลายต่อหลายเรื่องและอื่น ๆ รองรับการอ้างอิงที่อ่อนแอ
  • Louieเป็น PyDispatcher ที่นำกลับมาทำใหม่และควรทำงาน "ในบริบทที่หลากหลาย"
  • pypydispatcherขึ้นอยู่กับ (คุณเดาได้ว่า ... ) PyDispatcher และทำงานใน PyPy
  • django.dispatchเป็น PyDispatcher ที่เขียนใหม่ "พร้อมกับอินเทอร์เฟซที่ จำกัด แต่มีประสิทธิภาพสูงกว่า"
  • pyeventdispatcherขึ้นอยู่กับเฟรมเวิร์กของผู้จัดทำ Symfony ของ PHP
  • โปรแกรมเลือกจ่ายงานถูกดึงมาจาก django.dispatch แต่ได้รับความเก่า
  • EventMangerของ Cristian Garcia นั้นเป็นการใช้งานที่สั้นมาก

อื่น ๆ :

  • pluggyมีระบบ hook ซึ่งใช้โดยpytestปลั๊กอิน
  • RxPy3ใช้รูปแบบที่สังเกตได้และอนุญาตให้รวมเหตุการณ์ลองใหม่อีกครั้ง
  • สัญญาณ Qt และสล็อตที่มีอยู่จากPyQt หรือPySide2 พวกเขาทำงานเป็นโทรกลับเมื่อใช้ในเธรดเดียวกันหรือเป็นเหตุการณ์ (ใช้วนรอบเหตุการณ์) ระหว่างสองกระทู้ที่แตกต่างกัน สัญญาณและช่องมีข้อ จำกัด ที่พวกเขาทำงานในวัตถุของคลาสที่ได้รับมาQObjectเท่านั้น

2
นอกจากนี้ยังมีหลุยซึ่งอยู่บนพื้นฐาน PyDispatcher: pypi.python.org/pypi/Louie/1.1
the979kid

@ the979 เด็ก ๆ ดูเหมือนว่าจะได้รับการดูแลอย่างไม่ดีนักเพจ pypi เชื่อมโยงกับยุค 404 ใน GitHub: 11craft.github.io/louie ; github.com/gldnspud/louie ควรจะgithub.com/11craft/louie
florisla

1
ผู้ฟังเหตุการณ์ที่อ่อนแอเป็นความต้องการร่วมกัน มิฉะนั้นการใช้งานในโลกแห่งความเป็นจริงก็จะลำบาก บันทึกที่โซลูชันสนับสนุนซึ่งอาจมีประโยชน์
kxr

Pypubsub 4 มีจำนวนมากและมีเครื่องมือในการแก้ไขข้อบกพร่องที่มีประสิทธิภาพสำหรับข้อความและหลายวิธีในการ จำกัด การรับส่งข้อความเพื่อให้คุณทราบก่อนหน้านี้เมื่อคุณส่งข้อมูลที่ไม่ถูกต้องหรือขาดข้อมูล PyPubSub 4 รองรับ Python 3 (และ PyPubSub 3.x รองรับ Python 2)
Oliver

ฉันเพิ่งเผยแพร่ไลบรารีชื่อ pymq github.com/thrau/pymqซึ่งอาจเหมาะสำหรับรายการนี้
thrau

100

ฉันทำอย่างนี้:

class Event(list):
    """Event subscription.

    A list of callable objects. Calling an instance of this will cause a
    call to each item in the list in ascending order by index.

    Example Usage:
    >>> def f(x):
    ...     print 'f(%s)' % x
    >>> def g(x):
    ...     print 'g(%s)' % x
    >>> e = Event()
    >>> e()
    >>> e.append(f)
    >>> e(123)
    f(123)
    >>> e.remove(f)
    >>> e()
    >>> e += (f, g)
    >>> e(10)
    f(10)
    g(10)
    >>> del e[0]
    >>> e(2)
    g(2)

    """
    def __call__(self, *args, **kwargs):
        for f in self:
            f(*args, **kwargs)

    def __repr__(self):
        return "Event(%s)" % list.__repr__(self)

อย่างไรก็ตามเช่นเดียวกับทุกสิ่งทุกอย่างที่ฉันเคยเห็นไม่มี pydoc ที่สร้างขึ้นโดยอัตโนมัติสำหรับสิ่งนี้และไม่มีลายเซ็นซึ่งแย่จริงๆ


3
ฉันพบว่าสไตล์นี้ค่อนข้างน่าสนใจ มันเป็นกระดูกเปลือยหวาน ฉันชอบความจริงที่ว่ามันทำให้ผู้ใช้สามารถจัดการเหตุการณ์และสมาชิกของพวกเขาเป็นการดำเนินการแบบอิสระ ฉันจะดูว่าค่าโดยสารเป็นอย่างไรในโครงการจริง
Rudy Lattae

2
สไตล์เรียบง่ายสวยงามมาก! super!
akaRem

2
ฉันไม่สามารถลงคะแนนเสียงได้เพียงพอนี้ตรงไปตรงมาและง่ายมาก

2
ความโปรดปรานที่ยิ่งใหญ่ใครบางคนสามารถอธิบายสิ่งนี้เช่นฉันเป็น 10? คลาสนี้ได้รับการสืบทอดโดยคลาสหลักหรือไม่? ฉันไม่เห็นชื่อinitดังนั้น super () จะไม่ถูกใช้ มันไม่ได้คลิกเพื่อฉันด้วยเหตุผลบางอย่าง
omgimdrunk

1
@omgimdrunk ตัวจัดการเหตุการณ์อย่างง่าย ๆ จะทำการปิดฟังก์ชั่น callable อย่างน้อยหนึ่งฟังก์ชั่นเมื่อมีเหตุการณ์เกิดขึ้น ชั้นเรียนที่ "จัดการ" สิ่งนี้สำหรับคุณจะต้องมีวิธีการดังต่อไปนี้อย่างน้อย - เพิ่มและไฟ ภายในคลาสนั้นคุณจะต้องเก็บรักษารายการตัวจัดการที่จะดำเนินการ ลองใส่ตัวแปรอินสแตนซ์_bag_of_handlersซึ่งเป็นรายการ self._bag_of_handlers.append(some_callable)วิธีการเพิ่มระดับของก็จะ วิธีการดับเพลิงของชั้นเรียนจะวนรอบผ่าน `_bag_of_handlers` ผ่าน args และ kwargs ที่เตรียมไว้ให้กับตัวจัดการและดำเนินการตามลำดับ
Gabe Spradlin

69

เราใช้ EventHook ตามคำแนะนำจาก Michael Foord ในรูปแบบกิจกรรมของเขา:

เพียงเพิ่ม EventHooks ในชั้นเรียนของคุณด้วย:

class MyBroadcaster()
    def __init__():
        self.onChange = EventHook()

theBroadcaster = MyBroadcaster()

# add a listener to the event
theBroadcaster.onChange += myFunction

# remove listener from the event
theBroadcaster.onChange -= myFunction

# fire event
theBroadcaster.onChange.fire()

เราเพิ่มฟังก์ชันการทำงานเพื่อลบผู้ฟังทั้งหมดออกจากวัตถุในคลาส Michaels และจบลงด้วยสิ่งนี้:

class EventHook(object):

    def __init__(self):
        self.__handlers = []

    def __iadd__(self, handler):
        self.__handlers.append(handler)
        return self

    def __isub__(self, handler):
        self.__handlers.remove(handler)
        return self

    def fire(self, *args, **keywargs):
        for handler in self.__handlers:
            handler(*args, **keywargs)

    def clearObjectHandlers(self, inObject):
        for theHandler in self.__handlers:
            if theHandler.im_self == inObject:
                self -= theHandler

ข้อเสียของการใช้สิ่งนี้คือคุณต้องเพิ่มกิจกรรมก่อนที่คุณจะลงทะเบียนเป็นสมาชิก หากมีเพียงผู้เผยแพร่เท่านั้นที่เพิ่มกิจกรรมของพวกเขา (ไม่ใช่ต้องเป็นเพียงแนวทางปฏิบัติที่ดี) คุณจะต้องเริ่มต้นผู้เผยแพร่ก่อนสมาชิกที่มีปัญหาในโครงการขนาดใหญ่
Jonathan

6
เมธอดสุดท้ายถูก bugged เนื่องจากตัวจัดการ. __ ตัวเองถูกแก้ไขระหว่างการทำซ้ำ แก้ไข: `ตัวจัดการ. __ ตัวเอง = [h สำหรับ h ในตัวจัดการ. __ ตัวเองถ้า h.im_self! = obj]`
Simon Bergot

1
@Simon นั้นถูกต้อง แต่แนะนำบั๊กเพราะเราสามารถมีฟังก์ชั่น unbound ในตัวจัดการ. __ ตัวเอง แก้ไข:self.__handlers = [h for h in self._handlers if getattr(h, 'im_self', False) != obj]
Eric Marcos

20

ฉันใช้zope.event มันเป็นกระดูกที่เปลือยเปล่าที่สุดที่คุณจินตนาการได้ :-) ที่จริงแล้วนี่คือรหัสที่มาที่สมบูรณ์:

subscribers = []

def notify(event):
    for subscriber in subscribers:
        subscriber(event)

โปรดทราบว่าคุณไม่สามารถส่งข้อความระหว่างกระบวนการได้ มันไม่ได้เป็นระบบการส่งข้อความเพียงแค่ระบบเหตุการณ์ไม่มีอะไรมากไม่น้อยไปกว่านี้


17
pypi.python.org/pypi/zope.event ... เพื่อช่วยคนจนใน Google bandwith ;-)
Boldewyn

ฉันยังต้องการส่งข้อความ ฉันจะใช้ระบบเหตุการณ์ในแอปพลิเคชันที่สร้างขึ้นบน Tkinter ฉันไม่ได้ใช้ระบบกิจกรรมเพราะไม่รองรับข้อความ
Josip

คุณสามารถส่งสิ่งที่คุณต้องการด้วย zope.event แต่ประเด็นของฉันคือมันไม่ใช่ระบบการส่งข้อความที่เหมาะสมเนื่องจากคุณไม่สามารถส่งเหตุการณ์ / ข้อความไปยังกระบวนการอื่นหรือคอมพิวเตอร์เครื่องอื่น คุณควรจะเฉพาะเจาะจงมากขึ้นกับความต้องการของคุณ
Lennart Regebro

15

ผมพบว่าสคริปต์เล็ก ๆ นี้ในบทเรียนมูลค่า ดูเหมือนว่าจะมีเพียงความเรียบง่าย / อัตราส่วนกำลังที่เหมาะสมหลังจากนั้น Peter Thatcher เป็นผู้เขียนรหัสต่อไปนี้ (ไม่มีการกล่าวถึงลิขสิทธิ์)

class Event:
    def __init__(self):
        self.handlers = set()

    def handle(self, handler):
        self.handlers.add(handler)
        return self

    def unhandle(self, handler):
        try:
            self.handlers.remove(handler)
        except:
            raise ValueError("Handler is not handling this event, so cannot unhandle it.")
        return self

    def fire(self, *args, **kargs):
        for handler in self.handlers:
            handler(*args, **kargs)

    def getHandlerCount(self):
        return len(self.handlers)

    __iadd__ = handle
    __isub__ = unhandle
    __call__ = fire
    __len__  = getHandlerCount

class MockFileWatcher:
    def __init__(self):
        self.fileChanged = Event()

    def watchFiles(self):
        source_path = "foo"
        self.fileChanged(source_path)

def log_file_change(source_path):
    print "%r changed." % (source_path,)

def log_file_change2(source_path):
    print "%r changed!" % (source_path,)

watcher              = MockFileWatcher()
watcher.fileChanged += log_file_change2
watcher.fileChanged += log_file_change
watcher.fileChanged -= log_file_change2
watcher.watchFiles()

1
การใช้ set () แทนที่จะเป็นรายการเป็นสิ่งที่ดีเพื่อหลีกเลี่ยงตัวจัดการที่ลงทะเบียนสองครั้ง สิ่งหนึ่งที่สำคัญคือตัวจัดการไม่ได้ถูกเรียกตามลำดับที่ลงทะเบียน ไม่จำเป็นว่าจะเป็นเรื่องเลวร้าย ...
florisla

1
@florisla สามารถเปลี่ยนเป็น OrderedSet ได้หากต้องการ
Robino

9

นี่คือการออกแบบขั้นต่ำที่ควรจะทำงานได้ดี สิ่งที่คุณต้องทำคือการสืบทอดObserverในชั้นเรียนและใช้observe(event_name, callback_fn)เพื่อฟังเหตุการณ์เฉพาะ เมื่อใดก็ตามที่เหตุการณ์เฉพาะเกิดขึ้นที่ใดก็ได้ในรหัส (เช่น. Event('USB connected')) การติดต่อกลับที่เกี่ยวข้องจะเริ่มขึ้น

class Observer():
    _observers = []
    def __init__(self):
        self._observers.append(self)
        self._observed_events = []
    def observe(self, event_name, callback_fn):
        self._observed_events.append({'event_name' : event_name, 'callback_fn' : callback_fn})


class Event():
    def __init__(self, event_name, *callback_args):
        for observer in Observer._observers:
            for observable in observer._observed_events:
                if observable['event_name'] == event_name:
                    observable['callback_fn'](*callback_args)

ตัวอย่าง:

class Room(Observer):
    def __init__(self):
        print("Room is ready.")
        Observer.__init__(self) # DON'T FORGET THIS
    def someone_arrived(self, who):
        print(who + " has arrived!")

# Observe for specific event
room = Room()
room.observe('someone arrived',  room.someone_arrived)

# Fire some events
Event('someone left',    'John')
Event('someone arrived', 'Lenard') # will output "Lenard has arrived!"
Event('someone Farted',  'Lenard')

ฉันชอบการออกแบบของคุณมันเรียบง่ายและเข้าใจง่าย และมันจะมีน้ำหนักเบาโดยไม่ต้องนำเข้าโมดูลบางอย่าง
Atreyagaurav

8

ฉันสร้างEventManagerคลาส (รหัสท้าย) ไวยากรณ์มีดังต่อไปนี้:

#Create an event with no listeners assigned to it
EventManager.addEvent( eventName = [] )

#Create an event with listeners assigned to it
EventManager.addEvent( eventName = [fun1, fun2,...] )

#Create any number event with listeners assigned to them
EventManager.addEvent( eventName1 = [e1fun1, e1fun2,...], eventName2 = [e2fun1, e2fun2,...], ... )

#Add or remove listener to an existing event
EventManager.eventName += extra_fun
EventManager.eventName -= removed_fun

#Delete an event
del EventManager.eventName

#Fire the event
EventManager.eventName()

นี่คือตัวอย่าง:

def hello(name):
    print "Hello {}".format(name)
    
def greetings(name):
    print "Greetings {}".format(name)

EventManager.addEvent( salute = [greetings] )
EventManager.salute += hello

print "\nInitial salute"
EventManager.salute('Oscar')

print "\nNow remove greetings"
EventManager.salute -= greetings
EventManager.salute('Oscar')

เอาท์พุท:

ทักทายเบื้องต้น
ออสการ์
สวัสดีออสการ์

ตอนนี้ลบคำทักทาย
Hello Oscar

รหัส EventManger:

class EventManager:
    
    class Event:
        def __init__(self,functions):
            if type(functions) is not list:
                raise ValueError("functions parameter has to be a list")
            self.functions = functions
            
        def __iadd__(self,func):
            self.functions.append(func)
            return self
            
        def __isub__(self,func):
            self.functions.remove(func)
            return self
            
        def __call__(self,*args,**kvargs):
            for func in self.functions : func(*args,**kvargs)
            
    @classmethod
    def addEvent(cls,**kvargs):
        """
        addEvent( event1 = [f1,f2,...], event2 = [g1,g2,...], ... )
        creates events using **kvargs to create any number of events. Each event recieves a list of functions,
        where every function in the list recieves the same parameters.
        
        Example:
        
        def hello(): print "Hello ",
        def world(): print "World"
        
        EventManager.addEvent( salute = [hello] )
        EventManager.salute += world
        
        EventManager.salute()
        
        Output:
        Hello World
        """
        for key in kvargs.keys():
            if type(kvargs[key]) is not list:
                raise ValueError("value has to be a list")
            else:
                kvargs[key] = cls.Event(kvargs[key])
        
        cls.__dict__.update(kvargs)

8

คุณอาจดูที่pymitter ( pypi) ) มันเป็นวิธีการเดียวไฟล์ขนาดเล็ก (~ 250 loc) "ให้ namespaces, wildcard และ TTL"

นี่คือตัวอย่างพื้นฐาน:

from pymitter import EventEmitter

ee = EventEmitter()

# decorator usage
@ee.on("myevent")
def handler1(arg):
   print "handler1 called with", arg

# callback usage
def handler2(arg):
    print "handler2 called with", arg
ee.on("myotherevent", handler2)

# emit
ee.emit("myevent", "foo")
# -> "handler1 called with foo"

ee.emit("myotherevent", "bar")
# -> "handler2 called with bar"

6

ฉันสร้างความแตกต่างของวิธีการที่เรียบง่ายของ Longpoke ที่ช่วยรับรองลายเซ็นสำหรับทั้งผู้โทรและผู้โทร:

class EventHook(object):
    '''
    A simple implementation of the Observer-Pattern.
    The user can specify an event signature upon inizializazion,
    defined by kwargs in the form of argumentname=class (e.g. id=int).
    The arguments' types are not checked in this implementation though.
    Callables with a fitting signature can be added with += or removed with -=.
    All listeners can be notified by calling the EventHook class with fitting
    arguments.

    >>> event = EventHook(id=int, data=dict)
    >>> event += lambda id, data: print("%d %s" % (id, data))
    >>> event(id=5, data={"foo": "bar"})
    5 {'foo': 'bar'}

    >>> event = EventHook(id=int)
    >>> event += lambda wrong_name: None
    Traceback (most recent call last):
        ...
    ValueError: Listener must have these arguments: (id=int)

    >>> event = EventHook(id=int)
    >>> event += lambda id: None
    >>> event(wrong_name=0)
    Traceback (most recent call last):
        ...
    ValueError: This EventHook must be called with these arguments: (id=int)
    '''
    def __init__(self, **signature):
        self._signature = signature
        self._argnames = set(signature.keys())
        self._handlers = []

    def _kwargs_str(self):
        return ", ".join(k+"="+v.__name__ for k, v in self._signature.items())

    def __iadd__(self, handler):
        params = inspect.signature(handler).parameters
        valid = True
        argnames = set(n for n in params.keys())
        if argnames != self._argnames:
            valid = False
        for p in params.values():
            if p.kind == p.VAR_KEYWORD:
                valid = True
                break
            if p.kind not in (p.POSITIONAL_OR_KEYWORD, p.KEYWORD_ONLY):
                valid = False
                break
        if not valid:
            raise ValueError("Listener must have these arguments: (%s)"
                             % self._kwargs_str())
        self._handlers.append(handler)
        return self

    def __isub__(self, handler):
        self._handlers.remove(handler)
        return self

    def __call__(self, *args, **kwargs):
        if args or set(kwargs.keys()) != self._argnames:
            raise ValueError("This EventHook must be called with these " +
                             "keyword arguments: (%s)" % self._kwargs_str())
        for handler in self._handlers[:]:
            handler(**kwargs)

    def __repr__(self):
        return "EventHook(%s)" % self._kwargs_str()

3

ถ้าฉันทำรหัสใน pyQt ฉันใช้ซ็อกเก็ต QT / กระบวนทัศน์สัญญาณเดียวกันสำหรับ django

ถ้าฉันทำ async I / OI ให้ใช้ native select select

ถ้าฉันใช้ตัวแยกวิเคราะห์ SAX python ฉันกำลังใช้ API เหตุการณ์ที่ SAX จัดทำ ดูเหมือนว่าฉันตกเป็นเหยื่อของ API พื้นฐาน :-)

บางทีคุณควรถามตัวเองว่าคุณคาดหวังอะไรจากกรอบงาน / โมดูลกิจกรรม การตั้งค่าส่วนตัวของฉันคือใช้กระบวนทัศน์ Socket / Signal จาก QT ข้อมูลเพิ่มเติมเกี่ยวกับที่สามารถพบได้ที่นี่


2

นี่คือโมดูลอื่นสำหรับการพิจารณา ดูเหมือนว่าเป็นตัวเลือกที่เหมาะสมสำหรับการใช้งานที่ต้องการมากขึ้น

Py-alert เป็นแพ็คเกจ Python ที่ให้เครื่องมือสำหรับการนำรูปแบบการเขียนโปรแกรม Observer ไปใช้ เครื่องมือเหล่านี้รวมถึงสัญญาณเงื่อนไขและตัวแปร

สัญญาณคือรายการของตัวจัดการที่เรียกว่าเมื่อสัญญาณถูกปล่อยออกมา เงื่อนไขนั้นเป็นตัวแปรบูลีนโดยทั่วไปประกอบกับสัญญาณที่ถูกปล่อยออกมาเมื่อมีการเปลี่ยนแปลงสถานะของเงื่อนไข สามารถรวมกันได้โดยใช้ตัวดำเนินการเชิงตรรกะมาตรฐาน (ไม่ใช่และอื่น ๆ ) ในสภาพผสม ตัวแปรซึ่งแตกต่างจากเงื่อนไขสามารถเก็บออบเจ็กต์ Python ใด ๆ ได้ไม่ใช่แค่บูลีน แต่ก็ไม่สามารถรวมกันได้


1
หน้าแรกไม่มีคอมมิชชั่นสำหรับหน้านี้อาจจะไม่ได้รับการสนับสนุนอีกต่อไป?
David Parks

1

หากคุณต้องการทำสิ่งที่ซับซ้อนมากขึ้นเช่นการรวมกิจกรรมหรือลองใหม่อีกครั้งคุณสามารถใช้รูปแบบการสังเกตและห้องสมุดที่เป็นผู้ใหญ่ที่ใช้งานได้ https://github.com/ReactiveX/RxPY สิ่งที่สังเกตได้นั้นพบได้ทั่วไปใน Javascript และ Java และสะดวกมากที่จะใช้สำหรับงาน async บางอย่าง

from rx import Observable, Observer


def push_five_strings(observer):
        observer.on_next("Alpha")
        observer.on_next("Beta")
        observer.on_next("Gamma")
        observer.on_next("Delta")
        observer.on_next("Epsilon")
        observer.on_completed()


class PrintObserver(Observer):

    def on_next(self, value):
        print("Received {0}".format(value))

    def on_completed(self):
        print("Done!")

    def on_error(self, error):
        print("Error Occurred: {0}".format(error))

source = Observable.create(push_five_strings)

source.subscribe(PrintObserver())

เอาท์พุท :

Received Alpha
Received Beta
Received Gamma
Received Delta
Received Epsilon
Done!

1

หากคุณต้องการ eventbus ที่ทำงานในกระบวนการหรือเครือข่ายขอบเขตคุณสามารถลองPyMQ ปัจจุบันรองรับ pub / sub, message queues และ RPC แบบซิงโครนัส เวอร์ชันเริ่มต้นทำงานบนแบ็กเอนด์ Redis ดังนั้นคุณต้องใช้เซิร์ฟเวอร์ Redis นอกจากนี้ยังมีแบ็กเอนด์ในหน่วยความจำสำหรับการทดสอบ คุณสามารถเขียนแบ็กเอนด์ของคุณเอง

import pymq

# common code
class MyEvent:
    pass

# subscribe code
@pymq.subscriber
def on_event(event: MyEvent):
    print('event received')

# publisher code
pymq.publish(MyEvent())

# you can also customize channels
pymq.subscribe(on_event, channel='my_channel')
pymq.publish(MyEvent(), channel='my_channel')

ในการเริ่มต้นระบบ:

from pymq.provider.redis import RedisConfig

# starts a new thread with a Redis event loop
pymq.init(RedisConfig())

# main application control loop

pymq.shutdown()

คำเตือน: ฉันเป็นผู้เขียนของห้องสมุดนี้


0

คุณสามารถลองใช้buslaneโมดูล

ไลบรารีนี้ทำให้การใช้งานระบบข้อความเป็นเรื่องง่ายขึ้น สนับสนุนคำสั่ง (ตัวจัดการเดียว) และวิธีการเหตุการณ์ (0 หรือหลายตัวจัดการ) Buslane ใช้คำอธิบายประกอบชนิด Python เพื่อลงทะเบียนตัวจัดการอย่างถูกต้อง

ตัวอย่างง่ายๆ:

from dataclasses import dataclass

from buslane.commands import Command, CommandHandler, CommandBus


@dataclass(frozen=True)
class RegisterUserCommand(Command):
    email: str
    password: str


class RegisterUserCommandHandler(CommandHandler[RegisterUserCommand]):

    def handle(self, command: RegisterUserCommand) -> None:
        assert command == RegisterUserCommand(
            email='john@lennon.com',
            password='secret',
        )


command_bus = CommandBus()
command_bus.register(handler=RegisterUserCommandHandler())
command_bus.execute(command=RegisterUserCommand(
    email='john@lennon.com',
    password='secret',
))

หากต้องการติดตั้ง buslane เพียงใช้ pip:

$ pip install buslane

0

ก่อนหน้านี้ฉันได้เขียนห้องสมุดที่อาจเป็นประโยชน์กับคุณ ช่วยให้คุณมีผู้ฟังในและต่างประเทศหลายวิธีในการลงทะเบียนพวกเขาจัดลำดับความสำคัญการดำเนินการและอื่น ๆ

from pyeventdispatcher import register

register("foo.bar", lambda event: print("second"))
register("foo.bar", lambda event: print("first "), -100)

dispatch(Event("foo.bar", {"id": 1}))
# first second

มีรูปลักษณ์แบบกระจาย

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.