How well do you know Python?

Chris Angelico rosuav at gmail.com
Tue Jul 5 05:17:17 EDT 2016


On Tue, Jul 5, 2016 at 4:33 PM, Steven D'Aprano
<steve+comp.lang.python at pearwood.info> wrote:
> > Got any other tricky questions to add?
S
P
O
I
L
E
R

S
P
A
C
E



A
N
D

A

B
I
T

M
O
R
E

[Thanks Steven, I just copied and pasted your space. See? You can copy
and paste blank space and use it over and over. Could be a useful tip
for the people who can't find enough newlines to space out their code
or readability.]

> Explain metaclasses, descriptors, the mro, multiple inheritance, and the
> interaction between them. Why is the mro needed?

A metaclass is simply the type of the type of something. By default,
that's type, but if you type "metaclass=X" in a class definition, you
can make your class a type of something else. (Can I use the word
"type" in any other senses? I couldn't work "font" into the above.)

Descriptors are objects stored in the class which, when referenced
from an instance, return something other than themselves. The most
common example is functions; a function in a class becomes a bound
method on an instance, thus proving that descriptors make Python
better than JavaScript.

The MRO is simply the class hierarchy, starting from the current class
and ending with 'object', flattened out into a line. In a pure
single-inheritance situation, this is just the line of parents; when
multiple inheritance comes into play, the MRO becomes more
complicated, but still follows straight-forward rules (children before
parents, and parents in the order they're listed). The MRO's job is
twofold: attributes of classes earlier in the list will shadow those
later, and super() means "next in the MRO".

> obj.spam is a property. How do you get access to the underlying property object
> itself?

Presumably you mean that obj.spam returns the result of calling the
property function, something like this:

class X:
    @property
    def spam(self):
        return "something"

obj = X()

In that case, type(obj).spam is the property object, and
type(obj).spam.{fget,fset,fdel} are the three functions (of which two
will be None in my example).

> Why doesn't the property decorator work inside a Python 2 classic class?

Guido put an explicit check in to prevent you from doing so.
Attempting it will automatically download the latest Python 3.x, point
your web browser to a porting tutorial, and raise SyntaxError("Please
learn to use Python 3.").

Or: Instances of classic classes are all actually instances of
Instance(), so the property would have to be attached to Instance.

Or: It does.

rosuav at sikorsky:~$ python2
Python 2.7.11+ (default, Jun  2 2016, 19:34:15)
[GCC 5.3.1 20160528] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class X:
...   @property
...   def spam(self):
...     print("Spamming")
...     return "Hello"*5
...
>>> x=X()
>>> x
<__main__.X instance at 0x7f06e44fad88>
>>> x.spam
Spamming
'HelloHelloHelloHelloHello'

Take your pick.

> Explain Python scoping rules, in particular with respect to nested classes and
> nested functions (or even nested classes inside nested functions).

If you have an explicit global or nonlocal directive, the listed
name(s) are looked up in that scope only. Otherwise, Python looks in
local names, then in enclosing function scopes, then the module
globals, and finally the built-ins. While executing a class or
function body, local scope is that class/function. Enclosing class
scopes are not searched.

> Explain attribute lookup rules. When does an instance attribute take priority
> over a class attribute?

With simple attributes, an instance attribute always takes priority,
and then class attributes in method resolution order. With
descriptors... I'd have to go look it up. I've never done any
shadowing of descriptors, at least not deliberately/consciously.

> When is locals() writable? When is locals() writable, AND the effect shows up
> in the local scope? Explain how exec works inside a function, and the
> interaction with locals().

In CPython, locals() is always writable. It's just a dictionary. I'm
not sure whether this is a language guarantee or not.

When does the effect show up in local scope? When locals() is
globals(). Otherwise, the answer has to be "undefined" or at best
"implementation defined". To go further than that, I have to actually
experiment, rather than going from memory.

rosuav at sikorsky:~$ python3
Python 3.6.0a2+ (default:57f3514564f6, Jun 29 2016, 16:27:34)
[GCC 5.3.1 20160528] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def f():
...    x = 1
...    print(id(locals()), id(globals()))
...    locals()["x"] = 2
...    print(x)
...
>>> f()
140191368813960 140191369099208
1
>>>

Same is true of CPython 2.7, PyPy 5.1 (including PyPy.js), Jython 2.5,
MicroPython (although this has its own bizarrenesses - locals() is
globals(), somehow), but NOT of Brython. In Brython, locals and
globals have distinct ids, and mutating locals *does* change the local
name binding. And this is legal, according to the docs. End of
experimentation, now back to doing stuff from memory.

In Python 3, I don't think exec() can interact with locals() in any
way. It's a perfectly ordinary function; you can give it a reference
to locals() if you like, but it's no different from other mutations of
locals. In Python 2... I don't use exec inside functions. So I'd have
to look it up or experiment. But I'd rather just stick to Py3.

> Name all the falsey builtins.

Fred, Joe, Stanley, Margaret, and Louise.

I don't think there are actually any falsey builtins. Most builtins
are either classes (object types like int/str/range, and the
exceptions) or functions (next, ascii, eval). None and False are
keywords, so they don't count. Literals like (), 0, 0.0, 0.0j, u"",
b"" aren't builtins either. And [] and {} aren't literals OR builtins,
they're special forms of constructor that return falsey objects.

> Apart from exceptions, list the builtins, from memory. You can list the
> exceptions as well.

Oh great. Uhh... there's a lot of them. int, bool, str, bytes, list,
tuple, set, dict, object, type, float, complex, next, ascii, eval,
exec, range, sorted, super, zip, map, enumerate, len, iter, repr,
locals, globals, id...  I'm stopping there.

> An easy one: list the Python keywords.

Not that easy actually. I'm far from sure that I got them all.

True, False, None, if, elif, else, for, while, break, continue, try,
except, finally, raise, with, as, async, def, return, and, or, not,
is, in, class, pass, yield, global, nonlocal, yield, from, import

How many have I missed?

> What happens in this code snippet?
>
>     L = [1]
>     t = (L,)
>     t[0] += 1
>
> Explain what value t has, and why.

Correction from other post: Last line becomes "t[0] += [1]"

t has the same value it had before - the same tuple. That tuple
references L, which has just been extended and is now [1, 1]. So if
you print out t, you'll see ([1, 1],) - but it's still the same value
it was.

Also, you'll have seen a TypeError at the moment when you attempted to
mutate the tuple, which is a violation of the Guildpact, subsection...
whatever. The same one that says that coffee is an acceptable
substitute for rest for the Living Guildpact.

> Explain what "yield from it" does and how it is different from:
>
>     for item in it:
>         yield item

If 'it' is another generator, 'yield from' also passes along any
values sent, or any exception thrown in. With non-generator iterators,
I believe it's functionally identical to the above for loop.

ChrisA



More information about the Python-list mailing list