【问题标题】:Pythonic way to specify comparison operators?指定比较运算符的 Pythonic 方式?
【发布时间】:2013-08-31 19:53:35
【问题描述】:

(编辑:我得到了很多关于实现的答案(我很感激),但我更关心的是规范语法。这个的用户插件(即 Python/Django 开发人员 - 不是网站用户)将需要以我正在发明的语法指定条件。所以重新表述这个问题......在编写模型或表单类时...... .原型 Python/Django 开发人员更喜欢以下哪种语法来指定表单字段的条件显示逻辑?)

我正在寻找一些关于最 Pythonic(可读、直接等)方式的建议,以指定比较运算符以供以后在比较中使用(通过 javascript 执行)。类似的东西(这只是我想到的一个例子——还有很多其他可能的格式):

comparisons = (('a', '>', 'b'), ('b', '==', 'c'))

稍后将在 Javascript 中进行评估。

上下文是我正在开发一个 Django 应用程序(最终作为插件分发),这将要求用户以我选择的任何语法编写比较(因此有关使其成为 Pythonic 的问题)。比较将引用表单字段,并最终转换为 javascript 条件表单显示。我想一个例子是为了:

class MyModel(models.Model):
    yes_or_no = models.SomeField...choices are yes or no...
    why = models.SomeField...text, but only relevant if yes_or_no == yes...
    elaborate_even_more = models.SomeField...more text, just here so we can have multiple conditions

    #here i am inventing some syntax...open to suggestions!!
    why.show_if = ('yes_or_no','==','yes')
    elaborate_even_more.show_if = (('yes_or_no','==','yes'),('why','is not','None'))

    #(EDIT - help me choose a syntax that is Pythonic and...Djangonic...and that makes your fingers happy to type!)
    #another alternative...
    conditions = {'why': ('yes_or_no','==','yes'), 
                  'elaborate_even_more': (('yes_or_no','==','yes'),('why','is not','None'))
                  }

    #or another alternative...
    """Showe the field whiche hath the name *why* only under that circumstance 
    in whiche the field whiche hath the name *yes_or_no* hath the value *yes*, 
    in strictest equality."""
    etc...

(挥手......使用model_form_factory()将MyModel转换为ModelForm......在字典中收集所有“field.show_if”条件并作为MyModelForm.conditions或其他东西附加到ModelForm......)

现在在模板中的一段 javascript 中,MyModelForm.condtions 中的每个条件都将成为一个函数,用于侦听字段值的变化,并显示或隐藏另一个字段作为响应。基本上(在伪 Javascript/Jquery 中):

