Login | Register For Free | Help
Search for: (Advanced)

Mailing List Archive: Python: Dev

The Return Of Argument Clinic

 

 

Python dev RSS feed   Index | Next | Previous | View Threaded


larry at hastings

Aug 5, 2013, 1:48 AM

Post #1 of 11 (57 views)
Permalink
The Return Of Argument Clinic

It's time to discuss Argument Clinic again. I think the
implementation is ready for public scrutiny.

(It was actually ready a week ago, but I lost a couple of
days to "make distclean" corrupting my hg data store--yes,
I hadn't upped my local clinic branch in a while. Eventually
I gave up on repairing it and just brute-forcd it. Anyway...)

My Clinic test branch is here:
https://bitbucket.org/larry/python-clinic/

And before you ask, no, the above branch should never ever
ever be merged back into trunk. We'll start clean once Clinic
is ready for merging and do a nice neat job.

___________________________________________________________________


There's no documentation, apart from the PEP. But you can see
plenty of test cases of using Clinic, just grep for the string
"clinic" in */*.c. But for reference here's the list:
Modules/_cursesmodule.c
Modules/_datetimemodule.c
Modules/_dbmmodule.c
Modules/posixmodule.c
Modules/unicodedata.c
Modules/_weakref.c
Modules/zlibmodule.c
Objects/dictobject.c
Objects/unicodeobject.c

I haven't reimplemented every PyArg_ParseTuple "format unit"
in the retooled Clinic, so it's not ready to try with every
single builtin yet.

The syntax is as Guido dictated it during our meeting after
the Language Summit at PyCon US 2013. The implementation has
been retooled, several times, and is now both nicer and more
easily extensible. The internals are just a little messy,
but the external interfaces are all ready for critique.

___________________________________________________________________

Here are the external interfaces as I forsee them.

If you add your own data types, you'll subclass
"Converter" and maybe "ReturnConverter". Take a
look at the existing subclasses to get a feel for
what that's like.

If you implemented your own DSL, you'd make something
that quacked like "PythonParser" (implementing __init__
and parse methods), and you'd deal with "Block",
"Module", "Class", "Function", and "Parameter" objects
a lot.

What do you think?

___________________________________________________________________


What follows are six questions I'd like to put to the community,
ranked oddly enough in order of how little to how much I
care about the answer.

BTW, by convention, every time I need a arbitrary sample
function I use "os.stat".

(Please quote the question line in your responses,
otherwise I fear we'll get lost in the sea of text.)

___________________________________________________________________
Question 0: How should we integrate Clinic into the build process?

Clinic presents a catch-22: you want it as part of the build process,
but it needs Python to be built before it'll run. Currently it
requires Python 3.3 or newer; it might work in 3.2, I've never
tried it.

We can't depend on Python 3 being available when we build.
This complicates the build process somewhat. I imagine it's a
solvable problem on UNIX... with the right wizardry. I have no
idea how one'd approach it on Windows, but obviously we need to
solve the problem there too.

___________________________________________________________________
Question 1: Which C function nomenclature?

Argument Clinic generates two functions prototypes per Python
function: one specifying one of the traditional signatures for
builtins, whose code is generated completely by Clinic, and the
other with a custom-generated signature for just that call whose
code is written by the user.

Currently the former doesn't have any specific name, though I
have been thinking of it as the "parse" function. The latter
is definitely called the "impl" (pronounced IM-pull), short
for "implementation".

When Clinic generates the C code, it uses the name of the Python
function to create the C functions' names, with underscores in
place of dots. Currently the "parse" function gets the base name
("os_stat"), and the "impl" function gets an "_impl" added to the
end ("os_stat_impl").

Argument Clinic is agnostic about the names of these functions.
It's possible it'd be nicer to name these the other way around,
say "os_stat_parse" for the parse function and "os_stat" for the
impl.

Anyone have a strong opinion one way or the other? I don't much
care; all I can say is that the "obvious" way to do it when I
started was to add "_impl" to the impl, as it is the new creature
under the sun.

___________________________________________________________________
Question 2: Emit code for modules and classes?

Argument Clinic now understands the structure of the
modules and classes it works with. You declare them
like so:

module os
class os.ImaginaryClassHere
def os.ImaginaryClassHere.stat(...):
...

Currently it does very little with the information; right
now it mainly just gets baked into the documentation.
In the future I expect it to get used in the introspection
metadata, and it'll definitely be relevant to external
consumers of the Argument Clinic information (IDEs building
per-release databases, other implementations building
metadata for library interface conformance testing).

Another way we could use this metadata: have Argument
Clinic generate more of the boilerplate for a class
or module. For example, it could kick out all the
PyMethodDef structures for the class or module.

If we grew Argument Clinic some, and taught it about
the data members of classes and modules, it could
also generate the PyModuleDef and PyTypeObject structures,
and even generate a function that initialized them at
runtime for you. (Though that does seem like mission
creep to me.)

There are some complications to this, one of which I'll
discuss next. But I put it to you, gentle reader: how
much boilerplate should Argument Clinic undertake to
generate, and how much more class and module metadata
should be wired in to it?

___________________________________________________________________
Question 3: #ifdef support for functions?

Truth be told, I did experiment with having Argument
Clinic generate more of the boilerplate associated with
modules. Clinic already generates a macro per function
defining that function's PyMethodDef structure, for example:

#define OS_STAT_METHODDEF \
{"stat", (PyCFunction)os_stat, \
METH_VARARGS|METH_KEYWORDS, os_stat__doc__}

For a while I had it generating the PyMethodDef
structures, like so:

/*[clinic]
generate_method_defs os
[clinic]*/
#define OS_METHODDEFS \
OS_STAT_METHODDEF, \
OS_ACCESS_METHODDEF, \
OS_TTYNAME_METHODDEF, \

