Nhà điều hành là một nhà điều hành bất ngờ với các số nguyên


509

Tại sao những điều sau đây hoạt động bất ngờ trong Python?

>>> a = 256
>>> b = 256
>>> a is b
True           # This is an expected result
>>> a = 257
>>> b = 257
>>> a is b
False          # What happened here? Why is this False?
>>> 257 is 257
True           # Yet the literal numbers compare properly

Tôi đang sử dụng Python 2.5.2. Thử một số phiên bản khác nhau của Python, có vẻ như Python 2.3.3 cho thấy hành vi trên từ 99 đến 100.

Dựa trên những điều trên, tôi có thể đưa ra giả thuyết rằng Python được triển khai bên trong sao cho các số nguyên "nhỏ" được lưu trữ theo cách khác với các số nguyên lớn hơn và istoán tử có thể cho biết sự khác biệt. Tại sao sự trừu tượng bị rò rỉ? Cách tốt hơn để so sánh hai đối tượng tùy ý để xem liệu chúng có giống nhau không khi tôi không biết trước chúng có phải là số hay không?


1
Hãy xem ở đây > Việc triển khai hiện tại giữ một mảng các đối tượng số nguyên cho tất cả> số nguyên trong khoảng từ -5 đến 256, khi bạn tạo một int trong phạm vi đó bạn> thực sự chỉ cần lấy lại một tham chiếu đến đối tượng hiện có.
dùng5319825

2
Đây là chi tiết triển khai dành riêng cho CPython và hành vi không xác định, được sử dụng với cảnh báo
ospider

Câu trả lời:


393

Hãy xem này:

>>> a = 256
>>> b = 256
>>> id(a)
9987148
>>> id(b)
9987148
>>> a = 257
>>> b = 257
>>> id(a)
11662816
>>> id(b)
11662828

Đây là những gì tôi tìm thấy trong tài liệu Python 2, "Đối tượng số nguyên" (Nó giống với Python 3 ):

Việc triển khai hiện tại giữ một mảng các đối tượng số nguyên cho tất cả các số nguyên trong khoảng từ -5 đến 256, khi bạn tạo một int trong phạm vi đó, bạn thực sự chỉ cần lấy lại một tham chiếu đến đối tượng hiện có. Vì vậy, có thể thay đổi giá trị của 1. Tôi nghi ngờ hành vi của Python trong trường hợp này là không xác định. :-)


46
có ai biết phạm vi đó (-5, 256) được chọn không? tôi sẽ không quá ngạc nhiên nếu đó là (0, 255) hoặc thậm chí (-255, 255), nhưng một phạm vi gồm 262 số bắt đầu từ -5 có vẻ tùy tiện một cách đáng ngạc nhiên.
Woodrow Barlow

6
@WoodrowBarlow: -5 chỉ là một heuristic để nắm bắt các vị trí tiêu cực phổ biến, tôi nghĩ vậy. 0..255 bao gồm các mảng của các giá trị byte đơn. Đó là 256 điều bí ẩn, nhưng tôi đoán đó là (dis) lắp ráp các số nguyên thành / từ byte.
Davis Herring

3
Từ những gì tôi hiểu, phạm vi đã được chọn bằng cách xem xét các giá trị thường được sử dụng trên nhiều dự án (và nhiều ngôn ngữ).
Tony Suffolk 66

9
Theo reddit.com/r/Python/comments/18leav/ , phạm vi được sử dụng là [-5,100]. Nó được mở rộng để bao gồm đầy đủ các giá trị byte - cộng với 256, vì đó có lẽ là một số phổ biến.
mwfearnley

2
@Ashwani hãy thử đọc các bình luận ngay bên cạnh bình luận của bạn, được đăng hai năm trước bạn và bạn sẽ tìm thấy câu trả lời cho câu hỏi của mình.
jbg

116

Toán tử Python là toán tử hành xử bất ngờ với số nguyên?

Tóm lại - hãy để tôi nhấn mạnh: Không sử dụng isđể so sánh các số nguyên.