when yes_or_no changes...
    if (yes_or_no.value == 'yes'){
        $('#div that contains *why* field).show(); }
    else {
        $('#div that contains *why* field).hide(); }

这里的目标是让最终用户以直接的 Python 方式在模型定义中指定条件显示逻辑在模型定义中(可能有一个选项可以在表单类上指定条件,我认为这更像是“Djangonic”(??),但对于我的用例,它们需要进入模型)。然后我的幕后插件将其转换为模板中的 Javascript。因此,您无需编写任何 Javascript 即可获得条件表单显示。由于这将掌握在 python/django 开发人员手中,因此我正在寻找有关指定这些条件的最原生、最舒适的方法的建议。

【问题讨论】:

  • 如果您的问题是询问如何设计您的 API(而不是如何实现 API),也许您应该前往programmers.stackexchange.com。 StackOverflow 旨在为您提供如何实现事物的答案:API 设计有点主观,在程序员堆栈交换上可能会更好。
  • 谢谢马克,我不知道这种区别。有没有一种方法可以在不复制的情况下迁移它,或者我应该在那里发布一个问题并链接到这个?
  • 好吧,老实说,即使在那里,您也可能会发现问题已经结束。这是非常主观的:“您希望如何设计这个 API?”
  • 当然,这是主观的,正如“好代码”(或“pythonic 代码”)的原因。也许它会更好地表述为“这就是我打算如何设计这个 API;这是否符合规范和约定,以便 python/django 社区中的人易于阅读和编写?”

标签: jquery python django


【解决方案1】:

这可能有点矫枉过正,但您可以使用 Pyjaco 工具将 Python 源代码编译为 Javascript 源代码。然后你可以用普通的 Python 编写你的比较。很难比原生 Python 更 Pythonic!

【讨论】:

    【解决方案2】:

    您需要的是一个简单的 JavaScript 表达式生成器。粗略的草图:

    import json
    
    class JSExpr(object):
        #base class
        def gen(self):
            """Generate the js for this expression."""
            raise NotImplementedError()
    
    class CodeDump(JSExpr):
        def __init__(self, code_str):
            self.code_str = code_str
        def gen(self):
            return self.code_str
    
    class PyLit(JSExpr):
        """Py literal to js literal, e.g. 4 --> 4, "hi" --> '"hi"' """
        def __init__(self, py_lit):
            self.py_lit = py_lit
        def gen(self):
            return "%s" % (json.dumps(self.py_lit),)
    
    class If(JSExpr):
        """Generate an if statement from the given JSExprs."""
        def __init__(self, comp, if_true, if_false=None):
            self.comp = comp
            self.if_true = if_true
            self.if_false = if_false
        def gen(self):
            return "if (%s) { %s; } else { %s; }" % (
                self.comp.gen(), self.if_true.gen(),
                self.if_false.gen() if self.if_false else "")
    
    class BinOp(JSExpr):
        """Helper for common binary operations ==, >=, etc."""
        op = None
        def __init__(self, left, right):
            if self.op is None:
                raise ValueError("Must sub-class and define '.op'")
            self.left = left
            self.right = right
    
        def gen(self):
            return "((%s) %s (%s))" % (self.left.gen(), self.op, self.right.gen())
    
    class Eq(BinOp): 
        op = '=='
    
    class StrictEq(BinOp):
        op = '==='
    
    class Gt(BinOp):
        op = '>'
    
    class And(BinOp):
        op = '&&'
    
    class StrContains(JSExpr):
        """Non-bin op example"""
        def __init__(self, left, right):
            self.left = left
            self.right = right
    
        def gen(self,):
            return "((%s).indexOf(%s) != -1)" % (self.left.gen(), self.right.gen())
    

    那么您所要做的就是以某种方式从用户在网页上输入的内容生成JSExpr 实例,并从中生成If,使用PyLit 插入值:

    >>> print If(StrictEq(PyLit(4), PyLit([1, 2, 3])),
           CodeDump("console.log('weird things happening');")).gen()
    if (((4) === ([1, 2, 3]))) { console.log('weird things happening');; } else { ; }
    
    >>> print If(Gt(PyLit(4), PyLit(2)),
             CodeDump("alert('you did it!');"),
             CodeDump("alert('nope');")).gen()
    if (((4) > (2))) { alert('you did it!');; } else { alert('nope');; }
    

    或者您可以使用CodeDump 来检查变量名称。您问题中的示例是:

    >>> print If(Eq(CodeDump("yes_or_no.value"), PyLit("yes")),
         CodeDump("$('#div that contains *why* field').show();"),
         CodeDump("$('#div that contains *why* field').hide();")).gen()
    if (((yes_or_no.value) == ("yes"))) { $('#div that contains *why* field').show();; } else { $('#div that contains *why* field').hide();; }
    

    输出不是很漂亮,但它应该可以工作。你可以花一些时间让它变得更好。

    【讨论】:

      【解决方案3】:

      我真的很喜欢 SQLAlchemy 如何使用运算符重载来处理这个问题,所以您可能想看看他们的实现以获取一些想法。例如,这是它如何进行查询...

      session.query(User).filter(User.name == 'fred')
      

      如您所见,User.name == 'fred' 实际上创建了一个新对象,然后可以将其转换为 SQL 字符串。这不需要是字符串,您可以根据需要将其转换为 JSON 对象。当然,这取决于是否让 User.name 支持这种模型,我认为 Django 的模型不支持。

      但是,你可以做类似的事情,像这样......

      class Wrapper(object):
          def __init__(self, prop):
              self.prop = prop
      
          def __eq__(self, x):
              return "{0} == '{1}'".format(self.prop, x)
      
      def test(x, fun):
          w = Wrapper(x)
          return fun(w)
      
      class MyModel(object):
          yes_or_no = 'yes_or_no'
      
      print test(MyModel.yes_or_no, lambda x: x == 'yes') # "yes_or_no == 'yes'"
      

      “包装器”对象是支持实际运算符重载的对象。在此示例中,我们返回一个字符串,但您可以返回另一个 Wrapper 对象以允许复合语句(例如 (a == 2) & (b == 4))。究竟如何“包装”对象取决于您。您还可以包装模型以使复合语句更容易...

      model = f(MyModel)
      print model.yes_or_no == 'yes' | model.yes_or_no == 'no'
      

      【讨论】:

        【解决方案4】:

        你如何看待使用 django 的语法进行查询?

        >>> Entry.objects.filter(
        ...     headline__startswith='What'
        ... ).exclude(
        ...     pub_date__gte=datetime.date.today()
        ... ).filter(
        ...     pub_date__gte=datetime(2005, 1, 30)
        ... )
        

        所以它可能看起来像:

        class MyModel(models.Model):
            yes_or_no = models.SomeField()
            why = models.SomeField(yes_or_no='yes')
            ... or ...
            why = models.SomeField(field__yes_or_no='yes')
            ... or even ...
            why = models.SomeField(field__yes_or_no__contains='ye')
        

        【讨论】:

        • 我喜欢使用与现有功能相似的语法的想法。减少用户的心理开销,减少学习曲线等。为此 +1。我认为要使其工作,我必须提供所有模型字段的自定义子类(以便他们知道如何处理我正在发明的这些新参数),对吗?
        • 我偏爱使用现有的表格。我真的只是为一些信息添加了一个载体,这些信息稍后会被使用(通过 javascript),所以我试图保持最小化。但也许创建一个新的子类是有意义的,例如,还要验证提交时隐藏的字段都不包含任何数据......
        【解决方案5】:

        这是一个想法:

        import operator as op
        
        a, b, c = 10, 7, 7
        
        def f1():
            print 10
        
        def f2():
            print 20
        
        comparisons = ((a, op.gt, b, f1), (b, op.eq, c, f2))
        
        for lhs, oper, rhs, f in comparisons:
            if oper(lhs, rhs):
                f()
        
        => 10
        => 20
        

        通过适当的表示,您可以动态指定比较运算符及其相应的操作 - 实现为函数。查看operator 模块以查看可用的运算符。

        【讨论】:

        • 这很酷。我将检查该模块。如果我理解正确,该模块的实用程序是 执行 python 中的比较。我实际上只是在 python 中传递比较,最终在 javascript 中执行。我编辑了问题以使其更清楚。
        猜你喜欢
        • 2018-11-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-05-27
        相关资源
        最近更新 更多