Random Live Hacking

Data Types

  • Variables are just names that point to objects

  • Variables are not typed - the type is in the objects they points to

[1]:
i = 1
[2]:
type(i)
[2]:
int
[3]:
i = 1.5
[4]:
type(i)
[4]:
float
[5]:
i = [1,2,3]
[6]:
type(i)
[6]:
list

int is also a variable - a name. It points to an object containing the type for all int objects.

[7]:
print(int)
<class 'int'>

What is the type of that int type object? What’s the type of type objects, generally?

[8]:
print(type(int))
<class 'type'>

Aha …

[9]:
int('42')
[9]:
42

int is just a name, so let another name point to the same object (type int)

[10]:
franziska = int
[11]:
franziska('42')
[11]:
42

Assignments to int are possible. Although rarely useful.

[12]:
int = 42
[13]:
try:
    int('42')
except BaseException as e:
    print(e)
'int' object is not callable

Remove the last name for type int, so it is gone forever …

[14]:
del franziska

Ah, it is still there: 1 is an integer literal, so it has to carry a reference to its type. This comes to our rescue: we can restore the name int to point to what is should.

[15]:
type(1)
[15]:
int
[16]:
int = type(1)
[17]:
int('42')
[17]:
42

Pooh!

Mutable, Immutable

[18]:
l1 = [1, 2, 3]
l2 = l1
print(hex(id(l1)))
print(hex(id(l2)))
0x7f03d41165f0
0x7f03d41165f0
[19]:
l1.append(4)
l1
[19]:
[1, 2, 3, 4]
[20]:
l2
[20]:
[1, 2, 3, 4]

Exception, demonstrated using dict access

[21]:
d = {1:'one', 2:'two'}
[22]:
d
[22]:
{1: 'one', 2: 'two'}
[23]:
d[1]
[23]:
'one'
[24]:
d[2]
[24]:
'two'

Here we access a nonexisting dictionary member, demonstrating how we react on the error that ensues. * Catch the exception by type (KeyError) * Use the logging module to format the output (stack trace etc.). See its docs for more.

[25]:
import sys
import logging

try:
    d[3]
except KeyError as e:
    print('Jessas:', e)
    logging.exception('verdammt!')
ERROR:root:verdammt!
Traceback (most recent call last):
  File "<ipython-input-25-bd3a1af9b474>", line 5, in <module>
    d[3]
KeyError: 3
Jessas: 3
[26]:
print(type(KeyError))
<class 'type'>

All Exceptions are derived from BaseException

[27]:
issubclass(KeyError, BaseException)
[27]:
True

Even the ones that you define yourself have to be derived from BaseException. Better yet, derive them from Exception which should the base for all user-defined exceptions.

[28]:
try:
    raise 'bummer!'
except BaseException as e:
    print('Cannot raise str:', e)
Cannot raise str: exceptions must derive from BaseException

Indices and Slicing

[29]:
l = ['Peter', 'Paul', 'Mary']
[30]:
peter = l[0]
peter
[30]:
'Peter'
[31]:
peter[0:3]
[31]:
'Pet'
[32]:
l[0][0:3]
[32]:
'Pet'
[33]:
peter[:3]
[33]:
'Pet'
[34]:
l = [2,3,4]
l[0:0]
[34]:
[]
[35]:
l[0:0] = [0,1]
l
[35]:
[0, 1, 2, 3, 4]

for loops

Lists are perfectly iterable

[36]:
for item in ['blah', 'bloh', 'blech']:
    print(item)
blah
bloh
blech

range is not a list, but a generator. A list would contain (allocate in memory) all that it has. range produces the next element on demand - as the for loop iterates.

[37]:
for i in range(10):
    print(i)
0
1
2
3
4
5
6
7
8
9
[38]:
for i in range(5,10):
    print(i)
5
6
7
8
9
[39]:
range(10)
[39]:
range(0, 10)
[40]:
list(range(10))
[40]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[41]:
r = range(10)

Iterator protocol

[42]:
iterator = iter(r)
iterator
[42]:
<range_iterator at 0x7f03d402e570>
[43]:
next(iterator)
next(iterator)
next(iterator)
next(iterator)
next(iterator)
next(iterator)
next(iterator)
next(iterator)
next(iterator)
[43]:
8
[44]:
next(iterator)
[44]:
9
[45]:
# final element already consumed, consume another one
try:
    next(iterator)
except StopIteration as e:
    print(e)