Python中的'is'关键字是如何实现的?

69

...is关键字可用于字符串的相等性比较。

>>> s = 'str'
>>> s is 'str'
True
>>> s is 'st'
False

我尝试了__is__()__eq__(),但它们都没有起作用。

>>> class MyString:
...   def __init__(self):
...     self.s = 'string'
...   def __is__(self, s):
...     return self.s == s
...
>>>
>>>
>>> m = MyString()
>>> m is 'ss'
False
>>> m is 'string' # <--- Expected to work
False
>>>
>>> class MyString:
...   def __init__(self):
...     self.s = 'string'
...   def __eq__(self, s):
...     return self.s == s
...
>>>
>>> m = MyString()
>>> m is 'ss'
False
>>> m is 'string' # <--- Expected to work, but again failed
False
>>>
11个回答

142

测试字符串仅在字符串被内部化时才起作用。除非您确切知道自己在做什么并明确地将字符串interned,否则永远不应该在字符串上使用is

is测试对象的标识而不是相等。这意味着Python只比较对象所在的内存地址。is基本上回答了这个问题:“我是否有两个名称指向同一个对象?”-重载它没有任何意义。

例如,("a" * 100) is ("a" * 100)False。通常,Python将每个字符串写入不同的内存位置,内部化主要发生在字符串字面值中。


14
如果字符串的长度足够短,则在运行时计算和输入值时,字符串可能会被公用。例如,'a' * 100 不等于 'a' * 100,但 'a' * 20 等于 "a" * 20。同时,'a'.upper() 不等于 'a'.upper()。Jython、IronPython、PyPy等实现方法可能更加积极。总之,这取决于具体实现。调用字符串上的'intern()'函数将“强制”一个字符串具有与任何等效且先前已经intern()的字符串相同的对象标识符,正如你所说。然而,我不知道测试字符串标识符的有效用例。(除了可能的性能考虑)。 - Jim Dennis
6
在2010年,("a" * 100) 等于 ("a" * 100) 或许是错误的,但如今则为正确。 - goteguru
1
@goteguru,这不是我的问题。在2019年,使用CPython 3.5.6。我认为Jim在2010年的评论才是真正的胜者:它取决于实现。不要假设任何东西。 - Andrew
@Andrew 当然这是实现特定的,我们不应该使用“is”进行字符串比较。也许你的Cython优化器由于某种原因没有将字符串内部化。尝试使用更小的“a”*20。 - goteguru

25

is操作符相当于比较id(x)的值。例如:

>>> s1 = 'str'
>>> s2 = 'str'
>>> s1 is s2
True
>>> id(s1)
4564468760
>>> id(s2)
4564468760
>>> id(s1) == id(s2)  # equivalent to `s1 is s2`
True

id目前使用指针作为比较方式。所以你不能重载is本身,而且据我所知你也不能重载id

因此,你不能这样做。在Python中不同寻常,但事实就是如此。


1
你可以重载 id,但不是你可能想要的那种方式。只需执行 id = <function> 即可。 - user4157653
不是的。在Python中尝试打印print(id(a.T) is id(a.T)),你会看到结果。 - logicOnAbstractions
1
@logicOnAbstractions 我相信他的意思是用 == 而不是 is 来比较 id。因此,print(id(a.T) == id(a.T)) 应该等同于 print(a is a) - jameshfisher

16
Python 的关键字 is 用于测试对象的身份(identity)。你不应该使用它来测试字符串相等性。尽管在 Python 实现中,像许多高级语言一样会对字符串执行"内部化(interning)"处理,因此看起来经常有效。也就是说,字符串文字和值在内部保持为哈希列表,并且那些相同的字符串将呈现为对同一对象的引用。(这是可能的,因为Python的字符串是不可变的)。
但是,与任何实现细节一样,您不应该依赖于此。如果要测试相等性,请使用'=='运算符。如果您真的想测试对象标识,则使用is ---我很难想出一个需要关心字符串的对象标识的情况。不幸的是,由于前面提到的内部化,您不能确定两个字符串是否以某种方式是"有意地"相同的对象引用。