static PyMethodDef os_methods[] = {
OS_METHODDEFS
/* existing methoddefs here... */
NULL
}

But I ran into trouble with os.ttyname(), which is only
created and exposed if the platform defines HAVE_TTYNAME.
Initially I'd just thrown all the Clinic stuff relevant to
os.ttyname in the #ifdef block. But Clinic pays no attention
to #ifdef statements--so it would still add
OS_TTYNAME_METHODDEF,
to OS_METHODDEFS. And kablooey!

Right now I've backed out of this--I had enough to do without
getting off into extra credit like this. But I'd like to
return to it. It just seems natural to have Clinic generate
this nasty boilerplate.


Four approaches suggest themselves to me, listed below in order
of least- to most-preferable in my opinion:

0) Don't have Clinic participate in populating the PyMethodDefs.

1) Teach Clinic to understand simple C preprocessor statements,
just enough so it implicitly understands that os.ttyname was
defined inside an
#ifdef HAVE_TTYPE
block. It would then intelligently generate the code to take
this into account.

2) Explicitly tell Clinic that os.ttyname must have HAVE_TTYNAME
defined in order to be active. Clinic then generates the code
intelligently taking this into account, handwave handwave.

3) Change the per-function methoddef macro to have the trailing
comma:

#define OS_STAT_METHODDEF \
{"stat", (PyCFunction)os_stat, \
METH_VARARGS|METH_KEYWORDS, os_stat__doc__},

and suppress it in the macro Clinic generates:

/*[clinic]
generate_method_defs os
[clinic]*/
#define OS_METHODDEFS \
OS_STAT_METHODDEF \
OS_ACCESS_METHODDEF \
OS_TTYNAME_METHODDEF \

And then the code surrounding os.ttyname can look like this:

#ifdef HAVE_TTYNAME
// ... real os.ttyname stuff here
#else
#define OS_STAT_TTYNAME
#endif

And I think that would work great, actually. But I haven't
tried it.

Do you agree that Argument Clinic should generate this
information, and it should use the approach in 3) ?

___________________________________________________________________
Question 4: Return converters returning success/failure?

With the addition of the "return converter", we have the
lovely feature of being able to *return* a C type and have
it converted back into a Python type. Your C extensions
have never been more readable!

The problem is that the PyObject * returned by a C builtin
function serves two simultaneous purposes: it contains the
return value on success, but also it is NULL if the function
threw an exception. We can probably still do that for all
pointer-y return types (I'm not sure, I haven't played with
it yet). But if the impl function now returns "int", or some
decidedly other non-pointer-y type, there's no longer a magic
return value we can use to indicate "we threw an exception".

This isn't the end of the world; I can detect that the impl
threw an exception by calling PyErr_Occurred(). But I've been
chided before for calling this unnecessarily; it's ever-so
slightly expensive, in that it has to dereference TLS, and
does so with an atomic operation. Not to mention that it's
a function call!

The impl should know whether or not it failed. So it's the
interface we're defining that forces it to throw away that
information. If we provided a way for it to return that
information, we could shave off some cycles. The problem
is, how do we do that in a way that doesn't suck?

Four approaches suggest themselves to me, and sadly
I think they all suck to one degree or another. In
order of sucking least to most:

0) Return the real type and detect the exception with
PyErr_Occurred(). This is by far the loveliest option,
but it incurs runtime overhead.

