Save 37% off PRO during our Black Friday Sale! »

Pythonでつくる宣言的UIラッパーフレームワーク〜既存GUIフレームワークの調査を添えて

6e704ad00f166b420eba3d174713c82b?s=47 urushiyama
October 16, 2021

 Pythonでつくる宣言的UIラッパーフレームワーク〜既存GUIフレームワークの調査を添えて

PyCon JP 2021で発表したスライドです。

https://2021.pycon.jp/time-table/?id=272767

6e704ad00f166b420eba3d174713c82b?s=128

urushiyama

October 16, 2021
Tweet

Transcript

  1. !1Z$PO+1 
 :VUB6SVTIJZBNB 1ZUIPOͰͭ͘Δએݴత6*ϥούʔϑϨʔϜϫʔΫ طଘ(6*ϑϨʔϜϫʔΫͷௐࠪΛఴ͑ͯ

  2. ࣫ࢁ༟ଠʢ:VUB6SVTIJZBNBʣ w ଔ  ͱ͋Δ೶ۀͱձܭͷ*5اۀ w 1ZUIPOͱͷؔΘΓ  ޱύΫಈը࡞੒πʔϧ 

    1Z1*ͰϥΠϒϥϦެ։  %4ࣾ಺ۀ຿γεςϜʢ0+5ʣ  ࣾ಺ࢿ࢈ͷ"1*αʔόԽ ࣗݾ঺հ 
  3. 1ZUIPOº(6* 

  4. ܦҢ ޱύΫಈը࡞੒πʔϧʹ(6*Λ͚͍ͭͨʂ w $-*͸࡞੒ࡁΈ $ python convert.py -i data/ -o

    target/ w πʔϧͷಛੑ্Ϛ΢ε͚ͩͰૢ࡞Ͱ͖ΔͱָͪΜ  ϩδοΫ͸΋͏Ͱ͖ͯΔ͠ ָউͰ͠ΐ όΧ EBUB UFYU JNBHF WPJDF
  5. ܦҢ ͋Εʁ1ZUIPOͰ(6*ͭ͘Δͷʢଞݴޠͱൺ΂ͯʣ໘౗͍͘͞ʁ w 5LJOUFS͸໋ྩత  ίʔυ͔Βݟͨ໨Λ૝ىͮ͠Β͍ w 1Z4JNQMF(6*͸εοΩϦ͍ͯ͠Δ͚Ͳʜ  1ZUIPOͰ-(1-Wͳͷ͕νϣοτؾʹͳΔ

    w ,JWZ͸Ͳ͏ͯ͠ίʔυͱϏϡʔͷએݴ͕Θ͔ΕͯΔͷʁ 
  6. ܦҢ 3FBDU/BUJWFੈ୅?ͱͯ͠ͷෆຬ૑࡞ҙཉ 3FBDUΈ͍ͨͳએݴత6*ϑϨʔϜϫʔΫͳ͍͔ͳ͊ʙ ˝ 1ZUIPOͰ΋એݴత6*ϑϨʔϜϫʔΫͭ͘ΕΔͷ͔ͳʙ ˝ Α͠ɺ͍ͬͪΐ࡞ͬͯΈ·͔͢ʂ  ?3FBDU͸೥ʹΦʔϓϯιʔεԽͨ͠ͷͰɺߴߍ࣌୅ʹϓϩάϥϛϯάʹڵຯΛ࣋ͬͨ୅͸(6*એݴత6*ͱ͍͏ࢥߟʹͳΓ΍͍͢ʁ

  7. τϐοΫ w ͜Ε͔Β࿩͢͜ͱ  1ZUIPOͰ࢖͑Δ(6*ϑϨʔϜϫʔΫͷએݴత6*ࢹ఺Ͱͷ੔ཧ  1ZUIPOͰ3FBDUͷԾ૝%0.Λ໛฿ͨ͠࿩ w ࿩͞ͳ͍͜ͱ 

    σʔλόΠϯσΟϯά  ΠϕϯτϋϯυϦϯά 
  8. એݴత6*ͱ͸ 

  9. ʮએݴత6*ʯͱ͸ʁ ྺ࢙తܦҢʛ໋ྩత6* w ೥୅·Ͱͷ(6*͸໋ྩత6*ϥΠϒϥϦ͕ओྲྀ  Ͳͷ֊૚ɺͲͷҐஔʹ෦඼Λஔ͔͘ΛίʔυͰஞҰ੍ޚ  ͳ͍͠ͷը໘ͷલͰΩʔϘʔυͱϚ΢εΛૢ࡞͢Δ 
 ۀ຿༻ύοέʔδιϑτΛ࡞Δͷʹ͸ద͍ͯͨ͠

    ‣ 8'ͳΒը໘ઃܭ͸౓͖Γ  ‣ ΢Πϯυ΢ॖখ΋αϙʔτ͢Δ࠷খղ૾౓·ͰͰ0, 
  10. w ೥୅·Ͱͷ(6*͸໋ྩత6*ϥΠϒϥϦ͕ओྲྀ  Ͳͷ֊૚ɺͲͷҐஔʹ෦඼Λஔ͔͘ΛίʔυͰஞҰ੍ޚ  ͳ͍͠ͷը໘ͷલͰΩʔϘʔυͱϚ΢εΛૢ࡞͢Δ 
 ۀ຿༻ύοέʔδιϑτΛ࡞Δͷʹ͸ద͍ͯͨ͠ ‣ 8'ͳΒը໘ઃܭ͸౓͖Γ

     ‣ ΢Πϯυ΢ॖখ΋αϙʔτ͢Δ࠷খղ૾౓·ͰͰ0,  ྺ࢙తܦҢʛ໋ྩత6* ʮએݴత6*ϑϨʔϜϫʔΫʯͱ͸ʁ ͦΜͳ࣌୅͸΋͏աڈͷ΋ͷͰ͢ɻ ͦ͏ɺ˓1IPOFͳΒͶɻ
  11. ʮએݴత6*ϑϨʔϜϫʔΫʯͱ͸ʁ ྺ࢙తܦҢʛ3FMFBTF&BSMZ 3FMFBTF0GUFO w ϞόΠϧ୺຤ͷը໘αΠζ͸ଟ༷Ͱ౷੍Ͱ͖ͳ͍  ը໘ઃܭͷݟ௚͠ස౓61 w ʮݸਓ͕࣋ͪӡ΂ΔΠϯλʔωοτʯͱͯ͠ͷڊେͰ৽͍͠Ϛʔέοτ 

    εΫϥοϓˍϏϧυ΍ΞδϟΠϧ։ൃͷ૿Ճ  ೲ඼ͱอक⾣ܧଓతͳΞοϓσʔτ 
  12. ʮએݴత6*ϑϨʔϜϫʔΫʯͱ͸ʁ ྺ࢙తܦҢʛ3FMFBTF&BSMZ 3FMFBTF0GUFO w ϞόΠϧ୺຤ͷը໘αΠζ͸ଟ༷Ͱ౷੍Ͱ͖ͳ͍  ը໘ઃܭͷݟ௚͠ස౓61 w ʮݸਓ͕࣋ͪӡ΂ΔΠϯλʔωοτʯͱͯ͠ͷڊେͰ৽͍͠Ϛʔέοτ 

    εΫϥοϓˍϏϧυ΍ΞδϟΠϧ։ൃͷ૿Ճ  ೲ඼ͱอक⾣ܧଓతͳΞοϓσʔτ  ϥΠϑαΠΫϧ΍ঢ়ଶߋ৽ͷ࢓૊ΈΛࣗલͰ؅ཧͨ͘͠ͳ͍ 
 ˝ ϥΠϒϥϦ͔ΒϑϨʔϜϫʔΫ΁ ҟͳΔཁ݅Ͱมಈ͢Δ6*ͱঢ়ଶΛ෼ׂͯ͠؅ཧ͍ͨ͠ 
 ˝ ໋ྩత͔Βએݴతͳ6*΁
  13. ʮએݴత6*ϑϨʔϜϫʔΫʯͱ͸ʁ ιϦϡʔγϣϯʛ6*ͱঢ়ଶΛ෼཭͠ɺڞ௨ͷ࢓ֻ͚Ͱ݁߹ͤ͞Δ w 6*ͱঢ়ଶͷ෼཭  Ծ૝%0.ʢৄࡉ͸ޙ΄Ͳʣ ‣ ࠓճ1ZUIPOͰ࣮૷ͨ͠෦෼ w 6*ͱঢ়ଶͷ݁߹

     σʔλόΠϯσΟϯάʢ3FBDUJWF9΍1VC4VCͳͲʣ ‣ ༨ྗ͕ͳ࣮͘૷Ͱ͖͍ͯͳ͍😵💫 
  14. (6*ϑϨʔϜϫʔΫͷ੔ཧ 

  15. 1ZUIPOͰ࢖͑Δ(6*ϑϨʔϜϫʔΫ ˞ϑϨʔϜϫʔΫϥΠϒϥϦͷ۠෼͸ͪ͝Ό·ͥ  1Z2U1Z4JEF ENAML FUD 'MFYY &EJ fi DF

    XY1ZUIPO ,JWZ
  16. 1ZUIPOͰ࢖͑Δ(6*ϑϨʔϜϫʔΫ  ENAML 'MFYY &EJ fi DF FUD ໋ྩత6* XJUI2.-

    એݴత6* ෼཭ܕ ౷߹ܕ 1Z2U1Z4JEF XY1ZUIPO ,JWZ XJUILW
  17. 5LJOUFS एׯ͕ͤ͋͘Δ͕ࣗ༝౓ͷߴ͍ඪ४ϥΠϒϥϦ w ಺෦Ͱ5LͷίϚϯυΛݺͼग़͢ͷͰ 
 ඇৗʹखଓ͖త > pack .widget -side

    left w ͦͷͿΜॻ͖ํͷࣗ༝౓͕ߴ͍ w 5DM5Lͷ࣮ߦ؀ڥͷ४උ΍ 
 QZUIPO࣮ߦ؀ڥͱͷ૬ੑͳͲ 
 ͓खܰʹݟ͑ͯएׯ͓खܰͰͳ͍  ໋ྩత6* class HelloApp(tk.Frame): def __init__(self): self.master = tk.Tk() super().__init__(self.master, width=300, height=300) self.master.title("Hello Tkinter") # 双⽅向データバインディング self.m_text = tk.StringVar() # ラベル self.label = tk.Label( self, textvariable=self.m_text ).pack(side="top") # 1⾏テキストボックス self.textbox = tk.Entry( self, textvariable=self.m_text ).pack(side="left") # ボタン self.button = tk.Button( self, text="Clear", command=lambda: self.m_text.set("") ).pack(side="left") self.pack()
  18. XY1ZUIPO 1IPFOJY 8JOEPXTϥΠΫʁͳ໋ྩత6*ϥΠϒϥϦ w 5LJOUFSʹࣅͨखଓ͖తهड़ w 8JOEPXTͷ(6*πʔϧΩοτʹ 
 ͍ۙจ๏Β͍͠ 

    ݸਓతʹ͸ϝιου͕ 
 1BTDBM$BTFͳͷ͕޷ΈͰ͸ͳ͍  ࢲ͸🍎೿ͳͷͰΑ͘෼͔Γ·ͤΜ w ॻ͖ํΛؒҧ͑Δͱ༰қʹ 
 .&.03:@#"%@"$$&44ʹͳΔ  ໋ྩత6* class HelloFrame(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, 'Hello wxPython', size=(300, 300)) pnl = wx.Panel(self) # イベント駆動によるデータ更新 self.m_text = "" # ラベル self.st = wx.StaticText(pnl, label=self.m_text) # 1⾏テキストボックス self.tc = wx.TextCtrl(pnl) self.tc.Bind(wx.EVT_TEXT, self.on_type) # ボタン self.bt = wx.Button(pnl, wx.ID_CLEAR, label="Clear") self.bt.Bind(wx.EVT_BUTTON, self.on_clear) sizer = wx.GridBagSizer() sizer.Add(self.st, (0, 0), (1, 3), flag=wx.EXPAND) sizer.Add(self.tc, (1, 0), (1, 2), flag=wx.EXPAND) sizer.Add(self.bt, (1, 2), (1, 1), flag=wx.EXPAND) sizer.AddGrowableCol(1) pnl.SetSizer(sizer)
  19. class HelloWidget(QWidget): def __init__(self): QWidget.__init__(self) # スロットによるイベント駆動 self.m_text = ""

    self.label = QLabel(self.m_text) self.label.alignment = Qt.AlignCenter self.text_entry = QLineEdit() self.text_entry.textChanged.connect(self.on_type) self.button = QPushButton("Clear") self.button.clicked.connect(self.on_clear) layout = QGridLayout() layout.add_widget(self.label, 0, 0) layout.add_widget(self.text_entry, 1, 0) layout.add_widget(self.button, 1, 1) self.set_layout(layout) @Slot() def on_type(self): self.label.text = self.text_entry.text @Slot() def on_clear(self): self.label.text = "" self.text_entry.text = "" w ڧྗͳ4JHOBM4MPU  ΠϕϯτͷൃՐͱϩδοΫΛ 
 εϨουΛ௒͑ͯ෼཭Ͱ͖Δ w ΍΍͍͜͠બ୒ࢶͱϥΠηϯε  1Z2U 
 (1-W঎༻  2UGPS1ZUIPO 
 (1-W-(1-W঎༻ 1Z2U2UGPS1ZUIPOʢچ1Z4JEFʣ ϥΠηϯεͷҟͳΔ2Uͷ1ZUIPOΠϯλϑΣʔε  ໋ྩత6*
  20. # --- QML --- import QtQuick import QtQuick.Controls import QtQuick.Layouts

    ApplicationWindow { title: qsTr("Hello QML") width: 300 height: 300 visible: true ColumnLayout { Text { id: text text: "" } RowLayout { Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom TextField { id: textfield text: "" Layout.alignment: Qt.AlignHCenter | Qt.AlignTop Layout.margins: 5 onTextChanged: { text.text = textfield.text } } Button { text: qsTr("Clear") onClicked: { text.text = "" w 2.-ͱ͍͏ϚʔΫΞοϓΛ༻͍ͯ 
 Ϗϡʔߏ଄Λએݴతʹهड़Ͱ͖Δ  ؆୯ͳϩδοΫͳΒ2.-಺Ͱ 
 +BWB4DSJQUΛॻ͚ͯ͠·͏ 1Z2U1Z4JEFXJUI2.- 2UΛએݴతʹѻ͏  એݴత6*෼཭ܕ # --- Python --- if __name__ == "__main__": app = QApplication(sys.argv) engine = QQmlApplicationEngine() url = QUrl.fromLocalFile("view.qml") engine.load(url) if not engine.rootObjects(): sys.exit(-1) sys.exit(app.exec())
  21. # --- 命令的UI --- from kivy.app import App from kivy.uix.button

    import Button class TestApp(App): def build(self): return Button(text='Hello World') TestApp().run() w ѻ͍΍͍͢.*5ϥΠηϯε w ΫϥεͱCVJMEϝιουͷ໭Γ஋Ͱ 
 ϏϡʔΛ૊ΈཱͯΔ ,JWZ ϞόΠϧʹ΋ରԠ͢ΔϑϨΩγϒϧͳϑϨʔϜϫʔΫ  ໋ྩత6* IUUQTLJWZPSHIPNF
  22. <Controller>: label_wid: my_custom_label BoxLayout: orientation: 'vertical' padding: 20 Button: text:

    'My controller info is: ' + root.info on_press: root.do_action() Label: id: my_custom_label text: 'My label before button press' w ϩδοΫ͸جຊతʹ1ZUIPOଆʹॻ͘  QSFTFOUBUJPOͱMPHJDͷ෼཭ w 6*ͱঢ়ଶ͸Ϋϥε໊Ͱ݁߹ ,JWZ ,WMBOHVBHF ϞόΠϧʹ΋ରԠ͢ΔϑϨΩγϒϧͳϑϨʔϜϫʔΫ  એݴత6*෼཭ܕ # --- 宣⾔的UI logic --- class Controller(FloatLayout): label_wid = ObjectProperty() info = StringProperty() def do_action(self): self.label_wid.text = 'My label after button press' self.info = 'New info text' class ControllerApp(App): def build(self): return Controller(info='Hello world') if __name__ == '__main__': ControllerApp().run() IUUQTLJWZPSHEPDTUBCMFHVJEFMBOHIUNMUIFMBZPVUHPFTJODPOUSPMMFSLW IUUQTLJWZPSHEPDTUBCMFHVJEFMBOHIUNMUIFDPEFHPFTJOQZ fi MFT
  23. # person_view.enaml from enaml.widgets.api import ( Window, Form, Label, Field

    ) enamldef PersonView(Window): attr person title = 'Person View' Form: Label: text = 'First Name' Field: text := person.first_name Label: text = 'Last Name' Field: text := person.last_name w 1ZUIPOʹࣅͨߏจͰ6*Λهड़  ಈతʹ6*ͱঢ়ଶΛ݁߹Ͱ͖Δ w 1ZUIPOJDͳߏจͰ͋Δ͚ͩʹ 
 FOBNMϑΝΠϧͰͳ͘ 
 QZϑΝΠϧʹॻ͖ͨ͘ͳΔ &/".- 2Uͳ6*Λ1ZUIPOJDͳߏจͰهड़Ͱ͖ΔϑϨʔϜϫʔΫ  એݴత6*෼཭ܕ IUUQTFOBNMSFBEUIFEPDTJPFOMBUFTUHFU@TUBSUFEBOBUPNZIUNMWJFX fi MFT
  24. import edifice as ed from edifice import Label, TextInput, View

    class MyApp(ed.Component): def render(self): return View(layout="row")( Label("Measurement in meters:"), TextInput(""), Label("Measurement in feet:"), ) if __name__ == "__main__": ed.App(MyApp()).start() w 3FBDUΛ1ZUIPOʹϙʔτ͢Δͱ 
 ͜Μͳײ͡ɺΛ࣮ݱ͍ͯ͠Δ  Ҿ਺ͱ໭Γ஋Ͱ໦ߏ଄Λܗ੒ w ࢠཁૉΛҾ਺Ͱ౉ͨ͢Ίɺ 
 ࢠཁૉͷมߋͷͨΊʹ͸ 
 ؔ਺νοΫͳ৚݅෼ذΛॻ͘  ಺แදه  W JG D FMTF W &EJGJDF 2Uͳ6*Λ3FBDUͬΆ͘هड़Ͱ͖ΔϑϨʔϜϫʔΫ  એݴత6*౷߹ܕ IUUQTXXXQZFEJ fi DFPSHUVUPSJBMIUNM
  25. 'MFYY 1ZUIPOίʔυ্Ͱ8FCϕʔεͷ6*Λهड़Ͱ͖ΔϑϨʔϜϫʔΫ એݴత6*౷߹ܕ w XJUIεςʔτϝϯτΛ׆༻ͨ͠ 
 ϏϡʔͷϏϧυ  ࣗ࡞ޙʹطଘͷ΋ͷΛௐ΂ͨΒ 


    ΊͪΌͪ͘Όࣅ͍ͯͨ😅 w 8FCϕʔεͳͷͰಈ͔͢બ୒ࢶ͕ଟ͍ w 8FCϕʔεͳͷͰ 
 αʔόʢ1ZUIPOʣଆͱ 
 ΫϥΠΞϯτʢ+BWB4DSJQUʣଆΛ 
 ͦΕͧΕߟ͑Δඞཁ͋Γ  from flexx import flx class Example(flx.Widget): def init(self): with flx.HSplit(): flx.Button(text='foo') with flx.VBox(): flx.Widget(style='background:red;', flex=1) flx.Widget(style='background:blue;', flex=1) if __name__ == "__main__": app = flx.App(Example) app.launch('app') flx.run() IUUQT fl FYYSFBEUIFEPDTJPFOTUBCMFHVJEFXJEHFU@CBTJDTIUNM IUUQT fl FYYSFBEUIFEPDTJPFOTUBCMFHVJEFSVOOJOHIUNM
  26. Α͏΍͘ຊ୊ 

  27. 

  28.  ... with Body(clazz=["d-flex", ...]): with Header(clazz="mb-auto"): NavigationBar() # Component

    with Main(clazz=["container", ...]): with Division(clazz=["row", ...]): with Division(clazz=["col-sm-4"]): Image(clazz=["img-fluid"], src="/static/DeUI_logo.png") with Division(clazz=["col-sm-6"]): with Paragraph(clazz="h3"): Text(value="Declarative UI Wrapper Framework for Python") with Paragraph(): with Small(clazz="text-muted"): with Joined(): Text(value="The logo is inspired by ...") with Division(clazz=["row", "align-items-center"]): with Heading(level=1): Text(value="Register your account") with Paragraph(): Text(value="You have to make an account ...") Text(value="Please enter the form below ...") ...
  29. 1ZUIPOʹΑΔԾ૝%0.ͷ࣮૷ 

  30. Ծ૝%0. ʮԾ૝ʯతͳʮ%PDVNFOU0CKFDU.PEFMʯ w ʮ%PDVNFOU0CKFDU.PEFMʯ  λά෇͚͞ΕͨཁૉΛ໦ߏ଄ͰϞσϧԽ͢Δ͜ͱ͞Εͨ΋ͷ w ʮԾ૝ʯ  ࣮ࡍͷ%0.ʢ㲈ը໘ʣͱ͸ಠཱ͍ͯ͠Δɺͱ͍͏ҙຯ

    ‣ 4PSUFE΍$PNQPOFOUͳͲͷ࿦ཧతཁૉΛؚΊΒΕΔ 
  31. ͳͥԾ૝%0.Λ༻͍Δ͔ "໰୊ͷ୯७Խͱಈ࡞ͷߴ଎Խ w Ծ૝%0.͸໦ߏ଄  %0.ͷૢ࡞ΛҰൠͷάϥϑཧ࿦ͱͯ͠ղऍͰ͖Δ  ࠩ෼͚ͩΛݕग़Ͱ͖Ε͹ࠩ෼ͷ͋Δ࠷্ҐҎԼͷϊʔυ͚ͩߋ৽Ͱ͖Δ w Ծ૝%0.͸࣮ࡍͷ%0.ͱ͸ಠཱ

     ܰྔͳϊʔυ͚ͩΛ༻͍ͯ%0.Λಈతʹߋ৽Ͱ͖Δ 
  32.  ͦΜͳ͜ͱݴΘΕͯ΋ 
 ࣮ࡍͲ͏࣮૷͢ΔΜͩʂ ໦ͷࠩ෼ݕग़͸ 0 O ͔͔Δͧʂ

  33. େࣄͳ͜ͱ͸͢΂ͯ 
 3FBDU͕ڭ͑ͯ͘ΕΔ 

  34. 3FBDUͷࠩ෼ݕग़ݪཧ IUUQTKBSFBDUKTPSHEPDTSFDPODJMJBUJPOIUNM w ΄΅ઢܗ࣌ؒͰۙࣅղΛٻΊΔ͜ͱ͕Ͱ͖Δ  ਌ಉ͕࢜ҟͳΔཁૉͳΒͦΕҎ߱Λ͢΂ͯϦϏϧυ͢Δ  ಉ͡ܕͷཁૉͳΒࠩ෼ͷଐੑͷΈΛߋ৽͢Δ  ൺֱ͢ΔཁૉͷϖΞ͸ҰҙͳΩʔͰἧ͑Δ

     ͜ΕΛ1ZUIPOίʔυͰ࣮૷💪
  35. ࠩ෼ݕग़ݪཧͷίʔυ ΍ͬͯΔ͜ͱ͸ඇৗʹγϯϓϧ w ϊʔυ͕ଘࡏ͠ͳ͍ͳΒ 
 ৽ͨʹ6*෦඼Λੜ੒͢Δ w ϊʔυ͕ଘࡏ͢ΔͳΒ 
 ੜ੒ࡁΈͷ6*෦඼Λड͚౉͢

    w ϊʔυࣗ਎ͷঢ়ଶมԽ͸ 
 ϋογϡ஋ΛٻΊͯ௥੻ w ࠶ؼͰ౉͢ϊʔυରΛJEͰιʔτ  @classmethod def update_tree(cls, old_t, new_t, root=Root): if new_t is None: return if (old_t is None or new_t.w_type is not old_t.w_type): # building new tree new_t.build(root=root) return # copy widget from old v-DOM-node to new one new_t.widget = old_t.widget new_t.widget.owner = weakref.ref(new_t) if new_t.hashcode != old_t.hashcode: # update widget parameters new_t.widget.update(*new_t.args, **new_t.kwargs) new_t.need_update = True # continue comparison order by id for old_st, new_st in align( old_t.children, new_t.children, key='id'): App.update_tree(old_st, new_st, root=root) IUUQTHJUIVCDPNVSVTIJZBNB%F6*CMPCEEBFEBEFFGFGCDEFVJDPSFBQQQZ--
  36. NBHJD😎c🤯XJUI @@OFX@@ 

  37. XJUIεςʔτϝϯτΛΫϥεͰ༻͍Δฐ֐ 8IBU*JOJUJBMJ[FJTOPUXIBUXFSFBMMZXBOUUPJOJUJBMJ[F w XJUIεςʔτϝϯτʹΫϥεͷ 
 ΦϒδΣΫτΛ౉͢ίʔυ 👍 BTʹΘͨ͢஋͸ 
 @@FOUFS@@Ͱࣗ༝ʹܾఆͰ͖Δ

    👎 XJUIʹ౉࣌͢఺Ͱ 
 $POUFYUΫϥεͷ@@JOJU@@͕ 
 ૸ͬͯ͠·͏  class Context: def __init__(self): # some great initializations... pass def __enter__(self): # push context return some_obj # for `as` def __exit__(self, t, v, trace): # pop context pass ... # In use with Context() as context: pass
  38. XJUIεςʔτϝϯτΛΫϥεͰ༻͍Δฐ֐ 8IBU*JOJUJBMJ[FJTOPUXIBUXFSFBMMZXBOUUPJOJUJBMJ[F XJUIʹ౉࣌͢఺ͰॳظԽ͍ͨ͠ͷ͸Ծ૝%0.ʢ7JFXʣ ÷ ϓϩάϥϚ͕XJUIʹ౉͍ͨ͠ͷ͸࣮ࡍͷ%0.ʢ8JEHFUʣ  @@OFX@@ϝιουʹΑΔॳظԽ࣌ͷڍಈͷΦʔόʔϥΠυ

  39. @@OFX@@ͱ͸ 1ZUIPOͷΠϯελϯεੜ੒🛵 w 1ZUIPO͸Πϯελϯεੜ੒࣌ʹͭͷϝιουΛݺͼग़͢ 1.__new__(cls, ...)  ΫϥεͷΠϯελϯεੜ੒ͷͨΊʹݺ͹ΕΔ 2.__init__(self, ...)

     ΠϯελϯεͷॳظԽͷͨΊʹݺ͹ΕΔ  ܕͷੈք ࣮ମͷੈք ⁞  
  40. XJUI @@OFX@@ ΠϯελϯεԽͱίϯςΫετͷଋറͱͰΫϥεΛ෼཭  ࣮ࡍͷ%0.ʹରԠ͢Δ8JEHFUΛ 
 ΠϯελϯεԽ͠Α͏ͱ͢Δͱ 
 Ծ૝%0.ͷ7JFX͕ಉ࣌ʹ 


    ΠϯελϯεԽ͞Εɺ  8JEHFUͷΠϯελϯε͸ 
 ஗ԆධՁͰऔಘͰ͖Δ  def __new__(cls, *args, **kwargs): view = View(cls, *args, **kwargs) widget = super().__new__(cls) widget.update(*args, **kwargs) def get_initial_widget(): widget.owner = weakref.ref(view) widget.update(*args, **kwargs) return widget view.get_initial_widget \ = get_initial_widget return view IUUQTHJUIVCDPNVSVTIJZBNB%F6*CMPCEEBFEBEFFGFGCDEFVJDPSFXJEHFUQZ--
  41. @@OFX@@Λซ༻͢ΔϝϦοτ ΞεϖΫτࢦ޲ͳίϯςΫετଋറ w ίϯςΫετͷଋറΛܧঝͳ͠ʹผͷΫϥεʹҠৡͰ͖Δ 
 ΞεϖΫτࢦ޲ͳXJUIʹΑΔίϯςΫετଋറͷ࣮ݱ  8JEHFUΫϥεʹ͸@@FOUFS@@΍@@FYJU@@Λॻ͍͍ͯͳ͍  @@FOUFS@@ͱ@@FYJU@@Λϥοϓ͢Δ.BQQFSΛ༻ҙ͢Ε͹

    
 ෳ਺ͷίϯςΫετΛద༻Ͱ͖Δ ‣ IUUQTHJTUHJUIVCDPNVSVTIJZBNB DGFDBFECBECGEDD  ʊਓਓਓਓਓਓਓਓਓʊ ʼɹଋറ͞Εͨ༨നɹʻ ʉ:?:?:?:?:?:?:?:ʉ
  42. @@OFX@@Λ༻͍ΔσϝϦοτ ޫʁ͋Δͱ͜ΖʹӨ͋Γ w ҰൠతͳΠϯελϯεԽͱڍಈ͕ҟͳΔ  όάϑΟοΫεͷ೉қ౓্ঢ w @@JOJU@@Λซ༻͢Δͱ͞ΒʹΧΦε  ҆қʹ@@OFX@@಺ͰClass(…)Λݺͼग़͢ͱແݶϧʔϓʹؕΔ

    
  43. ࠷ޙʹ 

  44. ࠷ޙʹ ͜Ε͔Βͷએݴత6*ͱϑϩϯτΤϯυ w એݴత6*͸σʔληοτʹର͢Δႈ౳ੑ͕͋ΔͨΊ 
 ҎԼͷٕज़ͱඇৗʹ૬ੑ͕ྑ͍  એݴܕϓϩάϥϛϯάݴޠʢ&MJYJS )BTLFMM ઌߦࣄྫతʹ͸&MNͳͲʣ

     ௚ྻԽՄೳͳߏ଄ମ  ΞεϖΫτࢦ޲ͱ%*ʢઌߦࣄྫతʹ͸7VF$PNQPTJUJPO"1*ͳͲʣ  એݴతͰঢ়ଶಉظ͠΍͍͢"1*ʢ(SBQI2-ͳͲʣ 
  45. ࠷ޙʹ ͜Ε͔Βͷએݴత6*ͱϑϩϯτΤϯυͱ1ZUIPO w એݴత6*͸σʔληοτʹର͢Δႈ౳ੑ͕͋ΔͨΊ 
 ҎԼͷٕज़ͱඇৗʹ૬ੑ͕ྑ͍  એݴܕϓϩάϥϛϯάݴޠ  ௚ྻԽՄೳͳߏ଄ମ

     ΞεϖΫτࢦ޲ͱ%*  એݴతͰঢ়ଶಉظ͠΍͍͢"1*  👿Ҿ਺ͷείʔϓͰແ໊ؔ਺ΛఆٛͰ͖Ε͹ʜ 😇!EBUBDMBTT QZEBOUJD 😇UZQJOH1SPUPDPM NPOLFZQBUDI 😇๛෋ͳ(SBQI2-ϥΠϒϥϦ
  46. ࠷ޙʹ ͜Ε͔Βͷએݴత6*ͱϑϩϯτΤϯυͱ1ZUIPO w એݴత6*͸σʔληοτʹର͢Δႈ౳ੑ͕͋ΔͨΊ 
 ҎԼͷٕज़ͱඇৗʹ૬ੑ͕ྑ͍  એݴܕϓϩάϥϛϯάݴޠ  ௚ྻԽՄೳͳߏ଄ମ

     ΞεϖΫτࢦ޲ͱ%*  એݴతͰঢ়ଶಉظ͠΍͍͢"1*  👿Ҿ਺ͷείʔϓͰແ໊ؔ਺ΛఆٛͰ͖Ε͹ʜ 😇!EBUBDMBTT QZEBOUJD 😇UZQJOH1SPUPDPM NPOLFZQBUDI 😇๛෋ͳ(SBQI2-ϥΠϒϥϦ 👿😈👿😈👿1ZUIPOͰ൚༻తͳ(6*Λ૊Ήχʔζ
  47. ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠ 

  48. None
  49. ิ଍ɿXJUIͷબఆཧ༝ w CVJMUJOͷTUBUFNFOU͕࢖͑Δ  JGจ΍GPSจ͕࢖͑Δ  1ZUIPOͳΒ 
 NBUDI΋࢖͑Δ w

    ߏ଄͕ෳࡶԽͯ͠΋ 
 ؙׅހΛଟ༻ͤͣʹࡁΉ  with NewsColumn(): for news_item in news_list: match news_item: case [title]: Bulletin(title) case [title, summary]: Headline(title, summary) case _: raise ValueError( "Unknown kind of news_item") จࣈྻҾ਺ͱ໭Γ஋Λ༻͍Δύλʔϯͱൺֱͨ͠ϝϦοτ
  50. ิ଍ɿͳͥʮϥούʔʯʁ "ʮϥούʔʯʹ͠Α͏ͱ͍͔ͯͨ͠Β w Ծ૝%0.ʹج͍ͮͯ8JEHFUΛߋ৽͢ΔϥούʔίʔυΛॻ͚͹ 
 ཧ࿦্͸5LJOUFSͰ΋,JWZͰ΋ಉ༷ͷॻ͖ํͰϥοϓͰ͖Δ  )5.-͸จࣈྻཁૉͷϥούʔ  ,JWZͰ΋΍Ζ͏ͱ͍͕ͯͨ͠

    
 ʢຊۀָ͕͘͠๩͘͠ͳΓʣະணखͷ·· 
  51. ิ଍ɿσʔλόΠϯσΟϯάͷల๬ (SBQI2-Λ༻͍ͨԾ૝εςʔτΛಋೖ͍ͨ͠