More trees, introduction to objects
Mutating trees
We developed one last function on trees:
def replace_text(tree: HTMLTree, find: str, replace: str): for child in tree.children: replace_text(child, find, replace) if tree.tag == "text": tree.text = tree.text.replace(find, replace)
See the lecture capture for details.
Objects
Imagine you’re a DJ at a radio station. The station is very democratic–you only play songs that your listeners call in and request. In addition, every thousandth listener who calls in gets a prize! You want to keep track of the queue of songs you want to play, as well as enough information to give out prizes.
We could implement this with a custom data type like this:
@dataclass class DJData: num_callers: int queue: list
We can implement a function to update our data and to figure out what we’re going to say to a listener:
def request(data: DJData, caller: str, song: str) -> str: data.queue.append(song) data.num_callers += 1 if data.num_callers % 1000 == 0: return "Congrats, " + caller + "! You get a prize!" else: return "Cool, " + caller
So here we’ve got a datatype and a function that reads and modifies that datatype’s contents. We can see how it works:
> djdata = DJData(0, []) > request(djdata, "Doug", "Bulls on Parade") "Cool, Doug"
We could have written this slightly differently:
@dataclass class DJData: num_callers: int queue: list def request(self, caller: str, song: str) -> str: self.queue.append(song) self.num_callers += 1 if self.num_callers % 1000 == 0: return "Congrats, " + caller + "! You get a prize!" else: return "Cool, " + caller
We’ve put the request
function inside the definition of DJData
. We’ve also
modified the method a bit: instead of taking a data
argument, we’ve called the
argument “self” and left off the type annotation. This function is now a
method on the DJData
class.
We’ll call it slightly differently, too:
> djdata = DJData(0, []) > djdata.request("Doug", "Guerilla Radio") "Cool, Doug"
We call methods by writing the name of an object (djdata
, in this case),
then a dot, then the method arguments--excluding self
. Since we’re not
passing self
in, how does Python know which object to call the method on?
We’ll keep learning about classes, objects and methods. I want to emphasize,
though, that you’ve seen this before. We’ve called methods on lists, sets,
etc.–for instance, l.append(2))
. What we’re seeing now is how to add
methods to our custom objects!
The init method
Up until now we’ve been using the dataclasses
library to make our custom
datatypes work more like Pyret’s. From this point, we will generally not use
it, so that we can see how Python’s objects work. Instead, we’ll use
__init__()
methods to initialize data on objects:
class DJData: def __init__(self): self.queue = [] self.num_callers = 0 def request(self, caller: str, song: str) -> str: self.queue.append(song) self.num_callers += 1 if self.num_callers % 1000 == 0: return "Congrats, " + caller + "! You get a prize!" else: return "Cool, " + caller
Python calls the __init__
method in order to initialize an object’s
fields when it is created. We can construct instances of the DJData
class
like this:
> djdata = DJData() > djdata.request("Doug", "Ironic") "Cool, Doug"