This week you'll get more practice with variables and strings. You'll also learn how to make HTML documents using your web application.

Quotes

  • Python is interesting because there are so many types of quotes
  • Here's a summary:
Name Example Notes
Single Quote 'Hello, world!' Good for general use.
Double Quote “Hello, world!” Good for general use.
Triple Quotes '''Hello, world!'''
“”“Hello World”“”
Needed for multiline strings
Format Strings f“Hello, my name is {name}” Variable interpolation is convenient. You can use double or single or triple quotes too.
Raw Strings r“Newline (\n) is literal” Raw strings don't change the text at all
  • Variable interpolation is when you put the name of a variable in braces in an f-string
    • For example f“The content of the variable foo is {foo}”
    • Variable interpolation is new in Python 3.6
    • That's why the “Run” button doesn't work in Cloud9 IDE

Formatting

  • There are other ways in Python to make a formatted string
    • A formatted string mixes variables and text
    • They are very common in programming
  • The format function is similar to an f-string

Here are some examples of the format function:

>>> "Hello Mike.".format()
'Hello Mike.'
>>> "This does nothing".format()
'This does nothing'
>>> "Replace these brackets {}".format('with stuff')
'Replace these brackets with stuff'
>>> name = 'Mike'
>>> "Hello, my name is {}".format(name)
'Hello, my name is Mike'
>>> job = 'Nerd'
>>> """Hello, my name is {}
... and I'm a {}""".format(name, job)
"Hello, my name is Mike\nand I'm a Nerd"

Let's take a closer look at each example.

  • By itself format() does nothing.
>>> "Hello Mike.".format()
'Hello Mike.'
  • Format is a function
    • Functions take arguments
    • Arguments are enclosed in parenthesis ( and )
    • Arguments are separated by comas ,
      • The print function with no arguments:
        • print()
      • The print function with one argument:
        • print(“Hello”)
      • The print function with two arguments:
        • print(“The number is:”, 5)
  • Functions can have as many (or as few) arguments as they require.
  • The format function can use literal arguments
>>> "My name is {} and I like {}".format("Mike", "Python")
'My name is Mike and I like Python'
  • The format function often is used with variable arguments:
>>> name = 'Mike'
>>> like = 'Python' 
>>> "My name is {} and I like {}".format(name, like)
'My name is Mike and I like Python'
  • The format function can work on variables too!
  • The variables must contain a string.

Here's an example of formatting a string (it's very common to do this!)

>>> madlib = 'Sometimes, I like to {} with {}.'
>>> verb = 'jump'
>>> noun = 'potatoes' 
>>> madlib.format(verb, noun)
'Sometimes, I like to jump with potatoes.'
  • When you format the contents of a variable the original variable data is preserved
  • You can reuse the contents as much as you want
>>> madlib = 'Sometimes, I like to {} with {}.'
>>> madlib.format('run', 'chickens')
'Sometimes, I like to run with chickens.'
>>> madlib.format('program', 'Python')
'Sometimes, I like to program with Python.'
>>> madlib
'Sometimes, I like to {} with {}.'
  • An f-string is really just a cleaner way to use the format() function.
  • But you can't store an f-string in a variable the same way
>>> noun = 'run'
>>> verb = 'chickens'
>>> madlib = f'Sometimes, I like to {noun} with {verb}.'
>>> madlib
'Sometimes, I like to run with chickens.'
>>> noun = 'program'
>>> verb = 'Python'
>>> madlib
'Sometimes, I like to run with chickens.'
# The madlib didn't change!
  • Sometimes you need to put braces { and } into a format string
  • But the format string wants them.

Here's how you escape braces:

>>> f'{{ }}' 
'{ }'
>>> '{{ }}'.format()
'{ }'

Here's an example of when you would use the double brace to print correct CSS:

>>> '''body { padding-top: {}; }'''.format('1.5em')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: ' padding-top'

It's an error because Python tries to teat padding-top as a variable. To escape the braces you want to leave in double them like this:

>>> '''body {{ padding-top: {}; }}'''.format('1.5em')                                                                                                                                             
'body { padding-top: 1.5em; }'

Old-Style Formatting

The format() function was not always in Python. Formatting used to be done using the % (modulus) operator. There are still many programs that do it this way and it still works in Python 3. However, it's not as easy as the format() function and it's further use is discouraged. In programmer speak it's deprecated.

Here's an example of how to use the format (%) operator:

