Back when I was a wee lass, this was all we had:
greeting = "Ahoy"
noun = "Boat"
print(greeting + ", " + noun + "yMc" + noun + "Face")
print("%s, %syMc%sFace" % (greeting, noun, noun))
Python 2.6 introduced str.format()
:
print("{}, {}yMc{}Face".format(greeting, noun, noun))
print("{0}, {1}yMc{1}Face".format(greeting, noun))
print("{greeting}, {noun}yMc{noun}Face".format(
greeting=greeting, noun=noun))
Available since Python 3.5, f strings (formatted string literals) are the new recommended way to format strings.
Just put an f
in front of the quotes and then
put any valid Python expression in curly brackets inside:
greeting = "Ahoy"
noun = "Boat"
print(f"{greeting}, {noun}yMc{noun}Face")
ππππππ
Any valid Python expression!
print(f"{greeting.lower()}, {noun.upper()}yMc{noun}Face")
print(f"{greeting*3}, {noun[0:3]}yMc{noun[-1]}Face")
Using our standard 61A definitions, what will this show?
link = Link("A", Link("B", Link("C")))
tree = Tree(1, [Tree(2), Tree(3)])
print(f"{link} and\n {tree}")
It shows the result of calling __str__
on each object:
<A B C> and
1
2
3
def map_em(items, func):
"""Returns a list with FUNC applied to each item in ITEMS."""
mapped = []
for item in items:
mapped.append(func(item))
return mapped
What could items
be? Anything iterable!
The function map_em
is generic in the type of items
.
The object must have an __iter__
method
that returns an iterator.
Built-in iterables:
list
, tuple
, dict
, str
, set
Built-in functions that return iterables:
list()
, tuple()
, sorted()
Built-in functions that return iterators:
reversed()
, zip()
, map()
, filter()
The ability to use any type of object in a function based on its behavior (versus its type) is known as duck typing.
The duck test: π₯
βThe parameter to this function must be a duck. If it looks like a duck and quacks like a duck, then weβll say it IS a duck!β
Which parameters pass the duck test?
map_em([1, 2, 3, 4], lambda n: n * 2)
map_em(("A", "B", "C", "D"), lambda l: l.lower())
map_em({"CA": "poppy", "OR": "grape"}, lambda k: k[0])
map_em([(34, -144), (37, -122)], lambda latlon: latlon[0])
map_em("Supercalifragilisticexpialidocious", lambda s: s.upper())
map_em(Link(1, Link(2, Link(3))), lambda n: n * 3)
π« TypeError: 'Link' object is not iterable
Our 61A standard definition of Link:
class Link:
empty = ()
def __init__(self, first, rest=empty):
assert rest is Link.empty or isinstance(rest, Link)
self.first = first
self.rest = rest
def __repr__(self):
if self.rest:
rest_repr = ', ' + repr(self.rest)
else:
rest_repr = ''
return 'Link(' + repr(self.first) + rest_repr + ')'
def __str__(self):
string = '<'
while self.rest is not Link.empty:
string += str(self.first) + ' '
self = self.rest
return string + str(self.first) + '>'
Our object supports initialization and string representations, but not iteration.
We can make our own objects iterable by defining __iter__
.
class Link:
empty = ()
def __init__(self, first, rest=empty):
assert rest is Link.empty or isinstance(rest, Link)
self.first = first
self.rest = rest
def __iter__(self):
current = self
while current is not Link.empty:
yield current.first
current = current.rest
# The rest...
Given the addition of __iter__
,
can we now pass an instance of Link
into map_em
?
def map_em(items, func):
"""Returns a list with FUNC applied to each item in ITEMS."""
mapped = []
for item in items:
mapped.append(func(item))
return mapped
mapped1 = map_em(Link(1, Link(2, Link(3))), lambda n: n * 3)
What about an empty linked list?
mapped2 = map_em(Link.empty, lambda n: n * 3)
def sum_em(items, initial_value):
"""Returns the sum of ITEMS,
starting with a value of INITIAL_VALUE."""
sum = initial_value
for item in items:
sum += item
return sum
What could items
be? Any iterable.
What could initial_value
be? Any value that can be summed with the values in iterable.
The function sum_em
is generic in the type of items
and the type of initial_value
.
The duck test: π₯
βThe parameter to this function must be a duck. If it looks like a duck and quacks like a duck, then weβll say it IS a duck!β
Which parameters pass the duck test?
sum_em([1, 2, 3, 4], 0)
sum_em(("H", "E", "L", "L", "O"), "")
sum_em({"CA": "poppy", "OR": "grape"}, "")
sum_em([(10, 20), (30, 40)], (0, 1))
sum_em("Superkalifragilous", "Oh")
sum_em(Link(1, Link(2, Link(3))), 0)
Consider the following class:
from math import gcd
class Rational:
def __init__(self, numerator, denominator):
g = gcd(numerator, denominator)
self.numer = numerator // g
self.denom = denominator // g
def __str__(self):
return f"{self.numer}/{self.denom}"
def __repr__(self):
return f"Rational({self.numer}, {self.denom})"
Will it duck?
sum_em([Rational(1, 2), Rational(3, 4), Rational(2, 3)],
Rational(0, 1))
π« TypeError: unsupported operand type(s) for +: 'Rational' and 'Rational'
We can make custom objects addable by defining the __add__
method:
class Rational:
def __init__(self, numerator, denominator):
g = gcd(numerator, denominator)
self.numer = numerator // g
self.denom = denominator // g
def __add__(self, other):
new_numer = self.numer * other.denom + other.numer * self.denom
new_denom = self.denom * other.denom
return Rational(new_numer, new_denom)
# The rest...
P.S. We could also define __iadd__
to specifically override +=
.
Given the addition of __add__
,
can we now pass Rational
objects into sum_em
?
def sum_em(items, initial_value):
"""Returns the sum of ITEMS,
starting with a value of INITIAL_VALUE."""
sum = initial_value
for item in items:
sum += item
return sum
sum_em([Rational(1, 2), Rational(3, 4), Rational(2, 3)],
Rational(0, 1))
Python has many ways to make custom objects work generically with its syntax. For example:
Method | Implements |
---|---|
__getitem__(S, k) |
S[k] |
__setitem__(S, k, v) |
S[k] = v |
__len__(S) |
len(s) |
__setattr__(obj, "n", v) |
x.n = v |
__delattr__(obj, "n") |
del x.n |
__sub__(S, x) |
S - x |
__mul__(S, x) |
S * x |
__eq__(obj, x) |
obj == x |
__lt__(obj, x) |
obj < x |
... |
The method str.join(iterable)
returns a string which is the concatenation of the strings in an iterable.
names = ["Gray", "Fox"]
print("".join(names))
address_parts = ["123 Pining St", "Nibbsville", "OH"]
print(",".join(address_parts))
poem_lines = ["Forgive me", "they were delicious", "so sweet", "and so cold"]
print("\n".join(poem_lines))
Using our standard 61A definition, what will this do?
link = Link("A", Link("B", Link("C")))
letters = "->".join(link)
π« TypeError: 'Link' object is not iterable
What if we use the definition from earlier with __iter__
?