Đây không phải là hành vi bạn nên có bất kỳ mong đợi về.

Thay vào đó, sử dụng ==!=để so sánh cho bình đẳng và bất bình đẳng, tương ứng. Ví dụ:

>>> a = 1000
>>> a == 1000       # Test integers like this,
True
>>> a != 5000       # or this!
True
>>> a is 1000       # Don't do this! - Don't use `is` to test integers!!
False

Giải trình

Để biết điều này, bạn cần biết những điều sau đây.

Đầu tiên, islàm gì? Nó là một toán tử so sánh. Từ tài liệu :

Các toán tử isis notkiểm tra nhận dạng đối tượng: x is yđúng khi và chỉ khi x và y là cùng một đối tượng. x is not ymang lại giá trị thật ngược.

Và như vậy sau đây là tương đương.

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

Từ tài liệu :

id Trả về danh tính của người Viking về một đối tượng. Đây là một số nguyên (hoặc số nguyên dài) được đảm bảo là duy nhất và không đổi cho đối tượng này trong suốt vòng đời của nó. Hai đối tượng có tuổi thọ không chồng chéo có thể có cùng id()giá trị.

Lưu ý rằng thực tế là id của một đối tượng trong CPython (triển khai tham chiếu của Python) là vị trí trong bộ nhớ là một chi tiết triển khai. Các triển khai khác của Python (như Jython hoặc IronPython) có thể dễ dàng có một triển khai khác id.

Vậy trường hợp sử dụng để làm isgì? PEP8 mô tả :

Việc so sánh với các singletons như vậy Nonephải luôn luôn được thực hiện với ishoặc is not, không bao giờ là các toán tử đẳng thức.

Câu hỏi

Bạn hỏi và nêu câu hỏi sau (có mã):

Tại sao những điều sau đây hoạt động bất ngờ trong Python?

>>> a = 256
>>> b = 256
>>> a is b
True           # This is an expected result

không phải là một kết quả mong đợi. Tại sao nó được mong đợi? Nó chỉ có nghĩa là các số nguyên có giá trị được 256tham chiếu bởi cả hai ablà cùng một thể hiện của số nguyên. Số nguyên là bất biến trong Python, do đó chúng không thể thay đổi. Điều này sẽ không có tác động đến bất kỳ mã. Nó không nên được mong đợi. Nó chỉ đơn thuần là một chi tiết thực hiện.

Nhưng có lẽ chúng ta nên vui mừng vì không có một thể hiện riêng biệt mới trong bộ nhớ mỗi khi chúng ta nêu một giá trị bằng 256.

>>> a = 257
>>> b = 257
>>> a is b
False          # What happened here? Why is this False?

Có vẻ như chúng ta hiện có hai trường hợp số nguyên riêng biệt với giá trị 257trong bộ nhớ. Vì số nguyên là bất biến, điều này làm lãng phí bộ nhớ. Hãy hy vọng chúng ta không lãng phí nhiều thứ. Có lẽ chúng ta không. Nhưng hành vi này không được đảm bảo.

>>> 257 is 257
True           # Yet the literal numbers compare properly

Chà, có vẻ như việc triển khai Python cụ thể của bạn đang cố gắng trở nên thông minh và không tạo ra các số nguyên có giá trị dự phòng trong bộ nhớ trừ khi nó phải như vậy. Dường như bạn cho biết bạn đang sử dụng triển khai Python tham chiếu, đó là CPython. Tốt cho CPython.

Sẽ tốt hơn nữa nếu CPython có thể làm điều này trên toàn cầu, nếu nó có thể làm như vậy với giá rẻ (vì sẽ có chi phí trong việc tra cứu), có lẽ việc thực hiện khác có thể.

Nhưng đối với tác động lên mã, bạn không nên quan tâm nếu một số nguyên là một thể hiện cụ thể của một số nguyên. Bạn chỉ nên quan tâm giá trị của thể hiện đó là gì và bạn sẽ sử dụng các toán tử so sánh bình thường cho điều đó, tức là ==.

