PCAP-31-03: OOP

Try 10 focused PCAP-31-03 questions on OOP, with explanations, then continue with IT Mastery.

On this page

Open the matching IT Mastery practice page for timed mocks, topic drills, progress tracking, explanations, and full practice.

Try PCAP-31-03 on Web View full PCAP-31-03 practice page

Topic snapshot

FieldDetail
Exam routePCAP-31-03
Topic areaSection 4: Object-Oriented Programming
Blueprint weight34%
Page purposeFocused sample questions before returning to mixed practice

How to use this topic drill

Use this page to isolate Section 4: Object-Oriented Programming for PCAP-31-03. Work through the 10 questions first, then review the explanations and return to mixed practice in IT Mastery.

PassWhat to doWhat to record
First attemptAnswer without checking the explanation first.The fact, rule, calculation, or judgment point that controlled your answer.
ReviewRead the explanation even when you were correct.Why the best answer is stronger than the closest distractor.
RepairRepeat only missed or uncertain items after a short break.The pattern behind misses, not the answer letter.
TransferReturn to mixed practice once the topic feels stable.Whether the same skill holds up when the topic is no longer obvious.

Blueprint context: 34% of the practice outline. A focused topic score can overstate readiness if you recognize the pattern too quickly, so use it as repair work before timed mixed sets.

Sample questions

These questions are original IT Mastery practice items aligned to this topic area. They are designed for self-assessment and are not official exam questions.

Question 1

Topic: Section 4: Object-Oriented Programming

A developer inspects which attributes are stored directly on an instance versus on its class. What is printed by this code?

class Sample:
    level = 1
    __flag = "class"

    def __init__(self):
        self.name = "obj"
        self.__flag = "inst"

s = Sample()
s.level = 3

print(sorted(s.__dict__.keys()))
print(sorted(k for k in Sample.__dict__.keys() if "flag" in k or k == "level"))
print(s.__dict__.get("level"), Sample.__dict__["level"])
print(s.__dict__["_Sample__flag"], Sample.__dict__["_Sample__flag"])

Options:

  • A. [’__flag’, ’level’, ’name’] [’__flag’, ’level’] 3 1 inst class

  • B. [’_Sample__flag’, ’name’] [’_Sample__flag’, ’level’] 1 1 inst class

  • C. [’_Sample__flag’, ’level’, ’name’] [’_Sample__flag’, ’level’] 3 1 class inst

  • D. [’_Sample__flag’, ’level’, ’name’] [’_Sample__flag’, ’level’] 3 1 inst class

Best answer: D

Explanation: An instance __dict__ contains only attributes stored on that object, while a class __dict__ contains class-level attributes. Here, s.level = 3 creates an instance attribute named level, and __flag is name-mangled to _Sample__flag in both namespaces.

The key idea is that instance and class namespaces are separate. Sample.__dict__ stores class attributes such as level and the mangled private name _Sample__flag. The instance s.__dict__ stores only names assigned directly to s, which are name, _Sample__flag, and later level after s.level = 3.

Name mangling changes __flag inside Sample to _Sample__flag, so neither dictionary uses the plain name __flag. Because s.level = 3 assigns to the instance, s.__dict__ gets its own level entry with value 3, while the class still keeps level as 1.

The common mistake is to forget either name mangling or that assigning through an instance can create a separate instance attribute.

  • Unmangled private name fails because __flag is stored as _Sample__flag, not as __flag.
  • Missing instance level fails because s.level = 3 adds level to s.__dict__ and shadows the class attribute.
  • Swapped private values fails because the instance private attribute is "inst", while the class private attribute remains "class".

Question 2

Topic: Section 4: Object-Oriented Programming

A package contains pkg/device.py:

class Sensor:
    __version = 3

    def __init__(self, sid):
        self.__id = sid

In app.py, the code runs:

from pkg.device import Sensor
s = Sensor("A7")

Which statement prints the private instance ID and the private class version without raising an exception?

Options:

  • A. print(s._Sensor__id, Sensor._Sensor__version)

  • B. print(s._Sensor__id, Sensor.__version)

  • C. print(Sensor._Sensor__id, Sensor._Sensor__version)

  • D. print(s.__id, Sensor.__version)

Best answer: A

Explanation: Python name-mangles double-underscore attributes by prefixing them with _ClassName. Here, __id becomes _Sensor__id on the instance, and __version becomes _Sensor__version on the class.

At PCAP depth, a name like __id inside class Sensor is not truly inaccessible; Python rewrites it to a mangled form to reduce accidental access from outside the class or from subclasses. The rewritten name uses the class name as a prefix.

  • self.__id becomes self._Sensor__id
  • __version inside Sensor becomes Sensor._Sensor__version

