Книга: Practical Programming, Fourth Edition
Назад: Plugging into Python Syntax: Mo re Special Methods
Дальше: A Case Study: Molecules, Atoms, and PDB Fil es

A Little Bit of OO Theory

Classes and objects are two of programming’s power tools. They let good programmers accomplish a great deal in very little time, but with them, bad programmers can create a real mess. This section introduces some underlying theory that will help you design reliable and reusable object-oriented software.

Encapsulation

To encapsulate something means to enclose it within a container. In programming, encapsulation refers to keeping data and the code that uses it in one place, while hiding the details of how they interact. For example, each instance of a fictitious class file keeps track of disk reading and writing operations, as well as the current position within the file. The class conceals the implementation details, allowing programmers to use it without needing to understand the underlying mechanisms.

Polymorphism

Classes support polymorphism: if two classes have methods that work the same way, instances of those classes can replace one another

Polymorphism means “having more than one form.” In programming, it means that an expression involving a variable can perform differently depending on the type of object to which the variable refers. For example, if obj refers to a string, then obj[1:3] produces a two-character string. If obj refers to a list, on the other hand, the same expression produces a two-element list. Similarly, the expression left + right can produce a number, a string, or a list, depending on the types of left and right.

Polymorphism is widely used in modern programming to reduce the amount of code that programmers need to write and test. For example, it allows you to write a generic function to count non-blank lines:

 def​ ​non_blank_lines​(thing):
 """Return the number of nonblank lines in thing."""
 
  count = 0
 for​ line ​in​ thing:
 if​ line.strip():
  count += 1
 return​ count

You can apply this function to a list of strings, a file, or a web document halfway around the world (see ) or in the cloud. Each of those three types knows how to be the subject of a loop; in other words, each one knows how to produce its “next” element as long as there is one and then say “all done.” That means that instead of writing four functions to count interesting lines or copying the lines into a list and then applying one function to that list, you can apply one function to all those types directly.

Inheritance

New classes can be defined by inheriting features from existing ones

Implementing the same methods in several classes is one way to make them polymorphic. Still, it suffers from the same flaw as initializing an object’s instance variables from outside the object. If a programmer forgets just one line of code, the whole program can fail for reasons that will be difficult to track down. A better approach is to utilize a third fundamental feature of object-oriented programming called inheritance, which enables you to reuse code more effectively.

Whenever you create a class, you are using inheritance: your new class automatically inherits all of the attributes of the class object, much like a child inherits attributes from their parents. You can also declare that your new class is a subclass of some other class.

Here is an example. Let’s say you’re managing people at a university. There are students and faculty. (This is a gross oversimplification for purposes of illustrating inheritance; you’re ignoring administrative staff, caretakers, food providers, and more.)

Both students and faculty have names, postal addresses, and email addresses; each student also has a student number, a list of courses taken, and a list of courses they are currently enrolled in. Each faculty member has a faculty number and a list of courses they are currently teaching. (Again, this is a simplification.)

You’ll have a Faculty class and a Student class. You need both of them to have names, addresses, and email addresses, but duplicate code is generally a bad thing; so, avoid it by also defining a class, perhaps called Member, and keeping track of those features in Member. Then make both Faculty and Student subclasses of Member:

 class​ Member:
 """ A member of a university. """
 
 def​ ​__init__​(self, name: str, address: str, email: str) -> None:
 """Initialize a new member named name, with home address and email
  address.
  """
 
  self.name = name
  self.address = address
  self.email = email
 
 class​ Faculty(Member):
 """ A faculty member at a university. """
 
 def​ ​__init__​(self, name: str, address: str, email: str,
  faculty_num: str) -> None:
 """Initialize a new faculty named name, with home address, email
  address, faculty number faculty_num, and empty list of
  courses.
  """
 
  super().__init__(name, address, email)
  self.faculty_number = faculty_num
  self.courses_teaching = []
 
 
 class​ Student(Member):
 """ A student member at a university. """
 
 def​ ​__init__​(self, name: str, address: str, email: str,
  student_num: str) -> None:
 """Initialize a new student named name, with home address, email
  address, student number student_num, an empty list of courses
  taken, and an empty list of current courses.
  """
 
  super().__init__(name, address, email)
  self.student_number = student_num
  self.courses_taken = []
  self.courses_taking = []