>>> 'I have %s bikes' % ('fast')
'I have fast bikes'
>>> 'Sometimes, I like to %s with %s' % ('format', 'strings')
'Sometimes, I like to format with strings'
  • Notice that instead of {} you see %s
  • The %s tells Python to look for a string.
  • If you want numbers you have to use other formatters
    • %d for an integer
    • %f for a floating point number
>>> 'I have %d bikes' % (2) 
'I have 2 bikes'
>>> 'I have %f bikes' % (2.5) 
'I have 2.500000 bikes'
  • The format strings come from the C programming language's printf() function.
  • It's very easy to make mistakes (too easy!)

Picking the wrong number type is a common mistake:

>>> 'Drive %d more miles.' % (3.7)
'Drive 3 more miles.'
# Oops! I asked for an integer but supplied a float

Some combination of format operators “explode” when you use them:

>>> 'Drive %d more miles.' % ("many")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: %d format: a number is required, not str

Prompting the User and Type Conversions

Most programs need input of some kind. This is a preview from next week to get you started with user input.

  • Python can read input from the command line with the input() function.

Example of input:

>>> foo = input()
Hello, computer
>>> foo
'Hello, computer'
  • The input function returns a string (even if the user types a number).
>>> foo = input()
100
>>> foo
'100'
>>> type(foo)
<class 'str'>
  • Returning a string can be a problem if you want to do math.
  • In Python you can turn a string into a number using int() and float()
  • Be careful, you will cause an error if you try to convert something that can't be converted.

This works:

>>> num = int(input())
100
>>> num
100
>>> type(num)
<class 'int'>

This is an error:

>>> num = int(input())
twelve
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'twelve'
  • You can use the str(), int() and float() functions to convert variables or literals too.
  • Everything can be a str()
  • The numerical types have obvious restrictions.

Here's an example of doing type conversion on a variable:

>>> num = '12' # Not right! That's a string. 
>>> num = int(num)
>>> num
12
>>> type(num)
<class 'int'>

Advanced Formatting

  • The advantage of the format() function and f-strings is that they are easy!
  • Here are some useful features of the format() function.

You can use arguments in any order:

>>> "{1} is {0}".format('down', 'up')
'up is down'

You can reuse arguments:

>>> "{0}, {0} and {1}".format('up', 'away')
'up, up and away'

You can make arguments work similarly to an f-string:

>>> 'Hello, my name is {name}'.format(name='Mike')
'Hello, my name is Mike'

The advanced use of the format() function is extremely useful for building web pages because there's lots of HTML that's boilerplate. Here's an example of a Flask program that produces a nice looking HTML page.

pretty.py
''' CIS-15 pretty.py 
Mike Matera 
 
This webapp presents the user with a pretty HTML page that's parameterizalbe. 
'''
 
from flask import Flask 
 
app = Flask(__name__)
 
pretty_html = '''
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>{page_title}</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>  </head>
    <style>
    /* Space out content a bit */
    body {{
      padding-top: 1.5rem;
      padding-bottom: 1.5rem;
    }}
    </style>    
  <body>
    <div class="container">
      <div class="jumbotron">
        <h1 class="display-3">{main_heading}</h1>
        <p class="lead">{main_text}</p>
        <p><a class="btn btn-lg btn-success" href="#" role="button">Sign up today</a></p>
      </div>
      <footer class="footer">
        <p>&copy; {company_name} 2017</p>
      </footer>
    </div> <!-- /container -->
  </body>
</html>
'''
 
@app.route('/')
def index() :
    return pretty_html.format(
        page_title = "Another CIS-15 Example",
        project_name = "Mike's Awesome Website",
        main_heading = "Eggs, Milk and Sugar",
        main_text = "Making custard...",
        company_name = "M3, LLC."
        )
 
 
if __name__ == '__main__' :
    app.run(host='0.0.0.0', port=8080)
  • Does anything seem wrong here?
  • Most of our Python program is HTML and CSS
  • That makes things a bit of a pain to debug
  • Next we'll see how to fix this problem with Jinja templates

HTML Templates with Jinja2

It's best to separate your HTML and your Python code. Also, though Python format strings are powerful, there are things you might want to do in an HTML document that are too complicated to be done with format().

  • Templates keep your HTML code in a separate file
  • Templates allow you to change your HTML and code independently
  • Flask has built-in support for the Jinja2 template system.

