This post is part of the Python Crash Course series. The chronological order on how to read the articles is to be found on the agenda.
In python you can manipulate different objects. If you take your pocket calculator, you might have mostly played around with integers and floating numbers – e.g. when performing additions and adding numbers together. Python extends those capabilities – not only allowing you to manipulate integers – but also to interact with a variety of different objects/types.
Short example
Let’s create a variable a
and check its type:
zsh> python
>>> a = 42
>>> type(a)
<class `ìnt`>
You can see that a
is of type int
– which refers to integers.
Note: to learn how to start python on the terminal and to interact with it, see: python-via-command-line.
The different built-in types
By default, python provides different types which are already built-in. See: Built-in Types.
All of them possess their own associated methods so you can perform operations on them – e.g. integers additions etc.:
- Numeric Types:
int
,float
,complex
; - Boolean Type:
bool
; - Sequence Types:
list
,tuple
,range
; - Text Sequence Type:
str
; - Set Types:
set
,frozenset
; - Mapping Types:
dict
.
The most important and mainly used are: int
, bool
, list
, tuple
, str
, dict
and set
.
type() vs. isinstance() vs is
There are situations where you want to check if the object you are dealing with is of a specific type. You can do it using the isinstance()
method:
zsh> python
>>> isinstance("my_string", str)
True
>>> isinstance(42.0, int)
False
You could have also used the following:
>>> type(42.0) == float
True
However, there are differences between the both:
(1) From a design perspective, it is better to use ==
to check the value of a variable, not its type.
(2) To compare types, isinstance()
is expected to be slightly faster, even though negligible on the latest python versions:
zsh> python
>>> from timeit import timeit
>>> timeit("isinstance(42.0, float)")
0.06043211100040935
>>> timeit("42.0 == float")
0.07633306799834827
(3) Sometime you do not want to compare if the object is strictly of a specific type but rather if it behaves like so or inherits its properties.
For instance, a str
object has some methods that allow us to perform specific operations on it, like turning the text into uppercase:
zsh> python
>>> "This is my string".upper()
'THIS IS MY STRING'
Let’s now imagine that you want to create your own str
type which inherits the str
properties – such as the capacity to change the text case via upper()
or lower()
– but supplemented with your own custom methods:
zsh> python
>>> my_custom_str = ExtendedString("foo")
This custom type of yours is still to be considered as a str
as it inherits the main properties of the str
type:
>>> my_custom_str.upper()
"FOO"
However, python sees different:
>>> type(my_custom_str) is str
False
>>> type(my_custom_str) == str
False
>>> isinstance(my_custom_str, str)
True
What is important to remember here is that isinstance()
checks if my_custom_str
is – overall – a subclass of str
(it is, because ExtendedString
inherits from str
).
On the other hand, is
or ==
check if my_custom_str
is an instance of str
(it is not directly the case, because it is – strictly speaking – an instance of ExtendedString
).
Note: As for the difference between is
and ==
, is
will return True is two variables point to the same object in memory while ==
returns True if the values hold by the variables are equal.
zsh> python
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a == b # values are equal
True
>>> a is b # but it's 2 different object
False
>>> a = b # a becomes b
>>> a is b
True
>>> b.append(4) # thus, if b changes
>>> b
[1, 2, 3, 4]
>>> a
[1, 2, 3, 4] # a changes
In practice, you can use the above concept e.g. to surcharge and add your strings the possibility to turn your texts into Spongemock case. You will have to extend and add this functionality by yourself as this feature is not natively present:
class ExtendedString(str):
def spongebob(self) -> str:
return "".join(
char.upper() if i%2 == 0 else char.lower()
for i, char in enumerate(self.__str__())
)
You can imagine a following usage:
zsh> my_str = ExtendedString("The cake is a lie")
zsh> my_str.spongebob()
ThE CaKe iS A LiE
Going further about inheritance
If you go upstream, you can see that str
itself inherits from Sequence
. Thus, all str
objects are also of type Sequence
:
>>> from collections.abc import Sequence
>>> my_str = "foo"
>>> isinstance(my_str, str)
True
>>> isinstance(my_str, Sequence)
True
However, going up the stream even further – by winding the links all the way up – you will ultimately notice that all python types inherits from the catch-all python object
:
>>> isinstance(my_str, object)
True
>>> isinstance(42, object)
True
Data vs. Common Classes
In python you can also create your own types using the dataclasses
module:
from dataclasses import dataclass
@dataclass
class Point():
x: float
y: float
The main difference between common classes – like the ExtendedString
one previously created – and data classes lies in the fact that data classes are not expected to contain any logic or methods.
Data classes are strictly geared toward storing data, not performing operation on it.