The Cost of Dynamism (was Re: Pyhon 2.x or 3.x, which is faster?)

Chris Angelico rosuav at gmail.com
Sat Mar 12 16:45:31 EST 2016


On Sun, Mar 13, 2016 at 3:22 AM, Steven D'Aprano <steve at pearwood.info> wrote:
> On Sat, 12 Mar 2016 01:20 pm, Chris Angelico wrote:
>
>
>>>> Definitely agree with this. Having a way to declare that a name is
>>>> "truly constant" would be extremely handy; there currently isn't a
>>>> way, and I'm not sure whether FAT Python is looking into this or not.
>
> "Constants" would be a new language feature, not an optimization. Unless
> CPython adds a constant feature, FAT Python isn't going to do it.

Yes, they would. Having a way to *declare*, in your source code, is
what I'm talking about. Although FAT Python does have facilities for
noticing that something hasn't changed, this is talking about
demanding that it _never_ change.

>>>> I think it's more focusing on the situation where something simply has
>>>> never been rebound, which is probably the better optimization; but
>>>> sometimes it'd be nice to be able to assert that something *will not*
>>>> be rebound, to help with self-documenting code. (Note that "constant"
>>>> implies both "never rebound" and "immutable".
>
> I disagree that constant necessarily implies immutable, at least in the
> context of Python. At least, what *I* want from a const keyword is a way to
> make a certain name "write-once": you can bind to it the first time, and
> then never again for the life of the scope. It shouldn't matter whether it
> is a list or a float.

I mean that the word generally implies that. It's perfectly possible
to have a Python keyword "constant" that means "this will never be
rebound" (ie "constant by identity"), without having "constant value";
but there will be people who are confused by it, just as (and for the
same reason as) they're confused by mutable default arguments.
There'll be posts all over the place, "never use mutable constant
values, or they stop being constant".

I completely agree with you that the keyword should mean "write-once"
or "never rebind".

> Let's put efficiency aside and consider a naive "bind-once" const for a
> language like Python. Every namespace has a mapping of name:value, as we
> have now, plus a list of "constant names". Then every binding operation:
>
>    x = value
>    import x
>    from module import x
>    del x
>    def x(): ...
>    class x: ...
>    for x in seq: ...
>    with expr as x: ...
>    except error as x: ...
>
> first checks the list of constant names for the name (e.g. "x"). If the name
> is not found, then the binding operation is allowed, just like it is today.
> If it is found, then the namespace is checked to see if the name already
> exists. If it does, the operation raises a TypeError (or perhaps
> ConstError?). If not, then the operation continues as normal.

IMO, ConstError should probably be its own thing. The nearest
equivalent I can find is attempting to assign to a read-only property,
which raises AttributeError; this is broader than that.

I'd make this much, much simpler. This statement:

constant x

declares that 'x' will never, from this point on, be assigned to. And
this statement:

constant x = y

(re)binds x to the value of y, and then declares that x will never be
assigned to. All assignments simply check this list; if the name is on
the list, raise error.

So if you're doing a "constant import", or something else that does a
special form of bind, you would spell it differently:

from module import x
constant x

As a convenience, I would like to see a few common constructs, such as
"constant def" and "constant class", but we don't need "constant for"
or "constant with" (and definitely not "constant except", since that
has an implicit rebind and unbind at the end).

>> Not sure about that. Consider:
>>
>> MAX_SIZE = 1<<16
>> def some_func(blah):
>>     data = some_file.read(MAX_SIZE)
>>
>> Currently, this disassembles to show that MAX_SIZE is being fetched
>> with LOAD_GLOBAL. If, instead, it were LOAD_CONST, this would mean
>> that rebinding MAX_SIZE after function definition would fail;
>
> I don't think it would fail in the sense of raising an explicit exception. I
> think it would just be hard to understand, giving you strange and
> mysterious behaviour: there's a name which I can successfully rebind, but
> some functions accessing it still see the old value, while others see the
> new value.

Yeah, it would fail in the sense that it would do the wrong thing. If
you're expecting "module.MAX_SIZE = 1<<10" to reduce the maximum, that
would silently fail to do what you expect.

Basically, what I'm talking about here is the difference between
"import x" and "from x import y". Normally, globals are effectively
"my_module.MAX_SIZE", but the LOAD_CONST transformation turns them
into "from my_module import MAX_SIZE". Rebinding the module name has
no effect on something that already imported it by value.

> But then, that's not far from the current behaviour of "early binding" of
> default values.

Exactly; the only difference is that you can't rebind it in any way
(including inside the function; when you use the "MAX_SIZE=MAX_SIZE"
trick, the compiler has to assume that you might rebind MAX_SIZE
inside the function, so it still can't use LOAD_CONST).

>> but it
>> would run faster. That's the advantage of a "declared constant", even
>> without it being any sort of compile-time constant. As long as it can
>> be finalized before the function is defined, it can be called
>> constant.
>
> Constants should be treated as constant even outside of functions.

Yes; my point was just that it doesn't have to be like a C/Pascal
style of constant. For instance:

try: from _foo import bar
except ImportError: from foo import bar
constant bar

def have_drink():
    champagne = bar.pop()

Since 'bar' was declared 'constant', the compiler could take the value
as at function definition time and stash it into co_consts, because
it'll never be rebound. The actual source of that object depends on a
runtime check, and the exact value of it will vary as drinks get
popped off it, but it's a perfectly legit constant. The only
significance of function definitions is that it's a point at which a
constant will be captured, but a regular global can't be; it's the
point at which its constantness becomes an optimization, rather than
an assertion.

> Maybe the secret is to make modules more like functions in their internal
> details?

I'd be more inclined to make them more like classes, actually. Then
they could use descriptor protocol and properties and stuff. :)

ChrisA



More information about the Python-list mailing list