14: Class and Type

Summary

  • There are two main concepts of type
    • Primitive type: relates to the way data is represented and is the origin of the concept

    • The second relates to class in object oriented programming

  • In many object oriented languages defining a class introduces a new type to the type system

  • A strongly typed language enforces the rule that you have to declare the type of a variable and that variable can from then on only reference objects of that type

  • This rule is usually extended to include variables referencing objects of the specified type or a subtype
    • The reason for this si that it allows you to write partly generic methods which can process the type and all its subtypes

  • Class hierarchy is a model of the real world

  • The Liskov Substitution Principle is often used as a justification for hierarchial typing, but it is only an approximation to the real world

  • Variables do not have a type associated with them and can reference any type of object

  • Objects do have a limited notion of type in that their __class__ attribute is set to the class or metaclass that created them
    • It is important to realize that __class__ can be modified

  • You can use isinstance and issubclass to check that an object claims to be of the appropriate type

  • Another approach is to use defensive programming to test for the presence of any attribute or method you are planning to use via the hasattr function
    • This is generally called “duck typing”

Program

"""
Demonstration of Python Type Concepts

This program shows:
- Primitive types vs object types
- Classes as types in object-oriented programming
- Python's dynamic (not strictly strong) typing
- Class hierarchies and subtype relationships
- Liskov Substitution Principle (conceptual example)
- Type checking with isinstance and issubclass
- Duck typing using hasattr
"""

# -------------------------------
# Primitive Types
# -------------------------------
# Primitive types represent basic data
x = 10          # int
y = 3.14        # float
z = "hello"     # string

print("Primitive Types:")
print(type(x), type(y), type(z))
print()


# -------------------------------
# Classes Define New Types
# -------------------------------
class Animal:
    """Base class representing a general animal"""
    def speak(self):
        return "Some sound"


class Dog(Animal):
    """Dog is a subtype of Animal"""
    def speak(self):
        return "Bark"


class Cat(Animal):
    """Cat is another subtype of Animal"""
    def speak(self):
        return "Meow"


# -------------------------------
# Dynamic Typing (Python is NOT strongly typed)
# -------------------------------
# Variables do NOT have fixed types in Python
var = Dog()     # var refers to a Dog object
print("var is:", type(var))

var = 42        # same variable now refers to an int
print("var is now:", type(var))
print()


# -------------------------------
# Class Hierarchy & Substitution
# -------------------------------
def animal_sound(animal):
    """
    Function that works with Animal or any subtype.
    Demonstrates Liskov Substitution Principle:
    subtypes (Dog, Cat) can replace base type (Animal)
    """
    print(animal.speak())


print("Liskov Substitution Principle Demo:")
animal_sound(Dog())
animal_sound(Cat())
print()


# -------------------------------
# Type Checking
# -------------------------------
dog = Dog()

print("Type Checking:")
print(isinstance(dog, Dog))      # True
print(isinstance(dog, Animal))   # True (subtype relationship)
print(issubclass(Dog, Animal))   # True
print()


# -------------------------------
# Object Type via __class__
# -------------------------------
print("Object __class__ attribute:")
print(dog.__class__)

# NOTE: __class__ can technically be modified (not recommended)
dog.__class__ = Cat
print("After modifying __class__:", dog.__class__)
print(dog.speak())  # Now behaves like a Cat
print()


# -------------------------------
# Duck Typing (hasattr)
# -------------------------------
class Robot:
    """Not related to Animal, but has a speak method"""
    def speak(self):
        return "Beep boop"


def make_it_speak(obj):
    """
    Duck typing: we don't care about the type,
    only that the object has a 'speak' method.
    """
    if hasattr(obj, "speak"):
        print(obj.speak())
    else:
        print("Object cannot speak")


print("Duck Typing Demo:")
make_it_speak(Dog())
make_it_speak(Robot())   # Works even though Robot is not an Animal
make_it_speak(123)       # Fails safely

Program Output

Primitive Types:
<class 'int'> <class 'float'> <class 'str'>

var is: <class '__main__.Dog'>
var is now: <class 'int'>

Liskov Substitution Principle Demo:
Bark
Meow

Type Checking:
True
True
True

Object __class__ attribute:
<class '__main__.Dog'>
After modifying __class__: <class '__main__.Cat'>
Meow

Duck Typing Demo:
Bark
Beep boop
Object cannot speak