Home > Blockchain >  type(x) is list vs type(x) == list
type(x) is list vs type(x) == list

Time:09-04

In Python, suppose one wants to test whether the variable x is a reference to a list object. Is there a difference between if type(x) is list: and if type(x) == list:? This is how I understand it. (Please correct me if I am wrong)

  1. type(x) is list tests whether the expressions type(x) and list evaluate to the same object and type(x) == list tests whether the two objects have equivalent (in some sense) values.
  2. type(x) == list should evaluate to True as long as x is a list. But can type(x) evaluate to a different object from what list refers to?

What exactly does the expression list evaluate to? (I am new to Python, coming from C , and still can't quite wrap my head around the notion that types are also objects.) Does list point to somewhere in memory? What data live there?

CodePudding user response:

The "one obvious way" to do it, that will preserve the spirit of "duck typing" is isinstance(x, list). Rather, in most cases, one's code won't be specific to a list, but could work with any sequence (or maybe it needs a mutable sequence). So the recomendation is actually:

from collections.abc import MutableSequence

... 

if isinstance(x, MutableSequence):
   ...

Now, going into your specific questions:

What exactly does the expression list evaluate to? Does list point to somewhere in memory? What data live there?

list in Python points to a class. A class that can be inherited, extended, etc...and thanks to a design choice of Python, the syntax for creating an instance of a class is indistinguishable from calling a function. So, when teaching Python to novices, one could just casually mention that list is a "function" (I prefer not, since it is straightout false - the generic term for both functions and classes in regards to that they can be "called" and will return a result is callable)

Being a class, list does live in a specific place in memory - the "where" does not make any difference when coding in Python - but yes, there is one single place in memory where a class, which in Python is also an object, an instance of type, exists as a data structure with pointers to the various methods that one can use in a Python list.

As for:

type(x) is list tests whether the expressions type(x) and list evaluate to the same object and type(x) == list tests whether the two objects have equivalent (in some sense) values.

That is correct: is is a special operator that unlike others cannot be overriden for any class and checks for object itentity - in the cPython implementation, it checks if both operands are at the same memory address (but keep in mind that that address, though visible through the built-in function id, behaves as if it is opaque from Python code). As for the "sense" in which objects are "equal" in Python: one can always override the behavior of the == operator for a given object, by creating the special named method __eq__ in its class. (The same is true for each other operator - the language data model lists all available "magic" methods). For lists, the implemented default comparison automatically compares each element recursively (calling the .__eq__ method for each item pair in both lists, if they have the same size to start with, of course)

type(x) == list should evaluate to True as long as x is a list. But can type(x) evaluate to a different object from what list refers to?

Not if "x" is a list proper: type(x) will always evaluate to list. But == would fail if x were an instance of a subclass of list, or another Sequence implementation: that is why it is always better to compare classes using the builtins isinstance and issubclass.

CodePudding user response:

is checks exact identity, and only works if there is exactly one and only one of the list type. Fortunately, that's true for the list type (unless you've done something silly), so it usually works.

== test standard equality, which is equivalent to is for most types including list

Taking those together, there is no effective difference between type(x) is list and type(x) == list, but the former is construction better describes what's happening under the hood.

Consider avoiding use of the type(x) is sometype construction in favor of the isinstance function instead, because it will work for inherited classes too:

x = [1, 2, 3]
isinstance(x, list)  # True

class Y(list):
    '''A class that inherits from list'''
    ...

y = Y([1, 2, 3])
isinstance(y, list)  # True
type(y) is list  # False

Better yet, if you really just want to see if something is list-like, then use isinstance with either typing or collections.abc like so:

import collections.abc

x = [1, 2, 3]
isinstance(x, collections.abc.Iterable)  # True
isinstance(x, collections.abc.Sequence)  # True

x = set([1, 2, 3])
isinstance(x, collections.abc.Iterable)  # True
isinstance(x, collections.abc.Sequence)  # False

Note that lists are both Iterable (usable in a for loop) and a Sequence (ordered). A set is Iterable but not a Sequence, because you can use it in a for loop, but it isn't in any particular order. Use Iterable when you want to use in a for loop, or Sequence if it's important that the list be in a certain order.

Final note: The dict type is Mapping, but also counts as Iterable since you can loop over it's keys in a for loop.

  • Related