【问题标题】:Why is True returned when checking if an empty string is in another?为什么检查空字符串是否在另一个字符串时返回True?
【发布时间】:2015-02-20 14:57:01
【问题描述】:

我有限的大脑无法理解为什么会发生这种情况:

>>> print '' in 'lolsome'
True

在 PHP 中,等价比较返回 false:

var_dump(strpos('', 'lolsome'));

【问题讨论】:

  • 所以 PHP 在这里可能是错误的?空字符串 is 存在于所有字符串中。
  • 我实际上认为这是一个有趣的问题。我希望它在我研究时不会被关闭。
  • 不是一个很难被否决的问题。
  • @Marcelo 很抱歉,您的最后一次编辑大大改变了问题的含义。所以,我不得不恢复你的编辑。
  • 确实,这不是关于为什么 Python 行为正确,而是为什么 PHP 行为错误(一如既往)。

标签: python string python-internals


【解决方案1】:

From the documentation

对于 Unicode 和字符串类型,当且仅当 xy 的子字符串时,x in y 为真。等效测试是y.find(x) != -1。注意,xy 不必是同一类型;因此,u'ab' in 'abc' 将返回 True空字符串总是被认为是任何其他字符串的子字符串,因此"" in "abc" 将返回True

通过查看您的 print 电话,您使用的是 2.x。

要更深入,请查看字节码:

>>> def answer():
...   '' in 'lolsome'

>>> dis.dis(answer)
  2           0 LOAD_CONST               1 ('')
              3 LOAD_CONST               2 ('lolsome')
              6 COMPARE_OP               6 (in)
              9 POP_TOP
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

COMPARE_OP 是我们进行布尔运算的地方,查看source code 对应的in 会发现比较发生的位置:

    TARGET(COMPARE_OP)
    {
        w = POP();
        v = TOP();
        if (PyInt_CheckExact(w) && PyInt_CheckExact(v)) {
            /* INLINE: cmp(int, int) */
            register long a, b;
            register int res;
            a = PyInt_AS_LONG(v);
            b = PyInt_AS_LONG(w);
            switch (oparg) {
            case PyCmp_LT: res = a <  b; break;
            case PyCmp_LE: res = a <= b; break;
            case PyCmp_EQ: res = a == b; break;
            case PyCmp_NE: res = a != b; break;
            case PyCmp_GT: res = a >  b; break;
            case PyCmp_GE: res = a >= b; break;
            case PyCmp_IS: res = v == w; break;
            case PyCmp_IS_NOT: res = v != w; break;
            default: goto slow_compare;
            }
            x = res ? Py_True : Py_False;
            Py_INCREF(x);
        }
        else {
          slow_compare:
            x = cmp_outcome(oparg, v, w);
        }
        Py_DECREF(v);
        Py_DECREF(w);
        SET_TOP(x);
        if (x == NULL) break;
        PREDICT(POP_JUMP_IF_FALSE);
        PREDICT(POP_JUMP_IF_TRUE);
        DISPATCH();
    }

在哪里cmp_outcome is in the same file,很容易找到我们的下一条线索:

res = PySequence_Contains(w, v);

abstract.c:

{
    Py_ssize_t result;
    if (PyType_HasFeature(seq->ob_type, Py_TPFLAGS_HAVE_SEQUENCE_IN)) {
        PySequenceMethods *sqm = seq->ob_type->tp_as_sequence;
        if (sqm != NULL && sqm->sq_contains != NULL)
            return (*sqm->sq_contains)(seq, ob);
    }
    result = _PySequence_IterSearch(seq, ob, PY_ITERSEARCH_CONTAINS);
    return Py_SAFE_DOWNCAST(result, Py_ssize_t, int);
}

为了从源头获取空气,我们在documentation 中找到了下一个函数:

objobjproc PySequenceMethods.sq_contains

这个函数可能被PySequence_Contains() 使用并且具有相同的签名。这个槽可以留给NULL,在这种情况下PySequence_Contains() 只是简单地遍历序列直到找到匹配。

further down in the same documentation:

int PySequence_Contains(PyObject *o, PyObject *value)

确定o 是否包含。如果 o 中的一项等于 value,则返回1,否则返回0。出错时,返回-1。这相当于 Python 表达式value in o

如果'' 不是null,则可以认为序列'lolsome' 包含它。

