This chapter deepens our understanding of some concepts seen in http://www.andreamarino.it/python/thinkcspy/.

We quote from the official help system.

First,

Objectsare Python’s abstraction for data. All data in a Python

program is represented by objects or by relations between objects. (In a sense, and in conformance to Von Neumann’s model of a “stored program computer”, code is also represented by objects.)

Second,

Every object has an identity, a type and a value. An object’s

*identity* never changes once it has been created; you may think of it
as the object’s address in memory. The ‘"is"’ operator compares the
identity of two objects; the "id()" function returns an integer
representing its identity.

where

In [2]:

```
id
```

Out[2]:

<function id(obj, /)>

In [3]:

```
help(id)
```

In [4]:

```
help(help)
```

In [8]:

```
help(object)
```

In [12]:

```
o = object()
```

In [13]:

```
o
```

Out[13]:

<object at 0x10d85b310>

In [15]:

```
id(o)
```

Out[15]:

4521833232

In [16]:

```
w = object()
```

In [17]:

```
o is w
```

Out[17]:

False

In [18]:

```
o is o
```

Out[18]:

True

In [20]:

```
type(object)
```

Out[20]:

type

In [21]:

```
type(type)
```

Out[21]:

type

In [22]:

```
help(type)
```

In [23]:

```
type(3.14)
```

Out[23]:

float

In [24]:

```
type([1,2,3])
```

Out[24]:

list

In [25]:

```
type(help)
```

Out[25]:

_sitebuiltins._Helper

In [26]:

```
type(id)
```

Out[26]:

builtin_function_or_method

In [27]:

```
type(1)
```

Out[27]:

int

Third,

An object’s type determines the operations that the object supports (e.g., “does it have a length?”) and also defines the possible values for objects of that type. The "type()" function returns an object’s type (which is an object itself). Like its identity, an object’s

typeis also unchangeable.

Fourth,

The

valueof some objects can change. Objects whose value can

change are said to be *mutable*; objects whose value is unchangeable
once they are created are called *immutable*. (The value of an
immutable container object that contains a reference to a mutable
object can change when the latter’s value is changed; however the
container is still considered immutable, because the collection of
objects it contains cannot be changed. So, immutability is not
strictly the same as having an unchangeable value, it is more subtle.)
An object’s mutability is determined by its type; for instance,
numbers, strings and tuples are immutable, while dictionaries and
lists are mutable.

Fifth,

Some objects contain references to other objects; these are called

*containers*. Examples of containers are tuples, lists and
dictionaries. The references are part of a container’s value. In
most cases, when we talk about the value of a container, we imply the
values, not the identities of the contained objects; however, when we
talk about the mutability of a container, only the identities of the
immediately contained objects are implied. So, if an immutable
container (like a tuple) contains a reference to a mutable object, its
value changes if that mutable object is changed.

Sixth,

Types affect almost all aspects of object behavior. Even the

importance of object identity is affected in some sense: for immutable types, operations that compute new values may actually return a reference to any existing object with the same type and value, while for mutable objects this is not allowed.

In [28]:

```
class A(object):
x = 3
y = 4
```

In [31]:

```
a = A()
```

In [32]:

```
id(a)
```

Out[32]:

4534062624

In [33]:

```
type(a)
```

Out[33]:

__main__.A

In [ ]:

```
A().y
```

In [45]:

```
L = []
L
```

Out[45]:

[]

In [46]:

```
L.append(1)
L.append(3.14)
```

In [47]:

```
L
```

Out[47]:

[1, 3.14]

In [41]:

```
3, 3.14, 3 + 5J
```

Out[41]:

(3, 3.14, (3+5j))

In [49]:

```
t = (1, 3.14)
t
```

Out[49]:

(1, 3.14)

In [50]:

```
type(t)
```

Out[50]:

tuple

In [54]:

```
t.index(3.14)
```

Out[54]:

1

In [59]:

```
tt = (3.14, [])
tt, id(tt)
```

Out[59]:

((3.14, []), 4533921408)

In [60]:

```
tt[1].append(4)
```

In [61]:

```
tt, id(tt)
```

Out[61]:

((3.14, [4]), 4533921408)

In [1]:

```
type({})
```

Out[1]:

dict

In [2]:

```
type(set())
```

Out[2]:

set

In [3]:

```
S = set()
```

In [4]:

```
S
```

Out[4]:

set()

In [6]:

```
S.add(3.14)
S
```

Out[6]:

{3.14}

In [7]:

```
S.add('hello')
S.add(tuple())
```

In [8]:

```
S
```

Out[8]:

{(), 3.14, 'hello'}

In [9]:

```
S.add(tuple())
```

In [10]:

```
S
```

Out[10]:

{(), 3.14, 'hello'}

In [11]:

```
S.add([])
```

