7. Additional Object-Oriented Techniques

About this Tutorial –

Objectives –

Python is a powerful and popular object-oriented scripting language. This course provides a comprehensive introduction to the core syntax and functions provided by Python, including full coverage of its object-oriented features. The course also explores some of Python’s powerful APIs and techniques, including file handling, XML processing, object serialization, and Web service

Audience

This training course is aimed at new developers and experienced developers

Prerequisites

No previous experience in Python programming is required. But any experience you do have in programming will help. Also no experience in IDLE is required. But again any experience you do have with programming development environments will be a valuable.

Contents

The python course covers these topics and more:

  • Strings and Regular Expressions: Overview of strings in Python; Basic string manipulation; Introduction to regular expressions
  • XML Processing: XML essentials; Parsing XML documents; Searching for XML content; Generating XML data
  • Web Services: Overview of Web services; Implementing Web services using Python; Caching; Compression; Handling redirects

Download Solutions

HTML tutorial


Overview

Estimated Time – 1 Hour

Not what you are looking? Try the next tutorial – XML Processing in Python

Lab 1: A closer look at attributes

Lab 1: A closer look at attributes
  1. Managing an Object’s Attributes
    • Python provides several global functions that allow you to manage attributes on an object
      from accounting import BankAccount
      acc1 = BankAccount("Fred")
      setattr(acc1, "bonus", 2000)
      if hasattr(acc1, "bonus"):
        print("acc1.bonus is %d" % acc1.bonus)
      delattr(acc1, "bonus")
    • View Code File

    • You can use the global functions shown in the slide to query/add/remove attributes on a specific object instance. Here’s the general syntax of these global functions:
      • setattr(object, attributeName, value)
      • hasattr(object, attributeName)
      • delattr(object, attributeName)
  2. Adding and Removing Object Attributes
    • You can also add and remove attributes on an object directly, as follows:
      from accounting import BankAccount
      acc1 = BankAccount("Fred")
      # Add an attribute to an object.
      acc1.flag = "Whao watch this guy"  
      print("acc1.flag is %s" % acc1.flag)
      # Remove an attribute from an object.
      del acc1.flag
    • View Code File

    • This shows an alternative way to add and remove attributes on an object
      • This can be useful if you temporarily want to pin a bit of extra data on an object, and then remove it later
      • This is all possible because Python is a dynamically-typed language!
  3. Built-In Class Attributes
    • Every class provides metadata via the following built-in attributes
      from accounting import BankAccount
      print("BankAccount.__doc__:", BankAccount.__doc__)
      print("BankAccount.__name__:", BankAccount.__name__)
      print("BankAccount.__module__:", BankAccount.__module__)
      print("BankAccount.__bases__:", BankAccount.__bases__)
      print("BankAccount.__dict__:", BankAccount.__dict__)
    • View Code File

    • Every Python class keeps the following built-in attributes. You can access these attributes via the class name, as shown in the example:
      • __doc__ – Returns the class documentation string, or None if undefined
      • __name__ – Returns the name of the class.
      • __module__ – Returns the name of the module in which the class is defined. In interactive mode, this attribute is “__main__”.
      • __bases__ – A possibly empty tuple containing the base classes, in the order of their occurrence in the base class list. Yes, Python supports inheritance. We’re going to explore later in the chapter.
      • __dict__ – Returns a dictionary containing the contents of the class’s namespace.
    • The example displays the following info for the BankAccount class:
      BankAccount.__doc__: Simple BankAccount class
      BankAccount.__name__: BankAccount
      BankAccount.__module__: accounting
      BankAccount.__bases__: (,)
      BankAccount.__dict__: { ... omitted for brevity ... }
