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)
type(x) is list
tests whether the expressionstype(x)
andlist
evaluate to the same object andtype(x) == list
tests whether the two objects have equivalent (in some sense) values.type(x) == list
should evaluate toTrue
as long asx
is a list. But cantype(x)
evaluate to a different object from whatlist
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.