Review

  • Classes in Python are a way to make your own data types.
  • Classes have variables (data)
  • Classes have functions (methods)
  • The special function __init__ initializes a class.

Here's an example of a class:

class BlogEntry :
 
  def __init__(self, title, text) : 
    self.title = title
    self.text = text 
 
  def pretty_print(self) :
    print(self.title, '\n\t', self.text)

The blog entry class has:

  • A member variable self.title (really just title)
  • A member variable text
  • A member function pretty_print
  • An initializer function __init__

Class Design In Practice

  • The goal of object oriented design is to make the programmer's life easier!
  • One of the important ways classes do that is by fostering reuse of code.
  • Inheritance is a tool for allowing classes to share functions.
  • When classes are in an inherited relationship
    • One class is said to be the parent
    • One class is said to be the child
  • Child classes are said to have an is-a relationship with the parent.
    • The child class “is a” parent class.

Here's an example of a inheritance relationship:

class Animal : 
 
    def __init__(self, num_legs) :
        self.legs = num_legs
 
    def get_legs(self) :
        return self.legs 

Animal is the parent class. The child class will have an is-a relationship with an animal. What “is an” animal. A duck!

class Duck(Animal) :
 
    def __init__(self) : 
        super().__init__(2)
 
    def get_sound(self) : 
        return "quack"

The Duck class can be used this way:

>>> a = Duck()
>>> print ('A duck has', a.get_legs(), 'and says', a.get_sound())
A duck has 2 and says quack

There are a few important things to notice here:

  • The Animal class has the get_legs() method
  • The Duck class has the get_sound() method
    • The Duck class can use the parent's get_legs() method

There's one other important line of code to notice…

  • The Animal class has an __init__ function that takes on argument.
  • When Duck is created it has to give the parent class that argument.
    • You can reference the parent class (or superclass) using the super() function.
  • Child classes can override methods in the parent class.
  • When that happens the child class' method is called.

Here's an example where Duck overrides the get_legs() method:

class Duck(Animal) :
 
    def __init__(self) : 
        super().__init__(2)
 
    def get_sound(self) : 
        return "quack"
 
    def get_legs(self) :
        print ('Override! Wings are legs too!')
        return 4

Now calling the overridden method goes to Duck

Class Variables

  • Variables in classes that are used with the self variable are called instance variables.
  • Instance variables are unique for every instance of the class.
    • See self.legs in the example above.
  • Class variables are shared among every instance of the class.
  • Class variables are handy when you want to keep constants around.

Here's an example of the difference between class and instance variables:

class Foo : 
  class_var = 'Class Variable'
  def __init__(self) : 
    self.instance_var = 'Instance Variable'

You can use class_var with the name of the class, but not instance_var you have to use that with an instance of the class:

>>> f = Foo()
>>> f.class_var 
'Class Variable'
>>> f.instance_var 
'Instance Variable'
>>> Foo.class_var
'Class Variable'
>>> Foo.instance_var 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Foo' has no attribute 'instance_var'

Watch Out!

Class variables are shared!

class Dog:
 
    tricks = []             # mistaken use of a class variable
 
    def __init__(self, name):
        self.name = name
 
    def add_trick(self, trick):
        self.tricks.append(trick)
 
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # unexpectedly shared by all dogs
['roll over', 'play dead']

Using Classes

  • Handling forms on the web has security implications that I haven't prepared you for.
  • A good Flask program needs to validate form fields and tell the user when there are errors.
  • The WTForms package helps programmers with this.

Install WTForms using pip:

sudo pip3.6 install Flask-WTF
  • Flask has built in support for creating secure forms.
  • Forms are created by a class

Here's an example of a class that creates a form:

class RegistrationForm(FlaskForm):
 
    username = StringField('Username', [validators.Length(min=4, max=25)])
    email = StringField('Email Address', [validators.Length(min=6, max=35)])
    password = PasswordField('New Password', [
        validators.DataRequired(),
        validators.EqualTo('confirm', message='Passwords must match')
    ])
    confirm = PasswordField('Repeat Password')
    accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()])
  • The form fields are specified as class variables.
    • username - type StringField
    • email - type StringField
    • password (and confirm) - type PasswordField
    • accept_tos - type BooleanField
  • Each field is rendered as the proper HTML element
    • StringFields become input boxes
    • PasswordFields hid the contents of the bos
    • BooleanFields are a check box.
  • Forms also have validators
    • A validator is a way to specify how you want the field to work
    • They automatically check the input for you.
  • Forms have to be rendered into HTML which is a little complicated.

Here's a base class that does the rendering for you.

class RenderableForm(FlaskForm):
 
    def __init__(self, action=None, method='POST') :
        self.action = action 
        self.method = method 
        super().__init__()
 
    def render_form(self, *fields, **kwargs) : 
        if self.action is not None : 
            act = f'action="{form.action}"'
        else:
            act = '' 
        html = f'<form {act} method="{self.method}">'
        html += self.csrf_token()
        for field in fields : 
            html += '<dt>' + field.label() + '</dt>'
            html += '<ul>'
            for error in field.errors : 
                html += '<li>' + error + '</li>'
            html += '</ul>'
            html += '<dd>' + field(**kwargs)
            html += '</dd><br>'
        html += '<input type="submit" value="Submit">' + '</form>'
        return html
  • The class inherits from FlaskForm
    • It provided the render_form method.

Let's change the RegistrationForm class to inherit from RenderableForm

class RegistrationForm(RenderableForm):
 
    username = StringField('Username', [validators.Length(min=4, max=25)])
    email = StringField('Email Address', [validators.Length(min=6, max=35)])
    password = PasswordField('New Password', [
        validators.DataRequired(),
        validators.EqualTo('confirm', message='Passwords must match')
    ])
    confirm = PasswordField('Repeat Password')
    accept_tos = BooleanField('I accept the TOS', [validators.DataRequired()])

Now the form can be rendered easily in Flask:

@app.route('/register', methods=['GET', 'POST'])        
def register():
    form = RegistrationForm()
    if form.validate_on_submit() : 
        return redirect('/login')
    else:   
        return render_template('register.html', form=form.render_form(
                form.username,
                form.email,
                form.password,
                form.confirm,
                form.accept_tos,
            ))

Example Code

Example code for this week can be downloaded here.