Here's an example of the same program above in a Jina2 template:

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="../../favicon.ico">
 
    <title>{{page_title}}</title>
 
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
 
    <!-- Optional theme -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
 
    <!-- Latest compiled and minified JavaScript -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>  </head>
 
    <style>
 
    /* Space out content a bit */
    body {
      padding-top: 1.5rem;
      padding-bottom: 1.5rem;
    }
 
    /* Everything but the jumbotron gets side spacing for mobile first views */
    .header,
    .marketing,
    .footer {
      padding-right: 1rem;
      padding-left: 1rem;
    }
 
    /* Custom page header */
    .header {
      padding-bottom: 1rem;
      border-bottom: .05rem solid #e5e5e5;
    }
 
    /* Make the masthead heading the same height as the navigation */
    .header h3 {
      margin-top: 0;
      margin-bottom: 0;
      line-height: 3rem;
    }
    </style>
 
  <body>
 
    <div class="container">
      <div class="header clearfix">
        <nav>
          <ul class="nav nav-pills float-right">
            <li class="nav-item">
              <a class="nav-link active" href="#">Home <span class="sr-only">(current)</span></a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="#">About</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="#">Contact</a>
            </li>
          </ul>
        </nav>
        <h3 class="text-muted">{{project_name}}</h3>
      </div>
 
      <div class="jumbotron">
        <h1 class="display-3">{{main_heading}}</h1>
        <p class="lead">{{main_text}}</p>
        <p><a class="btn btn-lg btn-success" href="#" role="button">Sign up today</a></p>
      </div>
 
      <div class="row marketing">
        {% for block in content %}
          <div class="col-lg-6">
            <h4>{{block[0]['title']}}</h4>
            <p>{{block[0]['content']}}</p>
          </div>
          <div class="col-lg-6">
            <h4>{{block[1]['title']}}</h4>
            <p>{{block[1]['content']}}</p>
          </div>
        {% endfor %} 
      </div>
 
      <footer class="footer">
        <p>&copy; {{company_name}} 2017</p>
      </footer>
 
    </div> <!-- /container -->
  </body>
</html>
  • The jinja2 template above a lot like a format string.
  • Notice the differences:
    • In a format string variables are surrounded with braces { and }
    • In Jinja they're surrounded with double braces and

Here's an example:

      <footer class="footer">
        <p>&copy; {{company_name}} 2017</p>
      </footer>
  • Jinja lets you use looping constructs.

Look at the loop in the template:

        {% for block in content %}
          <div class="col-lg-6">
            <h4>{{block[0]['title']}}</h4>
            <p>{{block[0]['content']}}</p>
          </div>
          <div class="col-lg-6">
            <h4>{{block[1]['title']}}</h4>
            <p>{{block[1]['content']}}</p>
          </div>
        {% endfor %} 
  • The loop iterates over the contents of the Python content variable.
  • Calling the Jinja template is almost exactly the same as calling the formatted string

Here's the code that executes this template:

templates.py
''' CIS-15 templates.py 
Mike Matera 
 
This webapp presents the user with a pretty HTML page that's parameterizalbe. 
'''
 
from flask import Flask, render_template
 
app = Flask(__name__)
 
@app.route('/')
def index() :
    return render_template('index.html',
        page_title = "Another CIS-15 Example",
        project_name = "Mike's Awesome Website",
        main_heading = "Eggs, Milk and Sugar",
        main_text = "Making custard...",
        company_name = "M3, LLC.",
        content = [ 
          [{
            'title'   : 'On Toast.',
            'content' : 'Custard is good on toast.'
            },
          {
            'title'   : 'On Rice.', 
            'content' : 'Custard on rice is rice pudding.'
          }
          ], 
          [{
            'title'   : 'On Broccoli.',
            'content' : 'Cheese sauce is custard without egg, so not really custard. But, quiche as broccoli and that\'s custard!'
            },
          {
            'title'   : 'Dim Sum.', 
            'content' : 'Custard cups are my favorite desert dim sum.'
          }
          ], 
        ]
      )
 
 
if __name__ == '__main__' :
    app.run(host='0.0.0.0', port=8080)
  • Notice that the HTML is still ugly (because HTML is always ugly)
  • The Python is nice and clean
  • The content is passed in using an list.
    • It's a list of lists of hashes.
    • Each list element is a list
      • The sub-list has two elements (left side and right side)
      • The left and right side are a dictionary
        • The dictionary has 'title' and 'content' keys
  • Jinja searches for templates in the ./templates directory
  • You can change where Flask looks for templates
    • But they must be in a directory called templates

Here's an example that shows how to change the templates location in Flask:

from flask import Flask, render_template
 
app = Flask(__name__, template_folder='/path/to/my/templates') # Must end in templates!