PCAP · Domain 4 of 5

PCAP Domain 4: Object-Oriented Programming

Section 4 is the heaviest part of PCAP. It covers classes and objects, class versus instance variables, __dict__ and name mangling, methods and self, introspection, single and multiple inheritance, polymorphism and constructors.

Exam weight
34%
Official objectives
6
Estimated questions estimate
~12
Suggested study time estimate
18–25 hrs

About 13 min read Reviewed October 2026

Overview

Object-Oriented Programming is Section 4 of the PCAP-31-03 syllabus and has six objectives. Understanding the object-oriented approach covers classes, objects, properties, methods, encapsulation, inheritance and superclass and subclass relationships. Employing class and object properties covers instance versus class variables, the __dict__ attribute of objects and classes, and private components with name mangling. Equipping a class with methods covers declaring methods and the self parameter. Discovering the class structure covers introspection with hasattr() and the __name__, __module__ and __bases__ attributes. Building a class hierarchy covers single and multiple inheritance, isinstance(), overriding, the is and is not operators, polymorphism, overriding __str__() and diamond problems. Constructing and initializing objects covers declaring and invoking constructors.

At 34%, this section outweighs any other by a wide margin. Its items rely on precise attribute lookup rules: Python checks the instance first, then its class, then the base classes in method resolution order.

How it's tested

OOP items present one to four short class definitions followed by a few statements that create objects, assign attributes and print values. You choose the output or the line that raises an error. Multiple-select items may ask which expressions evaluate to True, mixing isinstance(), hasattr(), is and attribute lookups across a hierarchy.

  • Attribute lookup

    Class and instance variables share a name, and you trace whether each access reads the instance, the class or a base class.

  • Resolution order

    A method is defined in several classes of a multiple-inheritance hierarchy, and you identify which version runs for a given object.

  • Introspection result

    Code inspects __dict__, __bases__, __name__ or hasattr(), and you choose the exact value or Boolean returned.

Reasoning it through

Class A defines counter = 0, and a = A() runs a.counter += 1. Python reads a.counter, finds 0 on the class, adds one and writes the result to the instance, creating a new instance variable. A.counter stays 0 while a.counter is 1. Choosing 1 for both values assumes the assignment changed the shared class attribute, which only A.counter += 1 would do.

Objectives 6

Official objectives from the OpenEDG Python Institute outline, in outline order.