在Python中,你想要进行身份比较的唯一场合是当与单例(例如None)和需要唯一的哨兵值进行比较时。除此之外,几乎没有理由使用is。 - Lie Ryan
2
@Lie Ryan:我倾向于同意。我只在使用None和我创建的特殊标记时才使用它(通常作为对基本“object()”的调用)。然而,我不感到自信地断言没有其他有效的使用'is'运算符的方法;可能只是我自己无知的证明。 - Jim Dennis

10
< p > is 关键字用于比较对象(或者更确切地说是比较两个引用是否指向同一对象)。

我认为这就是为什么没有机制可以提供您自己的实现。

对于字符串来说,有时候它会起作用,因为Python会“聪明地”存储字符串,这样当你创建两个相同的字符串时,它们会被存储在一个对象中。

>>> a = "string"
>>> b = "string"
>>> a is b
True
>>> c = "str"+"ing"
>>> a is c
True

你可以通过一个简单的“复制”示例,希望能看到引用与数据比较:

>>> a = {"a":1}
>>> b = a
>>> c = a.copy()
>>> a is b
True
>>> a is c
False

5
如果你不怕弄乱字节码,可以拦截并修补COMPARE_OP,使用8 ("is")参数调用你的钩子函数来比较对象。查看dis模块文档以了解开始操作。
而且别忘了也要拦截__builtin__.id(),如果有人使用id(a) == id(b)而不是a is b

1
有趣的是,这是一整个可以玩弄Python函数的可能性世界,我从未想过。但是为什么这会是一个好主意呢? - alexis
在我们公司,我们有一个内部测试库,其中包含一个上下文装饰器,通过将datetime.datetime替换为始终从utcnow()返回特定时间的实现来冻结时间。如果您运行datetime.datetime.utcnow()并尝试pickle返回的值,则会失败,因为其类不一致(它正在假装成另一个类)。在这种情况下,覆盖is的方式可能是一种解决方案。 - Brad Johnson

3

'is'比较对象的身份,而'=='比较值。

示例:

a=[1,2]
b=[1,2]
#a==b returns True
#a is b returns False

p=q=[1,2]
#p==q returns True
#p is q returns True

2

当字符串以“-”开头时,使用Python 2.6.6版本无法将一个字符串变量与一个字符串值或两个字符串变量进行比较。

>>> s = '-hi'
>>> s is '-hi'
False 
>>> s = '-hi'
>>> k = '-hi'
>>> s is k 
False
>>> '-hi' is '-hi'
True

1

您正在使用身份比较。 == 可能是您想要的。例外情况是当您想要检查一个项目和另一个项目是否是完全相同的对象并且在相同的内存位置时。在您的示例中,项目不同,因为一个项目的类型(my_string)与另一个项目(string)不同。此外,在Python中没有类似于someclass.__is__的东西(除非您自己放置它)。如果有的话,使用is比较对象将无法可靠地比较内存位置。

当我第一次遇到is关键字时,它也使我感到困惑。我本以为is和==没有区别。它们在许多对象上都产生了解释器的相同输出。这种假设实际上正是is...的用途。是Python等效的“嘿,不要混淆这两个对象。它们是不同的。”,这本质上就是[纠正我的人]所说的。措辞不同,但一点等于另一点。

对于一些有用的例子和帮助理解有时令人困惑的差异的文本,请访问由"Danny Yoo"编写的python.org邮件主机上的文档

或者,如果那个不在线,可以使用我制作的未列出的pastebin

万一它们在20个左右的蓝月亮(蓝月亮是真实事件)中都失效了,我将引用代码示例。

###
>>> my_name = "danny"
>>> your_name = "ian"
>>> my_name == your_name
0                #or False
###

###
>>> my_name[1:3] == your_name[1:3]
1    #or True
###

###
>>> my_name[1:3] is your_name[1:3]
0
###

1

你不能重载 is 运算符。你想要重载的是 == 运算符。这可以通过在类中定义一个 __eq__ 方法来实现。


0

使用is关键字比较对象时,很容易出现断言错误。例如,对象ab可能持有相同的值并共享相同的内存地址。因此,进行

>>> a == b

将被评估为

True

但是如果

>>> a is b

评估为

False

你应该检查一下

>>> type(a)

>>> type(b)

这些可能不同,是失败的原因。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接