Типы, для которых ключевое слово "is" может быть эквивалентно оператору равенства в Python - PullRequest
3 голосов
/ 10 июля 2010

Для некоторых типов в 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 и чувствую, что это важно понимать. Есть ли какие-либо материалы, которые вы бы порекомендовали понять для решения подобных проблем?

Ответы [ 3 ]

9 голосов
/ 10 июля 2010

Оператор 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 */
7 голосов
/ 10 июля 2010

Связана ли == / эквивалентность с неизменностью?

Нет.

См. Python '==' vs '- это' сравнение строк, 'иногда' терпит неудачу, почему? о том, почему он работает со строками, и Оператор Python «is» неожиданно ведет себя с целыми числами о том, почему он работает с целыми числами (таким образом, bool по той же причине).

Вышеуказанное поведение или детали реализации?

Детали реализации.

как узнать, приведет ли назначение к копии объектаделается или ссылка на него делается?

Назначение всегда по ссылке.Копирование выполняется только в том случае, если вы явно используете copy.copy (или что-то в этом роде).

Редактировать: «По ссылке» я не имею в виду ссылки в C ++.Назначение Python будет перепривязывать переменную.Это больше похоже на

// int* a, *b;
a = new int(1);
b = a;
a = new int(2);
printf("%d\n", *b);
1 голос
/ 14 апреля 2012

Если вы пришли из C или C ++ фона, то, вероятно, проще объяснить, что все переменные в Python действительно являются указателями.Таким образом, оператор

 a = 1

действительно примерно похож на

 Object *a = new Integer(1);

. Оператор is проверяет равенство указателей, а оператор == вместо этого включает вычисления, которые зависят от типа.объектов.

Небольшое усложнение этой схемы состоит в том, что если объекты являются неизменяемыми (например, целое число), то из соображений эффективности приведенный выше код действительно немного больше похож на

 int *a = getFromCacheOrCreateNewInteger(1);

, поэтомуиногда (но это деталь реализации) неизменяемые объекты могут быть одним и тем же объектом для is, даже если они созданы независимо с логической точки зрения (например, это может быть 1+1 is 2-1, но без гарантий):

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

Чтобы добавить еще больше недоразумений, нужно сказать, что даже если переменные alla в Python действительно являются указателями, довольно удивительно, что в Python нет концепции указателя, другими словами, нет способа передать функцию, в которой из ваших переменных что-тодолжны быть сохранены.

Для этого вам нужно либо передать имя (если переменная является глобальной), либо передатьвызывающая функция (если переменная является локальной).Это на самом деле не является большим раздражением, так как в большинстве случаев вам просто нужно несколько возвращаемых значений, и это уже хорошо обрабатывается Python:

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

a, b = foo(12)

Еще одно дополнительное раздражение заключается в том, что если вам действительно нужно передать установщик дляЛокальная переменная без имени (например, элемент списка) не может быть анонимной 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 value: mylocal.__setitem__(0, value), но это более или менее нежелательный инцидент; lambda - этонастолько ненавистный в Python, что, вероятно, когда они обнаружат, что это возможно, в язык будет добавлено еще одно ограничение, запрещающее это ;-)).

Если вы хотите изменить именованный локальный код, это просто невозможно в Python 2.x (но возможно с Python 3.x и nonlocal).

По вопросу о том, когда выполняются копии и когда вместо этого копируется только указатель, ответ очень прост.Python никогда не делает копию автоматически ... если вы хотите сделать копию, вы должны явно сделать это самостоятельно.Вот почему, например, часто можно увидеть код, подобный следующему:

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

Здесь обозначение [:] означает, что экземпляр класса хочет сохранить копию переданного списка, так что есливы создаете экземпляр Polygon со списком точек, а затем модифицируете этот список, тогда геометрия не меняется.

...