Riddles
- 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
Inequality Loop (Python 2.7 only)wim
>>> # create x, y, z such that >>> x < y < z < x TrueSolution:>>> x = b"Z" >>> y = () >>> z = u"A" >>> x < y < z < x TrueThe 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.
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 worldno patching out print or any lame hack like thatSolution:with CM() as cm: def my_exit(*args): print('goodbye world') CM.__exit__.__code__ = my_exit.__code__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 requestsImporting 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 requestsImporting 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 requestsCrash 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.
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?
Sometimes
return Noneis different from noreturnat 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 NoneSince the code in the
finallyblock is executed after the code in thetryblock, it overrides the previous return value.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 SystemExitSystemExitexceptions are treated in a special way by the interpreter. If aSystemExitis raised and not caught, the process will exit without printing a stack trace.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.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: passIn newer python versions, the
exceptclause automatically deletes the name the exception was bound to.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.
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.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, ifint('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),typealso has a 3-argument form:type(cls_name, base_classes, attr_dict)creates a new class.Create a recursive list in a single lineKevin
Using a single statement, create a self-referential listasuch thata 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 toa = [1]followed bya[0] =. Note thata = [1]; a[0] = [1]does not have the same effect because the two[1]literals resolve to two different objects.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 FalsePython 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 returnFalse.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] = [{}] * 2Arbitrary 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. Usingexecas the factory is an easy way to run arbitrary code.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!- You have to help
Another curious interaction with
nonlocalAran-Fey# Fix thefoofunction without binding the namex! # (//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: intIt turns out that
nonlocalinteracts with type hints.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"])- 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
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 :(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:
We create a__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.)__dict__slot so thatobjwill have a dict to storeyin, and by creating anxslot we ensure thatxisn’t stored in the __dict__ as well.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.- The description said
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 minidomessentially turns intofrom xml.dom import minidom.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 fooSolution:foo = 5 # any value will work from . import fooThe trick here lies in the behavior of
from x import yimports. Python first importsx. Then, ifxhas ayattribute, it simply returns that. Otherwise, it looks for ax.ysubmodule, and imports it. That’s why we can prevent python from trying to import a non-existentfoomodule simply by assigning a value tofoo.*-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 indexedThe 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.- Think about what names a
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- The solution is related to the pickle protocol.
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.superlame 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)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.
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.
Implement
superin pure pythonAran-Fey# Implement thesuperfunction in pure python! (Specifically, the #super(class, instance)form.) class my_super: ... # YOUR CODE HERESolution: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 resultImplement the
@classmethoddecoratorAran-FeyImplement a clone of the
@classmethoddecorator. (Without usingclassmethod, duh.)- You’ll need a descriptor.
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
classmethodas a descriptor that binds the class (instead of the instance) to the method.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_hintsinto 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.
Cause a
RecursionErrorpiRSquaredRaise 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:_();_()
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.
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
xbinds to{}andybinds to0. Then the next target list is assigned, sox[0]gets set to(x, 0). So ultimately x is a dict with a single key0and a self-referential tuple value.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 0There 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 = 2bound inside class A is not visible to class B, even though class B’s context is inside class A. The next closest visible nonlocalxis the one inf, soprint(x)prints 1. Second, “[in a class definition] unbound local variables are looked up in the global namespace”. By addingx = 3to theclass Bscope,xbecomes a local variable. Since it hasn’t been assigned to yet whenprint(x)executes, it is an unbound local variable. In a normal context, this would crash withUnboundLocalError. But because it’s inside a class definition,xis looked up in the global namespace instead. Soprint(x)prints 0.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 fromobj, python determines its metaclass by callingtype(obj). This returnsMystery, soMysterywill be used as the metaclass forUnknown. Python then callsMysterywith the usual 3 arguments passed to metaclasses:Mystery("Unknown", [obj], {}).Mysteryprints the first of these 3 arguments, which gives us the output “Unknown”.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 2It turns out that python’s name-mangling mechanism for double-underscore variables doesn’t only apply to class or instance attributes.
Overriding
__type__does weird thingswimclass 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 BNested 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 tzzzzUsing
@classmethodon a classAran-Feyclass 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
classmethodadds the class as the first argument, and that__new__is also a classmethod.
Solution:<class '__main__.Foo.Bar'> <class '__main__.Foo'> 5Like the hint said,
classmethodadds the class as the first argument. Initially, the arguments are just5. SinceFoo.Baris a classmethod, it addsFoo, so now the arguments areFoo, 5.Bar.__new__is also a classmethod, so it addsBar, resulting inBar, Foo, 5.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 instanceThis 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, likeb.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:2If the input to
partialis anotherpartialinstance, its args and kwargs are replaced.Spot-The-Bug Riddles
These riddles ask you to find a bug in a given piece of code.
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 thathasattrcan find it, butlencan’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)) # crashesTo 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"infoo’s__dict__and in the__dict__of every class infoo’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 thehas_lengthfunction.hasattr(cls, '__len__')looks at the 2nd and 3rd levels (the class and metaclass), but special method lookup (like whenlen(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) makeshas_lengthreturn an incorrect result. Further, it’s not enough to check if the class has an attribute with that name - setting a special method toNoneindicates that the class doesn’t support it. Therefore, the correct implementation would bereturn next((vars(c)['__len__'] for c in cls.mro() if '__len__' in vars(c)), None) is not None.- You have to define