1) Have the impl take an extra parameter, "int *failed".
If the function fails, it sets that to a true value and
returns whatever.

2) Have the impl return its calculated return value through
an extra pointer-y parameter ("int *return_value"), and
its actual return value is an int indicating success or
failure.

3) Have the impl return a structure containing both the
real return value and a success/failure integer. Then
its return lines would look like this:
return {-1, 0};
or maybe
return {-3, PY_HORRIBLE_CLINIC_INTERFACE__SUCCESS};

Can we live with PyErr_Occurred() here?

___________________________________________________________________
Question 5: Keep too-magical class decorator Converter.wrap?

Converter is the base class for converter objects, the objects
that handle the details of converting a Python object into its
C equivalent. The signature for Converter.__init__ has become
complicated:

def __init__(self, name, function, default=unspecified,
*, doc_default=None, required=False)

"name" is the name of the function ("stat"), "function" is an
object representing the function for which this Converter is
handling an argument (duck-type compatible with
inspect.Signature), and default is the default (Python) value
if any. "doc_default" is a string that overrides repr(default)
in the documentation, handy if repr(default) is too ugly or
you just want to mislead the user. "required", if True
specifies that the parameter should be considered required,
even if it has a default value.

Complicating the matter further, converter subclasses may take
extra (keyword-only and optional) parameters to configure exotic
custom behavior. For example, the "Py_buffer" converter takes
"zeroes" and "nullable"; the "path_t" converter implemented
in posixmodule.c takes "allow_fd" and "nullable". This means
that converter subclasses have to define a laborious __init__,
including three parameters with defaults, then turn right around
and pass most of the parameters back into super().__init__.

This interface has changed several times during the development
of Clinic, and I got tired of fixing up all my existing prototypes
and super calls. So I made a class decorator that did it for me.
Shield your eyes from the sulferous dark wizardry of Converter.wrap:

@staticmethod
def wrap(cls):
class WrappedConverter(cls, Converter):
def __init__(self, name, function, default=unspecified,
*, doc_default=None, required=False, **kwargs):
super(cls, self).__init__(name, function, default,
doc_default=doc_default, required=required)
cls.__init__(self, **kwargs)
return functools.update_wrapper(WrappedConverter,
cls, updated=())

When you decorate your class with Converter.wrap, you only
define in your __init__ your custom arguments. All the
arguments Converter.__init__ cares about are taken care
of for you (aka hidden from you). As an example, here's
the relevant bits of path_t_converter from posixmodule.c:

@Converter.wrap
class path_t_converter(Converter):
def __init__(self, *, allow_fd=False, nullable=False):
...

So on the one hand I admit it's smelly. On the other hand it
hides a lot of stuff that the user needn't care about, and it
makes the code simpler and easier to read. And it means we can
change the required arguments for Converter.__init__ without
breaking any code (as I have already happily done once or twice).

I'd like to keep it in, and anoint it as the preferred way
of declaring Converter subclasses. Anybody else have a strong
opinion on this either way?

(I don't currently have an equivalent mechanism for return
converters--their interface is a lot simpler, and I just
haven't needed it so far.)

___________________________________________________________________


Well! That's quite enough for now.


//arry/


arigo at tunes

Aug 5, 2013, 2:42 AM

Post #2 of 11 (55 views)
Permalink
Re: The Return Of Argument Clinic [In reply to]

Hi Larry,

On Mon, Aug 5, 2013 at 10:48 AM, Larry Hastings <larry [at] hastings> wrote:
> Question 4: Return converters returning success/failure?

The option generally used elsewhere is: if we throw an exception, we
return some special value; but the special value doesn't necessarily
mean by itself that an exception was set. It's a reasonable solution
because the caller only needs to call PyErr_Occurred() for one special
value, rather than every time. See for example any call to
PyFloat_AsDouble().


A bientôt,

Armin.
_______________________________________________
Python-Dev mailing list
Python-Dev [at] python
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: http://mail.python.org/mailman/options/python-dev/list-python-dev%40lists.gossamer-threads.com


ncoghlan at gmail

Aug 5, 2013, 2:55 AM

Post #3 of 11 (55 views)
Permalink
Re: The Return Of Argument Clinic [In reply to]

On 5 August 2013 18:48, Larry Hastings <larry [at] hastings> wrote:
> Question 0: How should we integrate Clinic into the build process?
>
> Clinic presents a catch-22: you want it as part of the build process,
> but it needs Python to be built before it'll run. Currently it
> requires Python 3.3 or newer; it might work in 3.2, I've never
> tried it.
>
> We can't depend on Python 3 being available when we build.
> This complicates the build process somewhat. I imagine it's a
> solvable problem on UNIX... with the right wizardry. I have no
> idea how one'd approach it on Windows, but obviously we need to
> solve the problem there too.

Isn't solving the bootstrapping problem the reason for checking in the
clinic-generated output? If there's no Python available, we build what
we have (without the clinic step), then we build it again *with* the
clinic step.

> ___________________________________________________________________
> Question 1: Which C function nomenclature?

> Anyone have a strong opinion one way or the other? I don't much
> care; all I can say is that the "obvious" way to do it when I
> started was to add "_impl" to the impl, as it is the new creature
> under the sun.

Consider this from the client side, and I believe it answers itself:
other code in the module will be expected the existing signature, so
that signature needs to stay with the existing name, while the new C
implementation function gets the new name.

> ___________________________________________________________________
> Question 2: Emit code for modules and classes?
>
> There are some complications to this, one of which I'll
> discuss next. But I put it to you, gentle reader: how
> much boilerplate should Argument Clinic undertake to
> generate, and how much more class and module metadata
> should be wired in to it?

I strongly recommend deferring this. Incremental development is good,
and getting this bootstrapped at all is going to be challenging enough
without trying to do everything at once.

> ___________________________________________________________________
> Question 3: #ifdef support for functions?
>
>
> Do you agree that Argument Clinic should generate this
> information, and it should use the approach in 3) ?

