• Last edited on Dec 11, 2019, 7:19:03 PM by Aran-Fey

Members of Room 6 occasionally pose riddles involving lesser-known quirks of the Python language. These riddles are reproduced here.

(Click the title of a riddle to expand it.)

Regular Riddles

  1. Inequality Loop (Python 2.7 only)wim
    >>> # create x, y, z such that
    >>> x < y < z < x
    True
    
    Solution:
    >>> x = b"Z"
    >>> y = ()
    >>> z = u"A"
    >>> x < y < z < x
    True
    

    The default method of comparison between dissimilar non-number types is to compare their type names alphabetically. “str” comes before “tuple” and “tuple” comes before “unicode”, so x < y and y < z. Strings and unicode are compared by converting the 8-bit string to unicode and comparing their values alphabetically. u"A" < u"Z", so z < x.

  2. Circumvent a contextmanager’s __exit__wim
    >>> class CM:
    ...     def __enter__(self):
    ...         return self
    ...     def __exit__(self, *args):
    ...         print('hello world')
    ...
    >>> with CM() as cm:
    ...     ...
    ...     # your code
    ...
    goodbye world
    
    no patching out print or any lame hack like that
    
    Solution:
    with CM() as cm:
        def my_exit(*args):
            print('goodbye world')
    
        CM.__exit__.__code__ = my_exit.__code__
    
  3. Importing modules after deleting __builtins__.__import__wim
    $ python3 -ic 'url = "http://httpbin.org/status/402"'
    >>> del __builtins__.__import__
    >>> # your code here
    ...
    >>> requests.get(url).text
    'F**k you, pay me!'
    
    Solution:
    __builtins__.__spec__.loader.create_module(__builtins__.__spec__); import requests
    
  4. Importing modules after deleting __builtins__.__import__ and __builtins__wim
    $ python3 -ic 'url = "http://httpbin.org/status/402"'
    >>> del __builtins__.__import__, __builtins__
    >>> # your code here
    ...
    >>> requests.get(url).text
    'F**k you, pay me!'
    
    Solution:
    __builtins__ = __loader__.create_module(__loader__.find_spec('builtins')); import requests
    
  5. Importing modules after deleting __builtins__.__import__ and clearing globals()wim
    $ python3 -ic 'url = "http://httpbin.org/status/402"'
    >>> del __builtins__.__import__
    >>> globals().clear()
    >>> # your code here
    ...
    >>> requests.get(url).text
    'F**k you, pay me!'
    
    Solution:
    type = ''.__class__.__class__
    ABCMeta = type.__subclasses__(type)[0]
    abc_globals = ABCMeta.register.__globals__
    importlib_globals = abc_globals['__loader__'].get_data.__globals__
    __builtins__ = importlib_globals['sys'].modules['builtins']
    __builtins__.__spec__.loader.create_module(__builtins__.__spec__)
    import requests
    
  6. Crash a function with code that’s never executedKevin
    x = 23
    
    def f():
        print(x)
        return
        #add a statement here that will make this program crash with something other than a Syntax Error
    
    f()
    
    Solution:
    x = 23
    
    def f():
        print(x)
        return
        x = 1
    
    f()
    

    One way that python determines whether a name is global/nonlocal/local is by looking for assignment statements using that name. Whether that statement ever actually executes is irrelevant.

  7. Crash a function with code that’s never executed #2Kevin
    seq = []
    def a():
        def b():
            seq.append(23)
        return b()
        #add one statement here that will make this program crash
    a()
    
    Solution:
    seq = []
    def a():
        def b():
            seq.append(23)
        return b()
        seq = None
    a()
    

    Same as riddle 1. Oops, this is redundant, isn’t it?

  8. Sometimes return None is different from no return at allAran-Fey
    # Write a function that fulfils the following criteria:
    #
    # 1. The function ends with `return None`.
    # 2. If `return None` is changed to `pass`, the function's behavior changes.
    
    • The solution is related to exceptions. Kind of.
    Solution:
    def fn():
        try:
            return 42
        finally:
            return None
    

    Since the code in the finally block is executed after the code in the try block, it overrides the previous return value.

  9. Same code, different outputKevin
    #a.py
    try:
        #some statement goes here
    except:
        print("Oh no, something went wrong!")
    
    #output:
    #Oh no, something went wrong!
    
    
    #b.py
    #some statement goes here
    #output: nothing
    
    # Determine the contents of "some statement goes here" so these programs produce the output described.
    
    Solution:
    raise SystemExit
    

    SystemExit exceptions are treated in a special way by the interpreter. If a SystemExit is raised and not caught, the process will exit without printing a stack trace.

  10. Create an instance without executing __init__Aran-Fey
    # Create an instance of Bomb without crashing the program.
    # Monkeypatching __init__ is not allowed.
    
    class Bomb:
        def __init__(self):
            raise RuntimeError('BOOM')
    
    bomb = ...  # YOUR CODE HERE
    
    assert type(bomb) is Bomb, "You didn't create a Bomb instance, did you?"
    print('You win!')
    
    Solution:
    bomb = Bomb.__new__(Bomb)
    

    When a class is called (like Bomb()), python first calls __new__ to create a new instance of that class, and then automatically calls __init__ to initialize the newly created instance. By calling __new__ directly, we can avoid __init__ being called automatically.

  11. Delete a variable without using delAran-Fey
    # Delete the `x` variable without using the `del` statement, and without
    # accessing the globals dictionary.
    
    import builtins
    del builtins.globals, builtins.locals, builtins.vars
    
    x = 5
    
    ... # your code here
    
    try:
        x
        assert False, 'x still exists'
    except NameError:
        print('You win!')
    
    • The solution is related to exceptions.
    Solution:
    try:
        raise whatever
    except Exception as x:
        pass
    

    In newer python versions, the except clause automatically deletes the name the exception was bound to.

  12. Prevent an exception from being raisedKevin
    def f():
        raise Exception("Oops")
        #add a statement here so this program does not crash
    
    f()
    
    Solution:
    def f():
        raise Exception("Oops")
        yield
    
    f()
    

    A function with a yield statement is considered a generator function. This is true even if the yield statement can never be reached. Generators do not execute their code unless you iterate over them, so just calling f() here does not raise the Exception.

  13. A curious interaction with nonlocalKevin
    # replace '???' in this program with a single statement, satisfying these constraints:
    # - the statement is not an assignment statement or augmented assignment statement.
    # - the program runs without crashing.
    # - if you delete `nonlocal x`, the program crashes.
    
    def a():
        x = 1
        def b():
            nonlocal x
            #???
        b()
    a()
    
    Solution:
    exec("x")
    

    Normal code can see names in any containing scope, but code in exec can only see the local scope and the global scope. Without nonlocal x, x won’t be in the locals() dict.

  14. Find the builtin class that returns a subclass instanceAran-Fey
    # Find a builtin class (and a corresponding argument tuple) that returns an
    # instance of a subclass when it's called. For example, if int('True') returned
    # a boolean, it would be a solution to this puzzle.
    
    ...  # your code here
    cls = ...  # your code here
    args = ...  # your code here
    
    assert isinstance(cls(*args), cls), "Calling that class didn't return an instance of that class"
    assert type(cls(*args)) is not cls, "Calling that class returned a direct instance of that class"
    print('You win!')
    
    Solution:
    class Meta(type):
        pass
    
    class ClsWithMeta(metaclass=Meta):
        pass
    
    cls = type
    args = 'foo', (ClsWithMeta,), {}
    

    In addition to the well-known single-argument form type(obj), type also has a 3-argument form: type(cls_name, base_classes, attr_dict) creates a new class.

  15. Create a recursive list in a single lineKevin
    Using a single statement, create a self-referential list a such that a is a[0].
    
    Solution:
    a = a[0] = [1]
    

    An assignment with multiple target lists evaluates the right-hand expression, and assigns it to each of the target lists from left to right. So a = a[0] = [1] is equivalent to a = [1] followed by a[0] = . Note that a = [1]; a[0] = [1] does not have the same effect because the two [1] literals resolve to two different objects.

  16. Check if a bunch of iterables are empty without using any variablesAran-Fey
    # Complete the following function, which checks whether all iterables in
    # the input list are empty. Your function may not use any variables or other
    # functions (including lambdas) or list/dict/whatever comprehensions. Recursion
    # is also forbidden.
    
    def empty_test(list_of_iterables):
        ... # your code here
    
    assert empty_test(['', (), {}, iter([])]) == True
    assert empty_test(['foo']) == False
    
    import types
    assert len(empty_test.__code__.co_varnames) == 1, "You're not allowed to use variables"
    assert not empty_test.__code__.co_names, "You're not allowed to use functions"
    assert not any(isinstance(c, types.CodeType) for c in empty_test.__code__.co_consts), "You're not allowed to use lambdas"
    
    print('You win!')
    
    • Python lets us assign values to multiple variables in a single statement. But why am I telling you this if you’re not allowed to use variables? Hmm…
    Solution:
    def empty_test(list_of_iterables):
        try:
            for () in list_of_iterables:
                pass
            return True
        except:
            return False
    

    Python allows us to assign values to multiple variables at the same time with the a, b = [1, 2] syntax. What most people don’t realize is that the left-hand side of the assignment is actually parsed like a tuple ((a, b)). And it turns out that assigning to an empty tuple is perfectly valid: () = an_empty_iterable. If the iterable isn’t empty, an exception is thrown because the number of variables doesn’t match the number of values - the function simply needs to catch that exception and return False.

  17. Create two identical variables in a single linepiRSquared
    try:
        w, x = x[0], w[1] = ...  # Your Code Here
    except:
        raise Exception('Does Not Compute.  Try Again.')
    
    assert w == x, 'Not Equal!'
    print('You win!')
    
    Solution:
    w, x = x[0], w[1] = [{}] * 2
    
  18. Arbitrary code execution with pickle.loadAran-Fey
    # Write a class that sets the global `winner` variable to `True` when unpickled.
    
    import pickle
    
    class Evil:
        ...  # YOUR CODE HERE
    
    
    data = pickle.dumps(Evil())
    del Evil
    winner = False
    pickle.loads(data)
    
    assert winner, "You didn't toggle the variable"
    print('You win!')
    
    Solution:
    class Evil:
        def __reduce__(self):
            return (exec, ("globals()['winner'] = True",))
    

    The __reduce__ method allows us to customize how instances of our class are pickled. It can return a factory function and an argument tuple that will be used to re-create the pickled instance. Using exec as the factory is an easy way to run arbitrary code.

  19. In python you can choose your friends and your familyAran-Fey
    # Kid has inherited a bad hobby from its Dad. Find a way to remove the Kid's
    # hobby - without removing it from the Dad. Hacks that raise AttributeError
    # when the hobby is accessed are forbidden. Overwriting Kid isn't allowed.
    
    class Mom:
        talent = 'singing'
    
    class Dad:
        hobby = 'drinking'
    
    class Kid(Mom, Dad):
        pass
    
    ...  # YOUR CODE HERE (not limited to 1 line)
    
    assert not hasattr(Kid, 'hobby'), "You didn't remove the Kid's hobby"
    assert 'hobby' not in vars(Kid), "Kid must not have a 'hobby' property"
    assert hasattr(Dad, 'hobby'), "Removing the dad's hobby is not allowed"
    assert vars(Dad)['hobby'] == 'drinking', "Overwriting the dad's hobby is not allowed"
    print('You win!')
    
    • You have to help Kid’s parents get a divorce.
    Solution:
    Kid.__bases__ = (Mom,)
    

    __bases__ is a tuple that contains a class’s parents - and it turns out that you can change a class’s parents by assigning to it!

  20. Another curious interaction with nonlocalAran-Fey
    # Fix the foo function without binding the name x!
    # (//docs.python.org/3/reference/executionmodel.html#binding-of-names)
    
    def foo():
        ...  # YOUR CODE HERE
        
        def bar():
            nonlocal x
            x = 3
        
        bar()
        return x
    
    assert foo() == 3
    print('You win!')
    
    • “hints”. That’s the hint. The hint is “hints”.
    Solution:
    x: int
    

    It turns out that nonlocal interacts with type hints.

  21. Create 3 dicts that are equal, but not reallywim
    Create dict instances x,y,z such that `x == y` and `y == z` but not `x == z`
    • In 2.7 it is possible with normal dicts. In 3.x the 2.7 trick no longer works, but it’s still possible using dict subclass from collections.
    Solution:
    from collections import OrderedDict
    
    x = OrderedDict(["ab", "cd"])
    y = {"a": "b", "c": "d"}
    z = OrderedDict(["cd", "ab"])
    
  22. Create 3 datetimes that are equal, but not reallywim
    Create datetime instances x,y,z such that `x == y` and `y == z` but not `x == z`
    Solution:
    No solution posted yet :(
    
  23. Create an attribute that doesn’t show up in the __dict__Aran-Fey
    # Create a class with an attribute that doesn't show up in the __dict__
    # without using descriptors or __setattr__. (Note: Technically, the solution
    # DOES create descriptors... but it's against the rules to make your own.)
    
    class ValidatorMeta(type):
        def __new__(mcs, name, bases, attrs):
            assert 'x' not in attrs, "Using descriptors isn't allowed"
            assert '__setattr__' not in attrs, "Using __setattr__ isn't allowed"
            assert '__dict__' not in attrs, "Shadowing __dict__ isn't allowed"
            return super().__new__(mcs, name, bases, attrs)
    
    class SomeClass(metaclass=ValidatorMeta):
        ...  # YOUR CODE HERE
    
    obj = SomeClass()
    obj.x = 3
    obj.y = 5
    assert vars(obj) == {'y': 5}
    
    print('You win!')
    
    Solution:
    __slots__ = ('x', '__dict__')
    

    From the docs:

    __slots__ allow us to explicitly declare data members (like properties) and deny the creation of __dict__ and __weakref__ (unless explicitly declared in __slots__ or available in a parent.)
    We create a __dict__ slot so that obj will have a dict to store y in, and by creating an x slot we ensure that x isn’t stored in the __dict__ as well.

  24. Making __slots__ disappearAran-Fey
    # Complete the following class in such a way that its instances have a
    # __dict__, yet "__dict__" can't be found in the __slots__ definition.
    # WARNING: Some weasel words were used in the previous sentence.
    # The solution is a single line of code; no semicolons and no shadowing builtins.
    
    class Cls:
        __slots__ = ...  # YOUR CODE HERE
    
    obj = Cls()
    assert hasattr(obj, '__dict__'), "You didn't create a __dict__ slot"
    assert '__dict__' not in Cls.__slots__, "__dict__ may not appear in __slots__"
    print('You win!')
    
    • The description said "__dict__" can’t be found in the __slots__ definition", but that doesn’t mean it was never there. How can you make it disappear?
    Solution:
    __slots__ = iter(['__dict__'])
    

    Any kind of iterable can be assigned to __slots__. Using an iterator allows python to see "__dict__" in the __slots__ definition, but afterwards the iterator is exhausted and empty, and the assertions pass.

  25. Hacking relative importsAran-Fey
    # Add one statement that prevents the code from crashing.
    
    ...  # your code here
    
    from .dom import minidom
    
    print('You win!')
    
    Solution:
    __package__ = 'xml'
    

    Python uses the module-level __package__ variable to resolve relative imports. By setting it to "xml", from .dom import minidom essentially turns into from xml.dom import minidom.

  26. from . import solutionAran-Fey
    # You have a package with this __init__.py file.
    # Unfortunately there is no "foo" submodule in this package,
    # so the following import fails. How can you prevent
    # an exception from being thrown without modifying the
    # import or `sys.modules`?
    
    from . import foo
    
    Solution:
    foo = 5  # any value will work
    from . import foo
    

    The trick here lies in the behavior of from x import y imports. Python first imports x. Then, if x has a y attribute, it simply returns that. Otherwise, it looks for a x.y submodule, and imports it. That’s why we can prevent python from trying to import a non-existent foo module simply by assigning a value to foo.

  27. *-imports are now bannedAran-Fey
    # Create a module named "solution" that can be imported
    # with `import solution` but not with `from solution import *`.
    
    import solution
    try:
        from solution import *
    except:
        print('You win!')
    
    • Think about what names a *-import imports, what you can do to about that, and how you can make it fail.
    Solution:
    __all__ = ["some name that doesn't exist"]
    # alternatively:
    __all__ = [b"something that's not a string"]
    # or:
    __all__ = None  # anything that can't be indexed
    

    The module-level __all__ variable determines what names a *-import imports. If a name can’t be found or isn’t a string, python throws an exception.

  28. Rewinding a list iteratorAran-Fey
    # Turn this into an endless loop.
    # Using loops or recursion is forbidden.
    
    itr = iter([1, 2, 3])
    for num in itr:
        print(num)
        
        ...  # your code here
    
    Solution:
    itr.__setstate__(0)
    

    __setstate__ is part of the pickle protocol, which is used to restore unpickled objects to their original state. The “state” of a list iterator is simply the index of the element that it will yield next. By repeatedly setting this index to 0, we can make the list iterator yield the first list element forever.

  29. Throwing exceptions in lambdaswim
    Write a lambda that takes an exception as input and raises it, without using exec.
    Solution:
    lambda ex: (_ for _ in '').throw(ex)
    

    Generators have a throw() method!

  30. super lame riddlewim
    """
    The DismemberedZombie wants to terrorize the neighborhood,
    but one of his methods has fallen off. We tried reattaching
    it, but it still didn't work properly. We'll need the help
    of a super-surgeon, someone more experienced in this undead
    object model!
    
    Can you help to make ``zed.scare()`` work again in time for
    trick or treat tonight?
    
    EXPECTED OUTPUT:
       >>> zed.scare()
       shambling..
       DismemberedZombie groans..
       eating brains..
    """
    
    class Undead:
        def scare(self):
            print(f"{type(self).__name__} groans..")
    
    class DismemberedZombie(Undead):
        pass
    
    def scare(self):
        print("shambling..")
        super().scare()
        print("eating brains..")
    
    zed = DismemberedZombie()
    
    DismemberedZombie.scare = scare
    # YOUR ONE LINE OF CODE GOES HERE
    zed.scare()
    
    Solution:
    super = lambda s=super: s(DismemberedZombie, zed)
    
  31. No touch, only throwArne
    class Dog:
        __slots__ = ["foo"]
        def __setattr__(self, name, value):
            raise TypeError("(•ˋ _ ˊ•) no touch, only throw.")
    
    d = Dog()
    # your one line of code here
    assert type(d) is Dog
    print(d.foo)  # "take stick"
    
    Solutions:
    object.__setattr__(d, "foo", "take stick")
    
    Dog.foo = "take stick"
    

Open Ended Riddles

These riddles have no known solution, or many possible solutions.

  1. The recursive tupleKevin
    Is it possible to construct a tuple that contains itself? e.g. t is t[0] is True.
    Solution:

    Unknown. Experimentation with the ctypes module has had some success (example), but this causes reference counting problems and occasionally segfaults.

  2. Implement super in pure pythonAran-Fey
    # Implement the super function in pure python! (Specifically, the
    # super(class, instance) form.)
    
    class my_super:
        ...  # YOUR CODE HERE
    
    Solution:
    class my_super:
        def __init__(self, cls, inst):
            self.cls = cls
            self.inst = inst
        
        def __getattr__(self, attr):
            # from self.inst's class's MRO, get a list of classes that
            # appear AFTER self.cls
            mro = type(self.inst).mro()
            i = mro.index(self.cls)
            mro = mro[i+1:]
            
            # see if any of those classes has the requested attribute
            for cls in mro:
                try:
                    result = vars(cls)[attr]
                    break
                except KeyError:
                    pass
            else:
                # if no class has it, throw an exception
                raise AttributeError(attr)
            
            # if the result is a descriptor, call its __get__ method
            if hasattr(type(result), '__get__'):
                result = result.__get__(self.inst, type(self.inst))
            
            return result
    
  3. Implement the @classmethod decoratorAran-Fey
    Implement a clone of the @classmethod decorator. (Without using classmethod, duh.)
    Solution:
    from functools import partial
    
    # this class is a descriptor: //docs.python.org/3/howto/descriptor.html
    class my_classmethod:
        def __init__(self, func):
            self.func = func
        
        def __get__(self, instance, owner):
            # If accessed from an instance:
            #   instance = the instance
            #   owner = type(instance)
            # If accessed from a class:
            #   instance = None
            #   owner = the class
            
            # return a callable with the class baked in as the first argument
            return partial(self.func, owner)
    

    Functions are descriptors - when they’re accessed via an instance, they return a bound method, where the first argument is implicitly set to the instance. In order to prevent this from happening, we implement classmethod as a descriptor that binds the class (instead of the instance) to the method.

  4. Arbitrary code execution with pickle.load, round 2Aran-Fey
    # Provide a byte string that sets the global "winner" variable to True when
    # unpickled. The unpickler blocks you from directly accessing builtins like `exec`.
    # Try to use as few modules/classes/functions as possible.
    
    import io
    import pickle
    
    class RestrictedUnpickler(pickle.Unpickler):
        def find_class(self, module, name):
            if module == 'builtins':
                raise pickle.UnpicklingError("forbidden: {} {}".format(module, name))
            return super().find_class(module, name)
    
    data = b'YOUR DATA HERE'
    
    RestrictedUnpickler(io.BytesIO(data)).load()
    
    if winner:
        print('You win!')
    
    • One way to do it is to trick typing.get_type_hints into executing code for you.
    Solution:
    # define convenience stuff
    code = '''__import__('__main__').__dict__.__setitem__("winner", True)'''
    
    class Reducer:
        def __init__(self, factory, *args, state=None, elems=[], items={}):
            self.redux = (factory, args, state, iter(elems), iter(items.items()))
        
        def __reduce__(self):
            return self.redux
        
        __call__ = lambda: None
    
    # SOLUTION 1: Requires types and typing
    # We use `types.SimpleNamespace` to create an object with an `__annotations__`
    # attribute and call `typing.get_type_hints` on it to make it `eval` our code.
    annotated_obj = Reducer(types.SimpleNamespace, state={'__annotations__': {'x': code}})
    danger = Reducer(typing.get_type_hints, annotated_obj)
    data = pickle.dumps(danger)
    
    # SOLUTION 2: Requires functools, operator and types
    # We make `functools.single_dispatch`'s `register` method call
    # `typing.get_type_hints` for us, which will in turn `eval` our type
    # annotation - which is actually not a type annotation, but code.
    singledispatch_func = Reducer(functools.singledispatch, functools.singledispatch)
    register_func = Reducer(operator.attrgetter('register'), singledispatch_func)
    annotated_obj = Reducer(types.SimpleNamespace, state={'__annotations__': {'x': code}})
    danger = Reducer(register_func, annotated_obj)
    data = pickle.dumps(danger)
    

Code Golf Riddles

These riddles ask you to achieve a certain goal with as little code as possible.

  1. Cause a RecursionErrorpiRSquared
    Raise a RecursionError.
    Solutions:
    # 24 characters
    x,y=x[0],y[0]={},{};x==y
    
    # 20 characters
    raise RecursionError
    
    # 19 characters
    s="eval(s)";eval(s)
    
    # 16 characters
    f=lambda:f();f()
    
    # 15 characters
    def f():f()
    f()
    
    # 14 characters (REPL only)
    lambda:_();_()
    
  2. Cause a TypeErrorwim
    Raise a TypeError.
    Solutions:
    # 3 characters
    0()
    +''
    1@1
    

Guess-The-Output Riddles

These riddles do not ask you to write code; rather, they ask you to predict what the output of the code is without running it first.

  1. Fun with multiple target lists in assignmentwim
    >>> x, y = x[y] = {}, 0
    >>> x
    <you guess the output>
    
    Solution:
    {0: ({...}, 0)}
    

    The leftmost target list gets assigned first, so x binds to {} and y binds to 0. Then the next target list is assigned, so x[0] gets set to (x, 0). So ultimately x is a dict with a single key 0 and a self-referential tuple value.

  2. Class scope resolution curiositiesKevin
    Guess the output of these two programs: 
    #PART 1
    x = 0
    def f():
        x = 1
        class A:
            x = 2
            class B:
                print(x)
    
    f()
    
    #PART 2
    x = 0
    def f():
        x = 1
        class A:
            x = 2
            class B:
                print(x)
                x = 3
    f()
    
    Solution:
    1
    0
    

    There are two quirks of the name resolution system that cause this behavior. Refer to the docs. First, “the scope of names defined in a class block is limited to the class block”. This means that in part 1, The x = 2 bound inside class A is not visible to class B, even though class B’s context is inside class A. The next closest visible nonlocal x is the one in f, so print(x) prints 1. Second, “[in a class definition] unbound local variables are looked up in the global namespace”. By adding x = 3 to the class B scope, x becomes a local variable. Since it hasn’t been assigned to yet when print(x) executes, it is an unbound local variable. In a normal context, this would crash with UnboundLocalError. But because it’s inside a class definition, x is looked up in the global namespace instead. So print(x) prints 0.

  3. What happens if you inherit from an object?Aran-Fey
    # What's the output of this code?
    
    class Mystery:
        def __init__(self, *args):
            if args:
                print(args[0])
    
    obj = Mystery()
    
    class Unknown(obj):
        pass
    
    • Metaclasses!
    Solution:
    Unknown
    #(Which is not to say that it's not known what the output is. The output is literally the word "Unknown")
    

    When you create a class, the first thing python does is to determine the metaclass. A metaclass is a class’s type. Python determines which metaclass to use by checking the metaclasses of all parent classes, and choosing the one that’s a child class of all the others (if no such class exists, a “metaclass conflict” exception is raised.) Once the metaclass has been determined, python calls it with 3 arguments: The name of the new class, a list of base classes, and a dict of class attributes. The return value is your new class.
    So what does this have to with the riddle? Well, it turns out that python doesn’t really care if you’re inheriting from classes or from something else. When we inherit from obj, python determines its metaclass by calling type(obj). This returns Mystery, so Mystery will be used as the metaclass for Unknown. Python then calls Mystery with the usual 3 arguments passed to metaclasses: Mystery("Unknown", [obj], {}). Mystery prints the first of these 3 arguments, which gives us the output “Unknown”.

  4. Name manglingAran-Fey
    #What's the output of this code?
    __var = 'option 1'
    _Cls__var = 'option 2'
    
    class Cls:
        __var = 'option 3'
    
        def test(self):
            return __var
    
    print(Cls().test())
    
    Solution:
    option 2

    It turns out that python’s name-mangling mechanism for double-underscore variables doesn’t only apply to class or instance attributes.

  5. Overriding __type__ does weird thingswim
    class A(object):
        def hello(self):
            print("A")
    
    class B(object):
        __class__ = A
    
        def hello(self):
            print("B")
    
    obj = B()
    
    print(type(obj).__name__)
    print(obj.__class__.__name__)
    print(isinstance(obj, A))
    print(isinstance(obj, B))
    obj.hello()
    
    Solution:
    B
    A
    True
    True
    B
    
  6. Nested f-stringsAran-Fey
    char = 't'
    num = 4
    t = 'foo'
    
    print(f'{char}')
    print(f'{f"{char}"}')
    print(f'{f"{char}":z<{num}}')
    print(f'{f"{char}":z<{f"{num+1}"}}')
    
    Solution:
    t
    t
    tzzz
    tzzzz
    
  7. Using @classmethod on a classAran-Fey
    class Foo:
        @classmethod
        class Bar:
            def __new__(*args):
                print(*args)
    
    Foo.Bar(5)
    
    • Don’t make it more complicated than it needs to be… Just remember that classmethod adds the class as the first argument, and that __new__ is also a classmethod.
    Solution:
    <class '__main__.Foo.Bar'> <class '__main__.Foo'> 5
    

    Like the hint said, classmethod adds the class as the first argument. Initially, the arguments are just 5. Since Foo.Bar is a classmethod, it adds Foo, so now the arguments are Foo, 5. Bar.__new__ is also a classmethod, so it adds Bar, resulting in Bar, Foo, 5.

  8. Instance attributes shadow class attributes, or do theyAran-Fey
    class Class:
        a = 'class'
        
        @property
        def b(self):
            return 'class'
        
        @property
        def c(self):
            return 'class'
        @c.setter
        def c(self, c):
            pass
        
        def d(self):
            return 'class'
    
    instance = Class()
    vars(instance).update({
        'a': 'instance',
        'b': 'instance',
        'c': 'instance',
        'd': lambda: 'instance'
    })
    
    print(instance.a)
    print(instance.b)
    print(instance.c)
    print(instance.d())
    
    Solution:
    instance
    class
    class
    instance
    

    This riddle is powered by descriptors. If a class attribute is a data descriptor (i.e. it implements __get__ and __set__), it shadows the instance attribute with the same name. In any other case, the instance attribute shadows the class attribute. Functions are non-data descriptors, and properties are data descriptors - even if they don’t actually have a setter, like b.

  9. partial of a partialAran-Fey
    import functools
    
    def func(arg):
        print(arg)
    
    p1 = functools.partial(func, arg=1)
    p2 = functools.partial(p1, arg=2)
    p2()
    
    Solution:
    2
    

    If the input to partial is another partial instance, its args and kwargs are replaced.

  10. __import__ shenaniganswim
    __import__("os.path").dirname("a/b")
    
    Solution:
    Traceback (most recent call last):
      File "", line 1, in 
    AttributeError: module 'os' has no attribute 'dirname'
    

    __import__ returns the top-level package per default. See the docs.

Spot-The-Bug Riddles

These riddles ask you to find a bug in a given piece of code.

  1. Attribute lookup, how does it workAran-Fey
    # Find an object `obj` so that `has_length(obj)` returns `True`, but `len(obj)` crashes because `obj` doesn't implement `__len__`.
    
    def has_length(obj):
        cls = type(obj)
        return hasattr(cls, '__len__')
    
    • You have to define __len__ somewhere so that hasattr can find it, but len can’t.
    Solution:
    class Meta(type):
        def __len__(cls):
            return 1
    
    class Class(metaclass=Meta):
        pass
    
    obj = Class()
    
    print(has_length(obj))  # True
    print(len(obj))  # crashes
    

    To understand the solution, you must first know how attribute lookup works in python: When you access an attribute of an object, like foo.bar, python searches for a key named "bar" in foo’s __dict__ and in the __dict__ of every class in foo’s MRO. So basically it only looks at two “levels”, the object and its class. By adding a metaclass we’re adding a 3rd “level”, and that is the key to breaking the has_length function. hasattr(cls, '__len__') looks at the 2nd and 3rd levels (the class and metaclass), but special method lookup (like when len(obj) looks for a __len__ method) only looks at the 2nd level (the class). That is why defining __len__ in the 3rd level (the metaclass) makes has_length return an incorrect result. Further, it’s not enough to check if the class has an attribute with that name - setting a special method to None indicates that the class doesn’t support it. Therefore, the correct implementation would be return next((vars(c)['__len__'] for c in cls.mro() if '__len__' in vars(c)), None) is not None.