So the instance value must be read from s._Sensor__id, while the class value must be read from Sensor._Sensor__version. Using the original double-underscore names outside the class body does not match the stored attribute names. The closest distractor is the one that correctly mangles the instance attribute but still tries to access the class attribute with its unmangled name.

  • The option using s.__id and Sensor.__version fails because unmangled double-underscore names are not the stored attribute names outside the class body.
  • The option using Sensor._Sensor__id fails because __id is an instance attribute, not a class attribute.
  • The option using s._Sensor__id and Sensor.__version gets the instance part right but leaves the class attribute unmangled.

Question 3

Topic: Section 4: Object-Oriented Programming

A developer wants to verify the final state after updating an attribute through one instance. Which two statements are true after this code runs? Select TWO.

class Account:
    fee = 10

    def __init__(self, owner):
        self.owner = owner

a = Account("Ann")
b = Account("Ben")

a.fee += 5

Options:

  • A. Reading b.fee still returns 10.

  • B. b now has its own instance attribute fee.

  • C. Account.fee is changed to 15.

  • D. Both a and b now store fee in their instance __dict__.

  • E. a now has its own instance attribute fee equal to 15.

Correct answers: A and E

Explanation: Assigning through one instance does not change the class variable here. a.fee += 5 creates or updates a’s own fee, while b continues to use the class attribute because it has no instance attribute of that name.

This tests the difference between class variables and instance variables. When Python evaluates a.fee += 5, it first looks up a.fee. Since a has no instance attribute named fee, Python uses the class attribute Account.fee, which is 10. It then adds 5 and assigns the result back to a.fee.

  • Lookup finds 10 on the class.
  • 10 + 5 produces 15.
  • Assignment writes fee into a’s instance namespace.
  • b still has no instance fee, so it keeps reading the class value 10.

The common mistake is assuming that changing an attribute through one instance automatically changes the class attribute for every object.

  • Class update confusion fails because the assignment target is a.fee, not Account.fee.
  • Second instance changed fails because b was never assigned a fee attribute.
  • Shared instance storage fails because only a gets a new instance-level fee; b still relies on class lookup.

Question 4

Topic: Section 4: Object-Oriented Programming

A developer is debugging unexpected shared state. Each Session object should keep its own mutable list of events.

class Session:
    events = []

    def __init__(self, user):
        self.user = user

    def add(self, name):
        self.events.append(name)

After creating a = Session('ann') and b = Session('bob'), calling a.add('login') also makes b.events contain 'login'. Which TWO changes independently fix this bug?

Options:

  • A. In __init__, assign self.__class__.events = [].

  • B. Change add() to use Session.events.append(name).

  • C. Replace __init__ with def __init__(self, user, events=[]): self.user = user; self.events = events.

  • D. Replace __init__ with def __init__(self, user, events=None): self.user = user; self.events = [] if events is None else list(events).

  • E. Replace events = [] with events = list().

  • F. Add self.events = [] inside __init__.

Correct answers: D and F

Explanation: events = [] in the class body creates one mutable list shared by all instances. A correct fix is to give each object its own self.events list during construction, either directly or with a None-sentinel constructor pattern.

This bug comes from the difference between class variables and instance variables. Python looks for self.events on the instance first; if it is not there, it falls back to the class. In the original code, events exists only on the class, so every Session object appends to the same shared list.

A safe repair is to assign an instance attribute during __init__, such as self.events = []. The longer events=None version is also safe because it creates a new list for each constructor call and can copy any provided starting values with list(events).

Changing only the class attribute syntax does not help, and using a mutable default argument would recreate the same sharing problem in the constructor.

  • Session.events.append(name) makes the shared class list explicit, so all instances still use one list.
  • events = list() is just another way to create one class-level list object.
  • self.__class__.events = [] rebinds the class attribute for every instance, so it remains shared.
  • events=[] as a default parameter creates one shared list at function definition time.

Question 5

Topic: Section 4: Object-Oriented Programming

A developer adds print statements to trace initialization in a small class hierarchy. What is the exact output of this program?

class Part:
    def __init__(self):
        print("part", end="-")

class Device:
    def __init__(self):
        print("device", end="-")
        self.part = Part()

class Phone(Device):
    def __init__(self):
        print("phone", end="-")
        super().__init__()
        print("done", end="")

Phone()

Options:

  • A. phone-part-device-done

  • B. phone-device-done-part

  • C. device-phone-part-done

  • D. phone-device-part-done

Best answer: D

Explanation: Object creation begins with the constructor of the class being instantiated, so Phone.__init__ runs first. The parent constructor runs only when super().__init__() is reached, and that parent constructor creates the Part object before execution returns to Phone.__init__.