I think you should postpone anything related to modules and classes
until the basic function support is in and working.

> ___________________________________________________________________
> Question 4: Return converters returning success/failure?
>
> Can we live with PyErr_Occurred() here?

Armin's suggestion of a valid return value (say, -1) that indicates
"error may have occurred" sounds good to me.

> ___________________________________________________________________
> Question 5: Keep too-magical class decorator Converter.wrap?
>
> I'd like to keep it in, and anoint it as the preferred way
> of declaring Converter subclasses. Anybody else have a strong
> opinion on this either way?

Can't you get the same effect without the magic by having a separate
"custom_init" method that the main __init__ method promises to call
with the extra keyword args after finishing the other parts of the
initialization? Them a custom converter would just look like:

class path_t_converter(Converter):
def custom_init(self, *, allow_fd=False, nullable=False):
...

Cheers,
Nick.

--
Nick Coghlan | ncoghlan [at] gmail | Brisbane, Australia
_______________________________________________
Python-Dev mailing list
Python-Dev [at] python
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: http://mail.python.org/mailman/options/python-dev/list-python-dev%40lists.gossamer-threads.com


v+python at g

Aug 5, 2013, 10:41 AM

Post #4 of 11 (55 views)
Permalink
Re: The Return Of Argument Clinic [In reply to]

On 8/5/2013 1:48 AM, Larry Hastings wrote:
>
> The impl should know whether or not it failed. So it's the
> interface we're defining that forces it to throw away that
> information. If we provided a way for it to return that
> information, we could shave off some cycles. The problem
> is, how do we do that in a way that doesn't suck?
> ...
> Can we live with PyErr_Occurred() here?

Isn't there another option? To have the impl call a special "failed"
clinic API, prior to returning failure? And if that wasn't called, then
the return is success. Or does that require the same level of overhead
as PyErr_Occurred?

Reducing the chances of PyErr_Occurred per Armin's suggestion seems good
if the above is not an improvement.


larry at hastings

Aug 5, 2013, 4:53 PM

Post #5 of 11 (55 views)
Permalink
Re: The Return Of Argument Clinic [In reply to]

On 08/05/2013 02:55 AM, Nick Coghlan wrote:
> On 5 August 2013 18:48, Larry Hastings<larry [at] hastings> wrote:
>> Question 0: How should we integrate Clinic into the build process?
> Isn't solving the bootstrapping problem the reason for checking in the
> clinic-generated output? If there's no Python available, we build what
> we have (without the clinic step), then we build it again *with* the
> clinic step.

It solves the bootstrapping problem, but that's not the only problem
Clinic presents to the development workflow.

If you modify some Clinic DSL in a C file in the CPython tree, then run
"make", should the Makefile re-run Clinic over that file? If you say
"no", then there's no problem. If you say "yes", then we have the
problem I described.


>> ___________________________________________________________________
>> Question 1: Which C function nomenclature?
> Consider this from the client side, and I believe it answers itself:
> other code in the module will be expected the existing signature, so
> that signature needs to stay with the existing name, while the new C
> implementation function gets the new name.