Có gì iskhông

iskiểm tra xem idhai đối tượng có giống nhau không Trong CPython, idvị trí trong bộ nhớ, nhưng nó có thể là một số nhận dạng duy nhất khác trong một triển khai khác. Để phục hồi điều này với mã:

>>> a is b

giống như

>>> id(a) == id(b)

Tại sao chúng ta lại muốn sử dụng is?

Đây có thể là một kiểm tra rất nhanh để nói, kiểm tra xem hai chuỗi rất dài có giá trị như nhau không. Nhưng vì nó áp dụng cho tính duy nhất của đối tượng, do đó chúng tôi đã giới hạn các trường hợp sử dụng cho nó. Trong thực tế, chúng tôi chủ yếu muốn sử dụng nó để kiểm tra None, đó là một singleton (một trường hợp duy nhất tồn tại ở một nơi trong bộ nhớ). Chúng tôi có thể tạo ra các singleton khác nếu có tiềm năng kết hợp chúng, mà chúng tôi có thể kiểm tra is, nhưng chúng tương đối hiếm. Đây là một ví dụ (sẽ hoạt động trong Python 2 và 3), vd

SENTINEL_SINGLETON = object() # this will only be created one time.

def foo(keyword_argument=None):
    if keyword_argument is None:
        print('no argument given to foo')
    bar()
    bar(keyword_argument)
    bar('baz')

def bar(keyword_argument=SENTINEL_SINGLETON):
    # SENTINEL_SINGLETON tells us if we were not passed anything
    # as None is a legitimate potential argument we could get.
    if keyword_argument is SENTINEL_SINGLETON:
        print('no argument given to bar')
    else:
        print('argument to bar: {0}'.format(keyword_argument))

foo()

Bản in nào:

no argument given to foo
no argument given to bar
argument to bar: None
argument to bar: baz

Và vì vậy chúng ta thấy, với isvà một sentinel, chúng ta có thể phân biệt giữa khi nào barđược gọi mà không có đối số và khi nào nó được gọi với None. Đây là các trường hợp sử dụng chính cho is- không sử dụng nó để kiểm tra sự bằng nhau của các số nguyên, chuỗi, bộ dữ liệu hoặc những thứ khác như thế này.


"Đây là những trường hợp sử dụng chính cho is- không sử dụng nó để kiểm tra sự bằng nhau của số nguyên, chuỗi, bộ dữ liệu hoặc những thứ khác như thế này." Tuy nhiên, tôi đang cố gắng tích hợp một máy trạng thái đơn giản vào lớp của mình và vì các trạng thái là các giá trị mờ đục mà thuộc tính duy nhất có thể quan sát được là giống hệt hoặc khác nhau, nên chúng có thể so sánh được với chúng is. Tôi có kế hoạch sử dụng chuỗi thực tập như các trạng thái. Tôi đã thích các số nguyên đơn giản, nhưng tiếc là Python không thể thực hiện các số nguyên ( 0 is 0là một chi tiết triển khai).
Alexey

@Alexey nghe có vẻ như bạn cần enum? stackoverflow.com/questions/37601644/ từ
Aaron Hall

Có lẽ, cảm ơn, không biết về họ. Đây có thể là một bổ sung thích hợp để bạn trả lời IMO.
Alexey

Có thể sử dụng một số đối tượng câm như câu thần chú trong câu trả lời của bạn sẽ là một giải pháp nhẹ hơn ...
Alexey

Các enum @Alexey nằm trong thư viện tiêu chuẩn Python 3 và điều đó có lẽ sẽ khuyến khích mã của bạn có ý nghĩa hơn một chút so với các câu lệnh trần.
Aaron Hall

60

Nó phụ thuộc vào việc bạn đang tìm xem liệu 2 thứ bằng nhau hay cùng một đối tượng.

iskiểm tra xem chúng có phải là cùng một đối tượng không, không chỉ bằng nhau. Các int nhỏ có lẽ đang trỏ đến cùng một vị trí bộ nhớ cho hiệu quả không gian