In [12]:

```
L = []
t = (3.14, L)
tt = (1.618, L)
```

In [13]:

```
id(L), id(t), id(tt)
```

Out[13]:

(4613187392, 4612579136, 4613347328)

In [15]:

```
S = { t, tt }
S
```

In [16]:

```
hash(L)
```

In [20]:

```
{(3.14, ()), (1.618, frozenset())}
```

Out[20]:

{(1.618, frozenset()), (3.14, ())}

In [22]:

```
D = {}
D[[]] = 3.14
```

In [8]:

```
a = 1; b = 1 # "a" and "b" may or may not refer to the same object with the value one,
id(a), id(b) # depending on the implementation
```

Out[8]:

(94869411972864, 94869411972864)

In [9]:

```
c = []; d = []
id(c), id(d) # "c" and "d" are guaranteed to refer to two different, unique, newly created empty lists.
```

Out[9]:

(140325921500736, 140325921500224)

In [11]:

```
type(c), type(d)
```

Out[11]:

(list, list)

A full description can be read by evaluating the following,

In [23]:

```
help('OBJECTS')
```

Some playground:

In [25]:

```
"Hello, World!", type("Hello, World!")
```

Out[25]:

('Hello, World!', str)

In [26]:

```
_
```

Out[26]:

('Hello, World!', str)

In [19]:

```
_[1]
```

Out[19]:

str

In [27]:

```
__
```

Out[27]:

('Hello, World!', str)

In [28]:

```
type(_)
```

Out[28]:

tuple

In [30]:

```
type(tuple)
```

Out[30]:

type

In [33]:

```
tuple_doc = tuple.__doc__
```

In [34]:

```
type(tuple_doc)
```

Out[34]:

str

In [35]:

```
tuple_doc + ' maybe that is actually true'
```

Out[35]:

"Built-in immutable sequence.\n\nIf no argument is given, the constructor returns an empty tuple.\nIf iterable is specified the tuple is initialized from iterable's items.\n\nIf the argument is a tuple, the return value is the same object. maybe that is actually true"

In [32]:

```
help(tuple)
```

In [7]:

```
print(tuple.__doc__)
```

In [36]:

```
3.2, type(3.2), 3 + 9J, type(3 + 9j)
```

Out[36]:

(3.2, float, (3+9j), complex)

In [27]:

```
'I''m a string'
```

Out[27]:

'Im a string'

In [106]:

```
'''"Oh no", she exclaimed, "Ben's bike is broken!"'''
```

Out[106]:

'"Oh no", she exclaimed, "Ben\'s bike is broken!"'

In [37]:

```
def a(b):
'''A short summary here.
Some more description here.
>>> a(4)
None
'''
...
```

In [38]:

```
def b(c):
'''A short summary here.
Some more description here.
'''
pass
```

In [39]:

```
help(a)
```

In [40]:

```
a.__doc__
```

Out[40]:

'A short summary here.\n \n Some more description here.\n \n >>> a(4)\n None\n \n '

In [41]:

```
print(a.__doc__)
```

A short summary here. Some more description here. >>> a(4) None

In [42]:

```
assert a(4) == b(3) == None
```

In [46]:

```
assert True == False, 'This is quite usual'
```

In [37]:

```
id(None), id(None), id(None)
```

Out[37]:

(94869411793504, 94869411793504, 94869411793504)

In [42]:

```
((3 < 4) < 10) < 7
```

Out[42]:

True

In [107]:

```
_
```

Out[107]:

'"Oh no", she exclaimed, "Ben\'s bike is broken!"'

In [108]:

```
type(_)
```

Out[108]:

str

In [11]:

```
help('TYPES')
```

In [45]:

```
min, next, object, open, property
```

Out[45]:

(<function min>, <function next>, object, <function io.open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)>, property)

In [46]:

```
True, False, None
```

Out[46]:

(True, False, None)

In [47]:

```
a
```

Out[47]:

<function __main__.a(b)>

In [49]:

```
assert a(42) is None
```

In [50]:

```
set()
```

Out[50]:

set()

In [51]:

```
type(set)
```

Out[51]:

type

In [52]:

```
(lambda x: x + 1)(4)
```

Out[52]:

5

In [53]:

```
(lambda x: x + 1)
```

Out[53]:

<function __main__.<lambda>(x)>

In [54]:

```
class A(object):
def __call__(self, b):
return b + 1
```

In [56]:

```
a = A()
a(4)
```

Out[56]:

5

In [57]:

```
type(A)
```

Out[57]:

type

In [5]:

```
L = list(range(10))
L
```

Out[5]:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [59]:

```
def inc(a): return a + 1
```

In [60]:

```
list(map(inc, L))
```

Out[60]:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

In [62]:

```
list(map(print, L))
```