One vote for "os_stat_impl". Bringing the sum total of votes up to 1! ;-)


>> ___________________________________________________________________
>> Question 2: Emit code for modules and classes?
>>
>> There are some complications to this, one of which I'll
>> discuss next. But I put it to you, gentle reader: how
>> much boilerplate should Argument Clinic undertake to
>> generate, and how much more class and module metadata
>> should be wired in to it?
> I strongly recommend deferring this. Incremental development is good,
> and getting this bootstrapped at all is going to be challenging enough
> without trying to do everything at once.

I basically agree. But you glossed over an important part of that
question, "how much more class and module metadata should be wired in
right now?".

Originally Clinic didn't ask for full class and module information, you
just specified the full dotted path and that was that. But that's
ambiguous; Clinic wouldn't be able to infer what was a module vs what
was a class. And in the future, if/when it generates module and class
boilerplate, obviously it'll need to know the distinction. I figure,
specifying the classes and modules doesn't add a lot of additional cost,
but it'll very likely save us a lot of time in the long run, so I made
it a requirement. (WAGNI!)

Anyway, I guess what I was really kind of trying to get at here was:
a) are there any other obvious bits of metadata Clinic should require
right now for functions,
b) what other metadata might Clinic take in the future--not because I
want to add it, but just so we can figure out the next question,
c) to what degree can we future-proof Clinic 1.0 so extension authors
can more easily straddle versions.

Thinking about it more with a fresh perspective, maybe all we need is a
Clinic version number directive. This would declare the minimum Clinic
version--which would really just track the Python version it shipped
with--that you may use to process this file. Like so:

/*[clinic]
clinic 3.5
[clinic]*/

As long as the code Clinic generates is backwards compatible for Python
3.4, I think this will has it covered. We may at times force developers
to use fresher versions of Python to process Clinic stuff, but I don't
think that's a big deal.


>> ___________________________________________________________________
>> Question 4: Return converters returning success/failure?
>>
>> Can we live with PyErr_Occurred() here?
> Armin's suggestion of a valid return value (say, -1) that indicates
> "error may have occurred" sounds good to me.

Yes indeed, and thanks Armin for pointing it out. This works perfectly
in Clinic, as each individual return converter controls the code
generated for cleanup afterwards. So it can be a completely local
policy per-return-converter what the magic value is. Heck, you could
have multiple int converters with different magic return values (not
that that seems like a good idea).


>> ___________________________________________________________________
>> Question 5: Keep too-magical class decorator Converter.wrap?
>>
>> I'd like to keep it in, and anoint it as the preferred way
>> of declaring Converter subclasses. Anybody else have a strong
>> opinion on this either way?
> Can't you get the same effect without the magic by having a separate
> "custom_init" method that the main __init__ method promises to call
> with the extra keyword args after finishing the other parts of the
> initialization? Them a custom converter would just look like:
>
> class path_t_converter(Converter):
> def custom_init(self, *, allow_fd=False, nullable=False):
> ...

I can get the same effect without reusing the name __init__, but I
wouldn't say I can do it "without the magic". The whole point of the
decorator is magic.

Let's say I go with your proposal. What happens if someone makes a
Converter, and wraps it with Converter.wrap, and defines their own
__init__? It would never get called. Silently, by default, which is
worse--though I could explicitly detect such an __init__ and throw an
exception I guess. Still, now we have a class where you can't use the
name __init__, you have to use this funny other name, for arbitrary
"correctness" reasons.

My metaphor for why I prefer my approach is the set of "os" module
functions that allow "specifying a file descriptor":

http://docs.python.org/3/library/os.html#path-fd

Taking the example of os.chdir(), yes, it would have been more correct
to require specifying the file descriptor as a separate argument, like

os.chdir(None, fd=my_dir_fd)

But this would have meant that when using "fd" the first parameter would
always be None. And the first parameter and the "fd" do the same thing,
just with different types. So while normally we eschew polymorphic
parameters (and with good reason) in this case I think practicality beat
purity.

And I think the same holds here. Since class instance initialization
functions in Python are called __init__, and this is a class instance
initialization function, I think it should be called __init__. By
decorating with Converter.wrap, you're signing a contract that says "yes
it's a fake __init__, that's what I want". A real __init__ in this
class would never be called, which is the whole point, so we might as
well reuse the name for our slightly-fake __init__.


Let me put it this way: Which is more surprising to the person
unfamiliar with the code? That this __init__ doesn't get all the
parameters, and the base class __init__ is getting called
automatically? Or that this funny function "custom_init" is what gets
called, and this class is not allowed to have a function called __init__?


