What's the best way to minimize the need of run time checks?

Steven D'Aprano steve+python at pearwood.info
Sat Aug 13 12:06:58 EDT 2016


On Sat, 13 Aug 2016 08:09 pm, BartC wrote:

> (The way I deal with records elsewhere is so ridiculously simple that it
> almost makes me want to cry when I see what most dynamic languages have
> to do. Example:
> 
> record date=                # define the record
>     var day, month, year
> end
> 
> d := date(25,12,2015)       # create an instance
> d.year := 1999              # change a field
> println d                   # show "(25,12,1999)"

Sure. But you've put most of the work into the compiler. Since you wrote the
compiler, you still had to do the work.

Python has gone over 20 years with no syntax or built-in function for
creating "records", because they're not really needed. We have tuples,
namedtuples and objects, which are much more powerful. But out of
curiosity, I thought I'd write something to define a record as you would
have it.

A couple of tricky bits:

- Since I'm not writing a compiler, I'm limited to syntax as 
  allowed by Python. That means I have to give the record name
  twice: once as the binding target, and once as an argument
  to the "record" function.

- The trickiest bit is defining the record class' __init__ 
  method, since I don't know how many arguments it will need.

Nevertheless, here you go. This was about 5 minutes work. Not too bad for a
first draft.

def record(name, fieldnames, verbose=False):
    if isinstance(fieldnames, str):
        fieldnames = fieldnames.split()
    class Inner(object):
        __slots__ = fieldnames
        def __str__(self):
            fields = ', '.join([repr(obj) for obj in self._fields])
            return "record %s(%s)" % (type(self).__name__, fields)
        __repr__ = __str__
        @property
        def _fields(self):
            return [getattr(self, name) for name in fieldnames]
    Inner.__name__ = name
    ns = {}
    template = "def __init__(self, %s):\n" % ', '.join(fieldnames)
    for name in fieldnames:
        template += '    self.%s = %s\n' % (name, name)
    if verbose:
        print("Using __init__ template:")
        print(template)
    exec(template, ns, ns)
    Inner.__init__ = ns['__init__']
    return Inner


py> date = record('date', 'year month day')
py> x = date(2016, 8, 14)
py> x
record date(2016, 8, 14)
py> x.year = 1999
py> x
record date(1999, 8, 14)


> I don't know how exec works. Is the 'value' name a local if this is in a
> function, or a global if outside a function?

exec takes a string, and executes it as Python code. You can specify the
name spaces to use for globals and locals; by default they use globals()
and locals().

Exec of a string on its own is like executing code at the top level of the
module:

exec("spam = 1")

is like:

spam = 1 

in the module scope. You can specify another namespace:

import math
exec("spam = 1", vars(math))

is like executing "spam = 1" in the top level of the math module.

ns = {}
exec("spam = cos(pi)", vars(math), ns)
print(ns['spam'])


will print -1.0. It is equivalent to executing the line "spam = cos(pi)"
inside an imaginary function running inside the math module. That imaginary
function's local variables (in this case, there's only one, "spam") is
recorded in the dictionary "ns" for later retrieval.

> If your example was in a function, wouldn't 'value' be assumed to be a
> global anyway, if it wasn't assigned to anywhere else in the function?

Maybe.

The rules for running exec inside a function in Python 2 are awfully
complex, as it interact with the CPython optimization of using a table for
locals instead of a dict. In Python 3, you cannot call exec() inside a
function unless you explicitly specify a namespace to use.



-- 
Steve
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.




More information about the Python-list mailing list