Both class headers—class Faculty(Member): and class Student(Member):—tell Python that Faculty and Student are subclasses of the class Member. That means they inherit all the attributes of the class Member.

The first lines of both Faculty.__init__ and Student.__init__ call the function super, which produces a reference to the superclass part of the object, Member. That means that both of those first lines call the method __init__, which was inherited from the class Member. Notice that you pass the relevant parameters in as arguments to this call, just as you would with any method call.

If you import these into the shell, you can create both faculty and students:

 >>>​​ ​​paul​​ ​​=​​ ​​Faculty(​​'Paul Gries'​​,​​ ​​'Ajax'​​,​​ ​​'[email protected]'​​,​​ ​​'1234'​​)
 >>>​​ ​​paul.name
 Paul Gries
 >>>​​ ​​paul.email
 [email protected]
 >>>​​ ​​paul.faculty_number
 1234
 >>>​​ ​​jen​​ ​​=​​ ​​Student(​​'Jen Campbell'​​,​​ ​​'Toronto'​​,​​ ​​'[email protected]'​​,
 ...​​ ​​'4321'​​)
 >>>​​ ​​jen.name
 Jen Campbell
 >>>​​ ​​jen.email
 [email protected]
 >>>​​ ​​jen.student_number
 4321

Both the Faculty and Student objects have inherited the features defined in the class Member.

Often, you’ll want to extend the behavior inherited from a superclass. As an example, you might write a __str__ method inside the class Member:

 def​ ​__str__​(self) -> str:
 """Return a string representation of this Member.
 
  >>> member = Member('Paul', 'Ajax', '[email protected]')
  >>> member.__str__()
  'Paul​​\\​​nAjax​​\\​​[email protected]'
  """
 
 return​ f​'{self.name}​​\n​​{self.address}​​\n​​{self.email}'

With this method added to class Member, both Faculty and Student inherit it:

 >>>​​ ​​paul​​ ​​=​​ ​​Faculty(​​'Paul'​​,​​ ​​'Ajax'​​,​​ ​​'[email protected]'​​,​​ ​​'1234'​​)
 >>>​​ ​​str(paul)
 'Paul\nAjax\[email protected]'
 >>>​​ ​​print(paul)
 Paul
 Ajax
 [email protected]

That isn’t quite enough, though: for class Faculty, you want to extend what the Member’s __str__ does, adding the faculty number and the list of courses the faculty member is teaching, and a Student string should include the equivalent student-specific information.

Use super again to access the inherited Member.__str__ method and to append the Faculty-specific information:

 def​ ​__str__​(self) -> str:
 """Return a string representation of this Faculty.
 
  >>> faculty = Faculty('Paul', 'Ajax', '[email protected]', '1234')
  >>> faculty.__str__()
  'Paul​​\\​​nAjax​​\\​​[email protected]​​\\​​n1234​​\\​​nCourses: '
  """
 
  member_string = super().__str__()
 
 return​ f​'''{member_string}
 {self.faculty_number}
 Courses: {' '.join(self.courses_teaching)}'''

With this, you get the desired output:

 >>>​​ ​​paul​​ ​​=​​ ​​Faculty(​​'Paul'​​,​​ ​​'Ajax'​​,​​ ​​'[email protected]'​​,​​ ​​'1234'​​)
 >>>​​ ​​str(paul)
 'Paul\nAjax\[email protected]\n1234\nCourses: '
 >>>​​ ​​print(paul)
 Paul
 Ajax
 [email protected]
 1234
 Courses:
Назад: Plugging into Python Syntax: Mo re Special Methods
Дальше: A Case Study: Molecules, Atoms, and PDB Fil es