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 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.
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 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 requests
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
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
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.
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 None
is different from noreturn
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 thetry
block, 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 SystemExit
SystemExit
exceptions are treated in a special way by the interpreter. If aSystemExit
is 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
del
Aran-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.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
nonlocal
Kevin# 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, 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.Create a recursive list in a single lineKevin
Using a single statement, create a self-referential list
a
such 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 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 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] = [{}] * 2
Arbitrary code execution with
pickle.load
Aran-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. Usingexec
as 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
nonlocal
Aran-Fey# Fix the
foo
function 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: int
It turns out that
nonlocal
interacts 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 thatobj
will have a dict to storey
in, and by creating anx
slot we ensure thatx
isn’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 minidom
essentially turns intofrom xml.dom import minidom
.from . import solution
Aran-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 importsx
. Then, ifx
has ay
attribute, it simply returns that. Otherwise, it looks for ax.y
submodule, and imports it. That’s why we can prevent python from trying to import a non-existentfoo
module 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 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.- 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.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)
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
super
in pure pythonAran-Fey# Implement the
super
function 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 result
Implement the
@classmethod
decoratorAran-FeyImplement a clone of the
@classmethod
decorator. (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
classmethod
as 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_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.
Cause a
RecursionError
piRSquaredRaise 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
x
binds to{}
andy
binds to0
. Then the next target list is assigned, sox[0]
gets set to(x, 0)
. So ultimately x is a dict with a single key0
and 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 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 nonlocalx
is the one inf
, soprint(x)
prints 1. Second, “[in a class definition] unbound local variables are looked up in the global namespace”. By addingx = 3
to theclass B
scope,x
becomes 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,x
is 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
, soMystery
will be used as the metaclass forUnknown
. Python then callsMystery
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”.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 B
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
Using
@classmethod
on 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
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 just5
. SinceFoo.Bar
is 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 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, 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:2
If the input to
partial
is anotherpartial
instance, 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 thathasattr
can find it, butlen
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"
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_length
function.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_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 toNone
indicates 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