【问题标题】:How would you design a very "Pythonic" UI framework?您将如何设计一个非常“Pythonic”的 UI 框架?
【发布时间】:2010-09-08 16:37:14
【问题描述】:

我一直在玩 Ruby 库“鞋子”。基本上你可以通过以下方式编写一个 GUI 应用程序:

Shoes.app do
  t = para "Not clicked!"
  button "The Label" do
    alert "You clicked the button!" # when clicked, make an alert
    t.replace "Clicked!" # ..and replace the label's text
  end
end

这让我想到 - 我将如何在 Python 中设计一个类似的好用的 GUI 框架?一个没有通常的绑定,基本上是 C* 库的包装器(在 GTK、Tk、wx、QT 等的情况下)

Shoes 从 Web 开发(如 #f0c2f0 样式颜色符号、CSS 布局技术,如 :margin => 10)和 ruby​​(以合理的方式广泛使用块)获取东西

Python 缺少“rubyish 块”使得(隐喻的)直接端口成为不可能:

def Shoeless(Shoes.app):
    self.t = para("Not clicked!")

    def on_click_func(self):
        alert("You clicked the button!")
        self.t.replace("clicked!")

    b = button("The label", click=self.on_click_func)

没有那么干净,也不会几乎那么灵活,我什至不确定它是否可以实现。

使用装饰器似乎是一种将代码块映射到特定操作的有趣方式:

class BaseControl:
    def __init__(self):
        self.func = None

    def clicked(self, func):
        self.func = func

    def __call__(self):
        if self.func is not None:
            self.func()

class Button(BaseControl):
    pass

class Label(BaseControl):
    pass

# The actual applications code (that the end-user would write)
class MyApp:
    ok = Button()
    la = Label()

    @ok.clicked
    def clickeryHappened():
        print "OK Clicked!"

if __name__ == '__main__':
    a = MyApp()
    a.ok() # trigger the clicked action

基本上,装饰器函数存储函数,然后当动作发生(例如,单击)时,将执行相应的函数。

各种东西的范围(例如,上面示例中的la 标签)可能相当复杂,但它似乎以相当简洁的方式可行..

【问题讨论】:

  • Ruby 和 Python 都适用于 DSL。区别在于“L”;它代表 Ruby 的“语言”和 Python 的“库”。你可以从 Python 中强制使用一些语法魔法,例如 Django 的模型,但你应该这样做吗?

标签: python user-interface frameworks


【解决方案1】:

你实际上可以做到这一点,但它需要使用元类,这是魔法(有龙)。如果您想了解元类,可以使用一系列 articles from IBM 来介绍这些想法,而不会融化您的大脑。

来自像 SQLObject 这样的 ORM 的源代码也可能会有所帮助,因为它使用了同样的声明性语法。

【讨论】:

【解决方案2】:

我从不满意 David Mertz 在 IBM 发表的关于 metaclsses 的文章,所以我最近写了自己的 metaclass article。享受吧。

【讨论】:

    【解决方案3】:

    这是非常做作的,根本不是 Python 语言,但这是我使用新的“with”语句进行半字面翻译的尝试。

    with Shoes():
      t = Para("Not clicked!")
      with Button("The Label"):
        Alert("You clicked the button!")
        t.replace("Clicked!")
    

    最难的部分是处理 Python 不会给我们提供包含多个语句的匿名函数这一事实。为了解决这个问题,我们可以创建一个命令列表并运行这些命令......

    无论如何,这是我运行它的后端代码:

    context = None
    
    class Nestable(object):
      def __init__(self,caption=None):
        self.caption = caption
        self.things = []
    
        global context
        if context:
          context.add(self)
    
      def __enter__(self):
        global context
        self.parent = context
        context = self
    
      def __exit__(self, type, value, traceback):
        global context
        context = self.parent
    
      def add(self,thing):
        self.things.append(thing)
        print "Adding a %s to %s" % (thing,self)
    
      def __str__(self):
        return "%s(%s)" % (self.__class__.__name__, self.caption)
    
    
    class Shoes(Nestable):
      pass
    
    class Button(Nestable):
      pass
    
    class Alert(Nestable):
      pass
    
    class Para(Nestable):
      def replace(self,caption):
        Command(self,"replace",caption)
    
    class Command(Nestable):
      def __init__(self, target, command, caption):
        self.command = command
        self.target  = target
        Nestable.__init__(self,caption)
    
      def __str__(self):
        return "Command(%s text of %s with \"%s\")" % (self.command, self.target, self.caption)
    
      def execute(self):
        self.target.caption = self.caption
    

    【讨论】:

      【解决方案4】:
      ## All you need is this class:
      
      class MainWindow(Window):
          my_button = Button('Click Me')
          my_paragraph = Text('This is the text you wish to place')
          my_alert = AlertBox('What what what!!!')
      
          @my_button.clicked
          def my_button_clicked(self, button, event):
              self.my_paragraph.text.append('And now you clicked on it, the button that is.')
      
          @my_paragraph.text.changed
          def my_paragraph_text_changed(self, text, event):
              self.button.text = 'No more clicks!'
      
          @my_button.text.changed
          def my_button_text_changed(self, text, event):
              self.my_alert.show()
      
      
      ## The Style class is automatically gnerated by the framework
      ## but you can override it by defining it in the class:
      ##
      ##      class MainWindow(Window):
      ##          class Style:
      ##              my_blah = {'style-info': 'value'}
      ##
      ## or like you see below:
      
      class Style:
          my_button = {
              'background-color': '#ccc',
              'font-size': '14px'}
          my_paragraph = {
              'background-color': '#fff',
              'color': '#000',
              'font-size': '14px',
              'border': '1px solid black',
              'border-radius': '3px'}
      
      MainWindow.Style = Style
      
      ## The layout class is automatically generated
      ## by the framework but you can override it by defining it
      ## in the class, same as the Style class above, or by
      ## defining it like this:
      
      class MainLayout(Layout):
          def __init__(self, style):
              # It takes the custom or automatically generated style class upon instantiation
              style.window.pack(HBox().pack(style.my_paragraph, style.my_button))
      
      MainWindow.Layout = MainLayout
      
      if __name__ == '__main__':
          run(App(main=MainWindow))
      

      在 python 中使用一些元类 python 魔法知识会相对容易。我有。以及 PyGTK 的知识。我也有。有想法吗?

      【讨论】:

        【解决方案5】:

        通过一些元类魔法来保持排序,我可以进行以下工作。我不确定它是多么的 Pythonic,但创建简单的东西很有趣。

        class w(Wndw):
          title='Hello World'
          class txt(Txt):  # either a new class
            text='Insert name here'
          lbl=Lbl(text='Hello') # or an instance
          class greet(Bbt):
            text='Greet'
            def click(self): #on_click method
              self.frame.lbl.text='Hello %s.'%self.frame.txt.text
        
        app=w()
        

        【讨论】:

          【解决方案6】:

          据我所知,这样做的唯一尝试是Hans Nowak's Wax(不幸的是,它已经死了)。

          【讨论】:

            【解决方案7】:

            最接近 ruby​​ish 块的是 pep343 中的 with 语句:

            http://www.python.org/dev/peps/pep-0343/

            【讨论】:

              【解决方案8】:

              如果您将PyGTKgladethis glade wrapper 一起使用,那么PyGTK 实际上会变得有点pythonic。至少一点点。

              基本上,您在 Glade 中创建 GUI 布局。您还可以在 glade 中指定事件回调。然后你像这样为你的窗口写一个类:

              class MyWindow(GladeWrapper):
                  GladeWrapper.__init__(self, "my_glade_file.xml", "mainWindow")
                  self.GtkWindow.show()
              
                  def button_click_event (self, *args):
                      self.button1.set_label("CLICKED")
              

              在这里,我假设我在某处有一个名为 button1 的 GTK 按钮,并且我将 button_click_event 指定为 clicked 回调。林间空地包装器在事件映射方面付出了很多努力。

              如果我要设计一个 Pythonic GUI 库,我会支持类似的东西,以帮助快速开发。唯一的区别是我会确保小部件也有一个更 Pythonic 的界面。当前的 PyGTK 类对我来说似乎非常 C,除了我使用 foo.bar(...) 而不是 bar(foo, ...) 尽管我不确定我会做些什么不同的事情。可能允许 Django 模型风格的声明性方式在代码中指定小部件和事件,并允许您通过迭代器访问数据(在有意义的地方,例如小部件列表可能),尽管我还没有真正考虑过。

              【讨论】:

                【解决方案9】:

                也许不如 Ruby 版本那么流畅,但是像这样的东西怎么样:

                from Boots import App, Para, Button, alert
                
                def Shoeless(App):
                    t = Para(text = 'Not Clicked')
                    b = Button(label = 'The label')
                
                    def on_b_clicked(self):
                        alert('You clicked the button!')
                        self.t.text = 'Clicked!'
                

                Like Justin said,要实现这一点,您需要在类App 上使用自定义元类,并在ParaButton 上使用一堆属性。这实际上不会太难。

                接下来遇到的问题是:如何跟踪事物在类定义中出现的顺序?在 Python 2.x 中,没有办法知道 t 是否应该高于 b 或相反,因为您将类定义的内容作为 python dict 接收。

                但是,在 Python 3.0 metaclasses are being changed 中有几种(次要)方式。其中之一是 __prepare__ 方法,它允许您提供自己的自定义类字典对象来代替使用——这意味着您将能够跟踪定义项目的顺序,并相应地定位它们窗户。

                【讨论】:

                【解决方案10】:

                这可能过于简单化,我认为尝试以这种方式制作通用 ui 库不是一个好主意。另一方面,您可以使用这种方法(元类和友元)来简化现有 ui 库的某些用户界面类的定义,并根据实际可以节省大量时间和代码行的应用程序。

                【讨论】:

                • “过度简化通常会适得其反”:重言式。鞋子绝对是一种“简化”——它是一种“过度简化”的天气取决于您的需求。我认为简单的 UI 创建非常有用。
                【解决方案11】:

                我也有同样的问题。我想为 Python 的任何 GUI 工具包创建一个包装器,该工具包易于使用,并受到鞋子的启发,但需要是一种 OOP 方法(针对 ruby​​ 块)。

                更多信息在:http://wiki.alcidesfonseca.com/blog/python-universal-gui-revisited

                欢迎任何人加入该项目。

                【讨论】:

                  【解决方案12】:

                  如果你真的想编写 UI,你可以尝试获得类似于 django 的 ORM 的东西;像这样获得一个简单的帮助浏览器:

                  class MyWindow(Window):
                      class VBox:
                          entry = Entry()
                          bigtext = TextView()
                  
                          def on_entry_accepted(text):
                              bigtext.value = eval(text).__doc__
                  

                  我们的想法是将一些容器(如窗口)解释为简单的类,将一些容器(如表、v/hbox)解释为对象名称,并将简单的小部件解释为对象。

                  我认为不必为窗口内的所有容器命名,因此需要一些快捷方式(例如通过名称将旧式类识别为小部件)。

                  关于元素的顺序:在上面的 MyWindow 中,您不必跟踪它(window 在概念上是一个单槽容器)。在其他容器中,您可以尝试跟踪顺序,假设每个小部件构造函数都可以访问一些全局小部件列表。这就是在 django (AFAIK) 中完成的方式。

                  这里很少有技巧,那里也很少调整...仍然需要考虑的事情很少,但我相信这是可能的...并且可用,只要您不构建复杂的 UI。

                  不过,我对 PyGTK+Glade 非常满意。 UI 对我来说只是一种数据,它应该被视为数据。需要调整的参数太多(比如不同位置的间距),最好使用 GUI 工具来管理。因此,我在 Glade 中构建我的 UI,保存为 xml 并使用 gtk.glade.XML() 进行解析。

                  【讨论】:

                    【解决方案13】:

                    就个人而言,我会尝试在 GUI 框架中实现 JQuery 之类的 API。

                    class MyWindow(Window):
                        contents = (
                            para('Hello World!'),
                            button('Click Me', id='ok'),
                            para('Epilog'),
                        )
                    
                        def __init__(self):
                            self['#ok'].click(self.message)
                            self['para'].hover(self.blend_in, self.blend_out)
                    
                        def message(self):
                            print 'You clicked!'
                    
                        def blend_in(self, object):
                            object.background = '#333333'
                    
                        def blend_out(self, object):
                            object.background = 'WindowBackground'
                    

                    【讨论】:

                      【解决方案14】:

                      这是一种使用基于类的元编程而不是继承来处理 GUI 定义的方法。

                      这是 Django/SQLAlchemy 的灵感来源,因为它在很大程度上基于元编程并将您的 GUI 代码与“代码代码”分开。我还认为它应该像 Java 那样大量使用布局管理器,因为当您删除代码时,没有人愿意不断调整像素对齐。我也认为如果我们可以拥有类似 CSS 的属性会很酷。

                      这是一个粗略的头脑风暴示例,它将显示一个顶部带有标签的列,然后是一个文本框,然后是一个用于单击底部的按钮以显示一条消息。

                      从 happygui.controls 导入 * MAIN_WINDOW = 窗口(宽度=“500px”,高度=“350px”, my_layout=ColumnLayout(padding="10px", my_label=Lab​​el(text="你叫什么名字?", bold=True, align="center"), my_edit=EditBox(placeholder=""), my_btn=Button(text="CLICK ME!", on_click=Handler('module.file.btn_clicked')), ), ) MAIN_WINDOW.show() def btn_clicked(sender): # 很容易在 handlers.py 文件中 名称 = MAIN_WINDOW.my_layout.my_edit.text # 同样的事情:name = sender.parent.my_edit.text # 最佳实践,不受结构变化影响:MAIN_WINDOW.find('my_edit').text MessageBox("你的名字是'%s'" % ()).show(modal=True)

                      要注意的一件很酷的事情是,您可以通过说MAIN_WINDOW.my_layout.my_edit.text 来引用 my_edit 的输入。在窗口的声明中,我认为能够在函数kwargs中任意命名控件是很重要的。

                      这是仅使用绝对定位的同一个应用程序(控件将出现在不同的位置,因为我们没有使用花哨的布局管理器):

                      从 happygui.controls 导入 * MAIN_WINDOW = 窗口(宽度=“500px”,高度=“350px”, my_label=Lab​​el(text="What's your name Kiddo?", bold=True, align="center", x="10px", y="10px", width="300px", height="100px"), my_edit=EditBox(placeholder="", x="10px", y="110px", width="300px", height="100px"), my_btn=Button(text="CLICK ME!", on_click=Handler('module.file.btn_clicked'), x="10px", y="210px", width="300px", height="100px"), ) MAIN_WINDOW.show() def btn_clicked(sender): # 很容易在 handlers.py 文件中 名称 = MAIN_WINDOW.my_edit.text # 同样的事情:name = sender.parent.my_edit.text # 最佳实践,不受结构变化影响:MAIN_WINDOW.find('my_edit').text MessageBox("你的名字是'%s'" % ()).show(modal=True)

                      我还不完全确定这是否是一种非常棒的方法,但我绝对认为它走在正确的道路上。我没有时间更多地探索这个想法,但如果有人把它作为一个项目,我会喜欢他们的。

                      【讨论】:

                        【解决方案15】:

                        声明式不一定比函数式恕我直言更多(或更少)pythonic。我认为分层方法是最好的(自下而上):

                        1. 接受和返回 python 数据类型的本机层。
                        2. 功能性动态层。
                        3. 一个或多个声明性/面向对象的层。

                        类似于Elixir + SQLAlchemy

                        【讨论】:

                        • 我认为在某些情况下,声明式肯定更直观,我认为它更 Pythonic,因为它使代码(恕我直言)更清晰、易于阅读且更易于修改 - 更传统的 Pythonic 代码通常具有的属性.这对 Elixir、Django 模型甚至 GUI 布局都很有意义。
                        • 我认为traditional python 代码甚至有可能是声明性的。声明式不可避免地涉及魔法,这并不总是与“显​​式胜于隐式”相得益彰。
                        • 但我同意你的最后一部分。声明式编程适用于任何类型的数据建模任务。 ORM 模型、GUI 布局、URL 架构...
                        猜你喜欢
                        • 2010-10-10
                        • 2022-08-20
                        • 1970-01-01
                        • 2011-02-27
                        • 2011-12-03
                        • 1970-01-01
                        • 2017-10-15
                        • 2011-07-21
                        • 2015-01-06
                        相关资源
                        最近更新 更多