Free Python Institute PCAP Practice Questions: OOP
Practice 10 free Python Institute PCAP - Certified Associate Python Programmer (PCAP-31-03) questions on OOP, with answers, explanations, and the IT Mastery next step.
Try the IT Mastery web app for a richer interactive practice experience with mixed sets, timed mocks, topic drills, explanations, and progress tracking.
Topic snapshot
| Field | Detail |
|---|---|
| Practice target | Python Institute PCAP |
| Topic area | Section 4: Object-Oriented Programming |
| Blueprint weight | 34% |
| Page purpose | Focused sample questions before returning to mixed practice |
How to use this topic drill
Use this page to isolate Section 4: Object-Oriented Programming for Python Institute PCAP. Work through the 10 questions first, then review the explanations and return to mixed practice in IT Mastery.
| Pass | What to do | What to record |
|---|---|---|
| First attempt | Answer without checking the explanation first. | The fact, rule, calculation, or judgment point that controlled your answer. |
| Review | Read the explanation even when you were correct. | Why the best answer is stronger than the closest distractor. |
| Repair | Repeat only missed or uncertain items after a short break. | The pattern behind misses, not the answer letter. |
| Transfer | Return 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 are original IT Mastery practice questions aligned to this topic area. They are not official Python Institute questions, copied live-exam content, or exam dumps. Use them to preview question style and explanation depth before continuing with topic drills, mixed sets, and timed mocks in IT Mastery.
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
__flagis stored as_Sample__flag, not as__flag. - Missing instance
levelfails becauses.level = 3addsleveltos.__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.__idbecomesself._Sensor__id__versioninsideSensorbecomesSensor._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.__idandSensor.__versionfails because unmangled double-underscore names are not the stored attribute names outside the class body. - The option using
Sensor._Sensor__idfails because__idis an instance attribute, not a class attribute. - The option using
s._Sensor__idandSensor.__versiongets 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.feestill returns10.B.
bnow has its own instance attributefee.C.
Account.feeis changed to15.D. Both
aandbnow storefeein their instance__dict__.E.
anow has its own instance attributefeeequal to15.
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
10on the class. 10 + 5produces15.- Assignment writes
feeintoa’s instance namespace. bstill has no instancefee, so it keeps reading the class value10.
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, notAccount.fee. - Second instance changed fails because
bwas never assigned afeeattribute. - Shared instance storage fails because only
agets a new instance-levelfee;bstill 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__, assignself.__class__.events = [].B. Change
add()to useSession.events.append(name).C. Replace
__init__withdef __init__(self, user, events=[]): self.user = user; self.events = events.D. Replace
__init__withdef __init__(self, user, events=None): self.user = user; self.events = [] if events is None else list(events).E. Replace
events = []withevents = 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-doneB.
phone-device-done-partC.
device-phone-part-doneD.
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__viasuper()Part.__init__viaPart()- 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
deviceis wrong because instantiatingPhoneentersPhone.__init__first. - The option printing
partbeforedeviceis wrong becauseDevice.__init__prints before it creates thePartobject. - The option placing
donebeforepartis wrong because the final print happens only aftersuper().__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 = nameandself.unit = unitinSensor.__init__.B. Replace
from devices.base import Devicewithimport devices.base.C. Move
Deviceintodevices/__init__.pyand import it from there.D. Call
super().__init__(name)inSensor.__init__, then setself.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
nameandunitstill skips_events, so inheritedstatus()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__.pychanges 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 = levelB.
self.level == levelC.
self.level = levelD.
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 = levelwrites to the class, so the value is shared as a class variable rather than stored per instance.level = self.levelreverses the assignment and attempts to readself.levelbefore initializing it.self.level == levelcompares 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 fromWidgetonlyIconButton, using multiple inheritance fromWidgetandClickable
Which class definitions satisfy the requirement? Select TWO.
Options:
A.
class Label(Widget, Clickable): passB.
class Label(Clickable): passC.
class IconButton(Widget, Draggable): passD.
class Label(Widget): passE.
class IconButton(Widget, Clickable): passF.
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
Labelinherit fromWidgetandClickablefails becauseLabelwas required to use single inheritance only. - The option making
IconButtoninherit only fromWidgetfails because it omitsClickable. - The option making
IconButtoninherit fromWidgetandDraggableuses multiple inheritance, but with the wrong second base class. - The option making
Labelinherit only fromClickablehas 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 ofDog, which istype.
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__returnsAnimal, because it inspects the first base class. - Metaclass confusion the option using
type(Dog).__name__returnstype, because classes are instances of the metaclasstype.
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
selfomits the instance reference that Python supplies automatically to object methods. - The version requiring
editionwrongly treats shared class data as if every object must receive it during construction. - The version with only
selfcannot accept the two values passed when creating the object.
Continue in the web app
Use IT Mastery for interactive Python Institute PCAP practice with mixed sets, timed mocks, topic drills, explanations, and progress tracking.
Try Python Institute PCAP on Web