In case I didn't make it clear: the actual call site for constructing
these objects is buried deep in clinic.py. Users don't create them
directly, or at least I don't know why they'd ever need to do so.
Instead, they're created inside Clinic preprocessor blocks in your
source files, where they look like tis:

parameter_name: Converter(argument=value, argument2=value) = default

Using the funny magic of Converter.wrap makes this and the
implementation look a great deal more alike. So I remain a fan of
Converter.wrap and calling the initialization function __init__.


//arry/


ncoghlan at gmail

Aug 5, 2013, 9:59 PM

Post #6 of 11 (55 views)
Permalink
Re: The Return Of Argument Clinic [In reply to]

On 6 August 2013 09:53, Larry Hastings <larry [at] hastings> wrote:
> On 08/05/2013 02:55 AM, Nick Coghlan wrote:
> On 5 August 2013 18:48, Larry Hastings <larry [at] hastings> wrote:
>
> Question 0: How should we integrate Clinic into the build process?
>
> Isn't solving the bootstrapping problem the reason for checking in the
> clinic-generated output? If there's no Python available, we build what
> we have (without the clinic step), then we build it again *with* the
> clinic step.
>
> It solves the bootstrapping problem, but that's not the only problem Clinic
> presents to the development workflow.
>
> If you modify some Clinic DSL in a C file in the CPython tree, then run
> "make", should the Makefile re-run Clinic over that file? If you say "no",
> then there's no problem. If you say "yes", then we have the problem I
> described.

Ah, I think I see the problem you mean. What is defined in the
makefile as the way to regenerate an object file from the C file. If
it is run clinic and then run the compiler, then you will get a
dependency loop. If it doesn't implicitly run clinic, then we risk
checking in inconsistent clinic metadata.

I think the simplest answer may be to have "make clinic" as an
explicit command, along with a commit hook that checks for clinic
metadata consistency. Then "make" doesn't have to change and there's
no nasty bootstrapping problem.


> ___________________________________________________________________
> Question 2: Emit code for modules and classes?
>
> There are some complications to this, one of which I'll
> discuss next. But I put it to you, gentle reader: how
> much boilerplate should Argument Clinic undertake to
> generate, and how much more class and module metadata
> should be wired in to it?
>
> I strongly recommend deferring this. Incremental development is good,
> and getting this bootstrapped at all is going to be challenging enough
> without trying to do everything at once.
>
>
> I basically agree. But you glossed over an important part of that question,
> "how much more class and module metadata should be wired in right now?".
>
> Originally Clinic didn't ask for full class and module information, you just
> specified the full dotted path and that was that. But that's ambiguous;
> Clinic wouldn't be able to infer what was a module vs what was a class. And
> in the future, if/when it generates module and class boilerplate, obviously
> it'll need to know the distinction. I figure, specifying the classes and
> modules doesn't add a lot of additional cost, but it'll very likely save us
> a lot of time in the long run, so I made it a requirement. (WAGNI!)

Note that setuptools entry point syntax solves the namespace ambiguity
problem by using ":" to separate the module name from the object's
name within the module (the nost test runner does the same thing). I'm
adopting that convention for the PEP 426 metadata, and it's probably
appropriate as a concise notation for clinic as well.

> As long as the code Clinic generates is backwards compatible for Python 3.4,
> I think this will has it covered. We may at times force developers to use
> fresher versions of Python to process Clinic stuff, but I don't think that's
> a big deal.

One of the nice things about explicitly versioned standards is that
you can set the "no version stated" to the first version released and
avoid the boilerplate in the common case :)

> ___________________________________________________________________
> Question 5: Keep too-magical class decorator Converter.wrap?
> Let's say I go with your proposal. What happens if someone makes a
> Converter, and wraps it with Converter.wrap, and defines their own __init__?
> It would never get called. Silently, by default, which is worse--though I
> could explicitly detect such an __init__ and throw an exception I guess.
> Still, now we have a class where you can't use the name __init__, you have
> to use this funny other name, for arbitrary "correctness" reasons.

You misunderstand me: I believe a class decorator is the *wrong
solution*. I am saying Converter.wrap *shouldn't exist*, and that the
logic for what it does should be directly in Converter.__init__.

The additional initialisation method could be given a better name like
"process_custom_params" rather than "custom_init".

That is, instead of this hard to follow magic:

@staticmethod
def wrap(cls):
class WrappedConverter(cls, Converter):
def __init__(self, name, function, default=unspecified,
*, doc_default=None, required=False, **kwargs):
super(cls, self).__init__(name, function, default,
doc_default=doc_default, required=required)
cls.__init__(self, **kwargs)
return functools.update_wrapper(
WrappedConverter,
cls, updated=())