4.1 Understand the object-oriented approachClass, object, property, method, encapsulation, inheritance, superclass and subclass; identifying class components.
What it means
This objective covers the vocabulary of object-oriented programming and your ability to point at each part of a class definition and name it correctly.
What to understand
A class is a template that defines properties (data) and methods (behavior). An object is an instance created from it. Encapsulation hides internal details behind methods, which Python supports with naming conventions and name mangling rather than enforced access control. Inheritance lets a subclass reuse and extend a superclass. Every class in Python 3 ultimately inherits from object, even when no base class is written.
How it shows up
Items show a class definition and ask which line declares a class variable, a method or a constructor.
Common confusion
Calling an object a class; the class is the blueprint and each object is a separate instance with its own state.
4.2 Employ class and object propertiesInstance vs. class variables, __dict__ of objects and classes, private components and name mangling.
What it means
This objective covers where attributes live, how they are created and inspected, and what Python does to names that start with two underscores.
What to understand
Class variables are defined in the class body and shared by all instances; instance variables are usually created through self in __init__ and belong to one object. Assigning to an attribute through an instance always creates or updates an instance variable. An object's __dict__ shows only its instance variables, while the class __dict__ holds class variables and methods. A name such as __secret inside class Bank is stored as _Bank__secret, so outside code must use the mangled form.
How it shows up
Expect snippets that print obj.__dict__ or access a double-underscore attribute from outside the class.
Common confusion
Expecting an instance's __dict__ to list class variables; they live in the class __dict__ instead.
4.3 Equip a class with methodsDeclaring and using methods; the self parameter.
What it means
This objective covers writing methods inside a class and calling them, with attention to the first parameter that receives the object.
What to understand
A method is a function defined in a class body. When you call obj.method(arg), Python passes obj automatically as the first parameter, conventionally named self, so the definition needs one more parameter than the call supplies. Methods reach instance data through self and other methods through self.other(). Calling Class.method(obj) is equivalent to obj.method(). Forgetting self in the definition raises TypeError when the method is called on an instance.
How it shows up
Items ask why a method call raises TypeError about the number of positional arguments.
Common confusion
Treating self as a keyword; it is only a convention, and any parameter name in the first position works.
4.4 Discover the class structureIntrospection, hasattr() for objects and classes, and the __name__, __module__ and __bases__ properties.
What it means
This objective covers inspecting classes and objects at run time to find out what they contain and where they come from.
What to understand
hasattr(obj, 'name') returns True when attribute lookup succeeds, which includes class variables and inherited attributes, not just instance variables. A class's __name__ holds its name as a string, __module__ holds the name of the module that defined it, which is '__main__' for a script, and __bases__ is a tuple of direct base classes. Objects themselves do not have __name__; use type(obj).__name__ or obj.__class__.__name__.
How it shows up
Items print __bases__ or call hasattr() on an object and its class and ask for the Boolean results.
Common confusion
Expecting __bases__ to list every ancestor; it holds only the direct parents, as a tuple.
4.5 Build a class hierarchy using inheritanceSingle and multiple inheritance, isinstance(), overriding, is and is not, polymorphism, __str__() and diamond problems.
What it means
This objective covers designing subclasses, choosing which method runs, comparing object identity, and resolving method conflicts when a class has more than one parent.
What to understand
A subclass inherits everything from its superclass and can override methods with the same name. Polymorphism means calling the same method on different objects runs each class's own version. isinstance(obj, Base) is True for instances of subclasses too. is compares identity, not equality. Overriding __str__() controls what print() shows. With multiple inheritance, Python searches the method resolution order, left to right through the bases, and in a diamond each shared ancestor is visited once, after all its subclasses.
How it shows up
Expect diamond hierarchies where two middle classes override a method and you predict which one runs.
Common confusion
Assuming depth-first search in a diamond; Python 3 uses the C3 order, so both middle classes come before the shared base.
4.6 Construct and initialize objectsDeclaring and invoking constructors.
What it means
This objective covers the __init__ method that initializes new objects, how arguments reach it and how subclasses call their parent's constructor.
What to understand
Python calls __init__ automatically after creating an object, passing the arguments given to the class call. A class has only one __init__; writing a second definition replaces the first, so overloading is done with default parameter values. If a subclass defines its own __init__, the parent's constructor does not run unless the subclass calls super().__init__() or Parent.__init__(self). A subclass without __init__ inherits the parent's constructor.
How it shows up
Items show a subclass constructor that omits the super() call and ask which attribute access then fails.
Common confusion
Believing two __init__ methods with different parameters coexist; the later definition silently wins.

High-yield concepts