Python starts initialization with the __init__ method of the class you instantiate. Here, Phone() calls Phone.__init__, so phone- is printed first. When super().__init__() executes, control moves to Device.__init__, which prints device- and then creates a Part instance. Creating that object immediately runs Part.__init__, printing part-. After Device.__init__ finishes, execution returns to Phone.__init__, which prints done.

  • Phone.__init__
  • Device.__init__ via super()
  • Part.__init__ via Part()
  • back to Phone.__init__

The key takeaway is that inherited and nested constructors run only where the code explicitly calls them.

  • The option starting with device is wrong because instantiating Phone enters Phone.__init__ first.
  • The option printing part before device is wrong because Device.__init__ prints before it creates the Part object.
  • The option placing done before part is wrong because the final print happens only after super().__init__() fully completes.

Question 6

Topic: Section 4: Object-Oriented Programming

A package contains these files:

devices/
    __init__.py
    base.py
    sensors.py
main.py
# devices/base.py
class Device:
    def __init__(self, name):
        self.name = name.strip()
        self._events = []

    def status(self):
        return f"{self.name}:{len(self._events)}"

# devices/sensors.py
from devices.base import Device

class Sensor(Device):
    def __init__(self, name, unit):
        self.unit = unit

# main.py
from devices.sensors import Sensor

s = Sensor(" temp ", "C")
print(s.status())

Running main.py raises an AttributeError. Which change best fixes the code while preserving initialization expected by the inherited status() method?

Options:

  • A. Set self.name = name and self.unit = unit in Sensor.__init__.

  • B. Replace from devices.base import Device with import devices.base.

  • C. Move Device into devices/__init__.py and import it from there.

  • D. Call super().__init__(name) in Sensor.__init__, then set self.unit.

Best answer: D

Explanation: When a subclass defines its own __init__, Python does not automatically run the parent class constructor. Here, status() depends on attributes created in Device.__init__, so Sensor must call super().__init__(name) before adding its own state.

The core concept is superclass initialization. If a subclass overrides __init__, any setup done by the inherited class is skipped unless the subclass explicitly calls the parent constructor.

In this package, Device.__init__ creates both name and _events, and status() uses them. Because Sensor.__init__ only assigns unit, the Sensor instance is missing state that inherited behavior expects.

class Sensor(Device):
    def __init__(self, name, unit):
        super().__init__(name)
        self.unit = unit

That pattern preserves base-class initialization and then adds subclass-specific attributes. Import style or package layout does not fix missing object state, and manually copying only part of the base initialization is incomplete.

  • Setting only name and unit still skips _events, so inherited status() lacks required state.
  • Changing the import form affects how the class is referenced, not whether the object is initialized correctly.
  • Moving the class into __init__.py changes package organization, not constructor behavior.

Question 7

Topic: Section 4: Object-Oriented Programming

A developer is debugging a class used to store per-device settings. After calling configure(5), the value should belong only to that object and appear in the object’s __dict__, not in the class.

class Sensor:
    def __init__(self, name):
        self.name = name

    def configure(self, level):
        # replace this line
        level = level

s = Sensor("A")
s.configure(5)

Which replacement line inside configure correctly declares and initializes an instance variable?

Options:

  • A. Sensor.level = level

  • B. self.level == level

  • C. self.level = level

  • D. level = self.level

Best answer: C

Explanation: Instance variables are created by assigning to an attribute of a specific object, usually through self inside a method. Using self.level = level stores level on that Sensor instance, so each object can keep its own value.

The core idea is that an instance variable is attached to one object, not to the class itself. Inside an instance method, self refers to the current object, so self.level = level both declares and initializes the attribute for that specific instance.

After that assignment, the new attribute appears in the instance namespace, such as s.__dict__. By contrast, assigning to Sensor.level creates or updates a class variable shared through the class. Writing level = self.level tries to read an attribute instead of creating it, and self.level == level is only a comparison, not an assignment.

A good rule is: use self.attribute = value in methods when you want per-object state.

  • Sensor.level = level writes to the class, so the value is shared as a class variable rather than stored per instance.
  • level = self.level reverses the assignment and attempts to read self.level before initializing it.
  • self.level == level compares values and does not create or store any attribute.

Question 8

Topic: Section 4: Object-Oriented Programming

In a GUI toolkit, these reusable classes already exist:

class Widget: pass
class Clickable: pass
class Draggable: pass

A developer must add:

  • Label, using single inheritance from Widget only
  • IconButton, using multiple inheritance from Widget and Clickable

Which class definitions satisfy the requirement? Select TWO.

