On 13/08/2016 17:06, Steven D'Aprano wrote:
On Sat, 13 Aug 2016 08:09 pm, BartC wrote:

record date=                # define the record
    var day, month, year
end

d := date(25,12,2015)       # create an instance

Sure. But you've put most of the work into the compiler.

Into the language so that it's always available and consistent rather than leaving it to individuals to somehow build something on top of existing language features.

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)

That's pretty good actually. Although I noticed you used the __slots__ feature that you were scathing about earlier on! (Also that you've thrown in examples of using decorators, and using exec(), free of charge..)

There are a number of ways this approach differs from the native record example (but I acknowledge your example was put together as a quick demo):

* The fields in my record are known at compile time; using the wrong name will usually be picked immediately. As will using the wrong number of initialisers.

* (My record fields have other features: aliases for any field; the ability to access by index, doing len() etc; fast conversions to and from a list; also the ability to create packed records that exactly match C structs, and so on.)

* Each defined record in my scheme ('date' and so on) is a distinct type. If I use the Python record() to create two records R and S, then type(R) and type(S) seem to give the same result (both 'type').

* Record names and fields are case insensitive in my implementation.

* The Python version can't take advantage of a simple and fast native implementation of records. (A simple loop creating a list of a few million date records, excluding overheads, took 12 times as long Python 3, as my language. PyPy only took twice as long, but it's likely just taking advantage of the big loop.

* But the biggest problem I think, as I hinted above, is that this is not a standard part of the language. It's same thing with trying to fix C issues by writing macros; everyone will roll their own versions. Or not bother at all.

--
Bartc
--
https://mail.python.org/mailman/listinfo/python-list

Reply via email to