(with N still growing)
Four basic things you need to learn to become a good programmer:
I will only teach you how Python works.
(And this is only the first part of it.)
In Python, everything is an object.
Examples:
42, 4.3, 'Hello world', True, False, None, [0, 1, 2, 3], {'key': 'value', 'other key': 'other value'}
('This', 'is', 'a', 'tuple'), np.eye(10)
Really everything!
math.sin, lambda x: x, class C, math, func.__code__
To understand Python, we should first understand what objects are!
a = [0, 1, 2]
b = [0, 1, 2]
a == b
True
a is b
False
id(a)
140008350782240
id(b)
140008350781880
(a is b) == (id(a) == id(b))
True
x = 6
y = 6
x is y
True
x = 666
y = 666
x is y
False
id(a) is id(a)
False
Do not use is
unless you have a good reason!
Reasonable exceptions:
x is True
x is False
x is None
This works because True
, False
, None
are signletons in Python,
i.e. there is only one object True
, etc., in the whole Python universe.
import numpy as np
a = np.ones(10)
b = np.zeros(10)
print(a)
print(b)
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.] [ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
a is b
False
a = b
print(a)
print(b)
[ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [ 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
a[0] = 1
print(a)
print(b)
[ 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [ 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
a is b
True
Objects have state (data) associated to them, which can change over the lifetime of an object.
The state is stored in the objects attributes.
from email.mime.text import MIMEText
m = MIMEText('Hi, this is an email!')
m
<email.mime.text.MIMEText instance at 0x7f563c04cf38>
print(m.as_string())
Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Hi, this is an email!
m._payload
m._headers
[('Content-Type', 'text/plain; charset="us-ascii"'), ('MIME-Version', '1.0'), ('Content-Transfer-Encoding', '7bit')]
The underscore means, _payload
and _headers
are private attributes.
You, the user, should not mess around with them.
Methods can change attributes:
m.add_header('From', 'stephan.rave@uni-muenster.de')
m.add_header('To', 'evil.overlord@the.hell.com')
m.add_header('Subject', 'World domination')
print(m.as_string())
Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: stephan.rave@uni-muenster.de To: evil.overlord@the.hell.com Subject: World domination Hi, this is an email!
m._headers
[('Content-Type', 'text/plain; charset="us-ascii"'), ('MIME-Version', '1.0'), ('Content-Transfer-Encoding', '7bit'), ('From', 'stephan.rave@uni-muenster.de'), ('To', 'evil.overlord@the.hell.com'), ('Subject', 'World domination')]
We can also change attributes, even private ones:
(Do not try this at home!)
m._payload = 'We need to talk!'
print(m.as_string())
Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: stephan.rave@uni-muenster.de To: evil.overlord@the.hell.com Subject: World domination We need to talk!
m.my_attribute = 666
m.my_attribute
666
So where do all these attributes come from?
m.__dict__
{'_charset': us-ascii, '_default_type': 'text/plain', '_headers': [('Content-Type', 'text/plain; charset="us-ascii"'), ('MIME-Version', '1.0'), ('Content-Transfer-Encoding', '7bit'), ('From', 'stephan.rave@uni-muenster.de'), ('To', 'evil.overlord@the.hell.com'), ('Subject', 'World domination')], '_payload': 'We need to talk!', '_unixfrom': None, 'defects': [], 'epilogue': None, 'my_attribute': 666, 'preamble': None}
m.__dict__['favourite_song'] = 'Hotel california'
m.__dict__['_payload'] = 'WE NEED TO TALK!!!'
m.favourite_song
'Hotel california'
print(m.as_string())
Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: stephan.rave@uni-muenster.de To: evil.overlord@the.hell.com Subject: World domination WE NEED TO TALK!!!
a = np.eye(10)
a.secret_answer = 42
Writing out traceback for vim ...
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-25-9d909bc47556> in <module>() 1 a = np.eye(10) ----> 2 a.secret_answer = 42 AttributeError: 'numpy.ndarray' object has no attribute 'secret_answer'
a.__dict__
Writing out traceback for vim ...
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-26-87da7e691200> in <module>() ----> 1 a.__dict__ AttributeError: 'numpy.ndarray' object has no attribute '__dict__'
(42).__dict__
Writing out traceback for vim ...
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-27-f22e1a648ac9> in <module>() ----> 1 (42).__dict__ AttributeError: 'int' object has no attribute '__dict__'
[0, 1, 2].__dict__
Writing out traceback for vim ...
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-28-cb92ad523bf5> in <module>() ----> 1 [0, 1, 2].__dict__ AttributeError: 'list' object has no attribute '__dict__'
If a
has a dict,
a.b
means
a.__dict__['b']
This is not the case for builtin types or C extension types.
class C(object):
pass
c = C()
c.__dict__
{}
c.secret_answer = 42
c.__dict__
{'secret_answer': 42}
class C(object):
secret_answer = 42
c = C()
c.secret_answer
42
c.__dict__
{}
c.__class__
__main__.C
c.__class__.__dict__
<dictproxy {'__dict__': <attribute '__dict__' of 'C' objects>, '__doc__': None, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'C' objects>, 'secret_answer': 42}>
If a
has a dict,
a.b
means
if 'b' in a.__dict__:
return a.__dict__['b']
elif 'b' in a.__class__.__dict__:
return a.__class__.__dict__['b']
elif 'b' in 'base class dicts':
return base_class.__dict__['b']
else:
raise AttributeError
This is not the case for builtin types or C extension types.
class C(object):
great_list_of_awesomeness = []
c1 = C()
c2 = C()
c1.great_list_of_awesomeness.append(42)
c1.great_list_of_awesomeness
[42]
c2.great_list_of_awesomeness
[42]
This might not be what you want!
Methods are functions defined in class definitions as follows:
class C(object):
def __init__(self, x):
self.x = x
def f(self, y):
return self.x + y
c = C(11)
c.f(31)
42
c.f(31)
translates to
C.f(c, 31)
i.e. calling a method on an object c
magically inserts c
as first argument of the method.
This also explains this strange error:
c.f()
Writing out traceback for vim ...
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-39-69c69864e152> in <module>() ----> 1 c.f() TypeError: f() takes exactly 2 arguments (1 given)
__init__
is one of Python's many special methods.
It is called, after a new object has been created.
Thus
c = C(11)
translates to
c = newly_created_C_instance
c.__init__(c, 11)
We can add new methods to the class at any time:
def g(ego, y):
return ego.x * y
C.mult = g
c.mult(2)
22
Note that self
as first argument name is pure convention.
You should really stick to it!
Adding functions directly to objects does not work:
def h(self, y):
return self.x - y
c.h = h
c.h(-31)
Writing out traceback for vim ...
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-41-f498a20a0d9d> in <module>() 3 4 c.h = h ----> 5 c.h(-31) TypeError: h() takes exactly 2 arguments (1 given)
Oh no, the magic is not working!
import types
c.h = types.MethodType(h, c)
c.h(-31)
42
Numbers, strings and tuples are immutable in Python:
t = (0, 1, 2)
t[1] = 2
Writing out traceback for vim ...
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-43-4ca911cddf58> in <module>() 1 t = (0, 1, 2) ----> 2 t[1] = 2 TypeError: 'tuple' object does not support item assignment
There is absolutely no way to modify them!
So what about here:
x = 41
y = x
x += 1
y
41
x is y
False
x
42
So, x
refers to a new int
object with value 42.
Think what would have happend, if the object had stayed the same!
This is, how inplace addition works:
class MyNumber(object):
def __init__(self, value):
self.value = value
def __iadd__(self, other):
return MyNumber(self.value + other)
n = MyNumber(12)
print(n.value)
print(id(n))
n += 7
print(n.value)
print(id(n))
12 140008016975120 19 140008149531472
n += 7
is really the same as
n = n.__iadd__(7)
What happens here?
class C(object):
value = 42
c1 = C()
c2 = C()
c1.value = 666
c1.value
666
c2.value
42
print(c1.__dict__)
print(c2.__dict__)
print(C.__dict__)
{'value': 666} {} {'__dict__': <attribute '__dict__' of 'C' objects>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'C' objects>, '__doc__': None, 'value': 42}
Default arguments are attributes of the function object:
def f(x, more_values=[]):
more_values.append(x)
print(more_values)
f.__defaults__
([],)
print(dir(f))
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']
def f(x, more_values=[]):
more_values.append(x)
print(more_values)
f(1)
[1]
f(42, [4, 8, 15, 16, 23, 42])
[4, 8, 15, 16, 23, 42, 42]
f(1)
f(1)
[1, 1] [1, 1, 1]
f.__defaults__
([1, 1, 1],)
f.__defaults__ = ([],)
f(1)
[1]