You would just have the simple:

class Converter:
def __init__(self, name, function, default=unspecified,
*, doc_default=None, required=False, **kwargs):
.... # Existing arg process
self.process_custom_params(**kwargs)

def process_custom_params(self):
# Default to no custom parameters allowed
pass

Those that just want to define custom parameters and leave the rest of
the logic alone can override "process_custom_params". Those that want
to completely control the initialisation can override __init__
directly.

Cheers,
Nick.

--
Nick Coghlan | ncoghlan [at] gmail | Brisbane, Australia
_______________________________________________
Python-Dev mailing list
Python-Dev [at] python
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: http://mail.python.org/mailman/options/python-dev/list-python-dev%40lists.gossamer-threads.com


rdmurray at bitdance

Aug 6, 2013, 2:44 AM

Post #7 of 11 (55 views)
Permalink
Re: The Return Of Argument Clinic [In reply to]

On Mon, 05 Aug 2013 16:53:39 -0700, Larry Hastings <larry [at] hastings> wrote:
> Let me put it this way: Which is more surprising to the person
> unfamiliar with the code? That this __init__ doesn't get all the
> parameters, and the base class __init__ is getting called
> automatically? Or that this funny function "custom_init" is what gets
> called, and this class is not allowed to have a function called __init__?

Definitely the former is more surprising. Especially since, as Nick
points out, the last part of your statement isn't true: there can
be a function called __init__, it just has to replicate the superclass
logic if it exists, which is the way Python normally works.

I use this "call a hook method from __init__" pattern in the email
package's new header parsing code, by the way, for whatever that is
worth :)

--David
_______________________________________________
Python-Dev mailing list
Python-Dev [at] python
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: http://mail.python.org/mailman/options/python-dev/list-python-dev%40lists.gossamer-threads.com


brett at python

Aug 6, 2013, 8:13 AM

Post #8 of 11 (48 views)
Permalink
Re: The Return Of Argument Clinic [In reply to]

On Tue, Aug 6, 2013 at 12:59 AM, Nick Coghlan <ncoghlan [at] gmail> wrote:

> On 6 August 2013 09:53, Larry Hastings <larry [at] hastings> wrote:
> > On 08/05/2013 02:55 AM, Nick Coghlan wrote:
> > On 5 August 2013 18:48, Larry Hastings <larry [at] hastings> wrote:
> >
> > Question 0: How should we integrate Clinic into the build process?
> >
> > Isn't solving the bootstrapping problem the reason for checking in the
> > clinic-generated output? If there's no Python available, we build what
> > we have (without the clinic step), then we build it again *with* the
> > clinic step.
> >
> > It solves the bootstrapping problem, but that's not the only problem
> Clinic
> > presents to the development workflow.
> >
> > If you modify some Clinic DSL in a C file in the CPython tree, then run
> > "make", should the Makefile re-run Clinic over that file? If you say
> "no",
> > then there's no problem. If you say "yes", then we have the problem I
> > described.
>
> Ah, I think I see the problem you mean. What is defined in the
> makefile as the way to regenerate an object file from the C file. If
> it is run clinic and then run the compiler, then you will get a
> dependency loop. If it doesn't implicitly run clinic, then we risk
> checking in inconsistent clinic metadata.
>
> I think the simplest answer may be to have "make clinic" as an
> explicit command, along with a commit hook that checks for clinic
> metadata consistency. Then "make" doesn't have to change and there's
> no nasty bootstrapping problem.


Can't we just do what we already do for the generated AST code or what we
used to do for importlib's frozen code; we have the touch extension for hg
integration for this kind of issue.


solipsis at pitrou

Aug 6, 2013, 2:24 PM

Post #9 of 11 (48 views)
Permalink
Re: The Return Of Argument Clinic [In reply to]

On Mon, 05 Aug 2013 16:53:39 -0700
Larry Hastings <larry [at] hastings> wrote:
>
> On 08/05/2013 02:55 AM, Nick Coghlan wrote:
> > On 5 August 2013 18:48, Larry Hastings<larry [at] hastings> wrote:
> >> Question 0: How should we integrate Clinic into the build process?
> > Isn't solving the bootstrapping problem the reason for checking in the
> > clinic-generated output? If there's no Python available, we build what
> > we have (without the clinic step), then we build it again *with* the
> > clinic step.
>
> It solves the bootstrapping problem, but that's not the only problem
> Clinic presents to the development workflow.
>
> If you modify some Clinic DSL in a C file in the CPython tree, then run
> "make", should the Makefile re-run Clinic over that file? If you say
> "no", then there's no problem. If you say "yes", then we have the
> problem I described.

