“是”的类型关键字可能相当于Python中的相等运算符

发布于 2024-09-08 13:46:29 字数 986 浏览 6 评论 0原文

对于 Python 中的某些类型,is 运算符似乎等同于 == 运算符。例如:

>>> 1 is 1
True
>>> "a spoon" is "a spoon"
True
>>> (1 == 1) is (2 == 2)
True

然而,情况并非总是如此:

>>> [] == []
True
>>> [] is []
False

这对于可变类型(例如列表)是有意义的。然而,诸如元组之类的不可变类型似乎表现出相同的行为:

>>> (1, 2) == (1, 2)
True
>>> (1, 2) is (1, 2)
False

这引发了几个问题:

  1. == / is 等价性与不变性相关吗?
  2. 上述行为是指定的还是实施细节?
  3. 最重要的是(也是最基本的),我如何知道赋值是否会导致生成对象的副本,或者生成对其的引用?

更新: 如果赋值总是通过引用,为什么下面的代码不打印 2?:

>>> a = 1
>>> b = a
>>> a = 2
>>> b
1

为什么这不等于下面的 C 代码片段:

int a = 1;
int *b = &a;
a = 2;
printf("%d\n", *b);

对于这个问题的新颖性表示歉意,但我是一个 Python 新手并认为理解这一点很重要。您是否建议阅读任何书籍来理解此类问题?

For some types in Python, the is operator seems to be equivalent to the == operator. For example:

>>> 1 is 1
True
>>> "a spoon" is "a spoon"
True
>>> (1 == 1) is (2 == 2)
True

However, this is not always the case:

>>> [] == []
True
>>> [] is []
False

This makes sense for mutable types such as lists. However, immutable types such as tuples seem to display the same behavior:

>>> (1, 2) == (1, 2)
True
>>> (1, 2) is (1, 2)
False

This raises several questions:

  1. Is the == / is equivalence related to immutability?
  2. Are the behaviors above specified, or an implementation detail?
  3. Most importantly (and basically), how can I know if an assignment will result in a copy of an object being made, or a reference to it being made?

Update:
If assignment is always by reference, why doesn't the following print 2?:

>>> a = 1
>>> b = a
>>> a = 2
>>> b
1

Why isn't this equivalent to the following C snippet:

int a = 1;
int *b = &a;
a = 2;
printf("%d\n", *b);

Apologies for the newbness of this question, but I am a Python novice and feel that it is important to understand this. Is there any reading you would recommend to understand these sort of issues?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

是你 2024-09-15 13:46:29

is 运算符测试两个对象在物理上是否相同,这意味着它们在内存中是否具有相同的地址。这也可以使用 id() 函数进行测试:

>>> a = 1
>>> b = 1
>>> a is b
True
>>> id(a) == id(b)
True

另一方面,== 运算符测试语义相等性。这也可以通过实现 __eq__() 函数由自定义类覆盖。从语义上讲,如果两个不同的列表的元素都相等,则它们相等,但从物理上讲,它们将是不同的对象。

像字符串和元组这样的不可变类型可能会被 Python 实现池化,因此两个文字字符串对象实际上在物理上是相同的。但这并不意味着您始终可以使用 is 来比较这些类型,如以下示例所示:

>>> "foobar" is "foobar"   # The interpreter knows that the string literals are
True                       # equal and creates only one shared object.
>>> a = "foobar"
>>> b = "foobar"
>>> a is b        # "foobar" comes from the pool, so it is still the same object.
True
>>> b = "foo"     # Here, we construct another string "foobar" dynamically that is
>>> b += "bar"    # physically not the same as the pooled "foobar".
>>> a == b
True
>>> a is b
False

Python 中的赋值总是将对对象的引用绑定到变量名称,并且从不暗示副本。

更新

与 C 类似,认为 Python 变量始终是指针:

>>> a = 1
>>> b = a
>>> a = 2
>>> b
1

大致相当于:

const int ONE = 1;
const int TWO = 2;

