Editor

PCAP practice test

Practise for the PCAP exam with fresh questions in its style, timed, then review every answer.

40 questions · 65 minutes · 70% to pass. Scored in your browser, then every answer is explained.

What it covers

  • Modules and packages
  • Exceptions
  • Strings
  • Object-oriented programming
  • Generators, closures and more
Every question with its answer
  1. Question 1 · Modules and packages

    What is the output of this code?

    import math as m
    from math import floor, ceil
    
    print(floor(-2.5), ceil(-2.5), m.trunc(-2.5))
    • A-2 -3 -2
    • B-3 -2 -2
    • C-3 -2 -3
    • D-2 -2 -3

    floor() rounds down to -3 and ceil() rounds up to -2. trunc() drops the fractional part, which for a negative number moves toward zero, so it also gives -2. The alias m and the from import reach the same module.

    Run this code in the editor
  2. Question 2 · Modules and packages

    Which two statements about importing are true? Select two.

    • Aimport math as m makes both m and math available.
    • BAfter import math, the name sqrt on its own is not defined.
    • CA module's code runs again each time the same program imports it.
    • DThe code in a package's __init__.py runs when the package is first imported.
    • EThe __pycache__ folder holds copies of each module's source code.

    A plain import math binds only the name math, so you write math.sqrt, and importing a package runs its __init__.py. An alias binds only m, a module runs once per program (later imports reuse it), and __pycache__ holds compiled bytecode.

  3. Question 3 · Modules and packages

    This code is saved as tools.py. What appears when another program runs import tools?

    def helper():
        return 42
    
    print("loading tools")
    
    if __name__ == "__main__":
        print("testing helper:", helper())
    • Aloading tools, then testing helper: 42
    • BNothing, because importing only defines names
    • COnly loading tools
    • DOnly testing helper: 42

    Importing runs the module's top-level code, so the first print runs. Inside an imported module __name__ is "tools", not "__main__", so the guarded test runs only when the file itself is the program being run.

    Run this code in the editor
  4. Question 4 · Modules and packages

    Which call can never return the value 10?

    • Arandom.randint(1, 10)
    • Brandom.choice([5, 10])
    • Crandom.sample(range(1, 11), 1)[0]
    • Drandom.randrange(1, 10)

    randrange(1, 10) picks from range(1, 10), which stops before 10. randint(1, 10) includes both ends, and the other two pick from collections that contain 10.

  5. Question 5 · Modules and packages

    Which function returns the Python version as a tuple of strings, such as ('3', '13', '5')?

    • Aplatform.python_version_tuple()
    • Bplatform.python_version()
    • Cplatform.version()
    • Dplatform.python_implementation()

    python_version_tuple() returns the major, minor and patch numbers as a tuple of strings. python_version() returns one string such as '3.13.5', version() describes the operating system, and python_implementation() names the interpreter, such as 'CPython'.

  6. Question 6 · Modules and packages

    What is the output of this code?

    import math
    
    print(math.factorial(4), math.hypot(6, 8), math.sqrt(16), 7 // 2.0)
    • A24 10.0 4.0 3.0
    • B24 10 4 3
    • C24 10.0 4 3
    • D24 10.0 4.0 3.5

    factorial() returns an int, while hypot() and sqrt() always return floats, even for whole results. Floor division with a float operand also gives a float, so 7 // 2.0 is 3.0, not 3.5.

    Run this code in the editor
  7. Question 7 · Exceptions

    What is the output of this code?

    def load(value):
        try:
            number = int(value)
        except ValueError:
            print("bad", end=" ")
            return -1
        else:
            print("ok", end=" ")
            return number
        finally:
            print("done", end=" ")
    
    print(load("7"), load("x"))
    • Aok done 7 bad done -1
    • Bok done bad done 7 -1
    • Cok bad 7 -1
    • Dok done bad 7 -1

    finally runs on the way out of the function even after return, so each call prints its branch word and then done. print evaluates both calls before it prints anything itself, so the return values 7 and -1 come last.

    Run this code in the editor
  8. Question 8 · Exceptions

    What is the output of this code?

    def check(age):
        if age < 0:
            raise ValueError("negative", age)
        return age
    
    try:
        check(-3)
    except (TypeError, ValueError) as error:
        print(error.args, len(error.args))
    • A('negative', -3) 1
    • B('negative -3',) 1
    • Cnegative -3 2
    • D('negative', -3) 2

    Every argument passed to an exception's constructor is stored in its args tuple, so it holds two items. A tuple of classes in except catches any of them, and as error names the exception object.

    Run this code in the editor
  9. Question 9 · Exceptions

    What is the output of this code?

    class AppError(Exception):
        pass
    
    class LoginError(AppError):
        def __init__(self, user):
            super().__init__(f"cannot log in: {user}")
            self.user = user
    
    try:
        raise LoginError("sam")
    except AppError as problem:
        print(type(problem).__name__, problem, problem.user)
    • AAppError cannot log in: sam sam
    • BLoginError sam sam
    • CLoginError cannot log in: sam sam
    • DLoginError ('cannot log in: sam',) sam

    except AppError also catches its subclasses, but the object is still a LoginError, so type(problem).__name__ names the subclass. Printing an exception shows the message passed to super().__init__(), and user is an ordinary attribute.

    Run this code in the editor
  10. Question 10 · Exceptions

    What is the output of this code?

    def risky():
        try:
            return 10 / 0
        except ArithmeticError:
            print("inner", end=" ")
            raise
    
    try:
        risky()
    except ZeroDivisionError:
        print("outer ZeroDivisionError")
    except ArithmeticError:
        print("outer ArithmeticError")
    • Ainner outer ZeroDivisionError
    • Binner outer ArithmeticError
    • Couter ZeroDivisionError
    • Dinner

    A bare raise inside except re-raises the same exception object, which is still a ZeroDivisionError. The caller's first matching branch is except ZeroDivisionError, even though the inner handler caught it as an ArithmeticError.

    Run this code in the editor
  11. Question 11 · Exceptions

    Which exception does this code raise?

    def set_speed(speed):
        assert 0 <= speed <= 120, "speed out of range"
        return speed
    
    print(set_speed(60), set_speed(150))
    • AValueError
    • BAssertionError
    • CRuntimeError
    • DTypeError

    assert raises AssertionError, with the text after the comma as its message, when its condition is false. Nothing is printed, because print needs both values before it runs and the second call fails.

    Run this code in the editor
  12. Question 12 · Exceptions

    Which two statements about handling exceptions are true? Select two.

    • AAn else branch after except runs only when the try block raised nothing.
    • Bfinally runs only when the try block raised an exception.
    • CA bare raise outside any except block re-raises the last handled error.
    • DA bare except: must be the last of the except branches.
    • ECode that calls a function cannot catch an exception raised inside it.

    else runs when the try block finished without an exception, and a bare except: placed before other branches is a SyntaxError. finally always runs, a bare raise with no active exception raises RuntimeError, and exceptions travel up to the caller.

  13. Question 13 · Exceptions

    What is the output of this code?

    errors = [KeyError("k"), IndexError(5), ZeroDivisionError(), ValueError("v")]
    labels = []
    for error in errors:
        try:
            raise error
        except LookupError:
            labels.append("L")
        except ArithmeticError:
            labels.append("A")
        except Exception:
            labels.append("E")
    print("".join(labels))
    • ALEAE
    • BLLAE
    • CLLEE
    • DELAE

    KeyError and IndexError are both subclasses of LookupError, ZeroDivisionError is a subclass of ArithmeticError, and ValueError matches only the general Exception branch. raise accepts an exception object that already exists.

    Run this code in the editor
  14. Question 14 · Exceptions

    What is the output of this code?

    class StockError(Exception):
        def __init__(self, item, left):
            super().__init__(item)
            self.left = left
    
        def __str__(self):
            return f"{self.args[0]} has {self.left} left"
    
    try:
        raise StockError("pens", 2)
    except Exception as error:
        print(error, error.args)
    • Apens has 2 left ('pens', 2)
    • B('pens', 2) ('pens', 2)
    • Cpens has 2 left ('pens',)
    • Dpens ('pens',)

    args holds what was passed to Exception.__init__(), which here is only item, so it is the one-item tuple ('pens',). print uses the overridden __str__, which builds the message from args[0] and the extra attribute.

    Run this code in the editor
  15. Question 15 · Strings

    What is the output of this code?

    print(ord("A"), chr(ord("a") + 2), ord("b") - ord("a"))
    • A65 b 1
    • B97 c 1
    • C65 c 2
    • D65 c 1

    ord() gives a character's code point and chr() turns a code point back into a character. Uppercase 'A' is 65 and lowercase letters run in order from 97, so 97 + 2 is 'c' and 'b' is 1 after 'a'.

    Run this code in the editor
  16. Question 16 · Strings

    What is the output of this code?

    words = ["pear", "Apple", "apple", "banana"]
    print(sorted(words), "Zebra" < "apple", "10" < "9")
    • A['Apple', 'apple', 'banana', 'pear'] True True
    • B['apple', 'Apple', 'banana', 'pear'] False False
    • C['Apple', 'apple', 'banana', 'pear'] False True
    • D['Apple', 'apple', 'banana', 'pear'] True False

    Strings compare character by character by code point, and every uppercase letter (65-90) comes before every lowercase one (97-122), so 'Zebra' < 'apple'. Digits compare as characters too, and '1' comes before '9', so '10' < '9' is true.

    Run this code in the editor
  17. Question 17 · Strings

    What is the output of this code?

    line = "red,,green,blue"
    parts = line.split(",")
    print(len(parts), "-".join(parts), " a b ".split())
    • A3 red-green-blue ['a', 'b']
    • B4 red--green-blue ['', 'a', 'b', '']
    • C4 red--green-blue ['a', 'b']
    • D3 red-green-blue ['', 'a', 'b', '']

    With a separator, split(",") keeps the empty string between the two commas, giving 4 parts, and join() puts a hyphen between every pair. With no argument, split() splits on runs of whitespace and drops empty strings.

    Run this code in the editor
  18. Question 18 · Strings

    What is the output of this code?

    path = "docs/2026/report.final.txt"
    print(path.find("/"), path.rfind("."), path.find("draft"), path[path.rfind("/") + 1:])
    • A4 16 -1 report.final.txt
    • B5 23 -1 report.final.txt
    • C4 22 -1 /report.final.txt
    • D4 22 -1 report.final.txt

    find() returns the index of the first match and rfind() the index of the last, both counting from 0, and both return -1 when there is no match. Slicing from one past the last / leaves the file name.

    Run this code in the editor
  19. Question 19 · Strings

    This code prints -1 first. Which exception does it raise next?

    code = "PY-2026"
    print(code.find("X"))
    print(code.index("X"))
    • AIndexError
    • BValueError
    • CKeyError
    • DTypeError

    find() and index() search the same way, but when the text is missing find() returns -1 while index() raises ValueError: substring not found. IndexError is for a position outside a sequence.

    Run this code in the editor
  20. Question 20 · Strings

    Which two statements about text encoding are true? Select two.

    • AUTF-8 stores every character in exactly one byte.
    • BA code point is the number that Unicode assigns to a character.
    • CASCII defines 256 characters.
    • Dlen() of a string counts bytes, not characters.
    • EUTF-8 stores each ASCII character as one byte with its ASCII value.

    Unicode gives every character a number, its code point, and UTF-8 encodes the 128 ASCII characters as single bytes with their ASCII values, so plain ASCII text is valid UTF-8. Other characters take 2 to 4 bytes, and len() counts characters.

  21. Question 21 · Strings

    What is the output of this code?

    word = "abcdefgh"
    print(word[5:2], word[::-2], word[-3:1:-1], sep="|")
    • A|hfdb|fedc
    • B|geca|fedc
    • C|hfdb|fedcb
    • Dfed|hfdb|fedc

    With a positive step, a slice whose start is after its stop is empty, so word[5:2] is ''. A step of -2 from the end takes h, f, d and b, and word[-3:1:-1] runs backwards from index 5 down to, but not including, index 1.

    Run this code in the editor
  22. Question 22 · Strings

    What is the output of this code?

    values = ["2026", "v2", "abc", "4.5", ""]
    print([text for text in values if text.isalnum()], "4.5".isdigit(), "".isalpha())
    • A['2026', 'v2', 'abc', ''] False True
    • B['2026', 'v2', 'abc'] True False
    • C['2026', 'v2', 'abc'] False False
    • D['v2'] False False

    isalnum() is true when every character is a letter or a digit, so the dot in '4.5' fails it, and isdigit() rejects the dot too. isalnum(), isalpha() and isdigit() all return False for an empty string.

    Run this code in the editor
  23. Question 23 · Object-oriented programming

    What is the output of this code?

    class Counter:
        total = 0
    
        def __init__(self):
            Counter.total += 1
            self.id = Counter.total
    
    a = Counter()
    b = Counter()
    print(a.id, b.id, a.total, Counter.total)
    • A1 2 1 2
    • B1 1 1 2
    • C0 1 2 2
    • D1 2 2 2

    total is a class variable shared by all instances, and each constructor call adds 1 to it, so it ends at 2. a.total finds no instance attribute called total and reads the class one, while id is stored on each object.

    Run this code in the editor
  24. Question 24 · Object-oriented programming

    This code prints North High first. Which exception does it raise next?

    class Person:
        def __init__(self, name):
            self.name = name
    
    class Student(Person):
        def __init__(self, name, school):
            self.school = school
    
    pupil = Student("Lea", "North High")
    print(pupil.school)
    print(pupil.name)
    • AAttributeError
    • BTypeError
    • CNameError
    • DKeyError

    Student defines its own __init__, which replaces the inherited one, and it never calls super().__init__(name). So self.name is never set, and reading it raises AttributeError.

    Run this code in the editor
  25. Question 25 · Object-oriented programming

    What is the output of this code?

    class Book:
        shelf = "A"
    
        def __init__(self, title):
            self.title = title
    
    novel = Book("Dune")
    novel.pages = 412
    print(novel.__dict__, "shelf" in Book.__dict__, "shelf" in novel.__dict__)
    • A{'title': 'Dune', 'pages': 412, 'shelf': 'A'} True True
    • B{'title': 'Dune'} True False
    • C{'title': 'Dune', 'pages': 412} True False
    • D{'title': 'Dune', 'pages': 412} False False

    An object's __dict__ holds only its own instance attributes, including ones added after it was created, such as pages. The class variable shelf lives in the class's __dict__, and the object reaches it through the class.

    Run this code in the editor
  26. Question 26 · Object-oriented programming

    Which exception does this code raise?

    class Greeter:
        def hello():
            return "hi"
    
    greeter = Greeter()
    print(greeter.hello())
    • AAttributeError
    • BTypeError
    • CNameError
    • DSyntaxError

    Calling a method through an object passes the object as the first argument. hello has no parameter to receive it, so Python raises TypeError: it takes 0 positional arguments but 1 was given. The fix is def hello(self):.

    Run this code in the editor
  27. Question 27 · Object-oriented programming

    What is the output of this code?

    class Vault:
        def __init__(self):
            self.__code = 1234
            self._hint = "year"
    
    vault = Vault()
    print(list(vault.__dict__))
    • A['_Vault__code', '_hint']
    • B['__code', '_hint']
    • C['_hint']
    • D['_Vault__code', '_Vault_hint']

    A name with two leading underscores inside a class is renamed to _ClassName__name, so __code is stored as _Vault__code. One leading underscore is only a convention and changes nothing, and private attributes still appear in __dict__.

    Run this code in the editor
  28. Question 28 · Object-oriented programming

    What is the output of this code?

    class Animal:
        def __init__(self, name):
            self.name = name
    
        def speak(self):
            return "..."
    
        def intro(self):
            return f"{self.name} says {self.speak()}"
    
    class Dog(Animal):
        def speak(self):
            return "woof"
    
    print(Animal("Generic").intro(), "|", Dog("Rex").intro())
    • AGeneric says ... | Rex says ...
    • BGeneric says woof | Rex says woof
    • CGeneric says ... | Rex says woof
    • D... | woof

    intro is inherited, but self.speak() is looked up on the actual object, and for a Dog that finds the overriding speak. The same inherited method gives a different result for each class: that is polymorphism.

    Run this code in the editor
  29. Question 29 · Object-oriented programming

    What is the output of this code?

    class Base:
        def who(self):
            return "Base"
    
    class Left(Base):
        def who(self):
            return "Left>" + super().who()
    
    class Right(Base):
        def who(self):
            return "Right>" + super().who()
    
    class Child(Left, Right):
        pass
    
    print(Child().who())
    • ALeft>Base
    • BLeft>Right>Base
    • CLeft>Base>Right>Base
    • DRight>Left>Base

    super() follows the method resolution order of the object's class, Child, Left, Right, Base, object, not the parent named in the class statement. So super() in Left reaches Right, and Base runs once, at the end.

    Run this code in the editor
  30. Question 30 · Object-oriented programming

    What is the output of this code?

    class Shape:
        pass
    
    class Square(Shape):
        pass
    
    s = Square()
    t = Square()
    print(isinstance(s, Shape), issubclass(Shape, Square), s is t, type(s) is type(t))
    • AFalse False False True
    • BTrue True False True
    • CTrue False True True
    • DTrue False False True

    A Square object is also an instance of its superclass Shape, but Shape is not a subclass of Square. is compares identity: s and t are two different objects, while their type is the same class object.

    Run this code in the editor
  31. Question 31 · Object-oriented programming

    What is the output of this code?

    class Money:
        def __init__(self, amount):
            self.amount = amount
    
        def __add__(self, other):
            return Money(self.amount + other.amount)
    
        def __str__(self):
            return f"${self.amount}"
    
    total = Money(5) + Money(7)
    print(total, str(total) == "$12", total.amount * 2)
    • A$12 True $24
    • B12 True 24
    • C$12 False 24
    • D$12 True 24

    + between two Money objects calls __add__, which returns a new Money holding 12. print() and str() both use __str__, so they agree, and total.amount * 2 is ordinary int arithmetic.

    Run this code in the editor
  32. Question 32 · Object-oriented programming

    Which two expressions are True for these classes? Select two.

    class Vehicle:
        wheels = 4
    
    class Bike(Vehicle):
        def __init__(self):
            self.wheels = 2
    • Ahasattr(Bike, 'wheels')
    • BBike.__bases__ == (object,)
    • CBike().wheels == Vehicle.wheels
    • DBike.__name__ == 'Vehicle'
    • E'wheels' in Bike().__dict__

    hasattr() on the class finds wheels inherited from Vehicle, and the constructor stores a separate wheels in each Bike object's __dict__. Bike.__bases__ is (Vehicle,), a Bike object has 2 wheels, not 4, and __name__ is 'Bike'.

    Run this code in the editor
  33. Question 33 · Object-oriented programming

    What is the output of this code?

    class Team:
        members = []
    
        def __init__(self, name):
            self.name = name
    
        def join(self, person):
            self.members.append(person)
    
    red = Team("red")
    blue = Team("blue")
    red.join("Ana")
    blue.join("Ben")
    print(red.members, blue.members is Team.members)
    • A['Ana'] False
    • B['Ana', 'Ben'] False
    • C['Ana', 'Ben'] True
    • D['Ana'] True

    self.members.append() does not assign to self.members: it finds the class's list and changes it, so every Team shares one list. Assigning self.members = [] in __init__ would give each team its own.

    Run this code in the editor
  34. Question 34 · Object-oriented programming

    This file is run directly. Which two statements about its classes are true? Select two.

    class Engine:
        pass
    
    class Car:
        def __init__(self):
            self.engine = Engine()
    • ACar is a subclass of Engine.
    • BCar.__module__ is '__main__'.
    • CEach Car object has its own Engine object.
    • DEngine.__bases__ is an empty tuple.
    • ECar.__name__ is 'Car()'.

    A class defined in the file being run belongs to the module __main__, and the constructor creates a new Engine for every Car, which is composition, not inheritance. A class with no listed parent still has (object,) as its __bases__.

    Run this code in the editor
  35. Question 35 · Generators, closures and more

    Which two statements about open() modes are true? Select two.

    • AMode "w" empties an existing file before writing.
    • BMode "a" writes new text at the start of the file.
    • CMode "r" creates the file if it does not exist.
    • DMode "x" replaces a file that already exists.
    • EReading in mode "rb" returns bytes objects.

    "w" truncates the file to zero length first, and binary modes read and write bytes. "a" adds to the end, "r" raises FileNotFoundError for a missing file, and "x" raises FileExistsError instead of replacing a file.

  36. Question 36 · Generators, closures and more

    Which expression gives the list [1, 4, 9]?

    numbers = [1, 2, 3]
    • Amap(lambda n: n ** 2, numbers)
    • Blist(map(lambda n: n ** 2, numbers))
    • Clist(filter(lambda n: n ** 2, numbers))
    • D[lambda n: n ** 2 for n in numbers]

    map() applies the lambda to every item but returns a lazy map object, so list() is needed to get a list. filter() keeps the items for which the lambda gives a true value, here all of them, and the comprehension builds a list of three functions.

    Run this code in the editor
  37. Question 37 · Generators, closures and more

    What is the output of this code?

    def make_counter(start):
        count = start
    
        def step():
            nonlocal count
            count += 1
            return count
    
        return step
    
    first = make_counter(10)
    second = make_counter(0)
    first()
    print(first(), second(), first())
    • A12 1 13
    • B11 1 12
    • C12 11 13
    • D11 1 11

    Each call to make_counter creates a new count that its step closure keeps alive, so the two counters are independent. first was already called once, so the next calls give 12 and 13, while second starts from 0.

    Run this code in the editor
  38. Question 38 · Generators, closures and more

    What is the output of this code?

    callbacks = []
    for n in range(3):
        callbacks.append(lambda: n * 10)
    print([callback() for callback in callbacks])
    • A[0, 10, 20]
    • B[0, 0, 0]
    • C[30, 30, 30]
    • D[20, 20, 20]

    A lambda looks up n when it is called, not when it is created. All three run after the loop, when n is 2, so each returns 20. A default argument, lambda n=n: n * 10, would capture each value.

    Run this code in the editor
  39. Question 39 · Generators, closures and more

    What is the output of this code?

    def countdown(n):
        while n > 0:
            yield n
            n -= 2
    
    steps = countdown(7)
    print(next(steps), list(steps), list(steps))
    • A7 [7, 5, 3, 1] [7, 5, 3, 1]
    • B7 [5, 3, 1] [5, 3, 1]
    • C7 [5, 3, 1] []
    • D7 [5, 3, 1, -1] []

    A generator produces each value once. next() takes 7, the first list() collects the remaining 5, 3 and 1, and the generator is then exhausted, so the second list() is empty. The loop stops before -1 because the condition is checked first.

    Run this code in the editor
  40. Question 40 · Generators, closures and more

    What is the output of this code?

    with open("notes.txt", "w") as file:
        file.write("one\ntwo\n")
        file.write("three")
    
    with open("notes.txt") as file:
        first = file.readline()
        rest = file.readlines()
    
    print(repr(first), rest)
    • A'one' ['two', 'three']
    • B'one\n' ['one\n', 'two\n', 'three']
    • C'one\n' ['two\n', 'three']
    • D'one\ntwo\nthree' []

    readline() reads one line and keeps its newline character, which repr() shows as \n. readlines() continues from where reading stopped, and the last line has no newline because none was written.

    Run this code in the editor

Next: the PCEP practice test, a free Python certification exam with a certificate anyone can check, or MCQ practice by topic.

PCEP and PCAP are certifications of the Python Institute (OpenEDG), which is not affiliated with this website and does not endorse it. These practice questions are written for this site in the style of those exams; they are not the exams’ own questions.