The terms, rules and formulas this domain turns into questions most often.

  • Class variable

    Defined in the class body and shared by all instances.

    Remember
    Change it through the class name to affect every object.
    Tested as
    Shared counter items.
    Trap
    Updating it through an instance.
  • Instance variable

    Belongs to one object, usually set through self.

    Remember
    Shadows a class variable of the same name.
    Tested as
    Attribute lookup items.
    Trap
    Expecting other instances to see the change.
  • __dict__

    Dictionary of attributes stored directly on an object or class.

    Remember
    Instance __dict__ omits class variables.
    Tested as
    Introspection output items.
    Trap
    Expecting inherited names in it.
  • Name mangling

    Names with two leading underscores become _ClassName__name.

    Remember
    Access from outside needs the mangled form.
    Tested as
    AttributeError items.
    Trap
    Calling it true privacy.
  • self

    First parameter of a method, bound to the instance.

    Remember
    The call supplies one fewer argument than the definition.
    Tested as
    TypeError argument-count items.
    Trap
    Omitting it from the definition.
  • hasattr()

    Tests whether attribute lookup on an object succeeds.

    Remember
    Inherited and class attributes count.
    Tested as
    Boolean output items.
    Trap
    Expecting only instance variables to count.
  • __bases__

    Tuple of a class's direct parent classes.

    Remember
    A class with no explicit base shows (object,).
    Tested as
    Structure items.
    Trap
    Expecting a list or every ancestor.
  • __module__

    Name of the module where a class was defined.

    Remember
    It is '__main__' in a script run directly.
    Tested as
    Structure items.
    Trap
    Expecting a file path.
  • isinstance()

    True if an object is an instance of a class or its subclasses.

    Remember
    Subclass instances pass base-class checks.
    Tested as
    Hierarchy Boolean items.
    Trap
    Treating it as an exact type check.
  • is vs. ==

    is compares identity; == compares values.

    Remember
    Two equal objects can be different objects.
    Tested as
    Comparison items.
    Trap
    Using is to compare values.
  • Overriding

    A subclass method replaces the inherited one with the same name.

    Remember
    Lookup stops at the first class that defines the name.
    Tested as
    Polymorphism items.
    Trap
    Expecting both versions to run.
  • __str__()

    Returns the string that print() and str() show for an object.

    Remember
    Without it, print shows a default with the class name and address.
    Tested as
    Output items.
    Trap
    Returning a non-string value.
  • Method resolution order

    The order Python searches classes for an attribute.

    Remember
    Left to right, shared ancestors last.
    Tested as
    Diamond problem items.
    Trap
    Using depth-first order.
  • super().__init__()

    Calls the parent's constructor from a subclass.

    Remember
    Without it, parent attributes are never set.
    Tested as
    Constructor items.
    Trap
    Assuming the parent constructor runs automatically.

Side by side

Concepts candidates mix up, compared on the point that decides the answer.

Class vs. instance variables

ConceptMeaningKey differenceCommon trap
Class variable Stored on the class, shared Read by all instances without their own copy Updating through an instance
Instance variable Stored on one object Created by assignment through self or obj Expecting it to be shared
Shadowing Instance variable with a class variable's name Instance value hides the class value Assuming the class value changed

Introspection tools

ConceptMeaningKey differenceCommon trap
obj.__dict__ Instance attributes only No class variables or methods Expecting inherited names
Class.__dict__ Class attributes and methods Excludes instance variables Expecting self attributes
hasattr() Whether lookup succeeds Follows the full lookup chain Treating it as a __dict__ check
__bases__ Direct parent classes A tuple, direct parents only Expecting every ancestor

Identity and type checks

ConceptMeaningKey differenceCommon trap
is Same object in memory Identity, not value Using it to compare equal values
== Equal values Uses __eq__ Assuming it checks identity
isinstance() Instance of class or subclass Honors inheritance Treating it as exact type match

Exam traps

Each trap is an answer that looks right. Learn the clue that should change your mind.

  1. Instance write changes class

    What candidates pick
    Answer that every object sees the new value after obj.count = 5.
    Why it looks right
    The name matches the class variable.
    Why it's wrong
    Assignment through an instance creates an instance variable and leaves the class variable unchanged.

    Clue to watch for The assignment uses an object name, not the class name.

  2. Private by plain name

    What candidates pick
    Choose the value for obj.__balance printed outside the class.
    Why it looks right
    The attribute was set in __init__.
    Why it's wrong
    Name mangling stores it as _ClassName__balance, so the plain name raises AttributeError.

    Clue to watch for The attribute name starts with two underscores.

  3. Depth-first diamond

    What candidates pick
    Pick the base class method in a diamond where the right middle class overrides it.
    Why it looks right
    The left branch reaches the base first.
    Why it's wrong
    The method resolution order visits both middle classes before their shared base.

    Clue to watch for Two parents share one ancestor.

  4. Overloaded constructors

    What candidates pick
    Expect the first __init__ to run when called with no arguments.
    Why it looks right
    Other languages overload by signature.
    Why it's wrong
    The second definition replaces the first, so only the later __init__ exists.

    Clue to watch for One class body defines __init__ twice.

  5. Parent constructor runs anyway

    What candidates pick
    Assume a parent attribute exists after a subclass defines its own __init__.
    Why it looks right
    Inheritance seems to include initialization.
    Why it's wrong
    A subclass __init__ replaces the parent's unless it calls super().__init__().

    Clue to watch for The subclass constructor contains no super() call.

  6. Object __dict__ is complete

    What candidates pick
    Include class variables in print(obj.__dict__).
    Why it looks right
    The object can read them.
    Why it's wrong
    Class variables are stored in the class __dict__, not the instance's.

    Clue to watch for The snippet prints __dict__ of an instance.

  7. isinstance is exact

    What candidates pick
    Answer False for isinstance(child, Parent).
    Why it looks right
    The object was created from the child class.
    Why it's wrong
    isinstance() returns True for instances of subclasses as well.

    Clue to watch for The second argument is a base class.

