diff --git a/README.rst b/README.rst
index 11c933d6..eea28014 100644
--- a/README.rst
+++ b/README.rst
@@ -19,7 +19,7 @@ These documents are written in `Restructured Text link to google
+
+
You should be able to subclass from ``Element``, and only override the ``__init__`` --- calling the ``Element`` ``__init__`` from the ``A`` ``__init__``
You can now add a link to your web page.
diff --git a/source/exercises/html_renderer_tutorial.rst b/source/exercises/html_renderer_tutorial.rst
index 3acce5ff..d2459ffa 100644
--- a/source/exercises/html_renderer_tutorial.rst
+++ b/source/exercises/html_renderer_tutorial.rst
@@ -4,7 +4,7 @@
Tutorial for the Html Render Assignment
#######################################
-If you are finding that you don't really know where to start with the html render assignemnt, this tutorial will walk you through the process.
+If you are finding that you don't really know where to start with the html render assignment, this tutorial will walk you through the process.
However, you generally learn more if you figure things out for yourself. So I highly suggest that you give each step a try on your own first, before reading that step in this tutorial. Then, if you are really stuck -- follow the process here.
@@ -131,9 +131,9 @@ Looking there, we can see why the tests did what they did -- we have the three k
So back to the assignment:
- The ``Element`` class should have a class attribute for the tag name ("html" first)
+ The ``Element`` class should have a class attribute for the tag name ("html" first).
-each html element has a different "tag", specifying what kind of element it is. so our class needs one of those. Why a class attribute? because each *instance* of each type (or class) of element will share the same tag. And we don't want to store the tag in the render method, because then we couldn't reuse that render method for a different type of element.
+Each html element has a different "tag", specifying what kind of element it is. so our class needs one of those. Why a class attribute? Because each *instance* of each type (or class) of element will share the same tag. And we don't want to store the tag in the render method, because then we couldn't reuse that render method for a different type of element.
So we need to add a tiny bit of code:
@@ -146,7 +146,7 @@ So we need to add a tiny bit of code:
def __init__(self, content=None):
pass
-That's not much -- will the test pass now? Probably not, we aren't doing anything with the tag. But you can run it to see if you like. It's always good to run tests frequently to make sure you haven't inadvertently broken anything.
+That's not much -- will the test pass now? Probably not, because we aren't doing anything with the tag. But you can run it to see if you'd like. It's always good to run tests frequently to make sure you haven't inadvertently broken anything.
Back to the task at hand:
@@ -156,7 +156,7 @@ Back to the task at hand:
So your class will need a way to store the content in a way that you can keep adding more to it.
-OK, so we need a way to store the content -- both what gets passed in to the ``__init__`` and what gets added with the ``append method``. We need a data structure that can hold an ordered list of things, and can be added to in the future -- sounds like a list to me. So let's create a list in __init__ and store it in ``self`` for use by the other methods:
+OK, so we need a way to store the content: both what gets passed in to the ``__init__`` and what gets added with the ``append`` method. We need a data structure that can hold an ordered list of things, and can be added to in the future -- sounds like a list to me. So let's create a list in __init__ and store it in ``self`` for use by the other methods:
.. code-block:: python
@@ -173,7 +173,7 @@ OK -- let's run the tests and see if anything changed::
test_html_render.py:72: AssertionError
-nope -- still failed at the first assert in test_render. Which makes sense, we haven't done anything with the render method yet!
+Nope -- still failed at the first assert in test_render. This makes sense because we haven't done anything with the render method yet!
.. rubric:: 1c.
@@ -181,7 +181,9 @@ From the assignment:
It should have a ``render(file_out)`` method that renders the tag and the strings in the content.
-we have the render method -- but it's rending arbitrary text to the file -- not an html tag or contents. So let's add that. First let's add the contents, adding a newline in between to keep it readable. Remember that there can be multiple pieces of content -- so we need to loop though the list:
+We have the render method, but it's rendering arbitrary text to the file, not an html tag or contents. So let's add that.
+
+First let's add the contents, adding a newline in between to keep it readable. Remember that there can be multiple pieces of content, so we need to loop though the list:
.. code-block:: python
@@ -233,11 +235,11 @@ And run the tests::
test_html_render.py:79: AssertionError
====================== 1 failed, 2 passed in 0.05 seconds ======================
-Failed in test_render again -- but look carefully -- it didn't fail on the first assert! It failed on this line::
+Failed in test_render again. But look carefully. It didn't fail on the first assert! It failed on this line::
assert file_contents.startswith("")
-which makes sense, we haven't rendered anything like that yet. So let's add that now. Recall that we want the results to look something like this:
+This makes sense because we haven't rendered anything like that yet. So let's add that now. Recall that we want the results to look something like this:
.. code-block:: html
@@ -326,7 +328,7 @@ And let's run it::
and this is some more text
-It failed on the assert False -- good sign, it didn't fail before that. We can now look at the results we printed, and whoops! we actually got *two* html elements, rather than one with two pieces of content. Why is that? Before you look at the code again, let's make sure the test catches that and fails. How about this?
+It failed on the ``assert False``. It's a good sign that it didn't fail before that. We can now look at the results we printed, and whoops! we actually got *two* html elements, rather than one with two pieces of content. Why is that? Before you look at the code again, let's make sure the test catches that and fails. How about this?
.. code-block:: python
@@ -342,7 +344,7 @@ And it does indeed fail on this line::
test_html_render.py:83: AssertionError
-Now that we know we can test for the issue -- we can try ot fix it, and we'll know it's fixed when the tests pass.
+Now that we know we can test for the issue, we can try to fix it, and we'll know it's fixed when the tests pass.
So looking at the code -- why did I get two ```` tags?
@@ -356,7 +358,7 @@ So looking at the code -- why did I get two ```` tags?
out_file.write("\n")
out_file.write("{}>\n".format(self.tag))
-Hmm -- when are those tags getting rendered? *inside* the loops through the contents! oops! we want to write the tag *before* the loop, and the closing tag *after* loop. (Did you notice that the first time? I hope so.) So a little restructuring is in order.
+Hmm -- when are those tags getting rendered? *Inside* the loops through the contents! Oops! We want to write the tag *before* the loop, and the closing tag *after* loop. (Did you notice that the first time? I hope so.) So a little restructuring is in order.
.. code-block:: python
@@ -381,9 +383,9 @@ That's it -- let's see if the tests pass now::
and this is some more text
-mine failed on the ``assert False`` -- so the actual test passed -- good. And the rendered html tag looks right, too. So we can go ahead and remove that ``assert False``, and move on!
+Mine failed on the ``assert False``. So the actual test passed -- good. And the rendered html tag looks right, too. So we can go ahead and remove that ``assert False``, and move on!
-We have tested to see that we could initialize with one piece of content, and then add another, but what if you initialized it with nothing, and then added some? Try uncommenting the next test: ``test_render_element2`` -- and see what you get.
+We have tested to see that we could initialize with one piece of content, and then add another, but what if you initialized it with nothing, and then added some content? Try uncommenting the next test: ``test_render_element2`` and see what you get.
This is what I got with my code::
@@ -431,13 +433,13 @@ This is what I got with my code::
html_render.py:23: TypeError
====================== 1 failed, 3 passed in 0.08 seconds ======================
-Darn -- something is wrong here. And this time it errored out before it even got results to test. So look and see exactly what the error is. (pytest does a really nice job of showing you the errors)::
+Darn! Something is wrong here. And this time it errored out before it even got results to test. So look and see exactly what the error is. (pytest does a really nice job of showing you the errors)::
out_file.write("<{}>\n".format(self.tag))
> out_file.write(content)
E TypeError: string argument expected, got 'NoneType'
-So it failed when we tried to write to the file. We're trying to write a piece of content, and we got a "NoneType". How in the world did a "NoneType" (which is the type of None) get in there?
+It failed when we tried to write to the file. We're trying to write a piece of content, and we got a ``NoneType``. How in the world did a ``NoneType`` (which is the type of None) get in there?
Where does the ``self.contents`` list get created? In the ``__init__``. Let's do a little print debugging here. Add a print to the __init__:
@@ -460,26 +462,29 @@ And run the tests again::
Same failure -- but pytest does a nice job of showing you what was printed (stdout) when a test fails. So in this case, at the end of the ``__init__`` method, the contents list looks like ``[None]`` -- a list with a single None object in it. No wonder it failed later when we tried to write that None to a file!
-But why? -- well, looking at the __init__ -- it looks like content gets set to None by default:
+But why? Well, looking at the ``__init__``, it looks like content gets set to None by default::
+
+.. code-block:: python
def __init__(self, content=None):
-and then we put that in the ``self.contents`` list. What do we want went content is None? An empty list, so that we can add to it later. So you need some code that checks for ``None`` (hint: use ``is None`` or ``is not None`` to check for ``None``), and only adds content to the list if it is not None.
+Then we put that ``None`` in the ``self.contents`` list. What do we want when content is ``None``?
+An empty list, so that we can add to it later, not a list with a ``None`` object in it. So you need some code that checks for ``None`` (hint: use ``is None`` or ``is not None`` to check for ``None``), and only adds content to the list if it is *not* ``None``.
-I'll leave it as an exercise for the reader to figure out how to do that -- but make sure all tests are passing before you move on! And once the tests pass, you may want to remove that ``print()`` line.
+I'll leave it as an exercise for the reader to figure out how to do that, but make sure all tests are passing before you move on! And once the tests pass, you may want to remove that ``print()`` line.
.. _render_tutorial_2_A:
Step 2:
-------
-OK, we have nice little class here -- it has a class attribute to store information about the tag -- information that's the same for all instances.
+OK, we have nice little class here; it has a class attribute to store information about the tag, information that's the same for all instances.
And we are storing a list of contents in "self" -- information that each instance needs its own copy of.
And we are using that data to render an element.
-So we're ready to move on:
+So we're ready to move on.
Part A
......
@@ -489,7 +494,7 @@ Part A
"Create a couple subclasses of ``Element``, for each of ````, ````, and ```` tags. All you should have to do is override the ``tag`` class attribute (you may need to add a ``tag`` class attribute to the ``Element`` class first, if you haven't already)."
-So this is very straightforward -- we have a class that represents an element -- and the only difference between basic elements is that they have a different tag. for example::
+So this is very straightforward. We have a class that represents an element, and the only difference between basic elements is that they have a different tag. For example::
Some content.
@@ -504,7 +509,7 @@ and::
-The ```` tag is for the entire contents of an html page, and the ```` tag is for a paragraph. But you can see that form of the tags is identical, so we don't have to change much to make classes for these tags. In fact, all we need to change is the ``tag`` class attribute.
+The ``
`` tag is for the entire contents of an html page, and the ```` tag is for a paragraph. But you can see that the form of the tags is identical, so we don't have to change much to make classes for these tags. In fact, all we need to change is the ``tag`` class attribute.
Before we do that -- let's do some test-driven development. Uncomment the next few tests in ``test_html_render.py``: ``test_html``, ``test_body``, and ``test_p``, and run the tests::
@@ -540,11 +545,12 @@ Before we do that -- let's do some test-driven development. Uncomment the next f
test_html_render.py:142: NameError
====================== 3 failed, 4 passed in 0.08 seconds ======================
-So we have three failure -- of course we do -- we haven't written code yet! Yes, this is pedantic, and there is no real reason to run tests you know are going to fail -- but there is a reason to *write* tests that you know are going to fail -- and you have to run them to know that you have written them correctly.
+So we have three failures. Of course we do, because we haven't written any new code yet!
+Yes, this is pedantic, and there is no real reason to run tests you know are going to fail. But there is a reason to *write* tests that you know are going to fail, and you have to run them to know that you have written them correctly.
Now we can write the code for those three new element types. Try to do that yourself first, before you read on.
-OK -- did you do something as simple as this?
+OK, did you do something as simple as this?
.. code-block:: python
@@ -563,9 +569,9 @@ and this line:
`` tag = 'body'``
-means: set the "tag" class attribute to 'body'. Since this class attribute was set by the Element tag already -- this is called "overriding" the tag attribute.
+means: set the ``tag`` class attribute to ``'body'``. Since this class attribute was set on the ``Element`` class already, this is called "overriding" the tag attribute.
-The end result is that we now have a class that is exactly the same as the Element class, except with a different tag. Where is that attribute used? It is used in the ``render()`` method.
+The end result is that we now have a class that is exactly the same as the ``Element`` class, except with a different tag. Where is that attribute used? It is used in the ``render()`` method.
Let's run the tests and see if this worked::
@@ -582,15 +588,17 @@ Let's run the tests and see if this worked::
Success!. We now have three different tags.
.. note::
- Why the ``Html`` element? doesn't the ``Element`` class already use the "html" tag?
- Indeed it does -- but the goal of the ``Element`` class is to be a base class for the other tags, rather than being a particular element.
+ Why the ``Html`` element? Doesn't the ``Element`` class already use the "html" tag?
+ Indeed it does, but the goal of the ``Element`` class is to be a base class for the other tags, rather than being a particular element.
Sometimes this is called an "abstract base class": a class that can't do anything by itself, but exists only to provide an interface (and partial functionality) for subclasses.
But we wanted to be able to test that partial functionality, so we had to give it a tag to use in the initial tests.
- If you want to be pure about it -- you could use something like "abstract_tag" in the ``Element`` class to make it clear that it isn't supposed to be used alone. And later on in the assignment, we'll be adding extra functionality to the ``Html`` element.
+ If you want to be pure about it, you could use something like ``abstract_tag`` in the ``Element`` class to make it clear that it isn't supposed to be used alone. And later on in the assignment, we'll be adding extra functionality to the ``Html`` element.
-Making a subclass where the only thing you change is a single class attribute may seem a bit silly -- and indeed it is. If that were going to be the ONLY difference between all elements, There would be other ways to accomplish that task that would make more sense -- perhaps passing the tag in to the initializer, for instance. But have patience, as we proceed with the exercise, some element types will have more customization.
+Making a subclass where the only thing you change is a single class attribute may seem a bit silly -- and indeed it is.
+If that were going to be the *only* difference between all elements, There would be other ways to accomplish that task that would make more sense -- perhaps passing the tag in to the initializer, for instance.
+But have patience, as we proceed with the exercise, some element types will have more customization.
-But another thing to keep in mind -- the fact that that is ALL we need to do to get a new type of element demonstrates the power of subclassing -- with that tiny change, we get a new element that we can add content to, and render to a file, etc. With virtually no repeated code.
+Another thing to keep in mind. The fact that writing a new subclass is ALL we need to do to get a new type of element demonstrates the power of subclassing. With the tiny change of adding a subclass with a single class attribute, we get a new element that we can add content to, and render to a file, etc., with virtually no repeated code.
.. _render_tutorial_2_B:
@@ -661,16 +669,17 @@ Uncomment ``test_subelement`` in the test file, and run the tests::
html_render.py:26: TypeError
====================== 1 failed, 7 passed in 0.11 seconds ======================
-Again, the new test failed -- no surprise, we haven't written any new code yet. But do read the report carefully -- it did not fail on an assert -- but rather with a ``TypeError``. The code itself raised an exception before it could produce results to test.
+Again, the new test failed; no surprise because we haven't written any new code yet. But do read the report carefully; it did not fail on an assert, but rather with a ``TypeError``. The code itself raised an exception before it could produce results to test.
-So now it's time to write the code -- look at where the exception was raised: line 26 in my code, inside the ``render()`` method. The line number will likely be different in your code, but it probably failed on the render method. Looking closer at the error::
+So now it's time to write the code. Look at where the exception was raised: line 26 in my code, inside the ``render()`` method. The line number will likely be different in your code, but it probably failed on the render method. Looking closer at the error::
> out_file.write(content)
E TypeError: string argument expected, got 'P'
-It occurred in the file ``write`` method, complaining that it expected to be writing a string to the file, but it got a 'P' -- 'P' is the name of the paragraph element class. So we need a way to write an element to a file. How might we do that? Inside the element's render method, we need to render an element...
+It occurred in the file ``write`` method, complaining that it expected to be writing a string to the file, but it got a ``'P'``. ``'P'`` is the name of the paragraph element class.
+So we need a way to write an element to a file. How might we do that? Inside the element's render method, we need to render an element...
-Well, elements already know how to render themselves -- this is what is meant by a recursive approach -- in the ``render`` method, we want to make use of the ``render`` method itself.
+Well, elements already know how to render themselves. This is what is meant by a recursive approach. In the ``render`` method, we want to make use of the ``render`` method itself.
Looking at the signature of the render method::
@@ -690,7 +699,7 @@ it becomes clear -- we render an element by passing the output file to the eleme
out_file.write("\n")
out_file.write("{}>\n".format(self.tag))
-So let's update our render by replacing that ``out_file.write()`` call with a call to the content's ``render`` method:
+So let's update our render by replacing that ``out_file.write()`` call with a call to the content's ``render`` method:
.. code-block:: python
@@ -750,7 +759,7 @@ And let's see what happens when we run the tests::
html_render.py:27: AttributeError
====================== 6 failed, 2 passed in 0.12 seconds ======================
-Whoaa! six failures! We really broke something! But that is a *good* thing -- it's the whole point of unit tests -- when you are making a change to address one issue, you know right away that you broke previously working code.
+Whoaa! Six failures! We really broke something! But that is a *good* thing. It's the whole point of unit tests. When you are making a change to address one issue, you know right away that you broke previously working code.
So let's see if we can fix these tests, while still allowing us to add the feature we intended to add.
@@ -760,10 +769,10 @@ Again -- look carefully at the error, and the solution might pop out at you::
E AttributeError: 'str' object has no attribute 'render'
Now we are trying to call a piece of content's ``render`` method, but we got a simple string, which does not *have* a ``render`` method.
-This is the challenge of this part of the exercise -- it's easy to render a string, and it's easy to render an element, but the content list could have either one -- so how do we switch between the two methods?
+This is the challenge of this part of the exercise. It's easy to render a string, and it's easy to render an element, but the content list could have either one. So how do we switch between the two methods?
There are a number of approaches you can take. This is a good time to read the notes about this here: :ref:`notes_on_handling_duck_typing`.
-You may want to try one of the more complex methods -- but for now, we're going to use the one that suggests itself from the error.
+You may want to try one of the more complex methods, but for now, we're going to use the one that suggests itself from the error.
We need to know whether we want to call a ``render()`` method, or simply write the content to the file. How would we know which to do? Again, look at the error:
We tried to call the render() method of a piece of content, but got an ``AttributeError``. So the way to know whether we can call a render method is to try to call it -- if it works, great! If not, we can catch the exception, and do something else. In this case, the something else is to try to write the content directly to the file:
@@ -793,17 +802,18 @@ And run the tests again::
=========================== 8 passed in 0.03 seconds ===========================
-Yeah! all eight tests pass! I hope you found that at least a little bit satisfying. And pretty cool, really, only two extra lines of code. This is an application of the EAFP method: it's Easier to Ask Forgiveness than Permission. You simply try to do one thing, and if that raises the exception you expect, than do something else.
+Yeah! all eight tests pass! I hope you found that at least a little bit satisfying. And pretty cool, really, that the solution requires only two extra lines of code. This is an application of the EAFP method: it's Easier to Ask Forgiveness than Permission. You simply try to do one thing, and if that raises the exception you expect, than do something else.
-It's also taking advantage of Python's "Duck Typing" notice that we don't know if that piece of content is actually an ``Element`` object -- all we know is that it has a render() method that we can pass a file-like object to. Which is quite deliberate -- if some future user (that might be you) wants to write their own element type, that can do that -- and all it needs to do is define a render method.
+It's also taking advantage of Python's "Duck Typing". Notice that we don't know if that piece of content is actually an ``Element`` object. All we know is that it has a ``render()`` method that we can pass a file-like object to.
+This is quite deliberate. If some future user (that might be you) wants to write their own element type, they can write it any way they like, as long as it defines a ``render()`` method that can take a file-like object to write to.
-So what are the downsides? Well, there are two:
+So what are the downsides to this method? Well, there are two:
-1. When we successfully call the ``render`` method, we have no idea if it's actually done the right thing -- it could do anything -- if someone puts some completely unrelated object in the content list that happens to have a render method, this is not going to work -- but what are the odds of that?
+1. When we successfully call the ``render`` method, we have no idea if it's actually done the right thing -- it could do anything. If someone puts some completely unrelated object in the content list that happens to have a ``render`` method, this is not going to work. But what are the odds of that?
-2. This is the bigger one -- if the object *HAS* a render method, but that render method has something wrong with it, then it could conceivably raise an AttributeError itself -- but it would not be the Attribute Error we are expecting. The trick here is that this is very hard to debug.
+2. This is the bigger one: if the object *HAS* a render method, but that render method has something wrong with it, then it could conceivably raise an ``AttributeError`` itself, but it would not be the Attribute Error we are expecting. The trick here is that this is very hard to debug.
-However, we are saved by tests. If the render method works in all the other tests, It's not going to raise an AttributeError only in this case. Another reason to have a good test suite.
+However, we are saved by tests. If the render method works in all the other tests, It's not going to raise an ``AttributeError`` only in this case. Another reason to have a good test suite.
.. _render_tutorial_3:
@@ -815,11 +825,11 @@ Now we are getting a little more interesting.
"Create a ``
`` element -- a simple subclass."
-This is easy -- you know how to do that, yes?
+This is easy; you know how to do that, yes?
-But the training wheels are off -- you are going to need to write your own tests now. So before you create the ``Head`` element class, write a test for it. You should be able to copy and paste one the previous tests, and just change the name of the class and the tag text. Remember to give it a new name, or it will simply replace the previous test.
+But the training wheels are off -- you are going to need to write your own tests now. So before you create the ``Head`` element class, write a test for it. You should be able to copy and paste one of the previous tests, and just change the name of the class and the tag value. Remember to give yo test a new name, or it will simply replace the previous test.
-I like to run the tests as soon as I make a new one -- if nothing else, I can make sure I have one more test!
+I like to run the tests as soon as I make a new one. If nothing else, I can make sure I have one more test.
OK, that should have been straightforward. Now this part:
@@ -829,9 +839,9 @@ OK, that should have been straightforward. Now this part:
PythonClass - Session 6 example
-Some html elements don't tend to have a lot content -- like the document title. So it makes sense to render them all on one line. This is going to require a new render method. Since there are multiple types of elements that should be rendered on one line, we want to create a base class for all one-line elements. It should subclass from Element, and override the render method with a new one, which will be pretty much the same as the main ``Element`` method, but without the newlines.
+Some html elements don't tend to have a lot of content, such as the document title. So it makes sense to render them all on one line. This is going to require a new render method. Since there are multiple types of elements that should be rendered on one line, we want to create a base class for all one-line elements. It should subclass from ``Element``, and override the render method with a new one, which will be pretty much the same as the main ``Element`` method, but without the newlines.
-Before we do that though -- let's write a test for that! as the ONeLIneTag class is a base class for actual elements that should be rendered on one line, we really don't need to write a test directly for it. We can write one for its first subclass: ``Title``. The title elements should be rendered something like this::
+Before we do that though -- let's write a test for that! Because the ``ONeLineTag`` class is a base class for actual elements that should be rendered on one line, we really don't need to write a test directly for it. We can write one for its first subclass: ``Title``. The title elements should be rendered something like this::
PythonClass - title example
@@ -839,7 +849,7 @@ Which should be generated by code like this::
Title("PythonClass - title example")
-Take a look at one of the other tests to get ideas -- and maybe start with a copy and paste, and then change the names:
+Take a look at one of the other tests to get ideas, and maybe start with a copy and paste, and then change the names:
.. code-block:: python
@@ -890,7 +900,7 @@ You can run the tests now if you like -- it will fail due to there being no Titl
class Title(OneLineTag):
tag = "title"
-The ``pass`` means "do nothing" -- but it is required to satisfy PYhton -- there needs to be *something* in the class definition. So in this case, we have a ``OneLineTag`` class that is exactly the same as the Element class. And a Title class that is the same except for the tag. Time to test again::
+The ``pass`` means "do nothing." But it is required to satisfy Python. There needs to be *something* in the class definition. So in this case, we have a ``OneLineTag`` class that is exactly the same as the ``Element`` class, and a ``Title`` class that is the same except for the tag. Time to test again::
$ pytest
============================= test session starts ==============================
@@ -931,7 +941,7 @@ The title test failed on this assertion::
> assert "\n" not in file_contents
-which is what we expected -- we haven't written a new render method yet. But look at the end of the output -- where is says ``-- Captured stdout call --``. That is showing you how the title element is being rendered -- with the newlines. That's there because there is a print in the test:
+Which is what we expected because we haven't written a new render method yet. But look at the end of the output, where is says ``-- Captured stdout call --``. That shows you how the title element is being rendered -- with the newlines. That's there because there is a print in the test:
.. code-block:: python
@@ -939,10 +949,10 @@ which is what we expected -- we haven't written a new render method yet. But lo
.. note::
- pytest is pretty slick with this. It "Captures" the output from print calls, etc, and then only shows them to you if a test fails.
+ pytest is pretty slick with this. It "Captures" the output from print calls, etc., and then only shows them to you if a test fails.
So you can sprinkle print calls into your tests, and it won't clutter the output -- you'll only see it when a test fails, which is when you need it.
-This is a good exercise to go through -- if a new test fails, it lets you know that the test itself is working -- testing what it is supposed to test.
+This is a good exercise to go through. If a new test fails, it lets you know that the test itself is working, testing what it is supposed to test.
So how do we get this test to pass? We need a new render method for ``OneLineTag``. For now, you can copy the render method from ``Element`` to ``OneLineTag``, and remove the newlines:
@@ -960,7 +970,7 @@ So how do we get this test to pass? We need a new render method for ``OneLineTag
out_file.write(content)
out_file.write("{}>\n".format(self.tag))
-notice that I left the newline in at the end of the closing tag -- we do want a newline there, so the next element won't get rendered on the same line. And the tests::
+Notice that I left the newline in at the end of the closing tag -- we do want a newline there, so the next element won't get rendered on the same line. And the tests::
$ pytest
============================= test session starts ==============================
@@ -972,7 +982,7 @@ notice that I left the newline in at the end of the closing tag -- we do want a
========================== 10 passed in 0.03 seconds ===========================
-We done good. But wait! there *is* a newline at the end, and yet the assert: `assert "\n" not in file_contents` passed! Why is that?
+We done good. But wait! there *is* a newline at the end, and yet the assert: ``assert "\n" not in file_contents`` passed! Why is that?
Take a look at the code in the tests that renders the element:
@@ -1013,7 +1023,7 @@ If you are nervous about people appending content that will then be ignored, you
file_contents = render_result(e).strip()
print(file_contents)
-and run the tests::
+And run the tests::
test_html_render.py:199:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
@@ -1028,8 +1038,8 @@ and run the tests::
html_render.py:57: NotImplementedError
===================== 1 failed, 10 passed in 0.09 seconds ======================
-hmm -- it raised a NotImplementedError, which is what we want -- but it is logging as a test failure. An exception raised in a test is going to cause a failure -- but what we want is for the test to pass only *if* that exception is raised.
-Fortunately, pytest has a utility to do just that. make sure there is an ``import pytest`` in your test file, and then add this code:
+Hmm. It raised a ``NotImplementedError``, which is what we want, but it is logging as a test failure. An exception raised in a test is going to cause a failure -- but what we want is for the test to pass only *if* that exception is raised.
+Fortunately, pytest has a utility to do just that. Make sure there is an ``import pytest`` in your test file, and then add this code:
.. code-block:: python
@@ -1041,9 +1051,9 @@ Fortunately, pytest has a utility to do just that. make sure there is an ``impor
with pytest.raises(NotImplementedError):
e.append("some more content")
-That ``with`` is a "context manager" (kind of like the file ``open()`` one). More on that later in the course, but what this means is that the test will pass if and only if the code inside that ``with`` block raised a ``NotImplementedError``. If it raises something else, or it doesn't raise an exception at all -- then the test will fail.
+That ``with`` is a "context manager" (kind of like the file ``open()`` one). More on that later in the course, but what this means is that the test will pass if and only if the code inside that ``with`` block raised a ``NotImplementedError``. If it raises something else, or it doesn't raise an exception at all, then the test will fail.
-OK -- I've got 11 tests passing now. How about you? Time for the next step.
+OK, I've got 11 tests passing now. How about you? Time for the next step.
.. _render_tutorial_4:
@@ -1108,9 +1118,9 @@ Note that this doesn't (yet) test that the attributes are actually rendered, but
test_html_render.py:217: TypeError
===================== 1 failed, 11 passed in 0.19 seconds ======================
-Yes, the new test failed -- isn't TDD a bit hard on the ego? So many failures! But why? well, we passed in the ``style`` and ``id`` attributes as keyword arguments -- but the ``__init__`` doesn't expect those arguments -- hence the failure.
+Yes, the new test failed -- isn't TDD a bit hard on the ego? So many failures! But why? Well, we passed in the ``style`` and ``id`` attributes as keyword arguments, but the ``__init__`` doesn't expect those arguments. Hence the failure.
-So should be add those two as keyword parameters? Well, no we shouldn't -- because those are two arbitrary attribute names -- we need to support virtually any attribute name. So how do you write a method that will accept ANY keyword argument? Time for our old friend ``**kwargs``. ``**kwargs**`` will allow any keyword argument to be used, and will store them in the ``kwargs`` dict. So time to update the ``Element.__init__`` like so:
+So should be add those two as keyword parameters? Well, no we shouldn't because those are two arbitrary attribute names; we need to support virtually any attribute name. So how do you write a method that will accept ANY keyword argument? Time for our old friend ``**kwargs``. ``**kwargs**`` will allow any keyword argument to be used, and will store them in the ``kwargs`` dict. So time to update the ``Element.__init__`` like so:
.. code-block:: python
@@ -1118,7 +1128,7 @@ So should be add those two as keyword parameters? Well, no we shouldn't -- becau
But then, make sure to *do* something with the ``kwargs`` dict -- you need to store those somewhere. Remember that they are a collection of attribute names and values -- and you will need them again when it's time to render the opening tag. How do you store something so that it can be used in another method? I'll leave that as an exercise for the reader.
-And lets try to run the tests again::
+And let's try to run the tests again::
========================== 12 passed in 0.07 seconds ===========================
@@ -1160,13 +1170,13 @@ So we need to render the ``<``, then the ``p``, then a bunch of attribute name=v
out_file.write("".join(open_tag))
...
-OK -- the rest of the tests are still passing for me -- I haven't broken anything else. Now to add code to render the attributes. You'll need to write some sort of loop to loop through each attribute -- probably looping through the keys and the values::
+OK, the rest of the tests are still passing for me; I haven't broken anything else. Now to add code to render the attributes. You'll need to write some sort of loop to loop through each attribute, probably looping through the keys and the values::
.. code-block:: python
for key, value in self.attributes:
-then you can render them in html form inside that loop.
+Then you can render them in html form inside that loop.
Once you've done that, run the tests again. When I do that, mine passes the asserts checking the attributes, but fails on the ``assert False``, so I can see how it's rendering::
@@ -1186,19 +1196,19 @@ Hmmm -- the attributes are rendered correctly, but there is no space between the
assert file_contents.startswith("
A paragraph of text
-However, my code for rendering the opening tag is a bit klunky -- how about yours? Perhaps you'd like to refactor it? Before you do that, you might want to make your tests a bit more robust. This is really tricky, it's very hard to test for everytihng that might go wrong, without nailing down the expected results to much. For example, in this case, we haven't tested that there is a space between the two attributes. IN fact, this would pass our test::
+However, my code for rendering the opening tag is a bit klunky -- how about yours? Perhaps you'd like to refactor it? Before you do that, you might want to make your tests a bit more robust. This is really tricky. It's very hard to test for everytihng that might go wrong, without nailing down the expected results too much. For example, in this case, we haven't tested that there is a space between the two attributes. In fact, this would pass our test::
A paragraph of text
-See how there is no space before "id"? But the order of the attributes is arbitrary, so we don't want to assume that the style will come before id. You could get really fnacy with parsing the results, but I think we could get away with assuring there are the right number of spaces in there in the opening tag.
+See how there is no space before "id"? But the order of the attributes is arbitrary, so we don't want to assume that the style will come before id. You could get really fancy with parsing the results, but I think we could get away with assuring there are the right number of spaces in the opening tag.
.. code-block:: python
@@ -1283,7 +1293,8 @@ We'll want multiple self closing tags -- so we'll start with a base class, and t
class Hr(SelfClosingTag):
tag = "hr"
-Test still fails -- but gets further. You'll need to override the ``render()`` method:
+Test still fails -- but gets further.
+You'll need to override the ``render()`` method:
.. code-block:: python
@@ -1292,7 +1303,10 @@ Test still fails -- but gets further. You'll need to override the ``render()`` m
def render(self, outfile):
# put rendering code here.
-So what needs to be there? Well, self closing tags can have attributes, same as other elements. So we need a lot of the same code here as with the other ``render()`` methods. So you could copy and paste the ``Element.render()`` method, and edit it a bit. But that is a "Bad Idea" -- remember DRY? (**D**on't **Repeat** **Y**ourself). you really don't want two copies of that attribute rendering code you worked so hard on. So how do we avoid that? We take advantage of the power of subclassing. If you put the code to render the opening (and closing) tags in it's own method, then we can call that method from multiple render methods, something like::
+What needs to be there? Well, self closing tags can have attributes, same as other elements.
+So we need a lot of the same code here as with the other ``render()`` methods. You could copy and paste the ``Element.render()`` method, and edit it a bit. But that's a "Bad Idea" -- remember DRY (Don't Repeat Yourself)?
+You really don't want two copies of that attribute rendering code you worked so hard on.
+How do we avoid that? We take advantage of the power of subclassing. If you put the code to render the opening (and closing) tags in it's own method, then we can call that method from multiple render methods, something like:
.. code-block:: python
@@ -1364,7 +1378,7 @@ And run your tests. I still get a single failure::
test_html_render.py:297: Failed
===================== 1 failed, 17 passed in 0.08 seconds ======================
-So ``append`` did the right thing. But we still have a failure when we try to initialize it with content. So we want to override the ``__init__``, check if there was any content passed in, and if there was, raise an error. Andn if not, then call the usual ``__init__``.
+So ``append`` did the right thing. But we still have a failure when we try to initialize it with content. So we want to override the ``__init__``, check if there was any content passed in, and if there is, raise an error. And if not, then call the usual ``__init__``.
.. code-block:: python
@@ -1386,7 +1400,7 @@ What's that ``super()`` call? That's a way to call a method on the "super class'
But ``super`` provides some extra features if you are doing multiple inheritance. And it makes your intentions clear.
-I've now got 18 tests passing -- how about you? And I can also uncomment step 5 in ``run_html_render.py``, and get something reasonable::
+I've now got 18 tests passing. How about you? And I can also uncomment step 5 in ``run_html_render.py``, and get something reasonable::
$ python run_html_render.py
@@ -1401,37 +1415,187 @@ I've now got 18 tests passing -- how about you? And I can also uncomment step 5
-If you get anything very different than this -- write some tests to catch the error, and then fix them :-)
+If you get anything very different than this, write some tests to catch the error, and then fix them :-)
Step 6:
-------
+Create an ``A`` class for an anchor (link) element. Its constructor should look like::
+
+ A(self, link, content)
+
+where ``link`` is the link, and ``content`` is what you see. It can be called like so::
+
+ A("http://google.com/", "link to google")
+
+and it should render like::
+
+ link to google
+
+Notice that while the a ("anchor") tag is kind of special, the link is simply an "href" (hyperlink reference) attribute. So we should be able to use most of our existing code, but simply add the link as another attribute.
+
+You know that drill now. Create a test first: one that makes the above call, and then checks that you get the results expected. Notice that this is a single line tag, so it should subclass from OneLineTag. If I start with that, I get::
+
+ =================================== FAILURES ===================================
+ _________________________________ test_anchor __________________________________
+
+ def test_anchor():
+ > a = A("http://google.com/", "link to google")
+ E TypeError: __init__() takes from 1 to 2 positional arguments but 3 were given
+
+ test_html_render.py:307: TypeError
+
+Hmm -- a ``TypeError`` in the ``__init__``. Well, that makes sense because we need to be able to pass the link in to it. We will need to override it, of course:
+
+.. code-block:: python
+
+ class A(OneLineTag):
+
+ tag = 'a'
+
+ def __init__(self, link, content=None, **kwargs):
+ super().__init__(content, **kwargs)
+
+Notice that I added the "link" parameter at the beginning, and that the rest of the parameters are the same as for the base ``Element`` class. This is a good approach. If you need to add an extra parameter when subclassing, put it at the front of the parameter list. We then call ``super().__init__`` with the content and any other keyword arguments. We haven't actually done anything with the link, but when I run the tests, it gets further, failing on the rendering.
+
+So we need to do something with the link. But what? Do we need a new render method? Maybe not. After all, the link is really just the value of the "href" attribute. So we can simply create an href attribute, and the existing rendering code should work.
+
+How are the attributes passed in? They are passed in in the ``kwargs`` dict. So we can simply add the link to the ``kwargs`` dict before calling the superclass initializer:
+
+.. code-block:: python
+
+ def __init__(self, link, content=None, **kwargs):
+ kwargs['href'] = link
+ super().__init__(content, **kwargs)
+
+And run the tests::
+
+ =================================== FAILURES ===================================
+ _________________________________ test_anchor __________________________________
+
+ def test_anchor():
+ a = A("http://google.com/", "link to google")
+ file_contents = render_result(a)
+ print(file_contents)
+ > assert file_contents.startswith('(' = 'link to google\n'.startswith
+
+ test_html_render.py:310: AssertionError
+ ----------------------------- Captured stdout call -----------------------------
+ link to google
+
+ ===================== 1 failed, 18 passed in 0.07 seconds =====================
+
+Darn -- not passing! (Did yours pass?) Even though we added the ``href`` to the kwargs dict, it didn't get put in the attributes of the tag. Why not? Think carefully about the code. Where should the attributes be added? In the ``render()`` method.
+But *which* render method is being used here? Well, the ``A`` class is a subclass of ``OneLineTag``, which has defined its own ``render()`` method.
+So take a look at the ``OneLineTag`` ``render()`` method. Oops, mine doesn't have anything in to render attributes -- I wrote that before we added that feature.
+So it's now time to go in and edit that render method to use the ``_open_tag`` and ``_close_tag`` methods.
+
+The tests should all pass now -- and you have a working anchor element.
+
+Step 7:
+-------
+
+Making the list elements is pretty straightforward -- go ahead and do those -- and write some tests for them!
+
+Header Elements
+...............
+
+You should have the tools to do this. First, write a couple tests.
+
+Then decide what to subclass for the header elements? Which of the base classes you've developed is most like a header?
+
+Then think about how you will update the ``__init__`` of your header subclass. It will need to take an extra parameter -- the level of the header:
+
+.. code-block:: python
+
+ def __init__(self, level, content=None, **kwargs):
+
+But what to do with the level parameter? In this case, each level will have a different tag: ``h1``, ``h2``, etc. So you need to set the tag in the ``__init__``. So far, the tag has been a class attribute -- all instances of the class have the same tag.
+In this case, each instance can have a different tag -- determined at initialization time. But how to override a class attribute? Think about how you access that attribute in your render methods: ``self.tag``.
+When you make a reference to ``self.something``, Python first checks if "something" is an instance attribute. Then, if not, it checks for a class attribute, and, if not, then it looks in the superclasses.
+So in this case, of you set an instance attribute for the tag -- that is what will be found in the other methods. So in the ``__init__``, you can set ``self.tag=the_new_tag_value``, which will be ``h1``, or ``h2``, or ...
+
+Step 8:
+-------
+
+You have all the tools now for making a proper html element -- it should render as so::
+
+
+
+
+ Python Class Sample page
+
+ ...
+
+That is, put a doctype tag at the top, before the html opening tag.
+(Note that that may break an earlier test. Update that test!)
+
+Step 9:
+-------
+**Adding Indentation**
+Be sure to read the instructions for this carefully -- this is a bit tricky. But it's also fairly straightforward once you "get it." The trick here is that a given element can be indented some arbitrary amount and there is no way to know until render time how deep it is. But when a given element is rendering itself, it needs to know how deep it's indented, and it knows that the sub-elements need to be indented one more level. So by passing a parameter to each ``render()`` method that tells that element how much to indent itself, we can build a flexible system.
+Begin by uncommenting the tests in the test file for indentation:
+``test_indent``, ``test_indent_contents``, ``test_multiple_indent``, and ``test_element_indent1``.
+These are probably not comprehensive, but they should get you started. If you see something odd in your results, make sure to add a test for that before you fix it.
+Running these new tests should result in 4 failures -- many (all?) of them like this::
+ AttributeError: type object 'Element' has no attribute 'indent'
+So the first step is to give you Element base class an ``indent`` attribute. This is the amount that you want one level of indentation to be -- maybe two or four spaces. You want everything the be indented the same amount, so it makes sense that you put it as a class attribute of the base class -- then *all* elements will inherit the same value. And you can change it in that one place if you want.
+Once I add the ``indent`` parameter, I still get four failures -- three of them are for the results being incorrect -- which makes sense, because we haven't implemented that code yet. One of them is::
+ # this so the tests will work before we tackle indentation
+ if ind:
+ > element.render(outfile, ind)
+ E TypeError: render() takes 2 positional arguments but 3 were given
+
+This is the next one to tackle; our ``render()`` methods all need to take an additional optional parameter, the current level of indentation. Remember to add that to *all* of your ``render()`` methods:
+
+.. code-block:: python
+ def render(self, out_file, cur_ind=""):
+Once I do that, I still get four failures. But they are all about the rendered results being incorrect; the rendered indentation levels are not right.
+Now it's time to go in one by one and add indentation code to get those tests to pass.
+I got them all to pass. But when I rendered a full page (by running ``run_html_render.py``), I found some issues. The elements that overrode the ``render()`` methods where not indented properly: ``OneLineTag`` and ``SelfClosingTag``.
+Write at least one test for each of those, and then go fix those ``render()`` methods!
+What have you done?
+-------------------
+**Congrats!**
+You've gotten to the end of the project. By going through this whole procedure, you've done a lot of test-driven development, and built up a class system that takes advantage of the key features of object oriented systems in Python. I hope this has given you a better understanding of:
+* class attributes vs. instance attributes
+* subclassing
+* overriding:
+ - class attributes
+ - class attributes with instance attributes
+ - methods
+* calling a superclass' method from an overridden method.
+* defining a "private" method to be used by sub-classes overridden methods to make DRY code.
+* polymorphism -- having multiple classes all be used in the same way (calling the ``render`` method in this case)
diff --git a/source/modules/Comprehensions.rst b/source/modules/Comprehensions.rst
index 97cfaac6..f94eb69a 100644
--- a/source/modules/Comprehensions.rst
+++ b/source/modules/Comprehensions.rst
@@ -4,15 +4,15 @@
Comprehensions
##############
+**A bit of functional programming.**
-List comprehensions
--------------------
-A bit of functional programming.
+List Comprehensions
+-------------------
The concept of "functional programming" is clearly defined in some contexts, but is also used in a less strict sense. Python is **not** a functional language in the strict sense, but it does support a number of functional paradigms.
-In general, code is considered "Pythonic" that used functional paradigms where they are natural, but not when they have to be forced in.
+In general, code is considered "Pythonic" that uses functional paradigms where they are natural, but not when they have to be forced in.
We will cover functional programming concepts more clearly later in the program, but for now, we'll talk about the syntax for a common functional paradigm: applying an expression to all the members of a sequence to produce another sequence:
@@ -22,32 +22,44 @@ Consider this common ``for`` loop structure:
new_list = []
for variable in a_list:
- new_list.append(expression(variable))
+ new_list.append(expression_with_variable))
-This is such a common pattern, that python added syntax to directly support it:
+This is such a common pattern that python added syntax to directly support it. THis syntax is known as "comprehensions". The most common of which is a list comprehension, used to build up a new list. There are a couple others, which we will get too later, but they all share a similar structure.
-It can be expressed with a single line using a "list comprehension"
+The above structure can be expressed with a single line using a "list comprehension" as so:
.. code-block:: python
- new_list = [expression for variable in a_list]
+ new_list = [expression_with_variable for variable in a_list]
+
+Nice and clear and compact. And the use of the "list" brackets (``[...]``) makes it clear you are making a list.
+
+Recall what an expression is in Python: a bit of code (names and operators) that evaluates to a value. So in the beginning of a comprehension, you can put anything that evaluates to a value -- and that value is what gets added to the list.
+This can be a simple (or complex) math operation: ``x * 3``, or a function or method call: ``a_string.upper()``, ``int(x)``.
+But it can not contain any statements: code that does not return a value, such as assignment (``x = 5``), or ``for`` loops, or ``if`` blocks.
+
-What about nested for loops?
+Nested Loops
+............
+
+What about nested for loops? Sometimes you need to build up a list by looping over two sequences like so:
.. code-block:: python
new_list = []
for var in a_list:
for var2 in a_list2:
- new_list.append(expression)
+ new_list.append(expression_with_var_and_var2)
-Can also be expressed in one line:
+This can also be expressed with a comprehension in one line:
.. code-block:: python
- new_list = [exp for var in a_list for var2 in a_list2]
+ new_list = [expression_with_var_and_var2 for var in a_list for var2 in a_list2]
-You get the "outer product", i.e. all combinations.
+But the two lists are not looped through in parallel. Rather, you get all combinations of the two lists -- Sometimes called the "outer product".
+
+For example:
.. code-block:: ipython
@@ -61,7 +73,7 @@ You get the "outer product", i.e. all combinations.
Note that it makes every combination of the two input lists, and thus will be ``len(list1) * len(list2)`` in size. And there is no reason for them to be the same size.
zip() with comprehensions
--------------------------
+.........................
If you want them paired up instead, you can use ``zip()``:
@@ -72,19 +84,19 @@ If you want them paired up instead, you can use ``zip()``:
Comprehensions and map()
-------------------------
+........................
Comprehensions are another way of expressing the "map" pattern from functional programming.
-Python does have a ``map()``, which pre-dates comprehensions. But it does much of the same things -- and most folks think comprehensions are the more "Pythonic" way to do it.
+Python does have a ``map()`` function, which pre-dates comprehensions. But it does much of the same things -- and most folks think comprehensions are the more "Pythonic" way to do it.
What about filter?
-------------------
+..................
-``filter()`` is another functional concept: building a new list with only *some* of the elements -- "filtering" out the ones you don't want.
+``filter()`` is another functional concept: building a new list with only *some* of the elements -- "filtering" out the ones you don't want. Python also has a ``filter()`` function, also pre-dating comprehensions, but you can do it with a comprehension as well, but it does the application of the expression and the filtering in one construct.
-This is to support the common case of when you have a conditional in the loop:
+This is to support the common case of having a conditional in the loop:
.. code-block:: python
@@ -93,16 +105,16 @@ This is to support the common case of when you have a conditional in the loop:
if something_is_true:
new_list.append(expression)
-You can do the same thing by adding a conditional to the comprehension:
+You can do this kind of "filtering" by adding a conditional to the comprehension:
.. code-block:: python
new_list = [expr for var in a_list if something_is_true]
-This is expressing the "filter" pattern. (and map at the same time -- one reason I like comprehensions more)
+This is expressing the "filter" pattern and the "map" pattern at the same time -- one reason I like comprehensions more.
-**Examples:**
+.. rubric:: Examples:
.. code-block:: ipython
@@ -135,16 +147,17 @@ You can do a similar thing with sets, too:
.. code-block:: python
- new_set = { value for variable in a_sequence }
+ new_set = { expression_with_variable for variable in a_sequence }
+The curly brackets (``{...}``) indicate a set.
-same as for loop:
+Results in the same set as this for loop:
.. code-block:: python
new_set = set()
- for key in a_list:
- new_set.add(value)
+ for variable in a_sequence:
+ new_set.add(expression_with_variable)
**Example:** Finding all the vowels in a string...
@@ -158,7 +171,9 @@ same as for loop:
In [21]: { l for l in s if l in vowels }
Out[21]: {'a', 'e', 'i', 'o'}
-Side note: why did I do ``set('aeiou')`` rather than just `aeiou` ?
+.. note::
+
+ Why did I use ``set('aeiou')`` rather than just `aeiou` ? ``in`` works with strings, but is it efficient?
Dict Comprehensions
@@ -179,6 +194,7 @@ Same as this for loop:
for key in a_list:
new_dict[key] = value
+A dict comprehension also uses curly brackets -- Python knows it's a dict comprehension due to the ``key: value`` construct.
**Example:**
@@ -189,10 +205,10 @@ Same as this for loop:
3: 'this_3', 4: 'this_4'}
-This is not as useful as it used to be, now that we have the ``dict()`` constructor...
-
A bit of history:
------------------
+.................
+
+dict comps are not as useful as they used to be, now that we have the ``dict()`` constructor.
In the early days of Python the only way to create a dict was with a literal::
@@ -200,7 +216,7 @@ In the early days of Python the only way to create a dict was with a literal::
or a dict that was already populated with a bunch of data.
-If you had a bunch of data in some other form, like a couple lists, you'd need to write a loop to fill it in:
+If you had a bunch of data in some other form, like a couple of lists, you'd need to write a loop to fill it in:
.. code-block:: ipython
@@ -226,7 +242,7 @@ now, with dict comps, you can do:
In [10]: d
Out[10]: {1: 'fred', 2: 'john', 3: 'mary'}
-But there is also now a ``dict()`` constructor (actually the type object for dict):
+But there is also a ``dict()`` constructor (actually the type object for dict):
.. code-block:: ipython
@@ -259,7 +275,7 @@ So we can create a dict from data like so:
In [15]: d
Out[15]: {1: 'fred', 2: 'john', 3: 'mary'}
-Which is more compact, and arguably more clear than the dict comprehension.
+Which is more compact, and arguably more clear, than the dict comprehension.
dict comps are still nice if you need to filter the results, though:
@@ -270,3 +286,128 @@ dict comps are still nice if you need to filter the results, though:
In [17]: d
Out[17]: {1: 'fred', 2: 'john'}
+
+generator expressions
+---------------------
+
+There is yet another type of comprehension: generator comprehensions, technically known as "generator expressions". They are very much like a list comprehension, except that they evaluate to an lazy-evaluated "iterable", rather than a list. That is, they *generate* the items on the fly.
+
+This is useful, because we often create a comprehension simply to loop over it right away:
+
+.. code-block:: python
+
+ for x in [y**2 for y in a_sequence]:
+ outfile.write(f"The number is: {x}")
+
+In this case, the comprehension: ``[y**2 for y in a_sequence]`` iterates over ``a_sequence``, computes the square of each item, and creates a whole new list with the new values.
+All this, just so it can be iterated over again right away. If the original sequence is large (or is itself a lazy-evaluated iterable), the the step of creating the extra list can be expensive.
+
+generator comprehensions, on the other hand, create an iterable evaluates the items as they are iterated over, rather than all at once ahead of time -- so the entire collection is never stored.
+
+The syntax for a generator comprehension is the same as a list comp, except it uses regular parentheses::
+
+ (y**2 for y in a_sequence)
+
+So what does that evaluate to? A list comp evaluates to a list:
+
+.. code-block:: ipython
+
+ In [1]: l = [x**2 for x in range(4)]
+
+ In [2]: l
+ Out[2]: [0, 1, 4, 9]
+
+ In [3]: type(l)
+ Out[3]: list
+
+A generator comp evaluates to a generator:
+
+.. code-block:: ipython
+
+ In [4]: g = (x**2 for x in range(4))
+
+ In [5]: g
+ Out[5]: at 0x102bbed00>
+
+ In [6]: type(g)
+ Out[6]: generator
+
+A generator is an object that can be iterated over with a for loop, and it will return the values as they are asked for:
+
+.. code-block:: ipython
+
+ In [7]: for i in g:
+ ...: print(i)
+ ...:
+ 0
+ 1
+ 4
+ 9
+
+Let's use a little function to make this clear:
+
+.. code-block:: ipython
+
+ In [8]: def test(x):
+ ...: print("test called with: ", x)
+ ...: return x ** 2
+
+It simply returns the square of the passed-in value, but prints it as it does so -- so we can see when it is called.
+If we use it in a list comp:
+
+.. code-block:: ipython
+
+ In [10]: [test(x) for x in range(3)]
+ test called with: 0
+ test called with: 1
+ test called with: 2
+ Out[10]: [0, 1, 4]
+
+We see that test gets called for all the values, and then a list is returned wth all the results.
+But if we use it in a generator comprehension:
+
+.. code-block:: ipython
+
+ In [11]: g = (test(x) for x in range(3))
+
+nothing gets printed (the function has not been called) until you loop through it:
+
+.. code-block:: ipython
+
+ In [16]: for i in g:
+ ...: print(i)
+ ...:
+ test called with: 0
+ 0
+ test called with: 1
+ 1
+ test called with: 2
+ 4
+
+you can see that ``test()`` is getting called *as* the loop is run. You usually don't put assign a generator expression to a variable, but rather, loop through it right away:
+
+.. code-block:: ipython
+
+ In [17]: for i in (test(x) for x in range(3)):
+ ...: print(i)
+ ...:
+ test called with: 0
+ 0
+ test called with: 1
+ 1
+ test called with: 2
+ 4
+
+When to use What
+................
+
+It's pretty simple:
+
+If you need a list (or a set or dict) for further work, then use a list comp.
+
+If you are going to immediately loop through the items created by the comprehension, use a generator comprehension.
+
+.. note::
+
+ The "official" term is "generator expression" -- that is what you will see in the Python docs, and a lot of online discussions. I've used the term "generator comprehension" to better make clear the association with list comprehensions.
+
diff --git a/source/modules/Modules.rst b/source/modules/Modules.rst
index 0157c49b..d953fdeb 100644
--- a/source/modules/Modules.rst
+++ b/source/modules/Modules.rst
@@ -40,7 +40,7 @@ You can put a one-liner after the colon:
In [168]: if x > 4: print(x)
12
-But this should only be done if it makes your code **more** readable.
+But this should only be done if it makes your code *more* readable. And that is rare.
So you need both the colon and the indentation to start a new a block. But the end of the indented section is the only indication of the end of the block.
@@ -61,9 +61,10 @@ If you want anyone to take you seriously as a Python developer:
`(PEP 8) `_
-Also note: if you DO use tabs (and really, don't do that!) python interprets them as the equivalent of *eight* spaces. Text editors can display tabs as any number of spaces, and most modern editors default to four -- so this can be *very* confusing! so again:
+.. note::
+ If you *do* use tabs (and really, don't do that!) python interprets them as the equivalent of *eight* spaces. Text editors can display tabs as any number of spaces, and most modern editors default to four -- so this can be *very* confusing! so again:
-**Never mix tabs and spaces in Python code**
+ **Never mix tabs and spaces in Python code**
Spaces Elsewhere
@@ -85,7 +86,7 @@ But you should strive for proper style. Isn't this easier to read?
x = (3 * 4) + (12 / func(x, y, z))
-*Read PEP 8 and install a linter in your editor.*
+**Read PEP 8 and install a linter in your editor.**
Modules and Packages
diff --git a/source/solutions/Lesson07/version2/html_render.py b/source/solutions/Lesson07/version2/html_render.py
new file mode 100644
index 00000000..f9ff4ca9
--- /dev/null
+++ b/source/solutions/Lesson07/version2/html_render.py
@@ -0,0 +1,155 @@
+#!/usr/bin/env python3
+
+"""A class-based system for rendering html."""
+
+
+# This is the framework for the base class
+class Element(object):
+
+ tag = "html"
+ indent = " "
+
+ def __init__(self, content=None, **kwargs):
+ self.contents = []
+ if content is not None:
+ self.contents.append(content)
+ self.attributes = kwargs
+
+ def append(self, new_content):
+ self.contents.append(new_content)
+
+ def _open_tag(self):
+ if self.attributes: # only if there are any attributes
+ open_tag = ["<{}".format(self.tag)]
+ atts = [f'{key}="{value}"' for key, value in self.attributes.items()]
+ tag = f"<{self.tag} {' '.join(atts)}>"
+ else:
+ tag = f"<{self.tag}>"
+ return tag
+
+ def _close_tag(self):
+ return f"{self.tag}>"
+
+ def render(self, out_file, cur_ind=""):
+ # loop through the list of contents:
+ out_file.write(cur_ind)
+ out_file.write(self._open_tag())
+ out_file.write("\n")
+ for content in self.contents:
+ try:
+ content.render(out_file, cur_ind + self.indent)
+ except AttributeError:
+ out_file.write(cur_ind + self.indent)
+ out_file.write(content)
+ out_file.write("\n")
+ out_file.write(cur_ind)
+ out_file.write(self._close_tag())
+ out_file.write("\n")
+
+
+class Body(Element):
+ tag = 'body'
+
+
+class P(Element):
+ tag = 'p'
+
+
+class Html(Element):
+ tag = 'html'
+
+ def render(self, out_file, cur_ind=""):
+ out_file.write(cur_ind)
+ out_file.write("\n")
+ super().render(out_file, cur_ind)
+
+
+class Head(Element):
+ tag = 'head'
+
+
+class OneLineTag(Element):
+
+ def append(self, content):
+ if self.contents:
+ raise TypeError("OneLineTag elements can not have content added")
+ else:
+ super().append(content)
+
+ def render(self, out_file, cur_ind=""):
+ out_file.write(cur_ind)
+ out_file.write(self._open_tag())
+ out_file.write(self.contents[0])
+ out_file.write(self._close_tag())
+ out_file.write("\n")
+
+
+class Title(OneLineTag):
+ tag = "title"
+
+
+class SelfClosingTag(Element):
+
+ def __init__(self, content=None, **kwargs):
+ if content is not None:
+ raise TypeError("SelfClosingTag can not contain any content")
+ super().__init__(content=content, **kwargs)
+
+ def render(self, out_file, cur_ind=""):
+ tag = self._open_tag()[:-1] + " />\n"
+ out_file.write(cur_ind)
+ out_file.write(tag)
+
+ def append(self, *args):
+ raise TypeError("You can not add content to a SelfClosingTag")
+
+
+class Hr(SelfClosingTag):
+
+ tag = "hr"
+
+
+class Br(SelfClosingTag):
+
+ tag = "br"
+
+
+class A(OneLineTag):
+
+ tag = 'a'
+
+ def __init__(self, link, content=None, **kwargs):
+ kwargs['href'] = link
+ super().__init__(content, **kwargs)
+
+
+class H(OneLineTag):
+ """
+ section head
+ """
+ tag = "H"
+
+ def __init__(self, level, *args, **kwargs):
+ self.tag = "h" + str(int(level))
+ super().__init__(*args, **kwargs)
+
+
+class Ul(Element):
+ """
+ unordered list
+ """
+ tag = "ul"
+
+
+class Li(Element):
+ """
+ list element
+ """
+ tag = "li"
+
+
+class Meta(SelfClosingTag):
+ """
+ metadata tag
+ """
+ tag = "meta"
diff --git a/source/solutions/Lesson07/version2/run_html_render.py b/source/solutions/Lesson07/version2/run_html_render.py
new file mode 100644
index 00000000..4f8c0058
--- /dev/null
+++ b/source/solutions/Lesson07/version2/run_html_render.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python3
+
+"""
+a simple script can run and test your html rendering classes.
+
+Uncomment the steps as you add to your rendering.
+
+"""
+
+from io import StringIO
+
+# importing the html_rendering code with a short name for easy typing.
+import html_render as hr
+
+
+# writing the file out:
+def render_page(page, filename, indent=None):
+ """
+ render the tree of elements
+
+ This uses StringIO to render to memory, then dump to console and
+ write to file -- very handy!
+ """
+
+ f = StringIO()
+ if indent is None:
+ page.render(f)
+ else:
+ page.render(f, indent)
+
+ print(f.getvalue())
+ with open(filename, 'w') as outfile:
+ outfile.write(f.getvalue())
+
+
+# # Step 1
+# #########
+
+# page = hr.Element()
+
+# page.append("Here is a paragraph of text -- there could be more of them, "
+# "but this is enough to show that we can do some text")
+
+# page.append("And here is another piece of text -- you should be able to add any number")
+
+# render_page(page, "test_html_output1.html")
+
+# The rest of the steps have been commented out.
+# Uncomment them as you move along with the assignment.
+
+# ## Step 2
+# ##########
+
+# page = hr.Html()
+
+# body = hr.Body()
+
+# body.append(hr.P("Here is a paragraph of text -- there could be more of them, "
+# "but this is enough to show that we can do some text"))
+
+# body.append(hr.P("And here is another piece of text -- you should be able to add any number"))
+
+# page.append(body)
+
+# render_page(page, "test_html_output2.html")
+
+# # Step 3
+# ##########
+
+# page = hr.Html()
+
+# head = hr.Head()
+# head.append(hr.Title("PythonClass = Revision 1087:"))
+
+# page.append(head)
+
+# body = hr.Body()
+
+# body.append(hr.P("Here is a paragraph of text -- there could be more of them, "
+# "but this is enough to show that we can do some text"))
+# body.append(hr.P("And here is another piece of text -- you should be able to add any number"))
+
+# page.append(body)
+
+# render_page(page, "test_html_output3.html")
+
+# # Step 4
+# ##########
+
+# page = hr.Html()
+
+# head = hr.Head()
+# head.append(hr.Title("PythonClass = Revision 1087:"))
+
+# page.append(head)
+
+# body = hr.Body()
+
+# body.append(hr.P("Here is a paragraph of text -- there could be more of them, "
+# "but this is enough to show that we can do some text",
+# style="text-align: center; font-style: oblique;"))
+
+# page.append(body)
+
+# render_page(page, "test_html_output4.html")
+
+# # Step 5
+# #########
+
+# page = hr.Html()
+
+# head = hr.Head()
+# head.append(hr.Title("PythonClass = Revision 1087:"))
+
+# page.append(head)
+
+# body = hr.Body()
+
+# body.append(hr.P("Here is a paragraph of text -- there could be more of them, "
+# "but this is enough to show that we can do some text",
+# style="text-align: center; font-style: oblique;"))
+
+# body.append(hr.Hr())
+
+# page.append(body)
+
+# render_page(page, "test_html_output5.html")
+
+# Step 6
+#########
+
+# page = hr.Html()
+
+# head = hr.Head()
+# head.append(hr.Title("PythonClass = Revision 1087:"))
+
+# page.append(head)
+
+# body = hr.Body()
+
+# body.append(hr.P("Here is a paragraph of text -- there could be more of them, "
+# "but this is enough to show that we can do some text",
+# style="text-align: center; font-style: oblique;"))
+
+# body.append(hr.Hr())
+
+# body.append("And this is a ")
+# body.append(hr.A("http://google.com/", "link"))
+# body.append("to google")
+
+# page.append(body)
+
+# render_page(page, "test_html_output6.html")
+
+# # Step 7
+# #########
+
+# page = hr.Html()
+
+# head = hr.Head()
+# head.append(hr.Title("PythonClass = Revision 1087:"))
+
+# page.append(head)
+
+# body = hr.Body()
+
+# body.append( hr.H(2, "PythonClass - Class 6 example") )
+
+# body.append(hr.P("Here is a paragraph of text -- there could be more of them, "
+# "but this is enough to show that we can do some text",
+# style="text-align: center; font-style: oblique;"))
+
+# body.append(hr.Hr())
+
+# list = hr.Ul(id="TheList", style="line-height:200%")
+
+# list.append( hr.Li("The first item in a list") )
+# list.append( hr.Li("This is the second item", style="color: red") )
+
+# item = hr.Li()
+# item.append("And this is a ")
+# item.append( hr.A("http://google.com/", "link") )
+# item.append("to google")
+
+# list.append(item)
+
+# body.append(list)
+
+# page.append(body)
+
+# render_page(page, "test_html_output7.html")
+
+# Step 8 and 9
+##############
+
+page = hr.Html()
+
+
+head = hr.Head()
+head.append( hr.Meta(charset="UTF-8") )
+head.append(hr.Title("PythonClass = Revision 1087:"))
+
+page.append(head)
+
+body = hr.Body()
+
+body.append( hr.H(2, "PythonClass - Example") )
+
+body.append(hr.P("Here is a paragraph of text -- there could be more of them, "
+ "but this is enough to show that we can do some text",
+ style="text-align: center; font-style: oblique;"))
+
+body.append(hr.Hr())
+
+list = hr.Ul(id="TheList", style="line-height:200%")
+
+list.append( hr.Li("The first item in a list") )
+list.append( hr.Li("This is the second item", style="color: red") )
+
+item = hr.Li()
+item.append("And this is a ")
+item.append( hr.A("http://google.com/", "link") )
+item.append("to google")
+
+list.append(item)
+
+body.append(list)
+
+page.append(body)
+
+render_page(page, "test_html_output8.html")
diff --git a/source/solutions/Lesson07/version2/test_html_render.py b/source/solutions/Lesson07/version2/test_html_render.py
new file mode 100644
index 00000000..05197666
--- /dev/null
+++ b/source/solutions/Lesson07/version2/test_html_render.py
@@ -0,0 +1,394 @@
+"""
+test code for html_render.py
+
+This is just a start -- you will need more tests!
+"""
+
+import io
+import pytest
+
+# import * is often bad form, but makes it easier to test everything in a module.
+from html_render import *
+
+
+# utility function for testing render methods
+# needs to be used in multiple tests, so we write it once here.
+def render_result(element, ind=""):
+ """
+ Call the element's render method, and returns what got rendered as a
+ string
+ """
+ # the StringIO object is a "file-like" object -- something that
+ # provides the methods of a file, but keeps everything in memory
+ # so it can be used to test code that writes to a file, without
+ # having to actually write to disk.
+ outfile = io.StringIO()
+ # this so the tests will work before we tackle indentation
+ if ind:
+ element.render(outfile, ind)
+ else:
+ element.render(outfile)
+ return outfile.getvalue()
+
+########
+# Step 1
+########
+
+
+def test_init():
+ """
+ This only tests that it can be initialized with and without
+ some content -- but it's a start.
+ """
+ e = Element()
+
+ e = Element("this is some text")
+
+
+def test_append():
+ """
+ This tests that you can append text
+
+ It doesn't test if it works --
+ that will be covered by the render test later
+ """
+ e = Element("this is some text")
+ e.append("some more text")
+
+
+def test_render_element():
+ """
+ Tests whether the Element can render two pieces of text
+ So it is also testing that the append method works correctly.
+
+ It is not testing whether indentation or line feeds are correct.
+ """
+ e = Element("this is some text")
+ e.append("and this is some more text")
+
+ # This uses the render_results utility above
+ file_contents = render_result(e).strip()
+ print(file_contents)
+ # making sure the content got in there.
+ assert("this is some text") in file_contents
+ assert("and this is some more text") in file_contents
+
+ # make sure it's in the right order
+ assert file_contents.index("this is") < file_contents.index("and this")
+
+ # making sure the opening and closing tags are right.
+ assert file_contents.startswith("")
+ assert file_contents.endswith("")
+
+ assert file_contents.count("") == 1
+ assert file_contents.count("") == 1
+
+
+# Uncomment this one after you get the one above to pass
+# Does it pass right away?
+def test_render_element2():
+ """
+ Tests whether the Element can render two pieces of text
+ So it is also testing that the append method works correctly.
+
+ It is not testing whether indentation or line feeds are correct.
+ """
+ e = Element()
+ e.append("this is some text")
+ e.append("and this is some more text")
+
+ # This uses the render_results utility above
+ file_contents = render_result(e).strip()
+
+ # making sure the content got in there.
+ assert("this is some text") in file_contents
+ assert("and this is some more text") in file_contents
+
+ # make sure it's in the right order
+ assert file_contents.index("this is") < file_contents.index("and this")
+
+ # making sure the opening and closing tags are right.
+ assert file_contents.startswith("")
+ assert file_contents.endswith("")
+
+
+
+# ########
+# # Step 2
+# ########
+
+# tests for the new tags
+def test_html():
+ e = Html("this is some text")
+ e.append("and this is some more text")
+
+ file_contents = render_result(e).strip()
+
+ assert("this is some text") in file_contents
+ assert("and this is some more text") in file_contents
+ print(file_contents)
+ assert file_contents.endswith("")
+
+
+def test_body():
+ e = Body("this is some text")
+ e.append("and this is some more text")
+
+ file_contents = render_result(e).strip()
+
+ assert("this is some text") in file_contents
+ assert("and this is some more text") in file_contents
+
+ assert file_contents.startswith("")
+ assert file_contents.endswith("")
+
+
+def test_p():
+ e = P("this is some text")
+ e.append("and this is some more text")
+
+ file_contents = render_result(e).strip()
+
+ assert("this is some text") in file_contents
+ assert("and this is some more text") in file_contents
+
+ assert file_contents.startswith("")
+ assert file_contents.endswith("
")
+
+def test_p_newlines():
+ """
+ we want each element to start where it should start, and end with a newline,
+ ready for the next element.
+
+ with each piece of content on its own line:
+ """
+ e = P("this is some text")
+ e.append("and this is some more text")
+
+ file_contents = render_result(e) # not stripping!
+
+ print(file_contents)
+
+ assert("this is some text") in file_contents
+ assert("and this is some more text") in file_contents
+
+ assert file_contents.startswith("\n")
+ assert file_contents.endswith("
\n")
+
+
+def test_sub_element():
+ """
+ tests that you can add another element and still render properly
+ """
+ page = Html()
+ page.append("some plain text.")
+ page.append(P("A simple paragraph of text"))
+ page.append("Some more plain text.")
+
+ file_contents = render_result(page)
+ print(file_contents) # so we can see it if the test fails
+
+ # note: The previous tests should make sure that the tags are getting
+ # properly rendered, so we don't need to test that here.
+ assert "some plain text" in file_contents
+ assert "A simple paragraph of text" in file_contents
+ assert "Some more plain text." in file_contents
+ assert "some plain text" in file_contents
+ # but make sure the embedded element's tags get rendered!
+ assert "" in file_contents
+ assert "
" in file_contents
+
+
+########
+# Step 3
+########
+# Add your tests here!
+
+
+def test_head():
+ e = Head("This is a Header")
+
+ file_contents = render_result(e).strip()
+
+ assert("This is a Header") in file_contents
+
+ assert file_contents.startswith("")
+ assert file_contents.endswith("")
+
+
+def test_title():
+ e = Title("This is a Title")
+
+ file_contents = render_result(e).strip()
+
+ assert("This is a Title") in file_contents
+ print(file_contents)
+ assert file_contents.startswith("")
+ assert file_contents.endswith("")
+ assert "\n" not in file_contents
+
+
+def test_one_line_tag_append():
+ """
+ You should not be able to append additional content to a OneLineTag
+ """
+ e = OneLineTag("the initial content")
+ with pytest.raises(TypeError):
+ e.append("some more content")
+
+
+def test_attributes():
+ e = P("A paragraph of text", style="text-align: center", id="intro")
+
+ file_contents = render_result(e).strip()
+ print(file_contents) # so we can see it if the test fails
+
+ # note: The previous tests should make sure that the tags are getting
+ # properly rendered, so we don't need to test that here.
+ # so using only a "P" tag is fine
+ assert "A paragraph of text" in file_contents
+ # but make sure the embedded element's tags get rendered!
+ # first test the end tag is there -- same as always:
+ assert file_contents.endswith("")
+
+ # but now the opening tag is far more complex
+ # but it starts the same:
+ assert file_contents.startswith("") > file_contents.index('id="intro"')
+ assert file_contents[:file_contents.index(">")].count(" ") == 3
+
+########
+# Step 5
+
+
+def test_hr():
+ """a simple horizontal rule with no attributes"""
+ hr = Hr()
+ file_contents = render_result(hr)
+ print(file_contents)
+ assert file_contents == '
\n'
+
+
+def test_hr_attr():
+ """a horizontal rule with an attribute"""
+ hr = Hr(width=400)
+ file_contents = render_result(hr)
+ print(file_contents)
+ assert file_contents == '
\n'
+
+
+def test_br():
+ br = Br()
+ file_contents = render_result(br)
+ print(file_contents)
+ assert file_contents == "
\n"
+
+
+def test_content_in_br():
+ with pytest.raises(TypeError):
+ br = Br("some content")
+
+
+def test_append_content_in_br():
+ with pytest.raises(TypeError):
+ br = Br()
+ br.append("some content")
+
+
+def test_anchor():
+ a = A("http://google.com/", "link to google")
+ file_contents = render_result(a)
+ print(file_contents)
+ assert file_contents.startswith('\n')
+ assert 'href="http://google.com/"' in file_contents
+ assert 'link to google' in file_contents
+
+
+#####################
+# indentation testing
+#####################
+
+
+def test_indent():
+ """
+ Tests that the indentation gets passed through to the renderer
+ """
+ html = Html("some content")
+ file_contents = render_result(html, ind=" ").rstrip() #remove the end newline
+
+ print(file_contents)
+ lines = file_contents.split("\n")
+ assert lines[0].startswith(" <")
+ print(repr(lines[-1]))
+ assert lines[-1].startswith(" <")
+
+
+def test_indent_contents():
+ """
+ The contents in a element should be indented more than the tag
+ by the amount in the indent class attribute
+ """
+ html = Element("some content")
+ file_contents = render_result(html, ind="")
+
+ print(file_contents)
+ lines = file_contents.split("\n")
+ assert lines[1].startswith(Element.indent)
+
+
+def test_multiple_indent():
+ """
+ make sure multiple levels get indented fully
+ """
+ body = Body()
+ body.append(P("some text"))
+ html = Html(body)
+
+ file_contents = render_result(html)
+
+ print(file_contents)
+ lines = file_contents.split("\n")
+ for i in range(3): # this needed to be adapted to the tag
+ assert lines[i + 1].startswith(i * Element.indent + "<")
+
+ assert lines[4].startswith(3 * Element.indent + "some")
+
+
+def test_element_indent1():
+ """
+ Tests whether the Element indents at least simple content
+
+ we are expecting to to look like this:
+
+
+ this is some text
+ <\html>
+
+ More complex indentation should be tested later.
+ """
+ e = Element("this is some text")
+
+ # This uses the render_results utility above
+ file_contents = render_result(e).strip()
+
+ # making sure the content got in there.
+ assert("this is some text") in file_contents
+
+ # break into lines to check indentation
+ lines = file_contents.split('\n')
+ # making sure the opening and closing tags are right.
+ assert lines[0] == ""
+ # this line should be indented by the amount specified
+ # by the class attribute: "indent"
+ assert lines[1].startswith(Element.indent + "thi")
+ assert lines[2] == ""
+ assert file_contents.endswith("")
diff --git a/source/supplemental/installing/python_for_windows_new.rst b/source/supplemental/installing/python_for_windows_new.rst
deleted file mode 100644
index e69de29b..00000000