I say "yes" and I think best-effort is the solution. Usually, the
current clinic should be good enough to compile future C changes. If it
isn't, just revert your working copy and start again (save your
changes and re-apply them if desired).

importlib has the same theoretical problem but it works well enough in
practice, even though it could be maddening at times when the code
wasn't quite stabilized.

Regards

Antoine.


_______________________________________________
Python-Dev mailing list
Python-Dev [at] python
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: http://mail.python.org/mailman/options/python-dev/list-python-dev%40lists.gossamer-threads.com


larry at hastings

Aug 8, 2013, 2:45 AM

Post #10 of 11 (38 views)
Permalink
Re: The Return Of Argument Clinic [In reply to]

On 08/05/2013 09:59 PM, Nick Coghlan wrote:
>> ___________________________________________________________________
>> Question 2: Emit code for modules and classes?
>>
>> [...] Originally Clinic didn't ask for full class and module information, you just
>> specified the full dotted path and that was that. But that's ambiguous;
>> Clinic wouldn't be able to infer what was a module vs what was a class. And
>> in the future, if/when it generates module and class boilerplate, obviously
>> it'll need to know the distinction. [...]
> Note that setuptools entry point syntax solves the namespace ambiguity
> problem by using ":" to separate the module name from the object's
> name within the module (the nost test runner does the same thing). I'm
> adopting that convention for the PEP 426 metadata, and it's probably
> appropriate as a concise notation for clinic as well.

So you're proposing that xml.etree.ElementTree.dump() be written as
"xml.etree:ElementTree.dump", and datetime.datetime.now() be written as
"datetime:datetime.now"? And presumably *not* specifying a colon as
part of the name would be an error.


>> ___________________________________________________________________
>> Question 5: Keep too-magical class decorator Converter.wrap?
> You misunderstand me: I believe a class decorator is the *wrong
> solution*. I am saying Converter.wrap *shouldn't exist*, and that the
> logic for what it does should be directly in Converter.__init__.

Well, nobody liked it, everybody hated it, so I'll go with what you
proposed, though with the name converter_init() for the custom converter
init function.


//arry/


ncoghlan at gmail

Aug 8, 2013, 8:52 AM

Post #11 of 11 (36 views)
Permalink
Re: The Return Of Argument Clinic [In reply to]

On 8 Aug 2013 02:48, "Larry Hastings" <larry [at] hastings> wrote:
>
> On 08/05/2013 09:59 PM, Nick Coghlan wrote:
>>>
>>> ___________________________________________________________________
>>> Question 2: Emit code for modules and classes?
>>>
>>> [...] Originally Clinic didn't ask for full class and module
information, you just
>>> specified the full dotted path and that was that. But that's ambiguous;
>>> Clinic wouldn't be able to infer what was a module vs what was a
class. And
>>> in the future, if/when it generates module and class boilerplate,
obviously
>>> it'll need to know the distinction. [...]
>>
>> Note that setuptools entry point syntax solves the namespace ambiguity
>> problem by using ":" to separate the module name from the object's
>> name within the module (the nost test runner does the same thing). I'm
>> adopting that convention for the PEP 426 metadata, and it's probably
>> appropriate as a concise notation for clinic as well.
>
>
> So you're proposing that xml.etree.ElementTree.dump() be written as
"xml.etree:ElementTree.dump", and datetime.datetime.now() be written as
"datetime:datetime.now"? And presumably *not* specifying a colon as part
of the name would be an error.

Assuming there's no way to tell argument clinic all the functions and
classes in a given C file belong to the same module, then yes, you would
need the colon in every name to indicate the module portion.

>
>
>>> ___________________________________________________________________
>>> Question 5: Keep too-magical class decorator Converter.wrap?
>>
>> You misunderstand me: I believe a class decorator is the *wrong
>>
>> solution*. I am saying Converter.wrap *shouldn't exist*, and that the
>> logic for what it does should be directly in Converter.__init__.
>
>
> Well, nobody liked it, everybody hated it, so I'll go with what you
proposed, though with the name converter_init() for the custom converter
init function.

My future code-reading self thanks you :)

Cheers,
Nick.

>
>
> /arry

Python dev RSS feed   Index | Next | Previous | View Threaded
 
 


Interested in having your list archived? Contact Gossamer Threads
 
  Web Applications & Managed Hosting Powered by Gossamer Threads Inc.