Saturday, March 7, 2020

Python - Classes


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

Ø 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.

Ø 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))

Ø 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

Ø 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]

Ø 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