In [29]: a = 3
In [30]: b = 3
In [31]: id(a)
Out[31]: 500729144
In [32]: id(b)
Out[32]: 500729144

Bạn nên sử dụng ==để so sánh sự bình đẳng của các đối tượng tùy ý. Bạn có thể chỉ định hành vi với __eq____ne__các thuộc tính.


Thumbs up cho thực sự giải thích làm thế nào để so sánh các đối tượng tùy ý, như OP yêu cầu !!
Joooeey

54

Tôi đến trễ nhưng, bạn muốn có một số nguồn với câu trả lời của bạn? Tôi sẽ thử và diễn đạt điều này theo cách giới thiệu để nhiều người có thể làm theo.


Một điều tốt về CPython là bạn thực sự có thể thấy nguồn cho việc này. Tôi sẽ sử dụng các liên kết cho bản phát hành 3.5 , nhưng việc tìm các liên kết 2.x tương ứng là không đáng kể.

Trong CPython, hàm C-API xử lý việc tạo một intđối tượng mới là PyLong_FromLong(long v). Mô tả cho chức năng này là:

Việc triển khai hiện tại giữ một mảng các đối tượng số nguyên cho tất cả các số nguyên trong khoảng từ -5 đến 256, khi bạn tạo một int trong phạm vi đó, bạn thực sự chỉ cần lấy lại một tham chiếu đến đối tượng hiện có . Vì vậy, có thể thay đổi giá trị của 1. Tôi nghi ngờ hành vi của Python trong trường hợp này là không xác định. :-)

(Chữ nghiêng của tôi)

Không biết về bạn nhưng tôi thấy điều này và nghĩ: Hãy tìm mảng đó!

Nếu bạn không quan tâm đến mã C đang triển khai CPython, bạn nên ; mọi thứ đều có tổ chức và dễ đọc Đối với trường hợp của chúng ta, chúng ta cần xem trong Objectsthư mục con của cây thư mục mã nguồn chính .

PyLong_FromLonggiao dịch với longcác đối tượng vì vậy không khó để suy luận rằng chúng ta cần nhìn trộm bên trong longobject.c. Sau khi nhìn vào bên trong, bạn có thể nghĩ mọi thứ thật hỗn loạn; họ, nhưng đừng sợ, chức năng mà chúng tôi đang tìm kiếm đang làm lạnh ở dòng 230 đang chờ chúng tôi kiểm tra. Đây là một chức năng nhỏ để cơ thể chính (không bao gồm khai báo) dễ dàng được dán ở đây:

PyObject *
PyLong_FromLong(long ival)
{
    // omitting declarations

    CHECK_SMALL_INT(ival);

    if (ival < 0) {
        /* negate: cant write this as abs_ival = -ival since that
           invokes undefined behaviour when ival is LONG_MIN */
        abs_ival = 0U-(unsigned long)ival;
        sign = -1;
    }
    else {
        abs_ival = (unsigned long)ival;
    }

    /* Fast path for single-digit ints */
    if (!(abs_ival >> PyLong_SHIFT)) {
        v = _PyLong_New(1);
        if (v) {
            Py_SIZE(v) = sign;
            v->ob_digit[0] = Py_SAFE_DOWNCAST(
                abs_ival, unsigned long, digit);
        }
        return (PyObject*)v; 
}

Bây giờ, chúng tôi không có C -code-haxxorz nhưng chúng tôi cũng không ngu ngốc, chúng tôi có thể thấy rằng CHECK_SMALL_INT(ival);nhìn trộm tất cả chúng tôi một cách quyến rũ; chúng ta có thể hiểu nó có liên quan đến điều này. Hãy cùng kiểm tra nào:

#define CHECK_SMALL_INT(ival) \
    do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
        return get_small_int((sdigit)ival); \
    } while(0)

Vì vậy, đó là một macro gọi hàm get_small_intnếu giá trị ivalthỏa mãn điều kiện:

if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)