0 1 2 3 4 5 6 7 8 9

Out[62]:

[None, None, None, None, None, None, None, None, None, None]

In [63]:

```
for i in L:
print(i)
```

0 1 2 3 4 5 6 7 8 9

In [65]:

```
LL = []
for i in L:
LL.append(i+1)
LL
```

Out[65]:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

In [2]:

```
an_int = 541
my_lambda = lambda x: x + an_int
```

In [3]:

```
my_lambda
```

Out[3]:

<function __main__.<lambda>(x)>

In [4]:

```
my_lambda(1)
```

Out[4]:

542

In [6]:

```
list(map(my_lambda, L))
```

Out[6]:

[541, 542, 543, 544, 545, 546, 547, 548, 549, 550]

In [47]:

```
a = 2
b = 3
a, b
```

Out[47]:

(2, 3)

In [48]:

```
3 + (a := 4)
```

Out[48]:

7

In [49]:

```
a
```

Out[49]:

4

In [ ]:

```
# the usual way...
c = a
a = b
b = c
```

In [18]:

```
b, a = a, b # swap
a, b
```

Out[18]:

(3, 2)

In [51]:

```
a, (b, *c, d), e, *f = (1, [2, 3, 4, 5, 6], 7, 8, 9) # unpacking of iterables.
```

In [52]:

```
a, b, c, d, e, f
```

Out[52]:

(1, 2, [3, 4, 5], 6, 7, [8, 9])

In [53]:

```
(1, [2, 3, 4, 5, 6], 7, 8, 9)[1][-1]
```

Out[53]:

6

In [54]:

```
d
```

Out[54]:

6

Built-ins are described at https://docs.python.org/3/library/functions.html#built-in-funcs.

In [9]:

```
int(1.1)
```

Out[9]:

1

In [28]:

```
help('INTEGER')
```

In [11]:

```
'banana' * 3
```

Out[11]:

'bananabananabanana'

In [12]:

```
type(_)
```

Out[12]:

str

In [19]:

```
help('STRINGS')
```

In [14]:

```
''.join(reversed('prova')) # `string`s are collections too
```

Out[14]:

'avorp'

In [55]:

```
a = 1; b = 2 # string interpolation
'{} < {} is actually true.'.format(a, b)
```

Out[55]:

'1 < 2 is actually true.'

In [56]:

```
f'{a} < {b} is actually true.'
```

Out[56]:

'1 < 2 is actually true.'

In [57]:

```
total_secs = 43943
hours = total_secs // 3600
secs_still_remaining = total_secs % 3600
minutes = secs_still_remaining // 60
secs_finally_remaining = secs_still_remaining % 60
"{}h {}' {}''".format(hours, minutes, secs_finally_remaining)
```

Out[57]:

"12h 12' 23''"

operators are described here https://docs.python.org/3/library/operator.html?highlight=operator.

In [18]:

```
help('OPERATORS')
```

`Slice`

s¶In [58]:

```
'hello'[1:-2]
```

Out[58]:

'el'

In [59]:

```
s = slice(5, 11, 2)
s.start, s.stop, s.step
```

Out[59]:

(5, 11, 2)

In [60]:

```
'hello'[slice(1, 3)]
```

Out[60]:

'el'

In [21]:

```
help('SLICINGS')
```

`range`

s¶In [7]:

```
range
```

Out[7]:

range

In [8]:

```
type(range)
```

Out[8]:

type

In [11]:

```
r = range(2, 20)
r
```

Out[11]:

range(2, 20)

In [12]:

```
len(r)
```

Out[12]:

18

In [10]:

```
id(_)
```

Out[10]:

4389733408

In [6]:

```
print(range.__doc__)
```

In [13]:

```
range(1_000_000_000_000_000)
```

Out[13]:

range(0, 1000000000000000)

In [14]:

```
range(1_000000000_000_000)
```

Out[14]:

range(0, 1000000000000000)

In [63]:

```
type(_)
```

Out[63]:

range

In [15]:

```
list(range(10))
```

Out[15]:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [16]:

```
list()
```

Out[16]:

[]

In [17]:

```
[]
```

Out[17]:

[]

In [18]:

```
set()
```

Out[18]:

set()

In [19]:

```
ranges = [range(10), range(10, 20)]
```

In [20]:

```
list(map(list, ranges))
```

Out[20]:

[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]]

In [23]:

```
my_map = map(list, ranges)
```

In Haskell, we can define the naturals using the following definition:

```
nats = [1..]
```

In [24]:

```
type(my_map)
```

Out[24]:

map

In [25]:

```
list(my_map)
```

Out[25]:

[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]]

In [30]:

```
L = range(20000000000000000000000000000000000000)
L
```

Out[30]:

range(0, 20000000000000000000000000000000000000)

In [31]:

```
M = map(lambda x: x % 2, L)
M
```

Out[31]:

<map at 0x1054d7d90>

In [32]:

```
next(M)
```

Out[32]:

0

In [33]:

```
next(M)
```

Out[33]:

1

In [34]:

```
next(M)
```

Out[34]:

0

In [35]:

```
next(M)
```

Out[35]:

1

In [36]:

```
def one_two_three():
yield 1
yield 2
yield 3
```

In [37]:

```
one_two_three
```

Out[37]:

<function __main__.one_two_three()>

In [38]:

```
OTT = one_two_three()
```

In [39]:

```
id(OTT)
```

Out[39]:

4391761872

In [40]:

```
type(OTT)
```

Out[40]:

generator

In [41]:

```
next(OTT)
```

Out[41]:

1

In [42]:

```
next(OTT)
```

Out[42]:

2

In [43]:

```
next(OTT)
```

Out[43]:

3

In [44]:

```
next(OTT)
```

In [45]:

```
for i in one_two_three():
print(i)
```

1 2 3

In [46]:

```
M = one_two_three()
while True:
try:
i = next(M)
print(i)
except StopIteration:
break
```

1 2 3

In [58]:

```
L = range(10)
L
```

Out[58]:

range(0, 10)

In [51]:

```
def f(x):
print('From f')
return x + 1
M = map(f, L)
```

In [52]:

```
type(M)
```

Out[52]:

map

In [55]:

```
def g(x):
print('From g')
return x + 2
N = map(g, M)
```

In [54]:

```
type(N)
```

Out[54]:

map

In [56]:

```
def h(x):
print('From h')
return x + 3
O = map(h, N)
```

In [57]:

```
type(O)
```

Out[57]:

map

In [59]:

```
next(O)
```

From f From g From h

Out[59]:

6

In [60]:

```
next(O)
```

From f From g From h

Out[60]:

7

In [61]:

```
list(O)
```

From f From g From h From f From g From h From f From g From h From f From g From h From f From g From h From f From g From h From f From g From h From f From g From h

Out[61]:

[8, 9, 10, 11, 12, 13, 14, 15]

In [62]:

```
next()
```

In [63]:

```
M = [1, 2, 3, 4]
```

In [75]:

```
G = (x + 1 for x in range(100))
```

In [73]:

```
H = [x + 1 for x in M]
H
```

Out[73]:

[2, 3, 4, 5]

In [65]:

```
list(G)
```

Out[65]:

[2, 3, 4, 5]

In [69]:

```
list(map(lambda x: x+1, M))
```

Out[69]:

[2, 3, 4, 5]

`lambda`

s¶In [69]:

```
def add(a, b):
return a + b
```

In [70]:

```
type(add)
```

Out[70]:

function

In [71]:

```
callable(add)
```

Out[71]:

True

In [72]:

```
add(3, 4)
```

Out[72]:

7

In [120]:

```
help('FUNCTIONS')
```

In [73]:

```
add_l = lambda a, b: a + b # is a lambda expression
```

In [74]:

```
type(add_l)
```

Out[74]:

function

In [75]:

```
callable(add_l)
```

Out[75]:

True

In [118]:

```
help('lambda')
```

In [76]:

```
assert add(1, 2) == add_l(1, 2)
```

`yield`

s¶In [1]:

```
L = range(5)
L
```

Out[1]:

range(0, 5)

In [2]:

```
M = map(lambda i: i + 1, L)
M
```

Out[2]:

<map at 0x7fb7c6626880>

In [3]:

```
type(_)
```

Out[3]:

map

In [4]:

```
next(M)
```

Out[4]:

1

In [5]:

```
help(next)
```

In [6]:

```
next(M)
```

Out[6]:

2

In [7]:

```
next(M)
```

Out[7]:

3

In [8]:

```
next(M)
```

Out[8]:

4

In [9]:

```
next(M)
```

Out[9]:

5

In [10]:

```
next(M)
```

In [ ]:

```
help(StopIterationx)
```

**INTERMEZZO**

In [11]:

```
1/0
```

In [12]:

```
try:
1/0
except ZeroDivisionError:
pass
```

$$
\mathcal{B} = \lbrace x\in\mathbb{R} : \sqrt{x + 1} > 4 \rbrace
$$

In [21]:

```
N = ( i for i in range(1, 10) ) # another way to build a generator
N
```

Out[21]:

<generator object <genexpr> at 0x7fb7c687c120>

In [22]:

```
type(N)
```

Out[22]:

generator

In [23]:

```
list(N)
```

Out[23]:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

In [17]:

```
def saturate(g, L=[]):
while True:
try:
L.append(next(g))
except StopIteration:
break
return L
```

In [18]:

```
saturate(N, [0])
```

Out[18]:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

`help`

¶In [19]:

```
help()
```