int *a = &ONE;
int *b = a;  /* b points to 1 */
a = &TWO;    /* a points to 2, b still points to 1 */

The is operator tests if two objects are physically the same, that means if they have the same address in memory. This can also be tested using the id() function:

>>> a = 1
>>> b = 1
>>> a is b
True
>>> id(a) == id(b)
True

The == operator on the other hand, tests for semantical equality. This can also be overridden by custom classes by implementing the __eq__() function. Semantically, two different lists are equal if their elements are all equal, but physically they will be different objects.

Immutable types like strings and tuples might be pooled by the Python implementation, so that two literal string objects are in fact physically identical. But this does not mean that you can always use is to compare those types, as demonstrated in the following example:

>>> "foobar" is "foobar"   # The interpreter knows that the string literals are
True                       # equal and creates only one shared object.
>>> a = "foobar"
>>> b = "foobar"
>>> a is b        # "foobar" comes from the pool, so it is still the same object.
True
>>> b = "foo"     # Here, we construct another string "foobar" dynamically that is
>>> b += "bar"    # physically not the same as the pooled "foobar".
>>> a == b
True
>>> a is b
False

Assignments in Python always bind the reference to an object to a variable name and never implies a copy.

UPDATE

Analogous to C, think of Python variables always being pointers:

>>> a = 1
>>> b = a
>>> a = 2
>>> b
1

Roughly equivalent to:

const int ONE = 1;
const int TWO = 2;

int *a = &ONE;
int *b = a;  /* b points to 1 */
a = &TWO;    /* a points to 2, b still points to 1 */
聚集的泪 2024-09-15 13:46:29

==/等价性与不变性相关吗?

否。

请参阅Python '== ' 与 'is' 比较字符串,'is' 有时会失败,为什么? 为什么它适用于字符串,以及 Python“is”运算符在整数上的表现出乎意料为什么它对整数起作用(因此布尔值对于相同的原因)。

上述行为是指定的还是实现细节?

实施细节。

我如何知道赋值是否会导致生成对象的副本或对其进行引用?

分配始终是通过引用进行的。仅当您显式使用 copy.copy (或类似的东西)时才会完成复制。

编辑:“通过引用”我并不是指 C++ 中的引用。 Python 的赋值将重新绑定变量。它更像是

// int* a, *b;
a = new int(1);
b = a;
a = new int(2);
printf("%d\n", *b);

Is the == / is equivalence related to immutability?

No.

See Python ‘==’ vs ‘is’ comparing strings, ‘is’ fails sometimes, why? on why it works on strings, and Python “is” operator behaves unexpectedly with integers on why it works on integers (thus bools for the same reason).

Are the behaviors above specified, or an implementation detail?

Implementation detail.

how can I know if an assignment will result in a copy of an object being made, or a reference to it being made?

Assignment is always by reference. Copying is done only if you explicitly use copy.copy (or something like that).

Edit: By "by reference" I don't mean references in C++. Python's assignment will rebind the variable. It's more like

// int* a, *b;
a = new int(1);
b = a;
a = new int(2);
printf("%d\n", *b);
柠栀 2024-09-15 13:46:29

如果您有 C 或 C++ 背景,那么可能更容易理解 Python 中的所有变量实际上都是指针。因此,该语句

 a = 1

确实大致类似于

 Object *a = new Integer(1);

is 运算符检查指针是否相等,而 == 运算符则涉及依赖于对象类型的计算。

这个方案有点复杂,如果对象是不可变的(例如整数),那么出于效率原因,上面的代码确实有点像

 int *a = getFromCacheOrCreateNewInteger(1);

有时(但这是一个实现细节)不可变对象可能是相同的对象 < code>is 即使它们是从逻辑角度独立创建的(例如,1+1 可能是 2-1,但不能保证):

>>> 1+2 is 2+1
True
>>> 99999+1 is 1+99999
False
>>> 

添加更多令人困惑的是,即使Python中的所有变量确实都是指针,令人惊讶的是Python中也没有指针概念,换句话说,没有办法传递一个函数来存储你的变量中的某些内容。