Vậy là gì NSMALLNEGINTSNSMALLPOSINTS? Macro! Họ đây rồi :

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif

Vì vậy, điều kiện của chúng tôi là if (-5 <= ival && ival < 257)cuộc gọi get_small_int.

Tiếp theo, hãy nhìn vào get_small_inttất cả vinh quang của nó (tốt, chúng ta sẽ chỉ nhìn vào cơ thể của nó bởi vì đó là nơi có những điều thú vị):

PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);

Được rồi, khai báo a PyObject, khẳng định rằng điều kiện trước giữ và thực thi phép gán:

v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];

small_intstrông rất giống mảng đó chúng tôi đã tìm kiếm, và nó là vậy! Chúng tôi chỉ có thể đọc tài liệu chết tiệt và chúng tôi sẽ biết tất cả cùng! :

/* Small integers are preallocated in this array so that they
   can be shared.
   The integers that are preallocated are those in the range
   -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];

Vì vậy, yup, đây là chàng trai của chúng tôi. Khi bạn muốn tạo một cái mới inttrong phạm vi, [NSMALLNEGINTS, NSMALLPOSINTS)bạn sẽ chỉ cần lấy lại một tham chiếu đến một đối tượng đã tồn tại đã được phổ biến.

Vì tham chiếu đề cập đến cùng một đối tượng, việc phát hành id()trực tiếp hoặc kiểm tra danh tính với isnó sẽ trả lại chính xác cùng một điều.

Nhưng, khi nào họ được phân bổ ??

Trong quá trình khởi tạo,_PyLong_Init Python sẽ sẵn sàng nhập vào một vòng lặp for làm điều này cho bạn:

for (ival = -NSMALLNEGINTS; ival <  NSMALLPOSINTS; ival++, v++) {

Kiểm tra nguồn để đọc cơ thể vòng lặp!

Tôi hy vọng lời giải thích của tôi đã làm cho bạn C mọi thứ rõ ràng (chơi chữ rõ ràng có ý định).


Nhưng , 257 is 257? Có chuyện gì vậy?

Điều này thực sự dễ giải thích hơn, và tôi đã cố gắng làm điều đó rồi ; đó là do thực tế là Python sẽ thực thi câu lệnh tương tác này dưới dạng một khối duy nhất:

>>> 257 is 257

Trong quá trình bổ sung tuyên bố này, CPython sẽ thấy rằng bạn có hai nghĩa đen phù hợp và sẽ sử dụng cùng một PyLongObjectđại diện 257. Bạn có thể thấy điều này nếu bạn tự biên soạn và kiểm tra nội dung của nó:

>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)

Khi CPython thực hiện thao tác, giờ nó sẽ tải chính xác cùng một đối tượng:

>>> import dis
>>> dis.dis(codeObj)
  1           0 LOAD_CONST               0 (257)   # dis
              3 LOAD_CONST               0 (257)   # dis again
              6 COMPARE_OP               8 (is)

Vậy issẽ trở về True.


37

Như bạn có thể kiểm tra trong tập tin nguồn intobject.c , Python lưu trữ các số nguyên nhỏ để đạt hiệu quả. Mỗi khi bạn tạo một tham chiếu đến một số nguyên nhỏ, bạn đang đề cập đến số nguyên nhỏ được lưu trong bộ nhớ cache, không phải là một đối tượng mới. 257 không phải là một số nguyên nhỏ, vì vậy nó được tính như một đối tượng khác.

Nó là tốt hơn để sử dụng ==cho mục đích đó.


19

Tôi nghĩ rằng giả thuyết của bạn là chính xác. Thử nghiệm với id(danh tính của đối tượng):

In [1]: id(255)
Out[1]: 146349024

In [2]: id(255)
Out[2]: 146349024

In [3]: id(257)
Out[3]: 146802752

In [4]: id(257)
Out[4]: 148993740

In [5]: a=255

In [6]: b=255

In [7]: c=257

In [8]: d=257

In [9]: id(a), id(b), id(c), id(d)
Out[9]: (146349024, 146349024, 146783024, 146804020)

Dường như các con số <= 255được coi là nghĩa đen và bất cứ điều gì ở trên được đối xử khác nhau!


1
Đó là bởi vì các đối tượng đại diện cho các giá trị từ -5 đến +256 được tạo tại thời điểm Khởi động - và vì vậy tất cả việc sử dụng các giá trị đó được sử dụng cho đối tượng dựng sẵn. Hầu như tất cả các tham chiếu đến các số nguyên ngoài phạm vi đó tạo ra một đối tượng nội bộ mới mỗi khi chúng được tham chiếu. Tôi nghĩ rằng việc sử dụng thuật ngữ theo nghĩa đen là khó hiểu - nghĩa đen thường đề cập đến bất kỳ giá trị nào được nhập vào một đoạn mã - vì vậy tất cả số trong mã nguồn là chữ.
Tony Suffolk 66

13

Đối với các đối tượng giá trị bất biến, như ints, chuỗi hoặc datetimes, danh tính đối tượng không đặc biệt hữu ích. Tốt hơn là nghĩ về sự bình đẳng. Danh tính về cơ bản là một chi tiết triển khai cho các đối tượng giá trị - vì chúng không thay đổi, nên không có sự khác biệt hiệu quả giữa việc có nhiều ref cho cùng một đối tượng hoặc nhiều đối tượng.


12

Có một vấn đề khác không được chỉ ra trong bất kỳ câu trả lời hiện có nào. Python được phép hợp nhất bất kỳ hai giá trị bất biến nào và các giá trị int nhỏ được tạo trước không phải là cách duy nhất điều này có thể xảy ra. Một triển khai Python không bao giờ được đảm bảo để làm điều này, nhưng tất cả chúng đều làm điều đó không chỉ cho các int nhỏ.


Đối với một điều, có một số giá trị trước tạo khác, chẳng hạn như trống rỗng tuple, strbytes, và một số chuỗi ngắn (trong CPython 3.6, đó là 256 ký tự duy nhất Latin-1 chuỗi). Ví dụ:

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

Nhưng ngoài ra, ngay cả các giá trị không được tạo trước cũng có thể giống hệt nhau. Hãy xem xét các ví dụ sau:

>>> c = 257
>>> d = 257
>>> c is d
False
>>> e, f = 258, 258
>>> e is f
True

Và điều này không giới hạn ở intcác giá trị:

>>> g, h = 42.23e100, 42.23e100
>>> g is h
True

Rõ ràng, CPython không đi kèm với floatgiá trị được tạo trước cho 42.23e100. Vì vậy, những gì đang xảy ra ở đây?

Trình biên dịch CPython sẽ sáp nhập giá trị không đổi của một số loại tiếng-bất biến như int, float, str, bytes, trong đơn vị biên soạn cùng. Đối với một mô-đun, toàn bộ mô-đun là một đơn vị biên dịch, nhưng tại trình thông dịch tương tác, mỗi câu lệnh là một đơn vị biên dịch riêng biệt. Từcdđược định nghĩa trong các câu lệnh riêng biệt, các giá trị của chúng không được hợp nhất. Vì efđược định nghĩa trong cùng một tuyên bố, các giá trị của chúng được hợp nhất.


Bạn có thể thấy những gì đang diễn ra bằng cách phân tách mã byte. Hãy thử xác định một chức năng e, f = 128, 128và sau đó gọidis.dis đó và bạn sẽ thấy rằng có một giá trị không đổi duy nhất(128, 128)

>>> def f(): i, j = 258, 258
>>> dis.dis(f)
  1           0 LOAD_CONST               2 ((128, 128))
              2 UNPACK_SEQUENCE          2
              4 STORE_FAST               0 (i)
              6 STORE_FAST               1 (j)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
>>> f.__code__.co_consts
(None, 128, (128, 128))
>>> id(f.__code__.co_consts[1], f.__code__.co_consts[2][0], f.__code__.co_consts[2][1])
4305296480, 4305296480, 4305296480

Bạn có thể nhận thấy rằng trình biên dịch đã được lưu trữ 128 dưới dạng hằng số mặc dù nó không thực sự được sử dụng bởi mã byte, điều này cho bạn ý tưởng về trình biên dịch của CPython tối ưu hóa như thế nào. Điều đó có nghĩa là các bộ dữ liệu (không trống) thực sự không được hợp nhất:

>>> k, l = (1, 2), (1, 2)
>>> k is l
False

Đặt nó trong một hàm, disnó và nhìn vào các co_constsbộ dữ liệu a 1và a 2, hai (1, 2)bộ dữ liệu có chung 12 không giống nhau, và một ((1, 2), (1, 2))bộ dữ liệu có hai bộ dữ liệu bằng nhau rõ ràng.


Có thêm một tối ưu hóa mà CPython thực hiện: thực hiện chuỗi. Không giống như trình biên dịch liên tục, điều này không bị giới hạn ở các mã nguồn:

>>> m = 'abc'
>>> n = 'abc'
>>> m is n
True

Mặt khác, nó bị giới hạn ở strloại và chuỗi loại lưu trữ nội bộ "ascii compact", "compact" hoặc "legacy ready" và trong nhiều trường hợp chỉ "ascii compact" mới được sử dụng.


Ở mọi mức độ, các quy tắc cho các giá trị phải là, có thể hoặc không thể khác biệt khác nhau giữa triển khai và thực hiện và giữa các phiên bản của cùng một triển khai và thậm chí giữa các lần chạy của cùng một mã trên cùng một bản sao của cùng một triển khai .

Có thể đáng để học các quy tắc cho một Python cụ thể vì sự thú vị của nó. Nhưng nó không đáng để dựa vào chúng trong mã của bạn. Quy tắc an toàn duy nhất là:

  • Không viết mã giả sử hai giá trị bất biến được tạo bằng nhau nhưng được tạo riêng biệt là giống hệt nhau (không sử dụng x is y , sử dụng x == y)
  • Không viết mã giả sử hai giá trị bất biến bằng nhau nhưng được tạo riêng biệt là khác biệt (không sử dụng x is not y, sử dụng x != y)

Hay nói cách khác, chỉ sử dụng isđể kiểm tra các singletons được ghi lại (như None) hoặc chỉ được tạo ở một nơi trong mã (như _sentinel = object()thành ngữ).


Lời khuyên ít khó hiểu hơn chỉ đơn giản là: không sử dụng x is yđể so sánh, sử dụng x == y. Tương tự như vậy, không sử dụng x is not y, sử dụngx != y
smci

Vì vậy, nhìn vào câu hỏi này , tại sao lại nằm a=257; b=257trên một dòng a is bTrue
Joe

8

is toán tử bình đẳng danh tính (hoạt động như id(a) == id(b)); chỉ là hai số bằng nhau không nhất thiết phải cùng một đối tượng. Vì lý do hiệu suất, một số số nguyên nhỏ xảy ra được ghi nhớ nên chúng sẽ có xu hướng giống nhau (điều này có thể được thực hiện vì chúng không thay đổi).

===Mặt khác, toán tử của PHP được mô tả là kiểm tra sự bình đẳng và loại: x == y and type(x) == type(y)theo nhận xét của Paulo Freitas. Điều này sẽ đủ cho các số phổ biến, nhưng khác isvới các lớp định nghĩa __eq__theo cách vô lý:

class Unequal:
    def __eq__(self, other):
        return False

PHP rõ ràng cho phép điều tương tự đối với các lớp "tích hợp" (mà tôi nghĩ là được thực hiện ở cấp độ C, không phải trong PHP). Việc sử dụng ít vô lý hơn có thể là một đối tượng hẹn giờ, có giá trị khác nhau mỗi lần nó được sử dụng làm số. Khá nhiều lý do tại sao bạn muốn mô phỏng Visual Basic Nowthay vì chỉ ra rằng đó là một đánh giá vớitime.time() tôi không biết.

Greg Hewgill (OP) đã đưa ra một nhận xét làm rõ "Mục tiêu của tôi là so sánh nhận dạng đối tượng, thay vì bình đẳng về giá trị. Ngoại trừ các con số, nơi tôi muốn coi danh tính đối tượng giống như bình đẳng về giá trị."

Điều này sẽ có một câu trả lời khác, vì chúng ta phải phân loại mọi thứ theo số hoặc không, để chọn xem chúng ta so sánh với ==hay is. CPython định nghĩa giao thức số , bao gồm PyNumber_Check, nhưng điều này không thể truy cập được từ chính Python.

Chúng ta có thể thử sử dụng isinstancevới tất cả các loại số mà chúng ta biết, nhưng điều này chắc chắn sẽ không đầy đủ. Mô-đun loại chứa danh sách StringTypes nhưng không có NumberTypes. Kể từ Python 2.6, các lớp số được xây dựng có một lớp cơ sở numbers.Number, nhưng nó có cùng một vấn đề:

import numpy, numbers
assert not issubclass(numpy.int16,numbers.Number)
assert issubclass(int,numbers.Number)

Nhân tiện, NumPy sẽ tạo ra các trường hợp riêng biệt với số lượng thấp.

Tôi thực sự không biết câu trả lời cho biến thể của câu hỏi này. Tôi cho rằng về mặt lý thuyết người ta có thể sử dụng ctypes để gọi PyNumber_Check, nhưng ngay cả chức năng đó đã được tranh luận , và nó chắc chắn không thể mang theo được. Chúng ta sẽ phải ít đặc biệt hơn về những gì chúng ta thử nghiệm bây giờ.

Cuối cùng, vấn đề này xuất phát từ Python ban đầu không có cây loại với các vị từ như Scheme's number? hoặc lớp kiểu Num của Haskell . kiểm tra danh tính đối tượng, không bình đẳng giá trị. PHP cũng có một lịch sử đầy màu sắc, trong đó rõ ràng chỉ hành xử trên các đối tượng trong PHP5, nhưng không phải là PHP4 . Đó là những khó khăn ngày càng tăng của việc di chuyển qua các ngôn ngữ (bao gồm cả phiên bản của một ngôn ngữ). is===is


4

Nó cũng xảy ra với chuỗi:

>>> s = b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Bây giờ mọi thứ có vẻ tốt.

>>> s = 'somestr'
>>> b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

Điều đó cũng được mong đợi.

>>> s1 = b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, True, 4555308080, 4555308080)

>>> s1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, False, 4555308176, 4555308272)

Bây giờ thật bất ngờ.


Đã xảy ra khi điều này - đồng ý, rằng thậm chí kỳ lạ hơn. Vì vậy, tôi đã chơi với nó, và nó kỳ lạ hơn - liên quan đến không gian. Ví dụ, chuỗi 'xx'như mong đợi, như là 'xxx', nhưng 'x x'không.
Brian

2
Đó là bởi vì nó trông giống như một biểu tượng nếu không có không gian trong đó. Tên được tự động thực hiện, vì vậy nếu có bất kỳ tên xxnào được đặt ở bất kỳ đâu trong phiên Python của bạn, chuỗi đó đã được thực hiện; và có thể có một heuristic làm điều đó nếu nó chỉ giống với một cái tên. Như với những con số, điều này có thể được thực hiện bởi vì chúng là bất biến. docs.python.org/2/library/functions.html#intern guilload.com/python-string-interning
Yann Vernier

3

Có gì mới trong Python 3.8: Thay đổi hành vi của Python :

Trình biên dịch hiện tạo một SyntaxWarning khi kiểm tra danh tính ( isis not) được sử dụng với một số loại chữ nhất định (ví dụ: chuỗi, ints). Chúng thường có thể hoạt động một cách tình cờ trong CPython, nhưng không được đảm bảo bởi thông số ngôn ngữ. Cảnh báo khuyên người dùng sử dụng các bài kiểm tra bình đẳng ( ==!=) thay vào đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.