Lab
  1. Improving stringification
    • In the Student folder, open company.py in the text editor. This is the solution code from the previous lab, and it defines a simple Employee class. Take a moment to familiarize yourself with the code
    • Note that the class currently has a toString() method, which returns a textual representation of the Employee object. This might look fine, especially if you’re coming from a Java or C++ background, but it’s not really the Python way to do it… In Python, the method should be named __str__() rather than toString(), so rename the method now. The implementation can stay the same:
      def __str__(self):
        return "[{0}] {1} earns {2}, joined {3}".format(self._id, self._name, self._salary, self._joined.strftime("%c"))
    • View Code File

    • Now open clientcode.py. The code creates a couple of Employee objects and prints them in string format. Note that the code currently calls toString() explicitly, to get the string representation for the Employee objects. You don’t need to do this now Python automatically calls __str__() whenever it needs to convert an object to a string, e.g. when you pass the object to print(). Therefore, replace the following statements:
      #### Replace these
      print(emp1.toString())
      print(emp2.toString())
      ####### With this
      print(emp1)
      print(emp2)
    • View Code File

    • Run the code in clientcode.py. It should all still work nicely

Lab 2: Implementing magic methods

Lab 2: Implementing magic methods
  1. Overview
    • There are various “special” methods you can implement in your Python classes
      • These methods allow your class objects to take advantage of standard Python idioms
    • It’s good practice to implement these methods where relevant
      • Python programmers will recognise these methods immediately
      • Makes your classes easier to maintain
  2. Implementing Constructors and Destructors
    • We’ve already see that __init__() is a constructor in Python. Python calls this method automatically just after an object has been allocated, in order to initialize the state of the object. With Python being a dynamic language, the typical task of a constructor is to add attributes to the object and to initialize these attributes.
    • Constructor
      • __init__(self, otherArgs)
    • There’s a complementary method named __del__(), which is the destructor. This method will be called automatically by Python just before an object is garbage collected. An object becomes available for garbage collection when there are no longer any references to the object in the application (e.g. by using the del operator to delete a reference to the object).
    • Destructor
      • __del__(self)
    • Example
      class Person:
        def __init__(self, name, age):
          self.name = name
          self.age = age
          print("In __init__() for %s and %d" % (self.name, self.age))
        def __del__(self):
          print("In __del__() for %s and %d" % (self.name, self.age))
        ...
      # Client Code
      p1 = new Person("Bill", 23)
      p2 = new Person("Ben", 25)
      ...
      del p1, p2
    • View Code File

    • Note that garbage collection is non-deterministic in Python, i.e. you can’t be sure exactly when it will occur. In fact, it might not happen at all; the program might terminate first before the garbage collector gets a chance to run. So you shouldn’t put any really critical code in __del__(); a better approach is to implement some kind of “dispose()” method that client code can call explicitly when it’s ready to tidy up an object.
  3. Implementing Stringify Methods
    • Return a machine-readable representation of an object
      • __repr__(self)
      • The __repr__() function will be invoked automatically when client code calls repr() on an instance of your class. The idea is to return a string that is a machine-readable representation of your object (in many cases, the method can even return valid Python code even).
    • Return a human-readable representation of an object
      • __str__(self)
      • The __str__() function will be invoked automatically when str() is called on an instance of your class. The idea is to return a string that is user-friendly and human-readable, similar to toString() in Java and ToString() in C#. It’s good to name the method __str__() in Python, because it’s a standard Python mechanism and it dovetails automatically with str() calls in the application.
    • Example
      class Person:
        def __repr__(self):
          return "{0} instance, name: {1}, age: {2}".format( \
                                 self.__class__.__name__,\
                                 self.name, self.age)
        def __str__(self):
          return "{0} is {1}.".format(self.name, self.age)
        ...
      ...
      print(repr(p1))
      print(str(p2))
    • View Code File

  4. Implementing Operator Methods
    • Here are some magic methods you can implement, so that your class objects can be used in conjunction with relational operators:
      • __eq__(self, other) – Implements the == operator.
      • __ne__(self, other) – Implements the != operator.
      • __lt__(self, other) – Implements the < operator.
      • __gt__(self, other) – Implements the > operator.
      • __le__(self, other) – Implements the <= operator.
      • __ge__(self, other) – Implements the >= operator.
    • View Code File

    • There are a lot of other methods you can implement too, to support a wide range of other operators. There’s an excellent article about this here: https://www.rafekettler.com/magicmethods.html
