Classes
v Classes:
Ø Pythons tool to create new types.
Ø All objects in Python have a type, and we
report that type using the type built-in function ,the result is couched in
terms of class.
Ø Classes are a means of defining the
structure and behavior of objects at the time we create the object.
Ø Classes act as a sort of template or pattern according to which new objects are
constructed. The class of an object controls
its initialization in which attributes and methods are available through the
object.
Ø Python is highly object oriented without forcing us to deal with classes until we really
need them. This sets the language starkly apart from Java and C#. This
means unlike Java, C#, not every code needs to be a part of some class.
Ø Class definitions are introduced by the class keyword followed by the class name. By
convention, the new class name in Python uses CamelCase,
sometimes known as PascalCase with a capital letter
for each component word.
Ø The class
statement introduces a new block, so we indent on the next line. Empty
blocks aren't allowed, So the simplest class that is syntactically admissible
is as follows:
class abc:
pass
pass
Ø Just as with def for defining functions, class is a
statement that can occur anywhere in a program and
which binds a class definition to a class name. The
def binds function name to the function definition.
Ø For class Flight in module airtravel.py, we
use “from airtravel import Flight” to import it in our code/REPL. The thing
we've just imported is the class object. Everything is an object in Python, and classes
are no exception
Ø To create a class object we call its constructor, which is done by calling the
class as we would a function. Ex: f=Flight()
Ø What are methods: Methods are just functions defined within
the class
Ø What are instance methods: Instance methods are functions which can
be called on objects or instances of our class.
Ø Instance methods must accept a reference to
the instance on which the method was called as the first argument, and by convention this argument is always called self.
Ø When we call the method, we do not provide
the instance as the actual argument itself in the argument list. That's because the standard method invocation form with
the dot is simply syntactic sugar for the class name followed by a dot followed
by the method name with the instance passed as the first argument. Ex:
f.mymethod() is same as classname.method(f). Both give the same result.
Ø The initializer method must be called
__init__ . If provided, the initialization method is called as part of the
process of creating a new object when we call the constructor. Like all
other instance methods, the first argument to __init__ must be self. Note that
having a __init__ method is not mandatory. However if its present its used to
initialize the instance.
Ø The initializer
should not return anything. It
simply modifies the object referred to by self.
Ø Although it's tempting to think of __init__
as being the constructor, this isn't quite accurate. In Python, the purpose of __init__
is to configure an object that already exists by
the time it's called. In Python, the actual
constructor is provided by the Python runtime system, and one of the
things it does is check for the existence of an
instance initializer and call it when present. Any arguments passed to
the flight constructor will be forwarded to the initializer. The self argument
is analogous to this in Java, C#, or C++.
Ø Assigning to an object attribute that doesn't yet exist is enough to bring it into being. Just as we
don't need to declare variables until we create them, neither
do we need to declare object attributes before we create them.
Ø Many a times instance attributes are
preceded by _ Ex: _number.
First because it avoids a name clash with the method of the same name.
Second, there is a widely followed convention that the implementation details of objects which are not intended for consumption or manipulation by clients of the object should be prefixed with an underscore.
First because it avoids a name clash with the method of the same name.
Second, there is a widely followed convention that the implementation details of objects which are not intended for consumption or manipulation by clients of the object should be prefixed with an underscore.
Ø Convention of leading
underscores: In language like Java or C# we have
public, private, and protected access modifiers. But
in Python's everything is pubic. The leading underscore convention has
proven enough protection even in large and complex Python systems we have
worked with. People know not to use these attributes directly, and in fact they
tend not to.
Ø Performing Checks in
__init__: It's good practice for the initializer of an object
to establish so-called class invariants. The
invariants are truths(checks) about the objects of that class that should
endure for the lifetime of the object. In Python we
establish class invariants in the __init__ method and raise exceptions if they
can't be attained.
class Flight:
"""A flight with a particular passenger aircraft. """
def __init__(self, number, aircraft):
if not number[:2].isalpha():
raise ValueError("No airline code in '{}'".format(number))
if not number[:2].isupper():
raise ValueError("Invalid airline code '{}'".format(number))
if not (number[2:].isdigit() and int(number[2:]) <= 9999):
raise ValueError("Invalid route number '{}'".format(number))
class Flight:
"""A flight with a particular passenger aircraft. """
def __init__(self, number, aircraft):
if not number[:2].isalpha():
raise ValueError("No airline code in '{}'".format(number))
if not number[:2].isupper():
raise ValueError("Invalid airline code '{}'".format(number))
if not (number[2:].isdigit() and int(number[2:]) <= 9999):
raise ValueError("Invalid route number '{}'".format(number))
Ø We
can also add a docstring to the class. These work just like function and module
docstrings.
Ø Note
that method calls within the same object also require explicit qualification
with the self prefix.
def allocate_seat(self,
seat, passenger):
"""Allocate a seat to a passenger.
Args:
seat: A seat designator such as '12C' or '21F'.
passenger: The passenger name.
Raises:
ValueError: If the seat is unavailable.
"""
row, letter = self._parse_seat(seat)
if self._seating[row][letter] is not None:
raise ValueError("Seat {} already occupied".format(seat))
self._seating[row][letter] = passenger
"""Allocate a seat to a passenger.
Args:
seat: A seat designator such as '12C' or '21F'.
passenger: The passenger name.
Raises:
ValueError: If the seat is unavailable.
"""
row, letter = self._parse_seat(seat)
if self._seating[row][letter] is not None:
raise ValueError("Seat {} already occupied".format(seat))
self._seating[row][letter] = passenger
Ø Note
that a module could have many class definitions and module level functions defined
using def keyword. Check below code.
Ø Use
of line continuation backslash character allows
us to split a long statement over several lines. This can be used together with
implicit string concatenation of adjacent strings to produce one long string
with no line breaks.
Ø To
join multiple strings on separate lines, we can use the join method on the “\n”
string.
Ø Polymorphism
is a programing language feature which allows us to
use objects of different types through a uniform interface. The concept
of polymorphism applies to functions and more complex objects.
Ø Polymorphism in Python is achieved through duck typing. Duck typing is in turn named after the duck test attributed to James William Riley the
American poet. "When I see a bird that walks
like a duck and swims like a duck and quacks like a duck, I call that bird a
duck. "
Duck
typing where an object's fitness for a particular
use is only determined at runtime is the cornerstone of Python's object system.
This is in contrast to statically typed languages where a compiler
determines if an object can be used. And in particular, it means that an object's suitability is not based on inheritance
hierarchies, base classes, or anything except the attributes an object has at
the time of use.
Duck typing is the basis for the collection
protocols such as iterator, iterable, and sequence.
Ø Inheritance is a mechanism whereby one class can be derived from a base-class allowing
us to make behavior more specific in the sub-class.
In typed languages such as Java, class-based inheritance is how runtime
polymorphism is achieved. This is not the case in Python as we saw with the use
of duck typing.
Ø The fact that no
Python method calls or attribute lookups are bound to actual objects until the
point at which they are called known as late
binding means we can attempt polymorphism with any object, and we'll
succeed if the object fits.
Ø Although inheritance
in Python can be used to facilitate polymorphism, after all derived
classes will have the same interfaces as the base classes, inheritance in Python is most useful for sharing implementation
between classes.
Ø We specify
inheritance in Python using parentheses containing the base class name
immediately after the class name in the class statement. Ex: class
Boeing(AirCraft)
Ø Due to duck typing, inheritance is less
used in Python than in other languages. This is generally seen as a good thing
because inheritance is a very tight coupling between classes.
"""Model
for aircraft flights"""
class Flight: """A flight with a particular passenger aircraft. """ def __init__(self, number, aircraft): if not number[:2].isalpha(): raise ValueError("No airline code in '{}'".format(number)) if not number[:2].isupper(): raise ValueError("Invalid airline code '{}'".format(number)) if not (number[2:].isdigit() and int(number[2:]) <= 9999): raise ValueError("Invalid route number '{}'".format(number)) self._number = number self._aircraft = aircraft rows, seats = self._aircraft.seating_plan() self._seating = [None] + [{letter: None for letter in seats} for _ in rows] def number(self): return self._number def airline(self): return self._number[:2] def aircraft_model(self): return self._aircraft.model() def _parse_seat(self, seat): """Parse a seat designator into a valid row and letter. Args: seat: A seat designator such as 12F Returns: A tuple containing an integer and a string for row and seat. """ row_numbers, seat_letters = self._aircraft.seating_plan() letter = seat[-1] if letter not in seat_letters: raise ValueError("Invalid seat letter {}".format(letter)) row_text = seat[:-1] try: row = int(row_text) except ValueError: raise ValueError("Invalid seat row {}".format(row_text)) if row not in row_numbers: raise ValueError("Invalid row number {}".format(row)) return row, letter def allocate_seat(self, seat, passenger): """Allocate a seat to a passenger. Args: seat: A seat designator such as '12C' or '21F'. passenger: The passenger name. Raises: ValueError: If the seat is unavailable. """ row, letter = self._parse_seat(seat) if self._seating[row][letter] is not None: raise ValueError("Seat {} already occupied".format(seat)) self._seating[row][letter] = passenger def relocate_passenger(self, from_seat, to_seat): """Relocate a passenger to a different seat. Args: from_seat: The existing seat designator for the passenger to be moved. to_seat: The new seat designator. """ from_row, from_letter = self._parse_seat(from_seat) if self._seating[from_row][from_letter] is None: raise ValueError("No passenger to relocate in seat {}".format(from_seat)) to_row, to_letter = self._parse_seat(to_seat) if self._seating[to_row][to_letter] is not None: raise ValueError("Seat {} already occupied".format(to_seat)) self._seating[to_row][to_letter] = self._seating[from_row][from_letter] self._seating[from_row][from_letter] = None def num_available_seats(self): return sum(sum(1 for s in row.values() if s is None) for row in self._seating if row is not None) def make_boarding_cards(self, card_printer): for passenger, seat in sorted(self._passenger_seats()): card_printer(passenger, seat, self.number(), self.aircraft_model()) def _passenger_seats(self): """An iterable series of passenger seating allocations.""" row_numbers, seat_letters = self._aircraft.seating_plan() for row in row_numbers: for letter in seat_letters: passenger = self._seating[row][letter] if passenger is not None: yield (passenger, "{}{}".format(row, letter)) class Aircraft: def __init__(self, registration): self._registration = registration def registration(self): return self._registration def num_seats(self): rows, row_seats = self.seating_plan() return len(rows) * len(row_seats) class AirbusA319(Aircraft): def model(self): return "Airbus A319" def seating_plan(self): return range(1, 23), "ABCDEF" class Boeing777(Aircraft): def model(self): return "Boeing 777" def seating_plan(self): # For simplicity's sake, we ignore complex # seating arrangement for first-class return range(1, 56), "ABCDEGHJK" def make_flights(): f = Flight("BA758", AirbusA319("G-EUPT")) f.allocate_seat('12A', 'Guido van Rossum') f.allocate_seat('15F', 'Bjarne Stroustrup') f.allocate_seat('15E', 'Anders Hejlsberg') f.allocate_seat('1C', 'John McCarthy') f.allocate_seat('1D', 'Richard Hickey') g = Flight("AF72", Boeing777("F-GSPS")) g.allocate_seat('55K', 'Larry Wall') g.allocate_seat('33G', 'Yukihiro Matsumoto') g.allocate_seat('4B', 'Brian Kernighan') g.allocate_seat('4A', 'Dennis Ritchie') return f, g def console_card_printer(passenger, seat, flight_number, aircraft): output = "| Name: {0}" \ " Flight: {1}" \ " Seat: {2}" \ " Aircraft: {3}" \ " |".format(passenger, flight_number, seat, aircraft) banner = '+' + '-' * (len(output) - 2) + '+' border = '|' + ' ' * (len(output) - 2) + '|' lines = [banner, border, output, border, banner] card = '\n'.join(lines) print(card) print() |
v Summary:
Ø All types in Python have a
class. Classes define the structure and behavior of an object. Classes are the
key support for object-oriented programming in Python.
Ø Classes are defined using the
class keyword followed by the class name, which is in CamelCase.
Ø Instances of a class are
created by calling the class as if it were a function.
Ø Instance methods are
functions defined inside the class, which should accept an object instance
called self as the first parameter.
Ø Methods are called using the
instance.method() syntax, which is syntactic sugar for passing the instance as
the formal self argument to the method.
Ø An optional special
initializer method called __init__() can be provided, which is used to
configure the self object at creation time. If present, the constructor calls
the __init__() method. Note that Double underscore init is not the constructor.
The object has been constructed by the time the initializer is called. Arguments
passed to the constructor are forwarded to the initializer.
Ø Instance attributes are brought
into existence simply by assigning to them.
Ø Attributes and methods which
are implementation details are by convention prefixed with an underscore.
Ø There are no public,
protected, or private access modifiers in Python.
Ø Class invariants should be
established in the initializer. If the invariants can't be established, raise
exceptions to signal failure.
Ø Methods can have docstrings,
just like regular functions. Classes can have docstrings.
Ø Even within an object
method, calls must be preceded with self.
Ø You can have as many
classes and functions in a module as you wish. Related classes and global
functions are generally grouped together this way.
Ø Polymorphism in Python is
achieved through duck typing where attributes and methods are only resolved at
point of use. This is called late binding.
Ø Polymorphism in Python does not require
shared base classes or named interfaces. Class inheritance in Python is
primarily useful for sharing implementation rather than being necessary for
polymorphism.
Ø We can nest comprehensions.
It can sometimes be useful to discard the current item in a comprehension using
a dummy reference, conventionally the underscore character.
[‘1’ for _ in list1]
[‘1’ for _ in list1]
Ø Functions are also objects.
Ø Complex comprehensions or
generator expressions can be split over multiple lines to aid readability.
Statements can be split over multiple lines using the backslash line continuation
character. Use this feature sparingly and only when it improves readability.
No comments:
Post a Comment