为此,您需要传递名称(如果变量是全局变量)或传递要调用的 setter 函数(如果变量是局部变量)。这其实并不是一个大烦恼,因为在大多数情况下你只需要多个返回值,而 Python 已经很好地处理了这个问题:

def foo(x):
    return x+1, x-1

a, b = foo(12)

另一个额外的烦恼是,如果你真的需要为没有名称的局部变量(例如元素)传递 setter列表)它不能是匿名 lambda,因为赋值是一个语句,而 lambda 只允许单个表达式 >。但是,您可以为此定义本地函数...

def foo(x, setter):
    setter(x + 1)

def bar():
    mylocal = [1,2,3]

    def setFirst(value):
        mylocal[0] = value

    foo(32, setFirst)

(好吧。我撒谎了...确实可以使用 lambda 值:mylocal.__setitem__(0, value) 但这或多或少是不需要的事件;lambda 在 Python 中是如此令人讨厌,以至于一旦他们发现这是可能的,就会在该语言中添加另一个限制来禁止它;-))。

如果您想更改命名的本地变量,这在 Python 2.x 中是不可能的(但在 Python 3.x 和 nonlocal 中是可能的)。

关于何时执行复制以及何时仅复制指针的问题,答案非常简单。 Python 永远不会自动创建副本...如果你想创建副本,你必须自己显式地执行。这就是为什么常见的代码如下:

class Polygon:
    def __init__(pointlist):
        self.pointlist = pointlist[:]

[:] 表示法在这里意味着类实例想要存储传递的列表的副本,这样如果您使用点列表创建一个Polygon实例,然后修改该列表,然后几何图形不会改变。

If you come from a C or C++ background then it's probably simpler to rationalize that all variables in Python are indeed pointers. So the statement

 a = 1

is indeed roughly similar to

 Object *a = new Integer(1);

The is operator checks for pointer equality and the == operator instead involves a computation that depends on the type of objects.

A little complication to this scheme is that if the objects are immutable (e.g. an integer) then for efficiency reasons the code above is indeed a bit more like

 int *a = getFromCacheOrCreateNewInteger(1);

so sometimes (but it's an implementation detail) immutable objects may be are the same object for is even if they are created independently from a logical point of view (e.g. may be that 1+1 is 2-1, but no guarantees):

>>> 1+2 is 2+1
True
>>> 99999+1 is 1+99999
False
>>> 

To add a bit more of confusion is that even if indeed alla variables in Python are pointers quite surprisingly there is no pointer concept in Python, in other words there is no way to pass a function in which of your variables something should be stored.

To do that you need either to pass a name (if the variable is a global) or to pass a setter function to be called (if the variable is a local). This is not really a big annoyance since in most cases you just want multiple return values and this is handled nicely already by Python:

def foo(x):
    return x+1, x-1

a, b = foo(12)

Another extra annoyance is that if you really need to pass a setter for a local variable without a name (e.g. an element of a list) it cannot be an anonymous lambda because assignment is a statement and lambda is only allowed a single expression. You can however define local functions for that...

def foo(x, setter):
    setter(x + 1)

def bar():
    mylocal = [1,2,3]

    def setFirst(value):
        mylocal[0] = value

    foo(32, setFirst)

(OK. I lied... it's indeed possible to use lambda value: mylocal.__setitem__(0, value) but this is more or less an unwanted incident; lambda is so hated in Python that probably once they discover this is possible another limitation will be added to the language to forbid it ;-) ).

If you want to change a named local instead this is just impossible in Python 2.x (but possible with Python 3.x and nonlocal).

About the question of when copies are performed and when instead just the pointer is copied the answer is very simple. Python never ever makes a copy automatically... if you want to make a copy the you must do it yourself explicitly. This is why for example is common to see code like:

class Polygon:
    def __init__(pointlist):
        self.pointlist = pointlist[:]

The [:] notation means here that the class instance wants to store a copy of the list passed so that if you create an instance of Polygon with a list of points and the later modify this list then the geometry doesn't change.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文