【讨论】:

    【解决方案2】:

    引自PHP's strpos documentation

    mixed strpos ( string $haystack , mixed $needle [, int $offset = 0 ] )
    

    haystack字符串中查找needle的第一次出现的数字位置。

    所以你实际尝试过的类似于下面看到的 Python 构造

    >>> print 'lolsome' in ''
    False
    

    所以,你实际上应该像下面这样写,以便在 PHP 中进行相应的比较

    var_dump(strpos('lolsome', ''));
    

    即便如此,它也会发出警告并返回false

    PHP 警告:strpos():第 3 行 /home/thefourtheye/Desktop/Test.php 中的空针

    bool(false)

    我挖得更深,发现the source code corresponding to the strpos function

        if (!Z_STRLEN_P(needle)) {
            php_error_docref(NULL, E_WARNING, "Empty needle");
            RETURN_FALSE;
        }
    

    他们认为正在搜索的空字符串是有问题的情况。因此,他们发出警告并返回false。除此之外,我找不到任何文档讨论为什么它被认为是一个问题。

    就 Python 而言,这种行为在 Comparisons section 中有很好的定义,

    空字符串总是被认为是任何其他字符串的子字符串,因此"" in "abc" 将返回True

    【讨论】:

    • 哈哈,太好了,你进PHP源码我进Python源码,这个问题真的很好解决了。
    【解决方案3】:

    假设您有 2 堆相似的对象,例如,您最喜欢的诗人的最佳诗节,分别为 5 和 2。更大的集合包含更小的集合吗?如何检查: 1)对于较小的一堆中的任何节,您可能会在较大的一个中找到它。 2)较小的堆不包含较大的堆中不存在的东西。

    所以我们可以使用这个伪代码来检查:

    for object in smaller:
        if object not in bigger:
           return 'we found object from smaller absent in bigger'
        else:
           go to next object
    return 'all is ok - all objects from smaller are in bigger'
    

    如果您还没有找到这样的对象,那么您将结束算法,并认为较小的是较大的子集。

    现在想象较小的一堆是 0 节。 应用上面相同的规则,我们执行 0 次检查,也没有从较小的对象中找到较大的对象。

    因此,将空字符串视为任何其他字符串的子集是正确且方便的。甚至自己。这是在python中实现的。

        >>> '' in 'adfsf'
        True
        >>> '' in ''
        True
    

    【讨论】:

      【解决方案4】:

      基本上,来自数学:

      空集是每个集合的子集

      同样的逻辑在这里也适用。您可以将'' 视为一个空集。因此,它是每个 string 集合的子集,因为它们必须是相同的类型。

      >>> a = ""
      >>> b = "Python"
      >>> a in b
      True
      >>> set(a).issubset(b)
      True
      >>> a = set() #empty set
      >>> b = set([1,2,3])
      >>> a.issubset(b)
      True
      >>> 
      

      但要小心!一个子集和一个成员是different things

      【讨论】:

      • 空字符串为空集逻辑不适用于其他 Python 序列。 set([]).issubset(set([1,2,3]))True。但是,[] in [1,2,3]Falsetuple 也是如此。空字符串为空集逻辑错误。
      • 文本截图在Stack Overflow 上很少有用。文本无法搜索,也无法复制粘贴。始终包括文本本身,至少在图像的替代文本中。此特定屏幕截图中的 UI 元素嘈杂且令人困惑(浏览页面时可能会被误认为是实际控件)。
      【解决方案5】:

      空字符串是长度为零的唯一字符串。
      空字符串是连接操作的标识元素。
      按照字典顺序,空字符串在任何其他字符串之前,因为它是所有字符串中最短的。
      空字符串是合法的字符串,大多数字符串操作都应该在该字符串上工作。
      Wikipedia

       > strlen("");
      => 0
       > "a" . "" == "a";
      => true
       > "" . "a" == "a";
      => true   
       > "" < "\0";
      => true   
      

      从上面看来,PHP 似乎将空字符串视为有效字符串。

      > strstr("lolsome", "");
      strstr(): Empty needle :1
      

      但它似乎并不认为空字符串是完全合法的。很可能 PHP 是唯一一种不允许在字符串中搜索子字符串为空字符串的语言。

      这是一种防御机制吗?显然,程序员不必用if 来保护针。如果是这样,为什么其他语言允许这个测试通过!!!语言设计师必须回答

      Python 字符串是由什么组成的?

      >>> ''.count('')
      1
      

      显然空字符串有一个空字符串。

      >>> 'a'.count('')
      2
      

      一个元素字符串有两个空字符串。

      >>> 'ab'.count('')
      3
      

      所以看起来 Python 字符串是一个元素字符串的串联。字符串中的每个元素都夹在两个空字符串之间。

      >>> "lolsome".split('')
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      ValueError: empty separator
      

      但这里 Python 与空字符串的有效性相矛盾。 这是一个错误吗?
      RubyJavaScript 在此处通过测试。

       > "lolsome".split("")
      => ["l", "o", "l", "s", "o", "m", "e"]
      

      我从Rosetta code 编译了几个语言示例,有趣注意到它们都在子字符串搜索中允许空字符串并返回 true。

      AWK

      awk 'BEGIN { print index("lolsome", "") != 0 }'
      

      C

      int main() {
          printf("%d\n", strstr("lolsome", "") != NULL);
          return 0;
      }
      

      C++

      #include <iostream>
      #include <string>
      
      int main() {
          std::string s = "lolsome";
          std::cout << (s.find("") != -1) << "\n";
          return 0;
      }
      

      C#

      using System;
      class MainClass {
        public static void Main (string[] args) {
          string s = "lolsome";
          Console.WriteLine(s.IndexOf("", 0, s.Length) != -1);
        }
      }
      

      Clojure

      (println (.indexOf "lolsome" ""))
      

      Go

      package main
      
      import (
          "fmt"
          "strings"
      )
      func main() {
          fmt.Println(strings.Index("lolsome", "") != -1)
      }
      

      时髦

      println 'lolsome'.indexOf('')
      

      返回 0,错误返回 -1

      Java

      class Main {
        public static void main(String[] args) {
          System.out.println("lolsome".indexOf("") != -1);
        }
      }
      

      JavaScript

      "lolsome".indexOf("") != -1
      

      Lua

      s = "lolsome"
      print(s:find "" ~= nil)
      

      Perl

      print index("lolsome", "") != -1;
      

      Python

      "lolsome".find("") != -1
      

      Ruby

      "lolsome".index("") != nil
      

      【讨论】:

      • C strstr 还会在 haystack 的开头找到空的 needle。考虑到这么多 PHP 字符串函数是仿照 C 对应的(尽管没有 strpos),这有点令人惊讶。
      猜你喜欢
      • 2014-04-17
      • 2010-10-17
      • 2019-05-11
      • 2011-02-07
      • 1970-01-01
      • 1970-01-01
      • 2013-03-13
      • 2013-05-12
      相关资源
      最近更新 更多