Options:

  • A. class Label(Widget, Clickable): pass

  • B. class Label(Clickable): pass

  • C. class IconButton(Widget, Draggable): pass

  • D. class Label(Widget): pass

  • E. class IconButton(Widget, Clickable): pass

  • F. class IconButton(Widget): pass

Correct answers: D and E

Explanation: Single inheritance means one base class in the class header, while multiple inheritance means two or more. Label therefore needs only Widget, and IconButton must list both Widget and Clickable.

In Python, the inheritance structure is defined in the class header inside parentheses. If a class has one parent, it uses single inheritance; if it has two or more parents, it uses multiple inheritance. Here, Label must inherit from only Widget, so its header should contain exactly that one base class. IconButton must combine behavior from Widget and Clickable, so both base classes must appear in its header.

Any definition that adds an extra parent to Label, omits Clickable from IconButton, or replaces a required base with another class does not match the stated hierarchy. The key is to check both the number of parents and whether they are the correct parents.

  • The option making Label inherit from Widget and Clickable fails because Label was required to use single inheritance only.
  • The option making IconButton inherit only from Widget fails because it omits Clickable.
  • The option making IconButton inherit from Widget and Draggable uses multiple inheritance, but with the wrong second base class.
  • The option making Label inherit only from Clickable has single inheritance, but from the wrong parent.

Question 9

Topic: Section 4: Object-Oriented Programming

A developer is adding diagnostic logging and must record the runtime class name of pet as the string Dog.

class Animal:
    pass

class Dog(Animal):
    pass

pet = Dog()

Which TWO expressions return the string Dog? Select TWO.

Options:

  • A. pet.__class__.__name__

  • B. type(pet).__name__

  • C. type(Dog).__name__

  • D. Dog.__module__

  • E. Dog.__bases__[0].__name__

  • F. pet.__name__

Correct answers: A and B

Explanation: A class object stores its class name in __name__. For an instance like pet, you must first get its class with __class__ or type(), then read __name__.

In Python, __name__ is metadata stored on the class object itself. That means you do not normally read a class name directly from an instance; you first obtain the instance’s class, then access that class object’s __name__ attribute. Here, both pet.__class__ and type(pet) refer to the Dog class, so appending .__name__ returns 'Dog'.

  • __module__ identifies where the class was defined.
  • __bases__ lists parent classes.
  • type(Dog) refers to the metaclass of Dog, which is type.

The key idea is to apply __name__ to the correct class object, not to unrelated metadata.

  • Instance attribute mix-up the option using pet.__name__ fails because ordinary instances do not expose their class name that way.
  • Wrong metadata the option using Dog.__module__ returns the module name, not the class name.
  • Superclass lookup the option using Dog.__bases__[0].__name__ returns Animal, because it inspects the first base class.
  • Metaclass confusion the option using type(Dog).__name__ returns type, because classes are instances of the metaclass type.

Question 10

Topic: Section 4: Object-Oriented Programming

A team is writing a Book class. edition must be a class variable shared by all objects, and each object must store its own title and pages. After calling Book("PCAP Guide", 250), the instance __dict__ should be {'title': 'PCAP Guide', 'pages': 250}.

Which constructor signature should replace the comment?

class Book:
    edition = "3rd"

    # replace this line

Options:

  • A. def __init__(self, title, pages):

  • B. def __init__(self):

  • C. def __init__(title, pages):

  • D. def __init__(self, edition, title, pages):

Best answer: A

Explanation: A constructor for an instance must include self and parameters only for the data each object needs at creation time. Here, the per-object data is title and pages, while edition is shared by the class and should not be passed to every new object.

__init__ is the initializer for a new instance, so its signature should match the values required to build that instance. In this scenario, each Book object needs its own title and pages, because those are the values that should appear in the instance __dict__. Python also passes the new object automatically to instance methods, so self must be the first parameter.

Because edition is shared across all Book objects, it belongs as a class variable, not as a required constructor argument. A signature with self, title, and pages matches both the call Book("PCAP Guide", 250) and the desired initialized state. The closest wrong idea is requiring edition, which incorrectly turns shared class data into per-instance input.

  • The version without self omits the instance reference that Python supplies automatically to object methods.
  • The version requiring edition wrongly treats shared class data as if every object must receive it during construction.
  • The version with only self cannot accept the two values passed when creating the object.

Continue with full practice

Use the PCAP-31-03 Practice Test page for the full IT Mastery route, mixed-topic practice, timed mock exams, explanations, and web/mobile app access.

Try PCAP-31-03 on Web View PCAP-31-03 Practice Test

Free review resource

Read the PCAP-31-03 Cheat Sheet on Tech Exam Lexicon, then return to IT Mastery for timed practice.

Revised on Thursday, May 14, 2026