Lab
  1. Implementing operator methods
    • Go back to company.py and add methods to support the following relational operators for Employee objects (these methods should compare Employee objects based on their salary):
      • ==
          def __eq__(self, other):
            return self._salary == other._salary
      • !=
          def __ne__(self, other):
            return self._salary == other._salary
      • <   def __lt__(self, other):
            return self._salary < other._salary
      • >
          def __gt__(self, other):
            return self._salary > other._salary
      • <=   def __le__(self, other):
            return self._salary <= other._salary
      • >=
          def __ge__(self, other):
            return self._salary >= other._salary
      • View Code File

    • Add code in clientcode.py to compare the two employees, to test all the above operators
      # Compare the employees salary-wise.
      print("emp1 == emp: %s" % (emp1 == emp2))
      print("emp1 != emp: %s" % (emp1 != emp2))
      print("emp1 < emp: %s" % (emp1 < emp2)) print("emp1 > emp: %s" % (emp1 > emp2))
      print("emp1 <= emp: %s" % (emp1 <= emp2)) print("emp1 >= emp: %s" % (emp1 >= emp2))
    • View Code File

Lab 3: Inheritance

Lab 3: Inheritance
  1. Overview of Inheritance
    • Inheritance is a very important part of object-oriented development
      • Allows you to define a new class based on an existing class
      • You just specify how the new class differs from the existing class
    • Note that some other OO programming languages use alternative terminology here; for example, C++ developers tend to talk about base classes and derived classes. No matter.
    • Terminology:
      • For the “existing class”: Base class, superclass, parent class
      • For the “new class”: Derived class, subclass, child class
    • Potential benefits of inheritance:
      • Improved OO model
      • Faster development
      • Smaller code base
  2. Superclasses and Subclasses
    • When you define a subclass, i.e. when you define a class that inherits from an existing class, the subclass inherits everything from the superclass (except for constructors, which we’ll address later).
      • You can define additional variables and methods
      • You can override existing methods from the superclass
      • You typically have to define constructors too
      • Note: You can’t cherry pick or “blank off” superclass members
  3. Sample Hierarchy
    • We’ll see how to implement the following simple hierarchy:
      T7P1
    • Note:
      • BankAccount defines common state and behaviour that is relevant for all kinds of account
      • SavingsAccount “is a kind of” BankAccount that earns interest
    • We might define additional subclasses in the future…
      • E.g. CurrentAccount, a kind of BankAccount that has cheques
  4. Defining a Subclass
    • To define a subclass, use the following syntax
      • Note that a Python class can inherit from multiple superclasses
      • We’ll discuss multiple inheritance later in this chapter
        class Subclass(Superclass1, Superclass2, ...) :
          # Additional attributes and methods ...
          # Constructor(s) ...
          # Overrides for superclass methods, if necessary ...
    • Example:
      class SavingsAccount(BankAccount):
        ...
        ...
        ...
    • View Code File

  5. Adding New Members
    • When you define a subclass, you automatically inherit all the members from the superclass (except constructors).
      • You can add whatever extra attributes and methods you need in your subclass.
      • This includes instance variables, instance methods, class-wide variables, and class-wide methods.
    • Example:
      class SavingsAccount(BankAccount):
        __DEFAULT_INTEREST_RATE = 1.5
        def earnInterest(self):
          self.balance *= (1 + self.interestRate)
          return self.balance
        ...
    • View Code File

    • The code in the above shows how SavingsAccount inherits from BankAccount and adds a class-wide variable and an instance method
      • Note that the earnInterest() method is allowed to access the balance variable from the superclass, because balance is public in the superclass (i.e. the variable is called balance, not __balance).
  6. Defining Constructors
    • The one thing you don’t inherit from the superclass is the constructor
      • You must redefine a constructor in your subclass, taking the full set of parameters that you deem necessary to fully initialize all the members in your subclass plus the superclass
    • When the client creates a subclass object, Python calls the constructor defined in your subclass.
      • The first thing your subclass constructor should do is to invoke the superclass constructor, to initialize the base part of the object. You do this via a call to super().__init__(), passing whatever parameters you need into the superclass constructor
      • After you’ve done this, your subclass constructor should add whatever instance variables are appropriate for the subclass
    • Example:
      • First we call the superclass constructor
      • Then we add an instance variable named interestRate to hold the interest rate for this particular SavingsAccount instance, based on a value passed into our constructor from client code. If the client code didn’t specify this value in the constructor call, we use the __DEFAULT_INTEREST_RATE class-wide variable instead.
        class SavingsAccount(BankAccount):
          def __init__(self, accountHolder="Anonymous", interestRate=None):
            super().__init__(accountHolder)
            if interestRate is None:
              self.interestRate = SavingsAccount.__DEFAULT_INTEREST_RATE
            else:
              self.interestRate = interestRate
          ...
      • View Code File

  7. Overriding Methods
    • In Python, all methods in a superclass are overridable. This means subclasses can re-implement methods with alternative application logic if appropriate.
      • Method overriding is a very common practice, especially when you’re using a third-party framework
      • The framework will typically define a large number of classes in a predefined arrangement, to implement core functionality for a simple application
      • To use the framework, you typically define subclasses for some of the existing classes, and override some of the methods with your own application-specific functionality
    • An override can call the original superclass method, to leverage existing functionality
      • Call super().methodName(params)
    • Example:
      class SavingsAccount(BankAccount):
        def withdraw(self, amount):
          if amount > self.balance:
            print("You can't go overdrawn in a savings account!")
          else:
            super().withdraw(amount)
          return self.balance  
        ...
    • View Code File

    • Consider the example above:
      • The withdraw() method first checks whether the withdrawal amount exceeds the current balance. This isn’t allowed for savings accounts, according to the business rules in our application
      • If the amount isn’t too large, we call the superclass version of withdraw() to perform the normal processing as defined by the superclass
    • We can also override the __str__() method as follows, to return a textual representation of the object data in the subclass plus its superclass:
        def __str__(self):
          baseStr = super().__str__()
          return "{0}, {1}".format(baseStr, self.interestRate)
  8. Multiple Inheritance
    • Python supports multiple inheritance
      T7P2
    • Python supports multiple inheritance, which is quite unusual in OO these days. C++ also supports multiple inheritance, but other OO languages such as C# and Java do not.
      • To use multiple inheritance in Python, the subclass specifies a comma-separated list of superclasses in its definition
      • For example, the Alerter class in the slide inherits from both the Logger and Beeper classes, and leverages the methods defined in both these superclasses.
    • Client code can access public members in the subclass or in any superclass
      alerter = Alerter()
      alerter.log("Wakey wakey!")
      for i in range(30):
        alerter.beep(50)
      msg = input("Enter an alert message: ")
      alerter.doShortAlert(msg)
      msg = input("Enter another alert message: ")
      alerter.doMediumAlert(msg)
      msg = input("And another: ")
      alerter.doLongAlert(msg)
    • View Code File

    • This shows that the client code can access any public methods defined in the subclass, or in any of its superclasses. Note the following points:
      • We define an Alerter object. This object will have various methods defined in the Alerter class, plus all the methods defined in Logger and Beeper
      • We call log() on the Alerter object. Python looks up the inheritance tree and locates this method in the Logger class
      • Then we call beep() on the Alerter object. Python looks up the inheritance tree again, and this time locates the method in the Beeper class
      • Then we call doShortAlert(), doMediumAlert(), and doLongAlert() on the Alerter object. Python locates these methods immediately in the Alerter class, so it doesn’t need to look any further up the inheritance tree.
