Iterables¶
Lists are iterable, which is pretty much the definition of an iterable
[1]:
l = [1, 2, 3, 4]
[2]:
for element in l:
print(element)
1
2
3
4
How about dictionary iteration?
[3]:
d = {1: 'one', 2:'two'}
[4]:
d
[4]:
{1: 'one', 2: 'two'}
Pairwise iteration: over keys and values in parallel
[5]:
for key, value in d.items():
print(key, value)
1 one
2 two
Question: can I use a dictionary to search for the value and get the key as an answer? Answer: use pairwise iteration like shown above, and search for the vale manually. Beware though that this is linear search and thus not nearly as fast as a dictionary key search.
[6]:
for key, value in d.items():
if value == 'two':
print(key)
break
2
Iterating over the dictionary itself (not using any iteration method of it) iterates over the keys
[7]:
for key in d:
print(key)
1
2
Iterating over the values
[8]:
for value in d.values():
print(value)
one
two
set
constructor¶
A set literal
[9]:
s = {1,2,3}
s
[9]:
{1, 2, 3}
Constructing a set from an iterable (in this case a string) absorbs what it iterates over.
[10]:
s = set('abc')
s
[10]:
{'a', 'b', 'c'}
Consequentially, you can make a set from a dictionary
[11]:
s = set(d)
s
[11]:
{1, 2}
Fast vs. Simple¶
[12]:
l = [1,2,3,4,5,6,7,8,9]
The in
operator on a list can only search through it from beginning to end. Here we use 9 comparisons. (In a list with millions of elements we would take at most millions of comparisons which is not fast.)
[13]:
9 in l
[13]:
True
Manually implementing what the in
operator does.
[14]:
answer = False
for elem in l: # linear search!!
if elem == 9:
answer = True
break
answer
[14]:
True
Using a set
is a better way to determine membership. It is implemented as a hash table internally.
[15]:
s = {1,2,3,4,5,6,7,8,9}
9 in s
[15]:
True
Insertion order is not guaranteed to be preserved by a set, although it is in the simplest cases.
[16]:
for elem in s:
print(elem)
1
2
3
4
5
6
7
8
9
for
, Iterables, range
and Generators¶
[17]:
for i in [0,1,2,3]:
print(i)
0
1
2
3
This is the same like above, from a functionality point of view. Only cheaper, memorywise, because no 4 integers are kept in memory. (Think of millions of integers, again.)
[18]:
for i in range(4):
print(i)
0
1
2
3
The iterator protocol, explained.
[19]:
r = range(4)
[20]:
it = iter(r)
[21]:
next(it)
[21]:
0
[22]:
next(it)
[22]:
1
[23]:
next(it)
[23]:
2
[24]:
next(it)
[24]:
3
Tuples, Tuple Unpacking, Returning Multiple Values from Functions¶
Johannes: “what’s this?”
[25]:
def f():
return 1, # comma?
Tuple unpacking: syntactic sugar
[68]:
a, b = 1, 2
is the same as
[69]:
(a, b) = (1, 2)
This allows us to swap two variables in one statement, for example
[70]:
a, b = b, a
Returning multiple values is the same as returning a tuple
[71]:
def f():
return (1, 2, 3)
This is the same as …
[29]:
def f():
return 1, 2, 3
[30]:
retval = f()
What is returned in both cases is a tuple
[31]:
retval
[31]:
(1, 2, 3)
[32]:
type(retval)
[32]:
tuple
The same is more expressively written as …
[37]:
a, b, c = f() # tuple unpacking
Back to Johannes’ question: 1,
is a one-tuple
[33]:
def f():
return 1,
[34]:
retval = f()
[72]:
retval
[72]:
(1, 2, 3)
The same concept - tuple unpacking - is used in pairwise iteration btw.
[45]:
d = { 1: 'one', 2: 'two'}
[46]:
for key, value in d.items():
print(key, value)
1 one
2 two
Object Oriented Programming¶
An empty class
[47]:
class Message:
pass
Creating a object of that class
[48]:
m = Message()
[49]:
type(m)
[49]:
__main__.Message
A constructor, to be called when an object is created
[50]:
class Message:
# prio
# dlc
# msg1
# ...
def __init__(self, prio, dlc, msg1):
print('prio:', prio, 'dlc:', dlc, 'msg1:', msg1)
[51]:
m = Message(1, 5, 'whatever message that could be')
prio: 1 dlc: 5 msg1: whatever message that could be
The same, only using keyword parameters for better readability and maintainability
[52]:
m = Message(prio=1, dlc=5, msg1='whatever message that could be')
prio: 1 dlc: 5 msg1: whatever message that could be
Order is irrelevant when using keyword parameters
[53]:
m = Message(dlc=5, prio=1, msg1='whatever message that could be')
prio: 1 dlc: 5 msg1: whatever message that could be
[54]:
m
[54]:
<__main__.Message at 0x7f41f5ff26a0>
self
is the object that is being created. You can use it to hold members (to remember values).
[55]:
class Message:
def __init__(self, prio, dlc, msg1):
self.prio = prio
self.dlc = dlc
self.msg1 = msg1
[56]:
m = Message(dlc=5, prio=1, msg1='whatever message that could be')
print('prio:', m.prio)
print('dlc:', m.dlc)
print('msg1:', m.msg1)
prio: 1
dlc: 5
msg1: whatever message that could be
[57]:
msglist = []
msglist.append(Message(dlc=5, prio=1, msg1='whatever message that could be'))
msglist.append(Message(prio=5, dlc=1, msg1='another wtf message'))
[58]:
msglist
[58]:
[<__main__.Message at 0x7f41f5ff4160>, <__main__.Message at 0x7f41f5ff41c0>]
datetime
¶
Date and time is a complex matter. The datetime
module has all of it.
[59]:
import datetime
[60]:
now = datetime.datetime.now()
now
[60]:
datetime.datetime(2020, 10, 28, 12, 34, 19, 291130)
[61]:
type(now)
[61]:
datetime.datetime
[62]:
import time
now_timestamp = time.time()
[63]:
now_timestamp
[63]:
1603884859.3412576
[64]:
now = datetime.datetime.fromtimestamp(now_timestamp)
now
[64]:
datetime.datetime(2020, 10, 28, 12, 34, 19, 341258)
[65]:
then = datetime.datetime(2019, 10, 22)
[66]:
now - then
[66]:
datetime.timedelta(days=372, seconds=45259, microseconds=341258)