Answer framework

For a Section 4 snippet, use these steps to trace attribute lookup and object creation the way PCAP-31-03 expects.

  1. Map every class with its base classes, write the method resolution order for each, and mark which classes define each attribute or method.
  2. For each object creation, decide which __init__ runs, what arguments it receives and whether it calls the parent constructor.
  3. For each assignment, decide whether it targets the class or an instance, remembering that writes through an instance create instance variables.
  4. For each read, search the instance __dict__, then the class, then the bases in method resolution order, applying name mangling for double underscores.
  5. For introspection and comparison calls, apply the exact rule: hasattr() follows lookup, __bases__ lists direct parents, isinstance() honors subclasses, is checks identity.

Practice question

What does this code print? class A: v = 1 class B(A): pass b = B(); b.v = 5; B.v = 3; print(A.v, B.v, b.v, 'v' in B.__dict__)

Choose one answer

Want more like this? The PCAP practice test has questions for every domain.

Checklist

0 of 14

Tick each one you can do without notes. Your ticks stay in this browser.

Study strategy

Give this section the largest share of your study time, since it is about a third of the exam. Begin with attribute storage. Create a class with a class variable, make two objects, assign to the variable through one object and through the class, and print both objects and their __dict__ values after each step. Repeat with a double-underscore attribute and find its mangled name in __dict__.

Next, build hierarchies. Write a base class, two subclasses that override a method and __str__(), and loop over a list of mixed objects to see polymorphism. Then build a diamond: one base, two middle classes, one class inheriting from both. Print the __mro__ attribute and move the method between classes to see which version wins.

Then focus on constructors. Write a subclass __init__ without super() and observe the missing attribute, then fix it. Define __init__ twice and confirm that only the last survives.

Finally, practice introspection with hasattr(), __name__, __module__ and __bases__, and test isinstance() and is on related objects. Write your own tracing questions and swap them with a study partner if you can.

FAQ

How much of PCAP is Section 4, Object-Oriented Programming?

Object-Oriented Programming is 34% of PCAP-31-03, the largest section, and the official syllabus assigns it 12 of the 40 exam items.

Does obj.count += 1 update the class variable?

No. It reads the class value, then writes the result to a new instance variable on obj. Use ClassName.count += 1 to change the shared value.

Are double-underscore attributes truly private?

No. Python mangles them to _ClassName__name, which blocks accidental access by the plain name but still allows deliberate access.

How does Python choose a method in multiple inheritance?

It follows the method resolution order, which you can inspect with ClassName.__mro__. Bases are searched left to right, and a shared ancestor comes after all its subclasses.

Can a Python class have several constructors?

Not by overloading. Only the last __init__ definition remains, so flexible construction uses default or optional parameters.

What does __bases__ return for a class with no explicit parent?

The tuple (object,), because every Python 3 class inherits from object.

Does hasattr() see class variables through an object?

Yes. hasattr() succeeds whenever normal attribute lookup succeeds, which includes class variables and inherited attributes.

Test your Domain 4 knowledge

You have covered classes, instance and class variables, name mangling, introspection, inheritance and constructors. Test whether you can trace these class snippets accurately under time pressure.

Keep going