Lab
  1. Defining a Programmer class
    • Go back to company.py and define a new class named Programmer. A programmer is a kind of employee, so inheritance is appropriate here
    • Suggestions and requirements for the Programmer class:
      • A programmer is a kind of employee, as we just said
      • A programmer has all the basic characteristics of a regular employee
        class Programmer(Employee):
          def __init__(self, name, salary):
            super().__init__(name, salary)
      • View Code File

      • A programmer also has a collection of skills, i.e. the programming languages that he/she knows. Implement this as a dictionary, where the key is the name of a programming language, and the value is the programmer’s current skill level in that language (1 to 5, where 1 means novice and 5 means guru). Initially, this skills dictionary should be empty. (You might want to take a look back in the “Data Structures” chapter to remind yourself about dictionaries in Python)
        class Programmer(Employee):
          def __init__(self, name, salary):
            super().__init__(name, salary)
            self.__skills = {}
      • A programmer can add a new language to his/her skillset. You must specify the name of the language and the level of proficiency
        # Business methods.
          def addSkill(self, language, level):
            self.__skills[language] = level
      • A programmer can improve his/her skill level in a particular language
        def improveSkill(self, language, level):
          if language in self.__skills:
            self.__skills[language] = level
      • View Code File

      • You should be able to ask a programmer for his/her skill level in a particular language. It’s OK for the programmer to say “none”
        def getSkillLevel(self, language):
          return self.__skills.get(language)
    • Once you’re happy with your Programmer class, go to clientcode.py and add some code to create a Programmer object and exercise its capabilities
      # Create a Programmer and use its superclass and subclass features.
      prog1 = Programmer("Bruce", 25000)
      prog1.addSkill("Python", 3)
      prog1.addSkill("Java", 4)
      prog1.addSkill("C++", 3)
      prog1.improveSkill("Python", 5)
      print("Python skill level: %s" % prog1.getSkillLevel("Python"))
      print("Cobol skill level: %s" % prog1.getSkillLevel("Cobol"))
    • View Code File

  2. Overriding superclass behavior
    • Go back to company.py and enhance the Programmer class so that it overrides the following methods:
      • payBonus() – For a programmer, the bonus calculation is different than for regular employees. A programmer gets the regular bonus payment as would any employee, but they also get an additional bonus of 100 for every programming language in their skillset. This is to encourage programmers to broaden their skillset (although it doesn’t necessarily encourage them to deepen their skillsets!). Note that you’ll probably need to access the salary attribute defined in the superclass, in order to add the bonus to the salary. This will present you with a problem, because the salary attribute is currently named __salary (which means its private to the base class). To overcome this problem, rename __salary to _salary everywhere in the base class. A single underscore is a Python convention that means “don’t access this variable from the client code, but it’s OK to access it from subclasses. It’s a bit like “protected” in some other OO languages.
        def payBonus(self, percentBonus=1, min=None, max=None):
          super().payBonus(percentBonus, min, max)
          self._salary += 100 * len(self.__skills)
      • View Code File

      • __str__() – For a programmer, you should output the number of languages in his/her skillset (as well as all the regular employee-related information, of course)
        def __str__(self):
          resultStr = super().__str__()
          resultStr += ", skills:\n"
          for language, level in self.__skills.items():
            resultStr += " {0}, level {1}\n".format(language, level)
          return resultStr
      • View Code File

    • When you’re done, go to clientcode.py and exercise the new bonus rules and string formatting capabilities
      prog1.payBonus()
      print(prog1)
    • View Code File

 

Well done. You have completed the tutorial in the Python course. The next tutorial is

8. XML Processing in Python


Back to beginning
Copyright © 2016 TalkIT®






If you liked this post, please comment with your suggestions to help others.
If you would like to see more content like this in the future, please fill-in our quick survey.
Scroll to Top