[Python-ideas] JavaScript-Style Object Creation in Python (using a constructor function instead of a class to create objects)

Nick Coghlan ncoghlan at gmail.com
Sat May 20 12:06:18 EDT 2017


On 20 May 2017 at 10:19, Steven D'Aprano <steve at pearwood.info> wrote:
> On Fri, May 19, 2017 at 11:24:53AM -0700, Guido van Rossum wrote:
>
>> 4. Easily allow to specify a conversion function. For example I have
>> some code like below:
>>     note that I can store a numpy array while keeping hashability and
>> I can make it convert
>>    to a numpy array in the constructor.
>>
>>  @attr.s(cmp=False, hash=False)
>>  class SvgTransform(SvgPicture):
>>      child = attr.ib()
>>      matrix = attr.ib(convert=numpy.asarray)
>
>
> I find that completely enigmatic, there's far too much implicit
> behaviour going on behind the scenes. I couldn't even begin to guess
> what SvgTransform as a class does, or what SvgTransform.child and
> SvgTransform.matrix are.
>
> I suppose that's okay for experts to whom the attrs module is second
> nature, but I think this approach is far too "magical" for my tastes.

Some of the key problems I personally see are that attrs reuses a
general noun (attributes) rather than using other words that are more
evocative of the "data record" use case, and many of the parameter
names are about "How attrs work" and "How Python magic methods work"
rather than "Behaviours I would like this class to have".

That's fine for someone that's already comfortable writing those
behaviours by hand and just wants to automate the boilerplate away
(which is exactly the problem that attrs was written to solve), but
it's significantly more problematic once we assume people will be
using a feature like this *before* learning how to write out all the
corresponding boilerplate themselves (which is the key additional
complication that a language level version of this will have to
account for).

However, consider instead the following API sketch:

    from autoclass import data_record, data_field

    @data_record(orderable=False, hashable=False)
    class SvgTransform(SvgPicture):
        child = data_field()
        matrix = data_field(setter=numpy.asarray)

Here, the core concepts to be learned would be:

- the "autoclass" module lets you ask the interpreter to automatically
fill in class details
- SvgTransform is a data record that cannot be hashed, and cannot be ordered
- it is a Python class inheriting from SvgPicture
- it has two defined fields, child & matrix
- we know "child" is an ordinary read/write instance attribute
- we know "matrix" is a property, using numpy.asarray as its setter

In this particular API sketch, data_record is just a class decorator
factory, and data_field is a declarative helper type for use with that
factory, so if you wanted to factor out particular combinations, you'd
just write ordinary helper functions.

> Instead of trying to cover every possible use-case from a single
> decorator with a multitude of keyword arguments, I think covering the
> simple cases is enough. Explicitly overriding methods is not a bad
> thing! It is much more comprehensible to see an explicit class with
> methods than a decorator with multiple keyword arguments and callbacks.

This isn't the case for folks that have to actually *read* dunder
methods to find out what a class does, thought. Reading an
imperatively defined class only works that way once you're able to
mentally pattern match "Oh, that's a conventional __init__, that's a
conventional __repr__, that's a conventional __hash__, that's a
conventional __eq__, that's a conventional __lt__ implementation, etc,
etc".

Right now, telling Python "I want to do the same stock-standard things
that everyone always does" means writing a lot of repetitive logic
(or, more likely, copying the logic from an existing class that you or
someone else wrote, and editing it to fit).

The idea behind offering some form of declarative class definitions is
to build out a vocabulary of conventional class behaviours, and make
that vocabulary executable such that folks can use it to write
applications even if they haven't learned how it works under the hood
yet. As with descriptors before it, that vocabulary may also take
advantage of the fact that Python offers first class functions to
allow callbacks and transformation functions to be injected at various
steps in the process *without* requiring you to also spell out all the
other steps in the process that you don't want to alter.

> I like the namedtuple approach: I think it hits the sweet spot between
> "having to do everything by hand" and "everything is magical".

It's certainly a lot better than nothing at all, but it brings a lot
of baggage with it due to the fact that it *is* a tuple. Declarative
class definitions aim to offer the convenience of namedtuple
definitions, without the complications that arise from the "it's a
tuple with some additional metadata and behaviours" aspects.

Database object-relational-mapping layers like those in SQL Alchemy
and Django would be the most famous precursors for this, but there are
also things like Django Form definitions, and APIs like JSL (which
uses Python classes to declaratively define JSON Schema documents).

For folks already familiar with ORMs, declarative classes are just a
matter of making in memory data structures as easy to work with as
database backed ones. For folks that *aren't* familiar with ORMs yet,
then declarative classes provide a potentially smoother learning
curve, since the "declarative class" aspects can be better separated
from the "object-relational mapping" aspects.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia


More information about the Python-ideas mailing list